Your IP : 216.73.217.79


Current Path : /var/git/cesa-v3/docs/specs/
Upload File :
Current File : //var/git/cesa-v3/docs/specs/member-transformation-declaration-design.md

# Member transformation declaration — design

## UI

- `templates/base.html.twig`: Member Portal dropdown when `app.user.granted('ROLE_MEMBER_FIRM')`.
- Route: `member_firm_transformation_form` (`/member-firm/transformation`).

## Data model

| Table | Role |
|-------|------|
| `ADec_tblMemberFirms` | Annual declaration firm profile (member-submitted) |
| `ADec_Transformation` | Annual declaration transformation grid (member-submitted) |
| `tblMemberFirms` / `tblMemberFirmsTransformation` | Operational/reporting data; prefill source only in portal; updated on staff approval |

`ADecTransformation` entity annotations must use table `ADec_Transformation` and column `TransformID` (MemberBundle uses Doctrine annotation mapping).

`ADecMemberFirm` column annotations must match `ADec_tblMemberFirms` exactly (e.g. `SMME`, `BBBEELevel`). Properties that exist only on live `tblMemberFirms` have no Doctrine column mapping on the ADec entity; turnover for declarations is stored on `ADec_Transformation`.

## Service (`MemberFirmTransformationPortalService`)

| Method | Purpose |
|--------|---------|
| `getCurrentDeclarationYear()` | `(int) date('Y')` |
| `prepareDeclarationCapture(MemberFirm)` | Resolve capture + metadata (`prefilledFromYear`, `declarationYear`) |
| `findDeclarationFirmForYear(liveFirmId, year)` | `ADecMemberFirm` by `prev_firm_id` + `declaration_year` |
| `findDeclarationTransformationForYear(...)` | `ADecTransformation` for ADec firm / year |
| `saveDeclaration(...)` | Create/update current-year ADec firm + transformation only |
| `sumTransformationHeadcount(array $trans)` | Sum all grid count fields |
| `validateStaffHeadcountMatchesGrid(capture)` | Returns error message when `totalStaff` ≠ grid sum, else `null` |

Validation: class constraint `TransformationStaffHeadcountMatches` on `MemberFirmTransformationCapture` delegates to the service; `saveDeclaration` rejects mismatches before flush.

## Data sources (prefill order for current year)

1. Current-year ADec firm + transformation (edit existing).
2. Prior-year ADec (newest first).
3. Live `MemberFirm` + `MemberFirmTransformation` (read-only prefill).
4. Empty capture defaults.

## Access

- `resolvePortalContact()`: member portal contact with firm (not limited to mandated principal for this feature).

## WordPress / cesa-main embed (external API)

Trusted server-to-server endpoints (caller IP must match `CESA_MAIN_ALLOWED_IPS`):

| Method & path | Body | Response |
|---------------|------|----------|
| `POST /api/external/member-portal/transformation/render` | `{"username":"<user_login>"}` | `html`, `declaration_year`, `firm_name` |
| `POST /api/external/member-portal/transformation/submit` | `{"username":"<user_login>","form":{...}}` | success or `field_errors` |

Username is supplied only by the cesa-main proxy after resolving `user_login` from the member portal session’s `contact_id`.