Skip to main content

BEHAVIOUR — Service of the Nota de Culpa (module: chargesdelivery)

Scope: #59 / PRD-006 SYS-REQ-301, 302. Phase 3/8 (CHARGES_ISSUED). Recording how and when the nota de culpa reached the arguido, and stamping chargesDeliveredAt — the anchor the Resposta clock runs from. Sibling of behaviour-case-charges-review (#58): that one owns the gate, this one owns the service, and the Consulta exit is guarded by both.

Why service is a guard and not a formality. Arts. 353.º/354.º require the nota de culpa in writing and its delivery to the worker; the response window in art. 355.º/1 runs from that delivery. A case file disclosed to someone who was never put on notice has no clock running and no defensible record — which is the first thing a challenge attacks. Hence chargesNotDelivered blocks OpenConsultationPortal, and is not overridable: service is a fact about the arguido's rights, and no seniority inside the employer can supply it after the event. Re-recording overwrites the anchor while the case stands in CHARGES_ISSUED (TODO(#238): confirm this against RecordProposal's once-only precedent with firm-qa).

Detection, not prevention, on the receipt itself. The system records what the firm says happened, with proof attached. It cannot verify a signature or a tracking number, so proof is mandatory but unvalidated — the guarantee is that a claim of service is always evidenced and audited, not that the evidence is true.

Service permanently interrupts caducidade (art. 353.º/3, statute — not a firm-qa question). Once notified, the arguido's right under art. 329.º can no longer lapse, so Case.Deadlines() stops reporting Caducidade from that point rather than reporting it as elapsed against a right that no longer runs.

Legal source: Código do Trabalho — disciplinary procedure (arts. 353.º, 354.º, 355.º/1).

Implementation: backend/MyLegalTeam.Domain/Cases/ (the delivery state, the guard, the Resposta rule), backend/MyLegalTeam.Application/Features/Cases/Charges/RecordChargesDelivery/ (the write slice), backend/MyLegalTeam.Application/Features/Cases/Charges/Reads/GetChargesDeliveryQuery.cs (the read), backend/MyLegalTeam.Api/Controllers/ChargesDeliveryController.cs. Tests: MyLegalTeam.Tests/Domain/CaseChargesDeliveryTests.cs, MyLegalTeam.Tests/Domain/CaseWorkflowChargesDeliveryTests.cs, MyLegalTeam.Tests/Commands/Cases/RecordChargesDeliveryCommandTests.cs, MyLegalTeam.Tests/Commands/Cases/GetChargesDeliveryQueryTests.cs, MyLegalTeam.IntegrationTests/Api/ChargesReviewEndpointHttpTests.cs.

Endpoint

POST /companies/{companyId}/cases/{caseId}/charges-delivery
req { method: in_person|registered_letter, proof, deliveredAt }
200 { caseId, chargesDeliveredAt }

GET /companies/{companyId}/cases/{caseId}/charges-delivery
200 { method, proof, deliveredAt }
404 ChargesNotDelivered — service has not been recorded yet

Unit — the aggregate and the workflow

# @BHV-chargesdelivery-unit-001 — service stamps method, proof and anchor
Scenario: A case manager records service of the nota de culpa
Given a case in CHARGES_ISSUED
When service is recorded as a registered letter with its tracking number
Then the method, the proof and the delivery date are held on the case
And a ChargesDelivered custody fact is raised

# @BHV-chargesdelivery-unit-002 — nothing to serve before the charges exist
Scenario: Service is refused before the charges are issued
Given a case still in INTAKE
When service is recorded
Then the case is unchanged and no custody fact is raised

# @BHV-chargesdelivery-unit-003 — a correction must be possible
Scenario: Re-recording service overwrites the anchor
Given a case whose service was recorded with the wrong date
When service is recorded again
Then the later delivery date is the one the clock runs from
And both attempts remain on the audit ledger

# @BHV-chargesdelivery-unit-004 — served charges open the portal
Scenario: An approved and served case reaches the consultation portal
Given a case in CHARGES_ISSUED whose gate stands approved and whose charges were served
When OpenConsultationPortal is fired
Then the case moves to CONSULTATION_PORTAL

# @BHV-chargesdelivery-unit-008 — service interrupts a DIFFERENT clock (art. 353.º/3, settled statute)
Scenario: Recording service permanently interrupts the caducidade clock
Given a case in CHARGES_ISSUED, whose caducidade clock is still reported
When service is recorded
Then the caducidade clock is no longer reported, at any later date

# @BHV-chargesdelivery-unit-005 — the anchor starts a real clock
Scenario: The Resposta deadline appears only once served
Given a case in CHARGES_ISSUED
Then no resposta deadline is reported
When service is recorded on Monday 5 January 2026
Then a resposta deadline falls due on Monday 19 January 2026
And it is 10 working days, not 10 calendar days

