An employer is a tree, and an application attaches to any part of it #138
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Depends on
#55 Parent and child companies
Postulo/postulo
#128 What happens to a plugin's table when the plugin goes
Postulo/postulo
#137 A department sits between a company and the people in it
Postulo/postulo
Reference
Postulo/postulo#138
Loading…
Add table
Add a link
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?
Observation
Extends #55, which specifies the parent-and-child half and is not superseded by this.
What exists
The chain from employer to attempt is fixed and one level deep:
JobPosting.companyis not nullable, so every application already has exactly onecompany, reached through its posting.
Application.contactis already an optional directlink to a person. Two of the four attachment points therefore exist in some form; the
company one is mandatory and indirect, the contact one is optional and direct.
Missing: a parent on
Company(#55), aDepartmentanywhere, and any way for anapplication to say it was for a part of an employer rather than the whole of it.
What this asks for
The employer as a structure a person can record, offered as an internal plugin, with an
application able to name whichever part of it the attempt was actually aimed at.
Worth being careful about
It is not one four-level tree. It is three different edges, and calling them one thing
would produce a model that cannot answer either question:
#55 already proposes the first as a self-referential nullable
parent, a tree rather than agraph, with cycles refused and depth capped. A department hangs off any company in that
chain, not only off a leaf. Modelling all three as one recursive parent would let somebody
make a company the child of a person.
"Attached to any of the four" risks two answers to one question. An application's
employer is
application.posting.companytoday, and that column is not nullable. Add adirect attachment on the application and there are two places that say who this was for,
which will disagree the first time a posting is edited. Three shapes, and they are not
equally safe:
application optionally names a department or a contact within it, and anything outside
that company's subtree is refused. One source of truth, and the fine detail is additive.
department, or a contact — and "which company" becomes a walk up the tree. One source of
truth, a larger migration, and every count in the application learns to walk.
This is the decision the issue turns on, and it should be made before anything is built.
Every count in Postulo means "at this company" today. Insights, the companies table,
the industries widget, applications at this company on the company page. With a tree, each
has to answer whether it counts the group or the node — #55 already says Insights gains that
choice, and the same question arrives everywhere else. A hierarchy that silently keeps
counting leaves is a hierarchy that changes nothing.
A plugin that can be switched off must leave every application with an employer. #90
settled that switching a plugin off deletes nothing and shows the primary; the same shape
works here — off means the department and the parent stop being shown and the company
remains, which is exactly today's behaviour. That is only true under shape 1 above. Under
shape 2, switching the plugin off would leave applications attached to departments with no
company to fall back to, which is a plugin toggle that breaks a page. Worth deciding with
that in view.
Merging companies exists and gets harder. Merge with moves one company's records into
another. With a tree it also has to decide what happens to children, to departments, and to
a merge that would make a company its own ancestor.
Deleting a parent must not delete a group.
JobPosting.companyandContact.companyareCASCADE; a parent link must not be, or removing a holding company removes every subsidiaryand every posting under it.
SET_NULL, and the children become roots.The export and the import carry the shape. #55 proposes resolving the tree by name within
the archive; departments and the application's attachment travel the same way, and the
importer still reads an archive written before any of this.
Wikidata knows most of the ownership edges (
P749,P355) and #42 means many companiesalready carry an identifier — but #55 deliberately leaves suggesting a parent out, and this
should too. The relation gets recorded by hand first.
As a plugin, this owns three things: a column on someone else's model (
Company.parent),a new model (
Department), and possibly a column onApplication. A plugin adding a fieldto a core model is a shape no plugin here has yet — the phone numbers feature added a table
and a
GenericRelation, which is not the same thing — and #126 and #128 are where that getssettled.
Done in
e2c74cc5, the last of three: #55 gave a company a parent, #137 gave acompany departments, and this is the attachment, the switch, and the counting.
The decision the issue turns on
Shape 1 — the finer attachment enriches the existing one. Your own plugin-toggle
analysis settles it, and I have made that argument a test rather than a paragraph:
So
JobPosting.companystays not nullable andApplication.departmentis an optionaladdition beside it.
test_off_leaves_every_application_with_an_employerasserts exactlythat consequence.
"Attached to any of the four" is three fields, not four
Worth saying plainly, because it changes what needed building:
posting.company— a company at any height of the treeposting.company— the same column, a different rowApplication.departmentApplication.contact, already thereA posting's company is the company applied to, whatever height it sits at. The tree turns
"which group" into a question you can ask, rather than into a fourth place to attach. Only
the department was actually missing.
Three edges, kept apart
As you set out: ownership nests arbitrarily, internal structure does not, membership is one
link. Three fields, each refusing what does not belong on it —
test_the_three_edges_are_three_fieldsandtest_ownership_nests_and_the_other_two_do_nothold that shape.
A department anywhere in the employer's group may be named, not only at the company on
the posting: an application through the Irish arm can be for the group's engineering team,
and refusing that would make the tree decorative. Anywhere outside it is refused in a
sentence naming the employer it is actually at, rather than silently cleared.
Every count answers group-or-node
{group} group beside the heading where there is a group — naming the company each posting
is at when it is showing more than one. In the address, so a page counting a whole group is
a thing to bookmark and send.
the other half of the same mistake, so this company stays what it has always been.
tree, grandchildren included. Each row still counts itself, which is true and visible.
That last is a walk rather than a join: a chain is arbitrarily deep and the cap is ten,
so expressing "anywhere in this group" as a lookup would be ten outer joins on every row of
every page. Two small queries instead, and an unknown name narrows to nothing rather than to
everything.
As a plugin
employer-structure, afeatureplugin, on by default so no upgrade takes anythingaway. Off is exactly what Postulo did before any of this existed — one company per posting,
contacts hanging directly off it — and deletes nothing: the parent links, the departments
and the attachments stay where they are and come back untouched. The gate lives in
jobs/structure.pyso "off shows the company alone" is one sentence in the code as well asin the interface.
It adds no column to a core model on its own account — the models are Postulo's, because
they are a person's data and Postulo does the ownership scoping. What the plugin owns is the
answer to is this offered.
Two of your concerns, resolved differently than expected
"Merging companies exists and gets harder." It does not, because it does not exist: only
Industryhas a merge with, and companies have no merge at all. Nothing to make harder.If a company merge is wanted, it is its own issue and the tree is one more thing it will
have to answer for.
The export surfaced a bug older than this change. A department travelled only as a name
beside a contact — so a team with nobody in it vanished from the archive entirely, and a
team you applied to before you knew anybody there is exactly the ordinary case #137 was
written for. Departments are records of their own in the file now.
test_a_team_nobody_is_recorded_at_survives_the_archivewas written after the new attachmentfailed to restore and the reason turned out to be that, rather than anything in this issue.
FORMAT_VERSIONis 13; the attachment travels by department name and by the companyholding it, resolved in a second pass after the ownership tree for the same reason that one
needs a second pass. Every earlier format still imports.
Also
tests/test_employer_structure.py(34). Suite 4739 passed, 29 skipped; browser suite 86passed. Twelve strings — ten core and the plugin's own two — filled in all 39 European
catalogues, in the plugin's own
locale/for its half. Wiki: three new sections underTracking applications.
Shipped on
0.3.0, withmainkept level.