Assignments Procedures
1. Executive Summary
This document specifies the step-by-step data mutation procedures for the assignments domain. Each procedure describes the exact sequence of database writes — inserts, updates, and history entries — required to create, transfer, transition, and cancel assignment records.
The assignments domain supports two categories of mutations:
Responsibility mutations — creating a new responsibility assignment for a business entity and transferring that responsibility between users. These operations enforce the one-active-responsibility-per-entity invariant through application-layer checks and use a deactivate-then-create pattern for transfers.
Task mutations — creating task assignments, transitioning tasks through a forward-only status lifecycle, updating task fields (title, assignee, due date), and cancelling sibling tasks when one is completed.
All mutations produce an immutable audit trail in the assignment_history table. No assignment mutation completes without a corresponding history entry.
Tables modified: assignment, assignment_history
2. Key Procedures
2.1 Create Responsibility Assignment
Operation: createResponsibility
Trigger: User explicitly assigns a single accountable person to a business entity (department, client, buyer, deal, meta-data pair, sales item, or payment term) for the first time.
Input Parameters:
entity_type_cd: String (required) —assignment.entity_type_cd; must be a responsibility-eligible type (DEPARTMENT,CLIENT,BUYER,DEAL,META_DATA_PAIR,SALES_ITEM,PAYMENT_TERM)entity_id: Integer (conditional) —assignment.entity_id; required for integer-keyed entity typesentity_reference: String (conditional) —assignment.entity_reference; required for string-referenced entity typesmeta_data_type_cd: String (conditional) —assignment.meta_data_type_cd; required whenentity_type_cd=META_DATA_PAIRmeta_data_value: String (conditional) —assignment.meta_data_value; required whenentity_type_cd=META_DATA_PAIRmeta_data_date_value: Date (optional) —assignment.meta_data_date_valueassigned_to_user_id: Integer (required) —assignment.assigned_to_user_id; FK tousers.user_idactor_id: String (required) — identifier for the user performing the operation
Assigns a single accountable user to a business entity at a specific hierarchy level. Enforces the constraint that exactly one active responsibility may exist per entity.
Step 1. Check for Existing Active Responsibility
- Source:
assignmenttable. - Action: SELECT from
assignment. - Logic:
- Query for a record matching all of:
entity_type_cd= inputentity_type_cd, entity key match (per polymorphic key pattern in Section 3.7),assignment_type_cd='RESPONSIBILITY',is_active_ind=true. - If a matching record exists, reject the operation with an error directing the caller to use the Transfer Responsibility procedure (Section 2.2) instead.
- Query for a record matching all of:
Step 2. Insert New Assignment Record
- Source: Input parameters.
- Action: INSERT into
assignment. - Logic:
- Set
assignment_id= new random UUID. - Set
assignment_type_cd='RESPONSIBILITY'. - Set
entity_type_cd= inputentity_type_cd. - Set
entity_id= inputentity_id(null if not applicable). - Set
entity_reference= inputentity_reference(null if not applicable). - Set
meta_data_type_cd= inputmeta_data_type_cd(null unlessentity_type_cd=META_DATA_PAIR). - Set
meta_data_value= inputmeta_data_value(null unlessentity_type_cd=META_DATA_PAIR). - Set
meta_data_date_value= inputmeta_data_date_value(null if not provided). - Set
assigned_to_user_id= inputassigned_to_user_id. - Set
task_status_cd=NULL. - Set
task_title=NULL. - Set
start_dt=NULL. - Set
end_dt=NULL. - Set
is_active_ind=true. - Set
created_by,updated_by= acting user identifier; setcreated_dt,updated_dt= current timestamp.
- Set
Step 3. Record History Entry
- Source: The
assignment_idfrom Step 2. - Action: INSERT into
assignment_history. - Logic:
- Set
assignment_history_id= new random UUID. - Set
assignment_id= theassignment_idfrom Step 2. - Set
action_cd='ASSIGNED'. - Set
from_user_id=NULL(initial assignment has no predecessor). - Set
to_user_id= inputassigned_to_user_id. - Set
from_status_cd=NULL,to_status_cd=NULL. - Set
comment_text=NULL. - Set
action_by_user_id= the user performing the action. - Set
action_dt= current timestamp.
- Set
Side-effects:
- None beyond the
assignmentandassignment_historyinserts described above.
Postconditions:
- Exactly one active responsibility exists for the given entity.
- One new row in
assignmentwithis_active_ind=true. - One new row in
assignment_historywithaction_cd='ASSIGNED'.
2.2 Transfer Responsibility Assignment
Operation: transferResponsibility
Trigger: User transfers ownership of a responsibility from the current holder to a new user.
Input Parameters:
assignment_id: UUID (required) —assignment.assignment_id; must identify an activeRESPONSIBILITYrecordnew_user_id: Integer (required) —users.user_id; must differ from the currentassigned_to_user_idactor_id: Integer (required) — identifier for the user performing the operationreason: String (optional) — free-text transfer reason stored inassignment_history.comment_text
Transfers ownership of a responsibility from the current holder to a new user using a two-phase deactivate-then-create pattern. Both phases produce history entries.
Step 1. Validate the Existing Assignment
- Source:
assignment(record matchingassignment_id). - Action: SELECT from
assignment. - Logic:
- Reject if the assignment does not exist.
- Reject if
assignment_type_cd!='RESPONSIBILITY'. - Reject if
is_active_ind=false. - Reject if the input
new_user_idequals the currentassigned_to_user_id. - Capture the current
assigned_to_user_idasprevious_user_id.
Step 2. Deactivate the Old Assignment
- Source:
assignment(the existing record identified in Step 1). - Action: UPDATE
assignment. - Logic:
- Set
is_active_ind=false. - Set
updated_by= acting user identifier. - Set
updated_dt= current timestamp.
- Set
Step 3. Record DEACTIVATED History on the Old Assignment
- Source: The original
assignment_id(now deactivated). - Action: INSERT into
assignment_history. - Logic:
- Set
assignment_history_id= new random UUID. - Set
assignment_id= the originalassignment_id. - Set
action_cd='DEACTIVATED'. - Set
from_user_id=previous_user_id. - Set
to_user_id=NULL. - Set
from_status_cd=NULL,to_status_cd=NULL. - Set
comment_text= optional transfer reason. - Set
action_by_user_id= the user performing the action. - Set
action_dt= current timestamp.
- Set
Step 4. Create the New Assignment
- Source: The deactivated
assignmentrecord from Step 2 (entity-identifying fields are copied verbatim). - Action: INSERT into
assignment. - Logic:
- Set
assignment_id= new random UUID. - Set
assignment_type_cd='RESPONSIBILITY'. - Copy
entity_type_cd,entity_id,entity_reference,meta_data_type_cd,meta_data_value,meta_data_date_valuefrom the old record. - Set
assigned_to_user_id= inputnew_user_id. - Set
task_status_cd=NULL,task_title=NULL,start_dt=NULL,end_dt=NULL. - Set
is_active_ind=true. - Set
created_by,updated_by= acting user identifier; setcreated_dt,updated_dt= current timestamp.
- Set
Step 5. Record REASSIGNED History on the New Assignment
- Source: The
assignment_idfrom Step 4 (the new record). - Action: INSERT into
assignment_history. - Logic:
- Set
assignment_history_id= new random UUID. - Set
assignment_id= theassignment_idfrom Step 4. - Set
action_cd='REASSIGNED'. - Set
from_user_id=previous_user_id. - Set
to_user_id= inputnew_user_id. - Set
from_status_cd=NULL,to_status_cd=NULL. - Set
comment_text= optional transfer reason. - Set
action_by_user_id= the user performing the action. - Set
action_dt= current timestamp.
- Set
Side-effects:
- The old
assignmentrecord is preserved as an inactive historical record withis_active_ind=false.
Postconditions:
- The old assignment record has
is_active_ind=falsewith aDEACTIVATEDhistory entry. - A new assignment record exists with
is_active_ind=truefor the new user. - The new assignment has a
REASSIGNEDhistory entry capturing bothfrom_user_idandto_user_id. - The one-active-responsibility-per-entity invariant is maintained.
IMPORTANT
The transfer creates a new assignment row rather than updating the existing one. This preserves the full chain of ownership: every past holder has their own assignment record (inactive) with its own history entries. The current holder is always the single record where assignment_type_cd = 'RESPONSIBILITY' and is_active_ind = true.
2.3 Create Task Assignment
Operation: createTask
Trigger: User creates a one-off work item tied to a transactional or organizational entity.
Input Parameters:
entity_type_cd: String (required) —assignment.entity_type_cd; any of the ten supported typesentity_id: Integer (conditional) —assignment.entity_id; required for integer-keyed entity typesentity_reference: String (conditional) —assignment.entity_reference; required for string-referenced entity typesmeta_data_type_cd: String (conditional) —assignment.meta_data_type_cd; required whenentity_type_cd=META_DATA_PAIRmeta_data_value: String (conditional) —assignment.meta_data_value; required whenentity_type_cd=META_DATA_PAIRmeta_data_date_value: Date (optional) —assignment.meta_data_date_valueassigned_to_user_id: Integer (required) —assignment.assigned_to_user_id; FK tousers.user_idtask_title: String (required) —assignment.task_title; minimum length 1start_dt: Date (optional) —assignment.start_dtend_dt: Date (optional) —assignment.end_dtactor_id: String (required) — identifier for the user performing the operation
Creates a one-off work item tied to any supported entity type. Unlike responsibilities, multiple tasks can exist for the same entity simultaneously, and tasks carry a forward-only status lifecycle.
Step 1. Insert New Assignment Record
- Source: Input parameters.
- Action: INSERT into
assignment. - Logic:
- Set
assignment_id= new random UUID. - Set
assignment_type_cd='TASK'. - Set
entity_type_cd= inputentity_type_cd. - Set
entity_id= inputentity_id(null if not applicable). - Set
entity_reference= inputentity_reference(null if not applicable). - Set
meta_data_type_cd= inputmeta_data_type_cd(null if not applicable). - Set
meta_data_value= inputmeta_data_value(null if not applicable). - Set
meta_data_date_value= inputmeta_data_date_value(null if not provided). - Set
assigned_to_user_id= inputassigned_to_user_id. - Set
task_status_cd='OPEN'(always; not configurable on creation). - Set
task_title= inputtask_title. - Set
start_dt= inputstart_dt(null if not provided). - Set
end_dt= inputend_dt(null if not provided). - Set
is_active_ind=true. - Set
created_by,updated_by= acting user identifier; setcreated_dt,updated_dt= current timestamp. - No uniqueness check is performed — multiple tasks for the same entity are permitted.
- Set
Step 2. Record History Entry
- Source: The
assignment_idfrom Step 1. - Action: INSERT into
assignment_history. - Logic:
- Set
assignment_history_id= new random UUID. - Set
assignment_id= theassignment_idfrom Step 1. - Set
action_cd='ASSIGNED'. - Set
from_user_id=NULL. - Set
to_user_id= inputassigned_to_user_id. - Set
from_status_cd=NULL,to_status_cd=NULL. - Set
comment_text=NULL. - Set
action_by_user_id= the user performing the action. - Set
action_dt= current timestamp.
- Set
Side-effects:
- None beyond the
assignmentandassignment_historyinserts described above.
Postconditions:
- One new row in
assignmentwithtask_status_cd='OPEN'. - One new row in
assignment_historywithaction_cd='ASSIGNED'.
NOTE
Tasks are always initialized to OPEN status regardless of any input. The initial status is not configurable.
2.4 Update Task Status
Operation: updateTaskStatus
Trigger: User transitions a task assignment to a new status (e.g., begins work, completes work, marks as waiting).
Input Parameters:
assignment_id: UUID (required) —assignment.assignment_id; must identify aTASK-type recordnew_status: String (required) —assignment.task_status_cd; the desired next statusactor_id: Integer (required) — identifier for the user performing the operationreason: String (optional) — free-text reason stored inassignment_history.comment_text
Transitions a task assignment from its current status to a new status, subject to the forward-only state machine defined in Section 3.2.
Step 1. Validate the Transition
- Source:
assignment(record matchingassignment_id). - Action: SELECT from
assignment. - Logic:
- Reject if the assignment does not exist.
- Reject if
assignment_type_cd!='TASK'. - Reject if
task_status_cdis null. - Read the current
task_status_cd. - Check the requested
new_statusagainst the allowed transitions matrix (see Section 3.2). Reject if the transition is invalid.
Step 2. Update the Task Status
- Source:
assignment(record matchingassignment_id). - Action: UPDATE
assignment. - Logic:
- Set
task_status_cd= inputnew_status. - Set
updated_by= acting user identifier. - Set
updated_dt= current timestamp.
- Set
Step 3. Record History Entry
- Source: The
assignment_idand the status values captured in Step 1. - Action: INSERT into
assignment_history. - Logic:
- Set
assignment_history_id= new random UUID. - Set
assignment_id= inputassignment_id. - Set
action_cd='STATUS_CHANGED'. - Set
from_user_id=NULL,to_user_id=NULL. - Set
from_status_cd= the previoustask_status_cd. - Set
to_status_cd= inputnew_status. - Set
comment_text= optional reason for the transition. - Set
action_by_user_id= the user performing the action. - Set
action_dt= current timestamp.
- Set
Side-effects:
- None beyond the
assignmentupdate andassignment_historyinsert described above.
Postconditions:
- The assignment's
task_status_cdreflects the new status. - One new row in
assignment_historywithaction_cd='STATUS_CHANGED'.
2.5 Update Task Fields
Operation: updateTask
Trigger: User edits one or more fields on an existing task (title, assignee, and/or due date).
Input Parameters:
assignment_id: UUID (required) —assignment.assignment_id; must identify aTASK-type recordtask_title: String (optional) —assignment.task_title; new task titleassigned_to_user_id: Integer (optional) —assignment.assigned_to_user_id; new assigneeend_dt: Date or null (optional) —assignment.end_dt; new due date (explicit null clears the field)actor_id: Integer (required) — identifier for the user performing the operation
Modifies editable fields on a task assignment. If the assignee changes, a REASSIGNED history entry is recorded. Changes to title and due date alone do not produce a history entry.
Step 1. Capture the Current Assignee
- Source:
assignment(record matchingassignment_id). - Action: SELECT from
assignment. - Logic:
- Reject if the assignment does not exist.
- Reject if
assignment_type_cd!='TASK'. - Read the current
assigned_to_user_idand store asold_user_id.
Step 2. Update the Assignment Record
- Source: Input parameters.
- Action: UPDATE
assignment. - Logic:
- If
task_titleprovided: setassignment.task_title= inputtask_title. - If
assigned_to_user_idprovided: setassignment.assigned_to_user_id= inputassigned_to_user_id. - If
end_dtprovided (including explicit null): setassignment.end_dt= inputend_dt. - Always set
updated_by= acting user identifier. - Always set
updated_dt= current timestamp.
- If
Step 3. Conditionally Record Reassignment History
- Source: The
assignment_idandold_user_idfrom Step 1. - Action: INSERT into
assignment_history(conditional). - Logic:
- Only execute this step if
assigned_to_user_idwas provided AND differs fromold_user_id. - Set
assignment_history_id= new random UUID. - Set
assignment_id= inputassignment_id. - Set
action_cd='REASSIGNED'. - Set
from_user_id=old_user_id. - Set
to_user_id= inputassigned_to_user_id. - Set
from_status_cd=NULL,to_status_cd=NULL. - Set
comment_text='Task reassigned via edit'. - Set
action_by_user_id= the user performing the action. - Set
action_dt= current timestamp.
- Only execute this step if
Side-effects:
- None beyond the conditional
assignment_historyinsert described in Step 3.
Postconditions:
- The assignment record reflects the updated fields.
- If the assignee changed, one new row in
assignment_historywithaction_cd='REASSIGNED'. - If only title or due date changed, no history entry is created.
NOTE
PoC Artifact: Changes to task_title and end_dt alone do not produce a history entry. Only a change in assigned_to_user_id triggers a REASSIGNED history record. This is a PoC simplification; the production system may choose to audit all field changes.
2.6 Cancel Sibling Tasks
Operation: cancelSiblingTasks
Trigger: A task has been completed; the caller explicitly invokes this procedure to cancel all other active tasks for the same entity.
Input Parameters:
assignment_id: UUID (required) —assignment.assignment_id; the completed task used as the referenceactor_id: Integer (required) — identifier for the user performing the operationreason: String (optional) — free-text cancellation reason stored inassignment_history.comment_text
Cancels all other active tasks for the same entity when a task is completed, preventing duplicate work when multiple users hold assignments for the same work item. Returns the count of tasks cancelled.
Step 1. Load the Reference Task
- Source:
assignment(record matchingassignment_id). - Action: SELECT from
assignment. - Logic:
- Reject if the assignment does not exist.
- Read
entity_type_cdand entity key (entity_idorentity_reference) from the record.
Step 2. Find Sibling Tasks
- Source:
assignmenttable. - Action: SELECT from
assignment. - Logic:
- Query for all records matching: same
entity_type_cdand entity key as the reference task,assignment_type_cd='TASK',assignment_id!= the inputassignment_id(exclude the triggering task).
- Query for all records matching: same
Step 3. Cancel Each Eligible Sibling
- Source: Each sibling task record from Step 2.
- Action: For each eligible sibling, UPDATE
assignmentand INSERT intoassignment_history. - Logic:
- For each sibling, check whether its current
task_status_cdpermits a transition to'CANCELLED'per the transition matrix in Section 3.2. Only siblings inOPEN,WORKING, orWAITINGare eligible. Skip siblings already inCOMPLETEorCANCELLED. - For each eligible sibling, UPDATE
assignment: settask_status_cd='CANCELLED', setupdated_by= acting user identifier, setupdated_dt= current timestamp. - For each eligible sibling, INSERT into
assignment_history: setassignment_history_id= new random UUID, setassignment_id= the sibling'sassignment_id, setaction_cd='CANCELLED', setfrom_user_id=NULL,to_user_id=NULL, setfrom_status_cd= the sibling's previoustask_status_cd, setto_status_cd='CANCELLED', setcomment_text= inputreasonor default'Cancelled: sibling task {reference_assignment_id} was completed', setaction_by_user_id= the user performing the action, setaction_dt= current timestamp. - Increment a count for each sibling cancelled.
- For each sibling, check whether its current
Side-effects:
- One
CANCELLEDhistory entry created per cancelled sibling inassignment_history.
Postconditions:
- All eligible sibling tasks now have
task_status_cd='CANCELLED'. - One
'CANCELLED'history entry exists per cancelled sibling. - Tasks already in
COMPLETEorCANCELLEDstatus are unaffected. - Returns the count of sibling tasks cancelled.
IMPORTANT
Sibling cancellation is not automatic upon task completion. It is a separate, explicitly invoked procedure. The caller decides whether to trigger sibling cancellation after a task is marked COMPLETE. This keeps the completion and cancellation concerns decoupled.
2.7 Hierarchy Resolution for Responsibility Assignments
Operation: resolveResponsibleUser
Trigger: The system needs to determine the accountable person for an entity by walking up the four-level responsibility hierarchy.
Input Parameters:
sales_item_ref: String (optional) —assignment.entity_referenceat Level 4; sales item referencepayment_term_ref: String (optional) —assignment.entity_referenceat Level 4; payment term referencemeta_data: Array (optional) — array of{type_cd, value}pairs forMETA_DATA_PAIRlookups at Level 3deal_reference: String (optional) —assignment.entity_referenceat Level 3; deal reference stringclient_id: Integer (optional) —assignment.entity_idat Level 2; client party IDbuyer_id: Integer (optional) —assignment.entity_idat Level 2; buyer party IDdepartment_id: Integer (optional) —assignment.entity_idat Level 1; department ID
Resolves the responsible person for a given entity by walking the hierarchy from most specific (Level 4) to broadest (Level 1). Short-circuits on the first active responsibility match found. This is a read-only procedure that does not mutate data.
Step 1. Execute Hierarchy Walk Lookups
- Source:
assignmenttable. - Action: Up to seven SELECT queries on
assignment, executed in strict order; short-circuit on first match. - Logic:
- Each lookup queries
assignmentWHEREentity_type_cd= the step's entity type, entity key matches per the polymorphic pattern,assignment_type_cd='RESPONSIBILITY',is_active_ind=true. - Execute lookups in this order, skipping any step where the corresponding input field is not provided:
- Level 4:
SALES_ITEM—entity_reference=sales_item_ref - Level 4:
PAYMENT_TERM—entity_reference=payment_term_ref - Level 3:
META_DATA_PAIR—meta_data_type_cd+meta_data_valueper each entry inmeta_dataarray - Level 3:
DEAL—entity_reference=deal_reference - Level 2:
CLIENT—entity_id=client_id - Level 2:
BUYER—entity_id=buyer_id - Level 1:
DEPARTMENT—entity_id=department_id
- Level 4:
- Stop and return the result upon the first match.
- Each lookup queries
Side-effects:
- None. This is a read-only procedure.
Postconditions:
- If a match is found: returns
assigned_to_user_id,assigned_to_user_name,resolved_from_entity_type_cd,resolved_from_level(1–4), andassignment_idof the matched record. - If no match is found at any level: returns a "no responsible person found" result; the entity is considered unassigned.
NOTE
Task-level entity types (CASH_RECEIPT, CASH_RECEIPT_SPLIT, PAYMENT) do not participate in the hierarchy. They exist outside the resolution chain. Responsibility resolution applies only to the organizational hierarchy (Levels 1–4).
2.8 Responsibility Chain Resolution
Operation: resolveResponsibilityChain
Trigger: The system or user needs to view the complete responsibility picture across all hierarchy levels for a given entity, showing which level has an active owner and which levels are unassigned.
Input Parameters:
entity_type_cd: String (required) — the entity type to start from (e.g.,DEPARTMENT,CLIENT,DEAL,SALES_ITEM,PAYMENT_TERM)entity_key: String or Integer (required) — theentity_idorentity_referenceof the starting entity
Builds the complete responsibility chain for an entity, showing every hierarchy level from broadest (Level 1) to most specific (Level 4). Unlike the walk-up resolution in Section 2.7, this procedure does not short-circuit — it queries every applicable level and returns the full picture. This is a read-only procedure that does not mutate data.
Step 1. Resolve Parent Entities
- Source: Financial data model tables (
department,party,deal,billing_item,revenue_items). - Action: SELECT from the appropriate parent tables based on the starting entity type.
- Logic:
- Based on
entity_type_cd, query the parent entity chain to resolve all applicable hierarchy levels:DEPARTMENT: querydepartmentfordepartment_id,department_name.CLIENT: querypartyfor client name, thenbilling_itemanddepartmentfor department.BUYER: querypartyfor buyer name, thenbilling_itemanddepartmentfor department.DEAL: querydealfor deal reference and name, thenbilling_item,party, anddepartmentfor client, buyer, and department.SALES_ITEM: queryrevenue_itemsfor sales item reference, thendeal,party, anddepartmentfor deal, client, buyer, and department.PAYMENT_TERM: querybilling_itemfor payment term reference, thendeal,party, anddepartmentfor deal, client, buyer, and department.
- Based on
Step 2. Execute Per-Level Responsibility Lookups
- Source:
assignmenttable, queried once per resolved hierarchy level. - Action: For each resolved parent entity, SELECT from
assignmentusing the same query pattern as Section 2.7, Step 1. - Logic:
- For each resolved parent entity, execute a Find Active Responsibility lookup (query
assignmentWHERE entity type and key match,assignment_type_cd='RESPONSIBILITY',is_active_ind=true). - Build an ordered array of chain levels, each containing:
level(1–4),entity_type_cd, entity key,entity_label(human-readable display name),assignment(the active responsibility at this level or null if unassigned),is_selected_level(whether this level corresponds to the entity originally queried). - The "effective" responsible person is the most specific level (highest level number) that has an active assignment.
- For each resolved parent entity, execute a Find Active Responsibility lookup (query
Side-effects:
- None. This is a read-only procedure.
Postconditions:
- Returns an ordered array of chain levels from Level 1 (broadest) to Level 4 (most specific), each with the active responsibility at that level (or null if unassigned).
- The "effective" responsible person is identified as the assignment at the most specific populated level.
NOTE
PoC Artifact: Parent entity resolution in the PoC uses direct database queries within a server action. In production, this resolution should be encapsulated in a dedicated service or repository to maintain the layered architecture.
3. Business Rules & Logic
3.1 Assignment Type Discrimination
Business rule: The assignment_type_cd field on assignment acts as a discriminator governing which fields are relevant and which business rules apply. A single table stores both tasks and responsibilities; the type code determines which subset of columns is active.
Data-level enforcement:
- Read:
assignment.assignment_type_cd— value must be'TASK'or'RESPONSIBILITY'. - Guard: For
TASKtype: reject iftask_titleis absent or empty; reject status transitions on responsibility records. ForRESPONSIBILITYtype: reject iftask_status_cdis non-null on creation; reject task-specific edits. - Write: See the following discriminator behavior table enforced in the service layer:
| Aspect | TASK | RESPONSIBILITY |
|---|---|---|
task_status_cd | Required; initialized to 'OPEN' | Must be NULL |
task_title | Required; minimum length 1 | Must be NULL |
start_dt, end_dt | Optional date fields | Not used |
is_active_ind | Always true (lifecycle via status) | true for current, false for superseded |
| Uniqueness per entity | Multiple allowed | Exactly one active |
| Hierarchy participation | Task-level types do not participate | Levels 1–4 participate in walk-up resolution |
3.2 Task Status Transition Rules
Business rule: Task status transitions are forward-only. Once a task reaches a terminal state (COMPLETE or CANCELLED), no further transitions are permitted.
Data-level enforcement:
- Read:
assignment.task_status_cd— current status before any transition. - Guard: If the requested transition is not in the matrix below, reject the operation with an error.
- Write: Set
assignment.task_status_cdto the new status on successful validation.
| Current Status | Permitted Next Statuses |
|---|---|
OPEN | WORKING, CANCELLED |
WORKING | WAITING, COMPLETE, CANCELLED |
WAITING | WORKING, CANCELLED |
COMPLETE | (none — terminal) |
CANCELLED | (none — terminal) |
3.3 Responsibility Uniqueness Invariant
Business rule: Exactly one active RESPONSIBILITY assignment may exist per (entity_type_cd, entity key) combination at any time. If an active responsibility already exists, the operation to create a new one is rejected; users must transfer instead.
Data-level enforcement:
- Read:
assignmentWHEREentity_type_cd= input type, entity key matches,assignment_type_cd='RESPONSIBILITY',is_active_ind=true. - Guard: If any matching record exists, reject the Create Responsibility operation with a message directing the caller to use Transfer Responsibility.
- Write: Proceed with INSERT only when no active responsibility is found.
IMPORTANT
This constraint is enforced at the application layer, not as a database unique index, because the composite key varies by entity type: integer-keyed entities use entity_type_cd + entity_id; string-referenced entities use entity_type_cd + entity_reference; meta-data pair entities use entity_type_cd + meta_data_type_cd + meta_data_value.
3.4 Transfer Mechanics: Deactivate-Then-Create
Business rule: Responsibility transfers never update the existing record in place. The old record is deactivated and a new record is created. This design ensures that every past holder has their own assignment row with its own history entries, and the complete chain of ownership is always recoverable.
Data-level enforcement:
- Read: Current
assignmentrecord to capture entity-identifying fields andprevious_user_id. - Guard: Reject if the assignment is not of type
RESPONSIBILITY, is already inactive, or if the new assignee equals the current assignee. - Write: UPDATE existing record
is_active_ind=false; INSERT new record withis_active_ind=trueand all entity fields copied verbatim from the old record.
3.5 Sibling Task Cancellation
Business rule: When a task is completed, the system supports cancelling all other active tasks for the same entity. This is an opt-in operation, not automatic. Only siblings whose current task_status_cd permits a transition to CANCELLED are affected.
Data-level enforcement:
- Read:
assignmentWHERE sameentity_type_cd, same entity key,assignment_type_cd='TASK',assignment_id!= the completed task. - Guard: Per sibling, check the status transition matrix. Skip siblings already in
COMPLETEorCANCELLED. - Write: UPDATE eligible siblings to
task_status_cd='CANCELLED'; INSERTassignment_historyper sibling.
3.6 History Immutability
Business rule: The assignment_history table is append-only. Records are never updated or deleted through normal operations. Every mutation described in this document produces at least one history entry. The complete and tamper-resistant audit trail is a design guarantee.
Data-level enforcement:
- Read: Not applicable.
- Guard: No UPDATE or DELETE operations are permitted on
assignment_historyin standard workflows. The cascade-delete foreign key onassignment_history.assignment_idexists as a safety mechanism but is not exercised in standard workflows. - Write: INSERT only. Every procedure in Section 2 that mutates
assignmentalso inserts a correspondingassignment_historyrecord.
3.7 Entity Key Polymorphism
Business rule: The assignment table uses a polymorphic association pattern. The entity key is stored differently depending on entity_type_cd. Application-layer validation must ensure that entity keys reference valid records; these are logical associations, not database-enforced foreign keys.
Data-level enforcement:
- Read:
assignment.entity_type_cddetermines which key field to use. - Guard: Reject if none of
entity_id,entity_reference, or (meta_data_type_cd+meta_data_value) is populated for the given entity type. - Write: Populate only the appropriate key field(s) per entity type:
| Entity Type | Key Field(s) |
|---|---|
DEPARTMENT | entity_id (integer, references department.department_id) |
CLIENT | entity_id (integer, references party.party_id) |
BUYER | entity_id (integer, references party.party_id) |
DEAL | entity_reference (string, matches deal.deal_reference) |
META_DATA_PAIR | meta_data_type_cd + meta_data_value |
SALES_ITEM | entity_reference (string, matches revenue_items.sales_item_ref) |
PAYMENT_TERM | entity_reference (string, matches billing_item.payment_term_ref) |
CASH_RECEIPT | entity_id (integer, references cash_receipt.cash_receipt_id) |
CASH_RECEIPT_SPLIT | entity_id (integer, references cash_receipt_split.cash_receipt_split_id) |
PAYMENT | entity_id (integer, references payment_item.payment_item_id) |
WARNING
These are logical associations, not database-enforced foreign keys. The polymorphic pattern trades referential integrity enforcement for flexibility. Application-layer validation must ensure that entity keys reference valid records.
3.8 Hierarchy Levels
Business rule: The four-level responsibility hierarchy determines assignment resolution order. Resolution walks from Level 4 (most specific) down to Level 1 (broadest). The first active responsibility found wins. Task-level entity types are excluded from hierarchy resolution entirely.
Data-level enforcement:
- Read:
assignment.entity_type_cddetermines which hierarchy level a record belongs to. - Guard: Reject hierarchy resolution requests for task-level entity types (
CASH_RECEIPT,CASH_RECEIPT_SPLIT,PAYMENT). - Write: Not applicable — hierarchy resolution is read-only.
| Level | Entity Types | Scope |
|---|---|---|
| Level 1 (broadest) | DEPARTMENT | Organizational unit |
| Level 2 | CLIENT, BUYER | Party relationship |
| Level 3 | DEAL, META_DATA_PAIR | Engagement or classification |
| Level 4 (most specific) | SALES_ITEM, PAYMENT_TERM | Line-item granularity |
| Task-level (no hierarchy) | CASH_RECEIPT, CASH_RECEIPT_SPLIT, PAYMENT | Transactional work items |
4. Field Mapping & Transformation
4.1 Create Responsibility: Input to assignment Mapping
| Source Table.Field | Target Table.Field | Transform |
|---|---|---|
Input entity_type_cd | assignment.entity_type_cd | Copied as-is; must be a responsibility-eligible type |
Input entity_id | assignment.entity_id | Copied as-is; null for string-referenced and meta-data entities |
Input entity_reference | assignment.entity_reference | Copied as-is; null for integer-keyed and meta-data entities |
Input meta_data_type_cd | assignment.meta_data_type_cd | Copied as-is; null unless entity_type_cd = 'META_DATA_PAIR' |
Input meta_data_value | assignment.meta_data_value | Copied as-is; null unless entity_type_cd = 'META_DATA_PAIR' |
Input meta_data_date_value | assignment.meta_data_date_value | Copied as-is; optional |
Input assigned_to_user_id | assignment.assigned_to_user_id | Copied as-is; FK to users.user_id |
| — | assignment.assignment_type_cd | System-generated; hardcoded to 'RESPONSIBILITY' |
| — | assignment.is_active_ind | System-generated; hardcoded to true |
| — | assignment.task_status_cd | System-generated; hardcoded to NULL |
| — | assignment.task_title | System-generated; hardcoded to NULL |
| — | assignment.assignment_id | System-generated (new UUID) |
4.2 Create Task: Input to assignment Mapping
| Source Table.Field | Target Table.Field | Transform |
|---|---|---|
Input entity_type_cd | assignment.entity_type_cd | Copied as-is; any of the ten supported types |
Input entity_id | assignment.entity_id | Copied as-is; null for string-referenced and meta-data entities |
Input entity_reference | assignment.entity_reference | Copied as-is; null for integer-keyed and meta-data entities |
Input meta_data_type_cd | assignment.meta_data_type_cd | Copied as-is; null unless entity_type_cd = 'META_DATA_PAIR' |
Input meta_data_value | assignment.meta_data_value | Copied as-is; null unless entity_type_cd = 'META_DATA_PAIR' |
Input meta_data_date_value | assignment.meta_data_date_value | Copied as-is |
Input assigned_to_user_id | assignment.assigned_to_user_id | Copied as-is; FK to users.user_id |
Input task_title | assignment.task_title | Copied as-is; required, minimum length 1 |
Input start_dt | assignment.start_dt | Copied as-is; optional |
Input end_dt | assignment.end_dt | Copied as-is; optional |
| — | assignment.assignment_type_cd | System-generated; hardcoded to 'TASK' |
| — | assignment.task_status_cd | System-generated; hardcoded to 'OPEN' |
| — | assignment.is_active_ind | System-generated; hardcoded to true |
| — | assignment.assignment_id | System-generated (new UUID) |
4.3 Transfer Responsibility: Old-to-New Assignment Mapping
When creating the replacement assignment during a transfer, entity-identifying fields are copied verbatim from the deactivated record.
| Source Table.Field | Target Table.Field | Transform |
|---|---|---|
assignment.entity_type_cd (old record) | assignment.entity_type_cd | Copied verbatim |
assignment.entity_id (old record) | assignment.entity_id | Copied verbatim |
assignment.entity_reference (old record) | assignment.entity_reference | Copied verbatim |
assignment.meta_data_type_cd (old record) | assignment.meta_data_type_cd | Copied verbatim |
assignment.meta_data_value (old record) | assignment.meta_data_value | Copied verbatim |
assignment.meta_data_date_value (old record) | assignment.meta_data_date_value | Copied verbatim |
Input new_user_id | assignment.assigned_to_user_id | New user ID from input |
| — | assignment.is_active_ind | System-generated; hardcoded to true |
| — | assignment.assignment_type_cd | System-generated; hardcoded to 'RESPONSIBILITY' |
| — | assignment.assignment_id | System-generated (new UUID) |
4.4 History Entry: Action-to-Field Mapping
Each action_cd value populates different fields on the assignment_history record.
action_cd | from_user_id | to_user_id | from_status_cd | to_status_cd | comment_text |
|---|---|---|---|---|---|
ASSIGNED | NULL | Assignee | NULL | NULL | NULL |
REASSIGNED | Previous owner | New owner | NULL | NULL | Optional reason |
STATUS_CHANGED | NULL | NULL | Previous status | New status | Optional reason |
DEACTIVATED | Previous owner | NULL | NULL | NULL | Optional transfer reason |
CANCELLED | NULL | NULL | Previous status | 'CANCELLED' | Cancellation reason or sibling reference |
5. Cross-References
| Document | Relationship |
|---|---|
| Assignments Data Model | Full schema definitions, status lifecycles, validation rules, and code master values for the assignment and assignment_history tables. The data model document is the authoritative reference for table structure; this procedures document describes the operations that mutate those tables. |
| Assignments Queries | Read operations that support and complement the procedures documented here, including Find Active Responsibility (used as a building block in hierarchy resolution and uniqueness checking), Find Sibling Tasks (used in the cancellation procedure), and the gap-detection queries that identify entities needing assignment. |
| Cash Receipts Data Model | Task assignments can be created for CASH_RECEIPT (assignment.entity_id → cash_receipt.cash_receipt_id) and CASH_RECEIPT_SPLIT (assignment.entity_id → cash_receipt_split.cash_receipt_split_id) entities. Receipt ingestion and split creation are upstream events that may trigger task creation. |
| Settlements Data Model | Payment items created from settlements may have task assignments for follow-up processing. The PAYMENT entity type on assignments references payment_item records via assignment.entity_id → payment_item.payment_item_id. |
| Deals, Sales Items & Payment Terms Data Model | Responsibility assignments at Level 3 (DEAL) reference deals by assignment.entity_reference matching deal.deal_reference. Parent entity resolution for chain building queries the deal table. SALES_ITEM assignments use assignment.entity_reference matching revenue_items.sales_item_ref; PAYMENT_TERM assignments use assignment.entity_reference matching billing_item.payment_term_ref. |
| Parties Data Model | Responsibility assignments at Level 2 (CLIENT, BUYER) reference parties by assignment.entity_id matching party.party_id. The assignment.assigned_to_user_id field references the users table (internal staff), not the party table (external entities). |
| Billing Items Data Model | Parent entity resolution for the responsibility chain queries billing_item to navigate from a client or deal up to a department. The PAYMENT_TERM entity type references billing_item.payment_term_ref. |
Procedure Dependency Graph
The following shows how procedures depend on each other and on query operations:
createResponsibility (2.1)
depends on: findActiveResponsibility query (uniqueness check)
writes to: assignment (INSERT), assignment_history (INSERT)
transferResponsibility (2.2)
depends on: getAssignmentById query (validation)
writes to: assignment (UPDATE old), assignment (INSERT new),
assignment_history (INSERT x2: DEACTIVATED + REASSIGNED)
createTask (2.3)
writes to: assignment (INSERT), assignment_history (INSERT)
updateTaskStatus (2.4)
depends on: getAssignmentById query (validation + current status)
depends on: Status Transition Matrix validation (Section 3.2)
writes to: assignment (UPDATE), assignment_history (INSERT)
updateTask (2.5)
depends on: getAssignmentById query (capture old assignee)
writes to: assignment (UPDATE), assignment_history (INSERT, conditional)
cancelSiblingTasks (2.6)
depends on: getAssignmentById query (load reference task)
depends on: findSiblingTasks query
depends on: updateTaskStatus logic (per sibling, validates transitions)
writes to: assignment (UPDATE per sibling), assignment_history (INSERT per sibling)
resolveResponsibleUser (2.7)
depends on: findActiveResponsibility query (up to 7 lookups)
writes to: nothing (read-only)
resolveResponsibilityChain (2.8)
depends on: parent entity resolution (financial data model queries)
depends on: findActiveResponsibility query (per level)
writes to: nothing (read-only)NOTE
PoC Artifact: The PoC does not implement automatic task creation from upstream events. All task creation is user-initiated through the assignments UI. In the production system, the following events should trigger automatic task assignment:
- New cash receipt ingested — create a
CASH_RECEIPTtask for the responsible user (resolved via hierarchy from receipt references). - New cash receipt split created — create a
CASH_RECEIPT_SPLITtask. - Payment hold or failure — create a
PAYMENTtask for follow-up.
The hierarchy resolution procedure (Section 2.7) provides the mechanism for determining who to assign these auto-created tasks to. The chain resolution (Section 2.8) provides the reference-to-hierarchy mapping needed to populate the hierarchy context from receipt reference data.