# @BHV-chargesdelivery-unit-006 — both wire tokens are accepted
Scenario Outline: The delivery method arrives as a snake_case token
Given a case in CHARGES_ISSUED
When a case manager records service as <token>
Then the case holds <method> and the response carries the anchor

Examples:
| token | method |
| in_person | InPerson |
| registered_letter | RegisteredLetter|

# @BHV-chargesdelivery-unit-007 — the instructor may also serve (TODO(#237): confirm with firm-qa)
Scenario: The assigned instructor records service without management standing
Given a case in CHARGES_ISSUED and its assigned instructor, holding no management role
When the instructor records service
Then the case holds the recorded delivery

Adversarial

# @BHV-chargesdelivery-adv-001 — service is a manager or instructor act (TODO(#237): confirm with firm-qa)
Scenario: A legal reviewer on the case team, neither manager nor instructor, is refused
Given a case in CHARGES_ISSUED and a caller holding only the legal-reviewer case-role
When they record service
Then the response is 403 NotAuthorizedToRecordDelivery

# @BHV-chargesdelivery-adv-002 — existence is not leaked
Scenario: A caller who cannot see the case gets a 404
Given a caller holding no case-role at all
When they record service
Then the response is 404 CaseNotFound, not a 403

# @BHV-chargesdelivery-adv-003 — the guard cannot be waived
Scenario: An unserved case cannot be advanced, and the block cannot be overridden
Given a case in CHARGES_ISSUED whose charges were never served
When OpenConsultationPortal is fired
Then it is blocked by chargesNotDelivered
And the override endpoint refuses it too

# @BHV-chargesdelivery-adv-004 — both reasons are reported at once
Scenario: An unreviewed and unserved case reports both guards
Given a case in CHARGES_ISSUED with no gate decision and no service
When OpenConsultationPortal is fired
Then both chargesReviewNotApproved and chargesNotDelivered are reported

# @BHV-chargesdelivery-adv-005 — the phase is enforced at the endpoint too
Scenario: Service before the charges are issued is a conflict
When a case manager records service on a case still in INTAKE
Then the response is 409 ChargesNotIssued

# @BHV-chargesdelivery-adv-006 — the clock cannot be started early
Scenario: A future delivery date is refused
When a case manager records service dated tomorrow
Then the response is 422 DeliveryDateInTheFuture and nothing is stamped

# @BHV-chargesdelivery-adv-007 — an unknown method never reaches the case
Scenario: An unrecognised delivery method is refused before anything is loaded
When a case manager records service with a method outside the two lawful ones
Then the response is 422 UnknownDeliveryMethod and the case is never read

# @BHV-chargesdelivery-adv-008 — a claim of service must be evidenced
Scenario: Service with no proof is rejected
When a case manager records service with an empty proof
Then the request is rejected

Integration

# @BHV-chargesdelivery-int-001 — the anchor and the clock over HTTP
Scenario: Recording service returns the anchor and starts the Resposta clock
When a case manager posts service to charges-delivery
Then the response carries chargesDeliveredAt
And the deadlines read reports a resposta clock of 10 working days from service

# @BHV-chargesdelivery-int-002 — the guard identity reaches the wire
Scenario: An approved but unserved case is refused the portal
Given a case whose charges-review gate stands approved but which was never served
When OpenConsultationPortal is fired
Then the response is 409 carrying chargesNotDelivered

# @BHV-chargesdelivery-int-003 — the read renders what was recorded
Scenario: Reading service returns the method, proof and anchor
Given a case in CHARGES_ISSUED whose service has been recorded
When a case-team member reads charges-delivery
Then the response carries the method, the proof and the delivery date

# @BHV-chargesdelivery-int-004 — nothing to read before service happens
Scenario: Reading service before it is recorded is a 404
Given a case in CHARGES_ISSUED whose service has not been recorded
When a case-team member reads charges-delivery
Then the response is 404 ChargesNotDelivered

Deliberately out of scope

  • The response window itself — consultation, the arguido's defence and its attachments are the External Portal epic (#226, #229, #230). This module stamps the anchor; those own what happens inside the window.
  • The method and proof on the case detail read. Served vs. not served is already distinguishable from the guard identity, and the deadlines read carries the consequence (a Resposta clock). The method and proof themselves now have their own read (GET .../charges-delivery, added once a screen needed them), but the case detail read still doesn't inline them — add that only if a screen needs both in one round trip.
  • A refused or unclaimed registered letter. PRD-006 §9 leaves open how the declaração receptícia rule anchors the clock when the worker refuses delivery or never collects the letter — today such a case is simply recorded with the date the firm judges service to have taken effect, with the proof attached. Flagged for firm-qa (#185); no behaviour is pinned here because the legal rule is not settled.