diff --git a/api/src/services/information-sharing-agreements/archive-items/create-service.ts b/api/src/services/information-sharing-agreements/archive-items/create-service.ts index 0581b610..4fbff84b 100644 --- a/api/src/services/information-sharing-agreements/archive-items/create-service.ts +++ b/api/src/services/information-sharing-agreements/archive-items/create-service.ts @@ -115,9 +115,7 @@ export class CreateService extends BaseService { ], }) - if (isNil(organization)) { - throw new Error("External group contact is missing its associated external organization") - } + if (isNil(organization)) return null const { name } = organization return [name] diff --git a/api/tests/services/information-sharing-agreements/archive-items/create-service.test.ts b/api/tests/services/information-sharing-agreements/archive-items/create-service.test.ts new file mode 100644 index 00000000..a3278864 --- /dev/null +++ b/api/tests/services/information-sharing-agreements/archive-items/create-service.test.ts @@ -0,0 +1,80 @@ +import { + externalOrganizationFactory, + informationSharingAgreementFactory, + userFactory, +} from "@/tests/factories" + +import CreateService from "@/services/information-sharing-agreements/archive-items/create-service" + +describe("api/src/services/information-sharing-agreements/archive-items/create-service.ts", () => { + describe("CreateService", () => { + describe("#perform", () => { + test("when external group contact has an associated external organization, sets yukonFirstNations from it", async () => { + // Arrange + const currentUser = await userFactory.create() + const externalOrganization = await externalOrganizationFactory.create({ + name: "Test External Organization", + }) + const externalGroupContact = await userFactory.create({ + isExternal: true, + externalOrganizationId: externalOrganization.id, + }) + const informationSharingAgreement = await informationSharingAgreementFactory.create({ + title: "Test Agreement", + purpose: "Test purpose", + externalGroupContactId: externalGroupContact.id, + }) + + // Act + const archiveItem = await CreateService.perform( + informationSharingAgreement, + { confidentialityReceipt: true }, + currentUser + ) + + // Assert + expect(archiveItem.yukonFirstNations).toEqual(["Test External Organization"]) + }) + + test("when external group contact is missing its associated external organization, creates the archive item without yukonFirstNations", async () => { + // Arrange + const currentUser = await userFactory.create() + const externalOrganization = await externalOrganizationFactory.create() + const externalGroupContact = await userFactory.create({ + isExternal: true, + externalOrganizationId: externalOrganization.id, + }) + const informationSharingAgreement = await informationSharingAgreementFactory.create({ + title: "Test Agreement", + purpose: "Test purpose", + externalGroupContactId: externalGroupContact.id, + }) + await externalOrganization.destroy() + + // Act + const archiveItem = await CreateService.perform( + informationSharingAgreement, + { confidentialityReceipt: true }, + currentUser + ) + + // Assert + expect(archiveItem.yukonFirstNations).toEqual([]) + }) + + test("when confidentiality receipt is not true, throws an informative error", async () => { + // Arrange + const currentUser = await userFactory.create() + const informationSharingAgreement = await informationSharingAgreementFactory.create({ + title: "Test Agreement", + purpose: "Test purpose", + }) + + // Act & Assert + await expect( + CreateService.perform(informationSharingAgreement, {}, currentUser) + ).rejects.toThrow("Confidentiality receipt is required, and must be true") + }) + }) + }) +}) diff --git a/web/README.md b/web/README.md index 121dea9c..f1059888 100644 --- a/web/README.md +++ b/web/README.md @@ -51,6 +51,21 @@ Vue 3 + Vuetify 3 + TypeScript + Vite + Pinia. --- +## Knowledge Item and ISA Navigation + +An information sharing agreement (ISA) and a knowledge item are separate domain records linked through the `InformationSharingAgreementArchiveItem` association. The relationship is many-to-many; neither `informationSharingAgreementId` nor `archiveItemId` identifies the association by itself. + +Use the ISA-scoped routes for navigation from an ISA: + +- `/sharing-agreements/:informationSharingAgreementId/knowledge-items` lists every linked knowledge item. +- `/sharing-agreements/:informationSharingAgreementId/knowledge-items/:informationSharingAgreementArchiveItemId` opens one linked item. + +The detail route validates that the association belongs to the ISA in the URL before rendering the underlying knowledge item. Do not select the first association or expose an archive-item ID as the canonical ISA link. + +### Model Naming Transition + +The domain model is still named `InformationSharingAgreement`. This PR shortens only the user-facing route prefix to `sharing-agreements`; model, API, and route-name identifiers remain unchanged. A later model rename must migrate those identifiers deliberately rather than mixing it into this URL change. + ## Open In Editor When the frontend runs in Docker, Vue Devtools cannot launch your host editor directly from inside the container. This project handles that by: diff --git a/web/package-lock.json b/web/package-lock.json index 143e8448..8997d75c 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1235,7 +1235,6 @@ "version": "2.5.6", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -1275,7 +1274,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1296,7 +1294,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1317,7 +1314,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1338,7 +1334,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1359,7 +1354,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1380,7 +1374,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1401,7 +1394,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1422,7 +1414,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1443,7 +1434,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1464,7 +1454,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1485,7 +1474,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1506,7 +1494,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1527,7 +1514,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1545,7 +1531,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -1586,7 +1571,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1600,7 +1584,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1614,7 +1597,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1628,7 +1610,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1642,7 +1623,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1656,7 +1636,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1670,7 +1649,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1684,7 +1662,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1698,7 +1675,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1712,7 +1688,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1726,7 +1701,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1740,7 +1714,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1754,7 +1727,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1768,7 +1740,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1782,7 +1753,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1796,7 +1766,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1810,7 +1779,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1824,7 +1792,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1838,7 +1805,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1852,7 +1818,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1866,7 +1831,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1880,7 +1844,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1894,7 +1857,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1908,7 +1870,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1922,7 +1883,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3012,7 +2972,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, "license": "Apache-2.0", "optional": true, "engines": { @@ -4085,7 +4044,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4105,7 +4064,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -4559,7 +4518,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "dev": true, "license": "MIT", "optional": true }, @@ -6780,9 +6738,9 @@ } }, "node_modules/vuetify": { - "version": "3.11.8", - "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.11.8.tgz", - "integrity": "sha512-4iKnntOnLFFklygZjzlVfcHrtLO8+iK4HOhiia6HP2U8v82x+ngaSCgm+epvPrGyCMfCpfuEttqD2qElrr1axw==", + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.13.2.tgz", + "integrity": "sha512-CuBtJ2/lKo4s6vDmDr/oJ+77oRUb7X8goUhTKZG/qSqBkL5J2EzeBEhaZ57ZSLcYnnViWHObuVjHjRFmVzx55Q==", "license": "MIT", "funding": { "type": "github", diff --git a/web/src/components/common/EnhancedPagination.vue b/web/src/components/common/EnhancedPagination.vue new file mode 100644 index 00000000..a53dbd0e --- /dev/null +++ b/web/src/components/common/EnhancedPagination.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/web/src/components/information-sharing-agreements/signed/InformationSharingAgreementSignedActionsMenu.vue b/web/src/components/information-sharing-agreements/signed/InformationSharingAgreementSignedActionsMenu.vue index 099bf350..dd1d020d 100644 --- a/web/src/components/information-sharing-agreements/signed/InformationSharingAgreementSignedActionsMenu.vue +++ b/web/src/components/information-sharing-agreements/signed/InformationSharingAgreementSignedActionsMenu.vue @@ -4,7 +4,7 @@ :loading="isLoading" >