Solution and publisher
docs/dataverse/solution/solution.md
# EPMS solution and publisher > **Build instruction.** Generated from the EPMS model on 2026-08-29. > Follow it in order. Every value is literal — do not substitute, do not infer, > and do not proceed past a step whose verification fails. Where something is > marked UNCONFIRMED, stop and ask rather than choosing a plausible value. > > Scope of this file: creating the publisher and solution, and importing them across environments. <!-- Generated from src/model/. Do not hand-edit: regenerate from the model. --> ## Publisher | Property | Value | | --- | --- | | Display name | SECO Tools | | Unique name | secotools | | Customisation prefix | `seco` | | Option value prefix | `10000` | The prefix is `seco` and the solution is named EPMS. **The two differ deliberately.** EPMS is the product; SECO Tools is the publisher. Do not create an `epms` publisher, and do not rename the prefix — the prefix is baked into every schema name and cannot be changed after the first table is created. ## Solution | Property | Value | | --- | --- | | Display name | EPMS | | Unique name | seco_epms | | Version | 1.0.0.0 | | Publisher | SECO Tools (`seco`) | | Managed state | Unmanaged in Dev; managed in Test and Production | ## What goes in the solution | Component | Count | Note | | --- | --- | --- | | Tables | 74 | Every registry table, including the standard tables added as existing with their custom columns. | | Columns | 656 | Custom columns only. System columns travel with the table. | | Relationships | 78 | Including the two many-to-many relationships; the intersect tables are generated. | | Vocabulary tables | 59 | All vocabulary tables, both tiers. | | Vocabulary rows | 240 | See the section below — rows are configuration data, not solution components. | | Views | 142 | Active and Inactive per table with columns. | | Alternate keys | 28 | Including every `seco_code` key. | | Model-driven app | 1 | EPMS. | | Sitemap | 1 | The EPMS sitemap. | | Field security profile | 1 | EPMS Finance — Rate Card. | ## What does not travel, and must be recreated per environment **Read this section before every import.** Each item below fails silently: the import succeeds, the app opens, and the behaviour is wrong. 1. **Power Pages table permissions and web roles.** Environment configuration, not solution components. Importing EPMS does not bring them. Recreate them, or move them with a Power Pages configuration-data export. 2. **Field security profile membership.** The profile itself travels; the teams and users assigned to it do not. After every import the profile exists with no members, so nobody can read the rate columns and no error is raised. 3. **Business unit and team rows.** The Global root, the regional children and the country teams are data. Create them in the target environment before assigning ownership or security roles. 4. **Vocabulary rows themselves.** Ship them as a **configuration-data package** (Configuration Migration tool, keyed on `seco_code`), not as a seeding script. A script run twice creates duplicates; a configuration-data package upserts on the alternate key, so `seco_code` values are identical in every environment. 5. **Dataflows for replicated tables.** `account`, `contact`, `seco_country`, `seco_customersegment`, `seco_channel`, `seco_strategicaccount`. A dataflow is authored per environment and points at that environment's D365. Without it the tables exist and are empty, and every lookup to them is unresolvable. ## Build order 1. **Publisher** — the prefix must exist before anything carries it. It cannot be changed later. 2. **Solution** — every component below is created inside it, so it exists before any of them. 3. **Vocabulary tables** — every other table has lookups into them; the target must exist before the lookup. 4. **Vocabulary rows** — defaults on later tables reference specific `seco_code` values, so the rows exist before the columns that default to them. 5. **Reference tables** — dimension-class tables that vocabulary rows and facts both point at. 6. **Dimension tables** — the remaining descriptive tables the facts reference. 7. **Fact tables** — created last of the tables, because every one of them carries lookups into the three groups above. 8. **Relationships** — created once both ends exist; a lookup cannot be authored against a missing table. 9. **Alternate keys** — created after rows are loadable and before any integration upserts against them. 10. **Views** — reference columns, so all columns exist first. 11. **Forms** — reference columns and subgrids, so views and relationships exist first. 12. **Field security** — the profile references the secured columns, which must already exist. 13. **Sitemap** — references every table, so all tables exist first. 14. **App** — references the sitemap, the forms and the views, and is therefore last. ## Deployment runbook ### After import into Test - [ ] Solution version matches the version exported from Dev. - [ ] Import log contains zero warnings about missing dependencies. - [ ] **Field security membership:** open EPMS Finance — Rate Card and add the finance team. The profile imports with no members. - [ ] **Dataflows:** author or re-point the dataflows for `account`, `contact`, `seco_country`, `seco_customersegment`, `seco_channel`, `seco_strategicaccount`, run each once, and confirm row counts are non-zero. - [ ] Vocabulary configuration-data package imported; spot-check that `seco_code` values match Dev exactly. - [ ] Business units, teams and country teams created. - [ ] Power Pages table permissions and web roles recreated; sign in as a test external contact and confirm the customer chain returns only that account's projects. - [ ] Open the EPMS app: the Active seco_project view renders and the project main form opens. ### After import into Production - [ ] Every Test check above, repeated against Production. None of them carries over. - [ ] **Field security membership** re-applied — this is the single most commonly missed step, and it fails silently. - [ ] **Dataflows** authored against the Production D365, not the Test one. - [ ] Vocabulary `seco_code` values compared against Dev row by row before any migration runs. - [ ] Migration mapping verified against `seco_legacyoptionvalue`, not against row GUIDs. - [ ] Solution imported as **managed**.