Give companies telephone numbers, postal addresses and an email address, and let two people record the same switchboard #329
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#329
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
A company's card on a CardDAV server carries what it answers on: a switchboard (
TEL), a general mailbox (EMAIL), an office (ADR). Postulo keeps none of them for a company.PhoneNumber,PostalAddressandWebLinkare generic rows for "a person or a contact", as their docstrings say (src/postulo/core/models.py), and onlyProfileandContactdeclare relations to them.Companyhas a website, a careers page, a location and notes, but no number, email or address;Contacthas oneemailfield; the only email rows are allauth's, for accounts. The DAV improvements ask to import a card "as a new company (or update existing one)", to pair a card with a company, and to export to CardDAV. A plugin cannot add a table of its own (#228), so a company card'sTEL,EMAILandADRcan only be dropped or pushed into notes.What stands in the way
Numbers are unique across the instance.
phone_number_unique_across_the_instance(src/postulo/core/models.py) constrains the normalised number, first come first served since #90, andtaken_elsewhere(src/postulo/core/phone_numbers.py) refuses a number any other row holds, the same account's included. For a person's own number the rule is load-bearing: a number that gets somebody back into an account has to be unique (#144), and how often anyone hears the refusal is limited (#142). Once companies hold numbers, it gets in the way: two people on one instance registered with one employment office (#202) could not both record its line, and one person could not record a recruiter whose direct line is also the company's. Contacts already meet it (test_two_accounts_cannot_both_hold_one_numberintests/test_phone_numbers.pyuses two contacts). Tracking applications names the difference: "a recruiter's switchboard is a number you recorded, never a number you are."Addresses are unique per owner, across every holder.
address_unique_per_owneris on(owner, comparable), while the form only catches a repeat within one holder (BasePostalAddressFormSet.cleaninsrc/postulo/core/postal.py). A company and a recruiter working in its building, or a parent and its subsidiary at one address, would hit the database constraint, not a message.Proposal
Companygainsphone_numbersandpostal_addresses, asContacthas, and anemailfield: shown on the company page, edited on its form with the partials the profile uses, following the Several telephone numbers switch, and carried inCompanyOut,CompanyInandCompanyPatch, the personal export (a new archive format,src/postulo/core/export.py) and its import._refuse_what_is_left(src/postulo/jobs/merging.py) refuses the merge.websiteandcareers_urlstay the company's links; noWebLinkrows.partials/postal_addresses.htmlis included only by the profile page); the contact form gains them, as a person card'sADRlands there.taken_elsewhere, which the forms, the API, the importers and the surface'sphone_number_is_takenall call, answers the chosen rule for the holder being written to; the docstrings, the collision message and the wiki (Tracking applications, Your career record) follow.Decisions
savewrites, as it writesnormalised: content type ids differ per database. Alternatives: verified numbers only (nothing callsPhoneNumber.record_verifiedyet, so today no rule at all), or companies alone exempt (two contacts sharing a switchboard, or two people recording one recruiter, would still collide). Unique per holder either way.WebLinkis. One list naming an address twice is still the mistake #92 meant; a company and the people who work in its building are not.Contacthas; several, for both, is a separate question.Departmentis deliberately only a name, so a department card'sTEL,EMAILandADRstill have nowhere to go; the alternative is the same rows onDepartment.Needed by
postulo/postulo-dav#8, postulo/postulo-dav#9 and postulo/postulo-dav#10, for a company card's
TEL,EMAILandADR. #328 is how a plugin writes these rows.Done when
From the list of DAV plugin improvements of 2026-09-30.
From the read-only review of 2026-09-29 (postulo at
95348908d, checked again at0a5cad587). A reviewer reached the same conclusion about the instance-wide constraint from the code; it was not filed again. What it adds to the above:ALREADY_IN_USE,core/phone_numbers.py), and each attempt spends one of the hourly collision answers (collision_message). #333 is the same allowance seen from the plugin surface.(content_type, object_id, normalised)so one holder cannot list a number twice.taken_elsewherethen looks only at profile numbers.The maintainer's list of 2026-10-02 asks for map pins for companies and contacts with a full address, and two new issues depend on this one.
#702 (pins from a full address) cannot pin a company until it can hold an address. On
mainatf09b6010bonlyProfileandContactcarrypostal_addresses(src/postulo/accounts/models.py:282,src/postulo/jobs/models.py:635);Companyhas none. #671 (employment services as NACE-coded companies) does not need an address, but it names the public employment office as a company whose number and address belong here.This issue has no milestone, so company pins follow whenever it gets one.
From the list of improvements of 2026-10-02.
From the maintainer's improvements list of 2026-10-07, for the company edit view: replace Location with addresses, possibly several, using the built-in postal plugin. Entering just a city and a country replaces today's Location; the exact place is inferred from a full address.
Today a company has a single free-text
location(templates/jobs/company_form.html:40) and a correction field, placed offline from GeoNames at city level (jobs/models.py:432-456). This issue gives companies postal addresses; #702 places an address (town, then postcode centroid, never online); #703 makes several addresses several pins. So the request is these three together, plus: thelocationfield retired once addresses exist, with each company'slocationcarried into a city-and-country address by a migration. Checked againstmainat8c3d03570.