diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index d78f5b7c7534..a81bdc6d4058 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -273,7 +273,7 @@ codemagic.yaml
/libs/api/domains/directorate-of-labour/ @island-is/deloitte
/libs/application/template-api-modules/src/lib/modules/templates/social-insurance-administration @island-is/deloitte
/libs/application/templates/social-insurance-administration/ @island-is/deloitte
-/libs/clients/social-insurance-administration/ @island-is/deloitte @island-is/stefna
+/libs/clients/social-insurance-administration/ @island-is/deloitte @island-is/stefna @island-is/hugsmidjan
/libs/application/templates/car-recycling/ @island-is/deloitte
/libs/application/template-api-modules/src/lib/modules/templates/car-recycling/ @island-is/deloitte
/libs/clients/car-recycling/ @island-is/deloitte
diff --git a/.github/actions/change-detection.ts b/.github/actions/change-detection.ts
index 5f15fea66944..80dc85092e4b 100644
--- a/.github/actions/change-detection.ts
+++ b/.github/actions/change-detection.ts
@@ -29,36 +29,43 @@ export async function findBestGoodRefBranch(
baseBranch: string,
workflowId: WorkflowID,
): Promise {
- const log = app.extend('findBestGoodRefBranch')
- log(`Starting with head branch ${headBranch} and base branch ${baseBranch}`)
- const commits = await getCommits(git, headBranch, baseBranch, 'HEAD~1')
- const builds = await githubApi.getLastGoodBranchBuildRun(
- headBranch,
- workflowId,
- commits,
- )
- if (builds)
- return {
- sha: builds.head_commit,
- run_number: builds.run_nr,
- branch: headBranch,
- ref: builds.head_commit,
+ return new Promise(async (resolve) => {
+ const log = app.extend('findBestGoodRefBranch')
+ log(`Starting with head branch ${headBranch} and base branch ${baseBranch}`)
+ const commits = await getCommits(git, headBranch, baseBranch, 'HEAD~1')
+ const builds = await githubApi.getLastGoodBranchBuildRun(
+ headBranch,
+ workflowId,
+ commits,
+ )
+ if (builds) {
+ resolve({
+ sha: builds.head_commit,
+ run_number: builds.run_nr,
+ branch: headBranch,
+ ref: builds.head_commit,
+ })
+ return
}
- const baseCommits = await githubApi.getLastGoodBranchBuildRun(
- baseBranch,
- workflowId,
- commits,
- )
- if (baseCommits)
- return {
- ref: baseCommits.head_commit,
- sha: baseCommits.head_commit,
- run_number: baseCommits.run_nr,
- branch: baseBranch,
+ const baseCommits = await githubApi.getLastGoodBranchBuildRun(
+ baseBranch,
+ workflowId,
+ commits,
+ )
+ if (baseCommits) {
+ resolve({
+ ref: baseCommits.head_commit,
+ sha: baseCommits.head_commit,
+ run_number: baseCommits.run_nr,
+ branch: baseBranch,
+ })
+ return
}
- return 'rebuild'
+ resolve('rebuild')
+ return
+ })
}
/***
@@ -111,82 +118,89 @@ export async function findBestGoodRefPR(
prBranch: string,
workflowId: WorkflowID,
): Promise {
- const log = app.extend('findBestGoodRefPR')
- log(`Starting with head branch ${headBranch} and base branch ${baseBranch}`)
- const lastCommitSha = await git.lastCommit()
- const prCommits = await getCommits(git, headBranch, baseBranch, 'HEAD')
+ return new Promise(async (resolve) => {
+ const log = app.extend('findBestGoodRefPR')
+ log(`Starting with head branch ${headBranch} and base branch ${baseBranch}`)
+ const lastCommitSha = await git.lastCommit()
+ const prCommits = await getCommits(git, headBranch, baseBranch, 'HEAD')
- const prRun = await githubApi.getLastGoodPRRun(
- headBranch,
- workflowId,
- prCommits,
- )
- const prBuilds: {
- distance: number
- hash: string
- run_nr: number
- branch: string
- ref: string
- }[] = []
- if (prRun) {
- log(`Found a PR run candidate: ${JSON.stringify(prRun)}`)
- try {
- const tempBranch = `${headBranch}-${Math.round(Math.random() * 1000000)}`
- await git.checkoutBranch(tempBranch, prRun.base_commit)
- log(`Branch checked out`)
- const mergeCommitSha = await git.merge(prRun.head_commit)
- log(`Simulated previous PR merge commit`)
- const distance = await githubApi.getChangedComponents(
+ const prRun = await githubApi.getLastGoodPRRun(
+ headBranch,
+ workflowId,
+ prCommits,
+ )
+ const prBuilds: {
+ distance: number
+ hash: string
+ run_nr: number
+ branch: string
+ ref: string
+ }[] = []
+ if (prRun) {
+ log(`Found a PR run candidate: ${JSON.stringify(prRun)}`)
+ try {
+ const tempBranch = `${headBranch}-${Math.round(
+ Math.random() * 1000000,
+ )}`
+ await git.checkoutBranch(tempBranch, prRun.base_commit)
+ log(`Branch checked out`)
+ const mergeCommitSha = await git.merge(prRun.head_commit)
+ log(`Simulated previous PR merge commit`)
+ const distance = await githubApi.getChangedComponents(
+ git,
+ lastCommitSha,
+ mergeCommitSha,
+ )
+ log(`Affected components since candidate PR run are ${distance}`)
+ prBuilds.push({
+ distance: diffWeight(distance),
+ hash: prRun.head_commit,
+ run_nr: prRun.run_nr,
+ branch: headBranch,
+ ref: mergeCommitSha,
+ })
+ } catch (e) {
+ log(
+ `Error processing PR candidate(${prRun.run_nr}) but continuing: %O`,
+ e,
+ )
+ } finally {
+ await git.checkout(prBranch)
+ }
+ }
+
+ const baseCommits = await getCommits(git, prBranch, baseBranch, 'HEAD~1')
+
+ const baseGoodBuilds = await githubApi.getLastGoodBranchBuildRun(
+ baseBranch,
+ 'push',
+ baseCommits,
+ )
+ if (baseGoodBuilds) {
+ let affectedComponents = await githubApi.getChangedComponents(
git,
lastCommitSha,
- mergeCommitSha,
+ baseGoodBuilds.head_commit,
)
- log(`Affected components since candidate PR run are ${distance}`)
prBuilds.push({
- distance: diffWeight(distance),
- hash: prRun.head_commit,
- run_nr: prRun.run_nr,
- branch: headBranch,
- ref: mergeCommitSha,
+ distance: diffWeight(affectedComponents),
+ hash: baseGoodBuilds.head_commit,
+ run_nr: baseGoodBuilds.run_nr,
+ branch: baseBranch,
+ ref: baseGoodBuilds.head_commit,
})
- } catch (e) {
- log(
- `Error processing PR candidate(${prRun.run_nr}) but continuing: %O`,
- e,
- )
- } finally {
- await git.checkout(prBranch)
}
- }
-
- const baseCommits = await getCommits(git, prBranch, baseBranch, 'HEAD~1')
-
- const baseGoodBuilds = await githubApi.getLastGoodBranchBuildRun(
- baseBranch,
- 'push',
- baseCommits,
- )
- if (baseGoodBuilds) {
- let affectedComponents = await githubApi.getChangedComponents(
- git,
- lastCommitSha,
- baseGoodBuilds.head_commit,
- )
- prBuilds.push({
- distance: diffWeight(affectedComponents),
- hash: baseGoodBuilds.head_commit,
- run_nr: baseGoodBuilds.run_nr,
- branch: baseBranch,
- ref: baseGoodBuilds.head_commit,
- })
- }
- prBuilds.sort((a, b) => (a.distance > b.distance ? 1 : -1))
- if (prBuilds.length > 0)
- return {
- sha: prBuilds[0].hash,
- run_number: prBuilds[0].run_nr,
- branch: prBuilds[0].branch.replace('origin/', ''),
- ref: prBuilds[0].ref,
+ prBuilds.sort((a, b) => (a.distance > b.distance ? 1 : -1))
+ if (prBuilds.length > 0) {
+ resolve({
+ sha: prBuilds[0].hash,
+ run_number: prBuilds[0].run_nr,
+ branch: prBuilds[0].branch.replace('origin/', ''),
+ ref: prBuilds[0].ref,
+ })
+ return
}
- return 'rebuild'
+ resolve('rebuild')
+ return
+ })
}
diff --git a/.github/actions/dist/main.js b/.github/actions/dist/main.js
index fb9ddace8f2a..d4ad424ecedd 100644
--- a/.github/actions/dist/main.js
+++ b/.github/actions/dist/main.js
@@ -26107,34 +26107,41 @@ var import_debug2 = __toESM(require_src());
var app2 = (0, import_debug2.default)("change-detection");
function findBestGoodRefBranch(commitScore, git, githubApi, headBranch, baseBranch, workflowId) {
return __async(this, null, function* () {
- const log = app2.extend("findBestGoodRefBranch");
- log(`Starting with head branch ${headBranch} and base branch ${baseBranch}`);
- const commits = yield getCommits(git, headBranch, baseBranch, "HEAD~1");
- const builds = yield githubApi.getLastGoodBranchBuildRun(
- headBranch,
- workflowId,
- commits
- );
- if (builds)
- return {
- sha: builds.head_commit,
- run_number: builds.run_nr,
- branch: headBranch,
- ref: builds.head_commit
- };
- const baseCommits = yield githubApi.getLastGoodBranchBuildRun(
- baseBranch,
- workflowId,
- commits
- );
- if (baseCommits)
- return {
- ref: baseCommits.head_commit,
- sha: baseCommits.head_commit,
- run_number: baseCommits.run_nr,
- branch: baseBranch
- };
- return "rebuild";
+ return new Promise((resolve) => __async(this, null, function* () {
+ const log = app2.extend("findBestGoodRefBranch");
+ log(`Starting with head branch ${headBranch} and base branch ${baseBranch}`);
+ const commits = yield getCommits(git, headBranch, baseBranch, "HEAD~1");
+ const builds = yield githubApi.getLastGoodBranchBuildRun(
+ headBranch,
+ workflowId,
+ commits
+ );
+ if (builds) {
+ resolve({
+ sha: builds.head_commit,
+ run_number: builds.run_nr,
+ branch: headBranch,
+ ref: builds.head_commit
+ });
+ return;
+ }
+ const baseCommits = yield githubApi.getLastGoodBranchBuildRun(
+ baseBranch,
+ workflowId,
+ commits
+ );
+ if (baseCommits) {
+ resolve({
+ ref: baseCommits.head_commit,
+ sha: baseCommits.head_commit,
+ run_number: baseCommits.run_nr,
+ branch: baseBranch
+ });
+ return;
+ }
+ resolve("rebuild");
+ return;
+ }));
});
}
function getCommits(git, headBranch, baseBranch, head, maxCount = 300) {
@@ -26152,75 +26159,82 @@ function getCommits(git, headBranch, baseBranch, head, maxCount = 300) {
}
function findBestGoodRefPR(diffWeight, git, githubApi, headBranch, baseBranch, prBranch, workflowId) {
return __async(this, null, function* () {
- const log = app2.extend("findBestGoodRefPR");
- log(`Starting with head branch ${headBranch} and base branch ${baseBranch}`);
- const lastCommitSha = yield git.lastCommit();
- const prCommits = yield getCommits(git, headBranch, baseBranch, "HEAD");
- const prRun = yield githubApi.getLastGoodPRRun(
- headBranch,
- workflowId,
- prCommits
- );
- const prBuilds = [];
- if (prRun) {
- log(`Found a PR run candidate: ${JSON.stringify(prRun)}`);
- try {
- const tempBranch = `${headBranch}-${Math.round(Math.random() * 1e6)}`;
- yield git.checkoutBranch(tempBranch, prRun.base_commit);
- log(`Branch checked out`);
- const mergeCommitSha = yield git.merge(prRun.head_commit);
- log(`Simulated previous PR merge commit`);
- const distance = yield githubApi.getChangedComponents(
+ return new Promise((resolve) => __async(this, null, function* () {
+ const log = app2.extend("findBestGoodRefPR");
+ log(`Starting with head branch ${headBranch} and base branch ${baseBranch}`);
+ const lastCommitSha = yield git.lastCommit();
+ const prCommits = yield getCommits(git, headBranch, baseBranch, "HEAD");
+ const prRun = yield githubApi.getLastGoodPRRun(
+ headBranch,
+ workflowId,
+ prCommits
+ );
+ const prBuilds = [];
+ if (prRun) {
+ log(`Found a PR run candidate: ${JSON.stringify(prRun)}`);
+ try {
+ const tempBranch = `${headBranch}-${Math.round(
+ Math.random() * 1e6
+ )}`;
+ yield git.checkoutBranch(tempBranch, prRun.base_commit);
+ log(`Branch checked out`);
+ const mergeCommitSha = yield git.merge(prRun.head_commit);
+ log(`Simulated previous PR merge commit`);
+ const distance = yield githubApi.getChangedComponents(
+ git,
+ lastCommitSha,
+ mergeCommitSha
+ );
+ log(`Affected components since candidate PR run are ${distance}`);
+ prBuilds.push({
+ distance: diffWeight(distance),
+ hash: prRun.head_commit,
+ run_nr: prRun.run_nr,
+ branch: headBranch,
+ ref: mergeCommitSha
+ });
+ } catch (e) {
+ log(
+ `Error processing PR candidate(${prRun.run_nr}) but continuing: %O`,
+ e
+ );
+ } finally {
+ yield git.checkout(prBranch);
+ }
+ }
+ const baseCommits = yield getCommits(git, prBranch, baseBranch, "HEAD~1");
+ const baseGoodBuilds = yield githubApi.getLastGoodBranchBuildRun(
+ baseBranch,
+ "push",
+ baseCommits
+ );
+ if (baseGoodBuilds) {
+ let affectedComponents = yield githubApi.getChangedComponents(
git,
lastCommitSha,
- mergeCommitSha
+ baseGoodBuilds.head_commit
);
- log(`Affected components since candidate PR run are ${distance}`);
prBuilds.push({
- distance: diffWeight(distance),
- hash: prRun.head_commit,
- run_nr: prRun.run_nr,
- branch: headBranch,
- ref: mergeCommitSha
+ distance: diffWeight(affectedComponents),
+ hash: baseGoodBuilds.head_commit,
+ run_nr: baseGoodBuilds.run_nr,
+ branch: baseBranch,
+ ref: baseGoodBuilds.head_commit
});
- } catch (e) {
- log(
- `Error processing PR candidate(${prRun.run_nr}) but continuing: %O`,
- e
- );
- } finally {
- yield git.checkout(prBranch);
}
- }
- const baseCommits = yield getCommits(git, prBranch, baseBranch, "HEAD~1");
- const baseGoodBuilds = yield githubApi.getLastGoodBranchBuildRun(
- baseBranch,
- "push",
- baseCommits
- );
- if (baseGoodBuilds) {
- let affectedComponents = yield githubApi.getChangedComponents(
- git,
- lastCommitSha,
- baseGoodBuilds.head_commit
- );
- prBuilds.push({
- distance: diffWeight(affectedComponents),
- hash: baseGoodBuilds.head_commit,
- run_nr: baseGoodBuilds.run_nr,
- branch: baseBranch,
- ref: baseGoodBuilds.head_commit
- });
- }
- prBuilds.sort((a, b) => a.distance > b.distance ? 1 : -1);
- if (prBuilds.length > 0)
- return {
- sha: prBuilds[0].hash,
- run_number: prBuilds[0].run_nr,
- branch: prBuilds[0].branch.replace("origin/", ""),
- ref: prBuilds[0].ref
- };
- return "rebuild";
+ prBuilds.sort((a, b) => a.distance > b.distance ? 1 : -1);
+ if (prBuilds.length > 0) {
+ resolve({
+ sha: prBuilds[0].hash,
+ run_number: prBuilds[0].run_nr,
+ branch: prBuilds[0].branch.replace("origin/", ""),
+ ref: prBuilds[0].ref
+ });
+ return;
+ }
+ resolve("rebuild");
+ return;
+ }));
});
}
@@ -26320,7 +26334,7 @@ var SimpleGit = class {
// main.ts
var FULL_REBUILD_NEEDED = "full_rebuild_needed";
(() => __async(exports, null, function* () {
- if (process.env.NX_AFFECTED_ALL === "true") {
+ if (process.env.NX_AFFECTED_ALL === "true" || process.env.TEST_EVERYTHING === "true") {
console.log(FULL_REBUILD_NEEDED);
return;
}
@@ -26345,11 +26359,12 @@ var FULL_REBUILD_NEEDED = "full_rebuild_needed";
);
if (rev === "rebuild") {
console.log(FULL_REBUILD_NEEDED);
- return;
+ process.exit(0);
}
rev.branch = rev.branch.replace(/'/g, "");
rev.ref = rev.ref.replace(/'/g, "");
console.log(JSON.stringify(rev));
+ process.exit(0);
}))();
/*!
* is-plain-object
diff --git a/.github/actions/main.ts b/.github/actions/main.ts
index 3deb43ad0586..5a9c0be3abe0 100644
--- a/.github/actions/main.ts
+++ b/.github/actions/main.ts
@@ -7,7 +7,10 @@ import { WorkflowID } from './git-action-status'
const FULL_REBUILD_NEEDED = 'full_rebuild_needed'
;(async () => {
- if (process.env.NX_AFFECTED_ALL === 'true') {
+ if (
+ process.env.NX_AFFECTED_ALL === 'true' ||
+ process.env.TEST_EVERYTHING === 'true'
+ ) {
console.log(FULL_REBUILD_NEEDED)
return
}
@@ -37,9 +40,10 @@ const FULL_REBUILD_NEEDED = 'full_rebuild_needed'
if (rev === 'rebuild') {
console.log(FULL_REBUILD_NEEDED)
- return
+ process.exit(0)
}
rev.branch = rev.branch.replace(/'/g, '')
rev.ref = rev.ref.replace(/'/g, '')
console.log(JSON.stringify(rev))
+ process.exit(0)
})()
diff --git a/.github/actions/package.json b/.github/actions/package.json
index 061faaf0a65e..28ff702a834d 100644
--- a/.github/actions/package.json
+++ b/.github/actions/package.json
@@ -19,7 +19,7 @@
"@octokit/rest": "19.0.4",
"@types/debug": "4.1.7",
"@types/jest": "^27.4.1",
- "@types/node": "20.11.4",
+ "@types/node": "20.12.12",
"debug": "4.3.4",
"esbuild": "0.15.10",
"esbuild-runner": "2.2.1",
diff --git a/.github/actions/tsconfig.json b/.github/actions/tsconfig.json
index ff324a0e1d98..1c3a88d4b3fb 100644
--- a/.github/actions/tsconfig.json
+++ b/.github/actions/tsconfig.json
@@ -12,7 +12,7 @@
"target": "es2015",
"module": "esnext",
"typeRoots": ["node_modules/@types"],
- "lib": ["es2019", "esnext.array"],
+ "lib": ["es2019", "esnext.array", "esnext"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"allowSyntheticDefaultImports": true,
diff --git a/.github/actions/unit-test/action.yml b/.github/actions/unit-test/action.yml
index 2d26e5bb94e0..dcea4d884e94 100644
--- a/.github/actions/unit-test/action.yml
+++ b/.github/actions/unit-test/action.yml
@@ -36,9 +36,9 @@ runs:
run: npm install -g yarn
shell: bash
- - uses: actions/setup-node@v2
+ - uses: actions/setup-node@v4
with:
- node-version: '18'
+ node-version-file: 'package.json'
- name: Setup yarn
run: npm install -g yarn
diff --git a/.github/actions/yarn.lock b/.github/actions/yarn.lock
index 366f3c0d008f..6a7376cdfd0d 100644
--- a/.github/actions/yarn.lock
+++ b/.github/actions/yarn.lock
@@ -1610,12 +1610,12 @@ __metadata:
languageName: node
linkType: hard
-"@types/node@npm:20.11.4":
- version: 20.11.4
- resolution: "@types/node@npm:20.11.4"
+"@types/node@npm:20.12.12":
+ version: 20.12.12
+ resolution: "@types/node@npm:20.12.12"
dependencies:
undici-types: ~5.26.4
- checksum: b9cf2c5397ea31f3355656edd204aee777a36db75b79b8b7aba2bed7ea5b29914fa808489da5c632c5eddbb33c3106188bef0bff3b7648bd39aa50dee466a73b
+ checksum: 5373983874b9af7c216e7ca5d26b32a8d9829c703a69f1e66f2113598b5be8582c0e009ca97369f1ec9a6282b3f92812208d06eb1e9fc3bd9b939b022303d042
languageName: node
linkType: hard
@@ -1715,7 +1715,7 @@ __metadata:
"@octokit/rest": 19.0.4
"@types/debug": 4.1.7
"@types/jest": ^27.4.1
- "@types/node": 20.11.4
+ "@types/node": 20.12.12
debug: 4.3.4
esbuild: 0.15.10
esbuild-runner: 2.2.1
diff --git a/.github/workflows/config-values.yaml b/.github/workflows/config-values.yaml
index a5e5655f5e81..af314b1368dd 100644
--- a/.github/workflows/config-values.yaml
+++ b/.github/workflows/config-values.yaml
@@ -61,9 +61,9 @@ jobs:
- uses: actions/checkout@v3
if: ${{ github.event_name != 'pull_request' }}
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v4
with:
- node-version: '18'
+ node-version-file: 'package.json'
- name: Setup yarn
run: npm install -g yarn
@@ -109,9 +109,9 @@ jobs:
matrix: ${{ fromJson(needs.prepare.outputs.ENVS) }}
steps:
- uses: actions/checkout@v3
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v4
with:
- node-version: '18'
+ node-version-file: 'package.json'
- name: Cache for NodeJS dependencies
id: node-modules
diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml
index ce3186ec3ea7..9c5dcaa47d2c 100644
--- a/.github/workflows/pullrequest.yml
+++ b/.github/workflows/pullrequest.yml
@@ -45,9 +45,9 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v4
with:
- node-version: '18.8.0'
+ node-version-file: 'package.json'
- name: Setup yarn
run: npm install -g yarn
@@ -80,11 +80,9 @@ jobs:
- name: Calculate cache key for node modules
id: calculate_node_modules_hash
run: |
- PACKAGE_JSON_HASH=$(cat package.json | jq '{resolutions,dependencies,devDependencies}' | sha1sum -t | cut -f1 -d" ")
- echo "PACKAGE_JSON_HASH: $PACKAGE_JSON_HASH"
- export NODE_MODULES_HASH=${{ runner.os }}-${{ hashFiles('yarn.lock') }}-$PACKAGE_JSON_HASH
- echo "NODE_MODULES_HASH: $NODE_MODULES_HASH"
- echo "node-modules-hash=$NODE_MODULES_HASH" >> $GITHUB_OUTPUT
+ HASH="$(./scripts/ci/get-node-modules-hash.mjs)"
+ echo "node-modules-hash: ${HASH}"
+ echo "node-modules-hash=${HASH}" >> $GITHUB_OUTPUT
- name: Calculate cache keys for generated files
id: calculate_generated_files_cache_key
@@ -119,9 +117,6 @@ jobs:
source ./scripts/ci/00_prepare-base-tags.sh $(git merge-base HEAD $GITHUB_BASE_REF)
git checkout $GITHUB_SHA
echo "BASE=$BASE" >> $GITHUB_ENV
- if [ -n "${NX_AFFECTED_ALL+x}" ]; then
- echo "NX_AFFECTED_ALL=$NX_AFFECTED_ALL" >> $GITHUB_ENV
- fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HTML_URL: ${{ github.event.pull_request.html_url }}
@@ -249,9 +244,9 @@ jobs:
with:
fetch-depth: 0
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v4
with:
- node-version: '18'
+ node-version-file: 'package.json'
- name: Setup yarn
run: npm install -g yarn
@@ -310,9 +305,9 @@ jobs:
steps:
- uses: actions/checkout@v3
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v4
with:
- node-version: '18'
+ node-version-file: 'package.json'
- name: Setup yarn
run: npm install -g yarn
@@ -373,9 +368,9 @@ jobs:
path: node_modules
key: ${{ needs.prepare.outputs.node-modules-hash }}-yarn
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v4
with:
- node-version: '18'
+ node-version-file: 'package.json'
- name: Setup yarn
run: npm install -g yarn
@@ -416,9 +411,9 @@ jobs:
- uses: actions/checkout@v3
if: ${{ github.ref == 'ref/heads/main' }}
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v4
with:
- node-version: '18'
+ node-version-file: 'package.json'
- name: Setup yarn
run: npm install -g yarn
@@ -460,9 +455,9 @@ jobs:
matrix: ${{ fromJson(needs.prepare.outputs.LINT_CHUNKS) }}
steps:
- uses: actions/checkout@v3
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v4
with:
- node-version: '18'
+ node-version-file: 'package.json'
- name: Setup yarn
run: npm install -g yarn
- name: Cache for NodeJS dependencies - host OS
@@ -509,9 +504,9 @@ jobs:
if: needs.prepare.outputs.BUILD_CHUNKS
steps:
- uses: actions/checkout@v3
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v4
with:
- node-version: '18'
+ node-version-file: 'package.json'
- name: Setup yarn
run: npm install -g yarn
- name: Cache for NodeJS dependencies - host OS
diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml
index 744f17f62839..ed33617bba7a 100644
--- a/.github/workflows/push.yml
+++ b/.github/workflows/push.yml
@@ -48,6 +48,7 @@ jobs:
CREATE_PATTERNS: ^release/
PRE_RELEASE_PATTERN: ^pre-release/
outputs:
+ NODE_IMAGE_TAG: ${{ steps.git-branch.outputs.NODE_IMAGE_TAG }}
GIT_BRANCH: ${{ steps.git-branch.outputs.GIT_BRANCH }}
GIT_BRANCH_DEPLOY: ${{ steps.git-branch-deploy.outputs.GIT_BRANCH_DEPLOY }}
FEATURE_NAME: ${{ steps.git-branch-deploy.outputs.FEATURE_NAME }}
@@ -75,7 +76,6 @@ jobs:
echo "GIT_BRANCH_DEPLOY=${GIT_BRANCH_DEPLOY}" >> $GITHUB_OUTPUT
echo "GIT_BRANCH_DEPLOY=$GIT_BRANCH_DEPLOY" >> $GITHUB_ENV
echo "FEATURE_NAME=$(echo $GIT_BRANCH_DEPLOY | cut -d"/" -f2- | tr -cd '[:alnum:]-' | tr '[:upper:]' '[:lower:]' | cut -c1-50)" >> $GITHUB_OUTPUT
-
- name: Check if we want to run workflow
id: should-run
env:
@@ -158,6 +158,7 @@ jobs:
outputs:
TEST_CHUNKS: ${{ steps.test_projects.outputs.CHUNKS }}
DOCKER_TAG: ${{ steps.docker_tags.outputs.DOCKER_TAG }}
+ NODE_IMAGE_TAG: ${{ steps.nodejs_image.outputs.NODE_IMAGE_TAG }}
LAST_GOOD_BUILD_DOCKER_TAG: ${{ steps.git_nx_base.outputs.LAST_GOOD_BUILD_DOCKER_TAG }}
UNAFFECTED: ${{ steps.unaffected.outputs.UNAFFECTED }}
BUILD_CHUNKS: ${{ steps.build_map.outputs.BUILD_CHUNKS }}
@@ -168,9 +169,9 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v4
with:
- node-version: '18.8.0'
+ node-version-file: 'package.json'
- name: Setup yarn
run: npm install -g yarn
@@ -235,6 +236,15 @@ jobs:
path: event.json
retention-days: 60
+ - name: Generate nodejs image tag
+ id: nodejs_image
+ continue-on-error: false
+ run: |
+ export NODE_IMAGE_TAG="$(./scripts/ci/get-node-version.mjs)"
+ echo "NODE_IMAGE_TAG: ${NODE_IMAGE_TAG}"
+ echo "NODE_IMAGE_TAG=${NODE_IMAGE_TAG}" >> $GITHUB_OUTPUT
+ echo "NODE_IMAGE_TAG=${NODE_IMAGE_TAG}" >> $GITHUB_ENV
+ echo "**NODE_IMAGE_TAG** ${NODE_IMAGE_TAG}" >> $GITHUB_STEP_SUMMARY
- name: Generate docker image tag
id: docker_tags
run: |
@@ -277,11 +287,9 @@ jobs:
- name: Calculate cache key for node modules
id: calculate_node_modules_hash
run: |
- PACKAGE_JSON_HASH=$(cat package.json | jq '{resolutions,dependencies,devDependencies}' | sha1sum -t | cut -f1 -d" ")
- echo "PACKAGE_JSON_HASH: $PACKAGE_JSON_HASH"
- export NODE_MODULES_HASH=${{ runner.os }}-${{ hashFiles('yarn.lock') }}-$PACKAGE_JSON_HASH
- echo "NODE_MODULES_HASH: $NODE_MODULES_HASH"
- echo "node-modules-hash=$NODE_MODULES_HASH" >> $GITHUB_OUTPUT
+ HASH="$(./scripts/ci/get-node-modules-hash.mjs)"
+ echo "node-modules-hash: ${HASH}"
+ echo "node-modules-hash=${HASH}" >> $GITHUB_OUTPUT
- name: Calculate cache keys for generated files
id: calculate_generated_files_cache_key
@@ -306,6 +314,15 @@ jobs:
if: steps.node-modules.outputs.cache-hit != 'true'
run: ./scripts/ci/10_prepare-host-deps.sh
+ - name: Set Test Everything true
+ run: |
+ echo "TEST_EVERYTHING=true" >> $GITHUB_ENV
+ if: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'test everything')
+
+ - name: Set Test Everything false
+ run: echo "TEST_EVERYTHING=false" >> $GITHUB_ENV
+ if: github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'test everything')
+
- name: Preparing BASE tags
id: git_nx_base
env:
@@ -328,11 +345,6 @@ jobs:
git checkout $GITHUB_SHA
echo "BASE=$BASE" >> $GITHUB_ENV
echo "LAST_GOOD_BUILD_DOCKER_TAG=${LAST_GOOD_BUILD_DOCKER_TAG}" >> $GITHUB_OUTPUT
-
- if [ -n "${NX_AFFECTED_ALL+x}" ]; then
- echo "NX_AFFECTED_ALL=$NX_AFFECTED_ALL" >> $GITHUB_ENV
- fi
-
- name: Docker login to ECR repo
run: ./scripts/ci/docker-login-ecr.sh
env:
@@ -370,7 +382,8 @@ jobs:
- name: Building NodeJS dependencies
if: steps.cache-deps.outputs.cache-hit != 'true' || steps.cache-deps-base.outputs.cache-hit != 'true'
- run: ./scripts/ci/10_prepare-docker-deps.sh
+ run: |
+ ./scripts/ci/10_prepare-docker-deps.sh
- name: set BRANCH env var
run: echo "BRANCH=$GIT_BRANCH" >> $GITHUB_ENV
@@ -452,9 +465,9 @@ jobs:
with:
fetch-depth: 0
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v4
with:
- node-version: '18'
+ node-version-file: 'package.json'
- name: Setup yarn
run: npm install -g yarn
@@ -505,6 +518,7 @@ jobs:
AFFECTED_ALL: ${{ secrets.AFFECTED_ALL }}
GIT_BRANCH: ${{ needs.pre-checks.outputs.GIT_BRANCH}}
DOCKER_TAG: ${{ needs.prepare.outputs.DOCKER_TAG}}
+ NODE_IMAGE_TAG: ${{ needs.prepare.outputs.NODE_IMAGE_TAG}}
PUBLISH: true
strategy:
fail-fast: false
@@ -519,22 +533,17 @@ jobs:
echo "AFFECTED_PROJECTS=$AFFECTED_PROJECTS" >> $GITHUB_ENV
echo "DOCKER_TYPE=$DOCKER_TYPE" >> $GITHUB_ENV
continue-on-error: true
- - uses: actions/setup-node@v3
- with:
- node-version: '18'
+ - uses: actions/checkout@v3
if: steps.gather.outcome == 'success'
-
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v4
with:
- node-version: '18'
+ node-version-file: 'package.json'
if: steps.gather.outcome == 'success'
- name: Setup yarn
run: npm install -g yarn
if: steps.gather.outcome == 'success'
- - uses: actions/checkout@v3
- if: steps.gather.outcome == 'success'
- name: Cache for generated files
id: generated-files-cache
if: steps.gather.outcome == 'success'
@@ -605,15 +614,25 @@ jobs:
continue-on-error: true
id: dockerbuild
if: steps.gather.outcome == 'success'
- run: ./scripts/ci/run-in-parallel.sh 90_$DOCKER_TYPE
env:
- EXTRA_DOCKER_BUILD_ARGS: '--build-arg DOCKER_IMAGE_REGISTRY=${{ env.DOCKER_BASE_IMAGE_REGISTRY }} --build-arg GIT_SHA=${{ github.sha }}'
+ NODE_IMAGE_TAG: ${{ needs.prepare.outputs.NODE_IMAGE_TAG }}
+ SHA: ${{ github.sha }}
+ DOCKER_BASE_IMAGE_REGISTRY: ${{ env.DOCKER_BASE_IMAGE_REGISTRY }}
+ run: |
+ echo Node image tag is: $NODE_IMAGE_TAG
+ export EXTRA_DOCKER_BUILD_ARGS="--build-arg DOCKER_IMAGE_REGISTRY=$DOCKER_BASE_IMAGE_REGISTRY --build-arg GIT_SHA=$SHA --build-arg NODE_IMAGE_TAG=$NODE_IMAGE_TAG"
+ ./scripts/ci/run-in-parallel.sh 90_$DOCKER_TYPE
- - name: Building Docker images Retry #This only exists until GHA starts supporting this
+ - name: Building Docker images Retry
if: steps.gather.outcome == 'success' && steps.dockerbuild.outcome == 'failure'
- run: ./scripts/ci/run-in-parallel.sh 90_$DOCKER_TYPE
env:
- EXTRA_DOCKER_BUILD_ARGS: '--build-arg DOCKER_IMAGE_REGISTRY=${{ env.DOCKER_BASE_IMAGE_REGISTRY }} --build-arg GIT_SHA=${{ github.sha }}'
+ NODE_IMAGE_TAG: ${{ needs.prepare.outputs.NODE_IMAGE_TAG }}
+ SHA: ${{ github.sha }}
+ DOCKER_BASE_IMAGE_REGISTRY: ${{ env.DOCKER_BASE_IMAGE_REGISTRY }}
+ run: |
+ echo Node image tag is: $NODE_IMAGE_TAG
+ export EXTRA_DOCKER_BUILD_ARGS="--build-arg DOCKER_IMAGE_REGISTRY=$DOCKER_BASE_IMAGE_REGISTRY --build-arg GIT_SHA=$SHA --build-arg NODE_IMAGE_TAG=$NODE_IMAGE_TAG"
+ ./scripts/ci/run-in-parallel.sh 90_$DOCKER_TYPE
helm-docker-build:
needs:
@@ -628,6 +647,7 @@ jobs:
FEATURE_NAME: ${{ needs.pre-checks.outputs.FEATURE_NAME }}
DOCKER_TAG: ${{ needs.prepare.outputs.DOCKER_TAG}}
GIT_BRANCH: ${{ needs.pre-checks.outputs.GIT_BRANCH }}
+ NODE_IMAGE_TAG: ${{ needs.prepare.outputs.NODE_IMAGE_TAG }}
steps:
- uses: actions/checkout@v3
@@ -647,6 +667,9 @@ jobs:
- name: Docker build image
working-directory: infra
run: |
+ echo Registry is: ${{env.DOCKER_BASE_IMAGE_REGISTRY}}
+ echo Image tag is: ${{env.NODE_IMAGE_TAG}}
+ export EXTRA_DOCKER_BUILD_ARGS="--build-arg DOCKER_IMAGE_REGISTRY=${{env.DOCKER_BASE_IMAGE_REGISTRY}} --build-arg NODE_IMAGE_TAG=${{env.NODE_IMAGE_TAG}}"
./scripts/build-docker-container.sh $DOCKER_TAG
echo "COMMENT<> $GITHUB_ENV
echo "Affected services are: ${{needs.prepare.outputs.IMAGES}}" >> $GITHUB_ENV
@@ -654,7 +677,6 @@ jobs:
echo 'EOF' >> $GITHUB_ENV
env:
PUBLISH: 'true'
- EXTRA_DOCKER_BUILD_ARGS: '--build-arg DOCKER_IMAGE_REGISTRY=${{ env.DOCKER_BASE_IMAGE_REGISTRY }}'
- name: Retag as latest
if: ${{ env.GIT_BRANCH == 'main' && env.NX_AFFECTED_ALL != 'true' }}
diff --git a/apps/api/infra/api.ts b/apps/api/infra/api.ts
index aae3e246ab52..b68e752223ae 100644
--- a/apps/api/infra/api.ts
+++ b/apps/api/infra/api.ts
@@ -370,6 +370,7 @@ export const serviceSetup = (services: {
'/k8s/api/WATSON_ASSISTANT_CHAT_FEEDBACK_API_KEY',
LICENSE_SERVICE_BARCODE_SECRET_KEY:
'/k8s/api/LICENSE_SERVICE_BARCODE_SECRET_KEY',
+ ULTRAVIOLET_RADIATION_API_KEY: '/k8s/api/ULTRAVIOLET_RADIATION_API_KEY',
})
.xroad(
AdrAndMachine,
diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts
index 054b9cd04ef0..3fbf0f5b2c94 100644
--- a/apps/api/src/app/app.module.ts
+++ b/apps/api/src/app/app.module.ts
@@ -184,6 +184,7 @@ import {
} from '@island.is/clients/university-careers'
import { HousingBenefitsConfig } from '@island.is/clients/hms-housing-benefits'
import { UserProfileClientConfig } from '@island.is/clients/user-profile'
+import { UltravioletRadiationClientConfig } from '@island.is/clients/ultraviolet-radiation'
const environment = getConfig
@@ -412,6 +413,7 @@ const environment = getConfig
UniversityGatewayApiClientConfig,
LicenseConfig,
UserProfileClientConfig,
+ UltravioletRadiationClientConfig,
],
}),
],
diff --git a/apps/application-system/api/src/app/modules/application/admin.controller.ts b/apps/application-system/api/src/app/modules/application/admin.controller.ts
index 14f3b6e245ce..73e8a219c90c 100644
--- a/apps/application-system/api/src/app/modules/application/admin.controller.ts
+++ b/apps/application-system/api/src/app/modules/application/admin.controller.ts
@@ -22,6 +22,7 @@ import { BypassDelegation } from './guards/bypass-delegation.decorator'
import {
ApplicationAdminPaginatedResponse,
ApplicationListAdminResponseDto,
+ ApplicationStatistics,
} from './dto/applicationAdmin.response.dto'
import { ApplicationAdminSerializer } from './tools/applicationAdmin.serializer'
@@ -42,6 +43,40 @@ export class AdminController {
@Inject(LOGGER_PROVIDER) private logger: Logger,
) {}
+ @Scopes(AdminPortalScope.applicationSystemAdmin)
+ @BypassDelegation()
+ @Get('admin/applications-statistics')
+ @Documentation({
+ description: 'Get applications statistics',
+ response: {
+ status: 200,
+ type: [ApplicationStatistics],
+ },
+ request: {
+ query: {
+ startDate: {
+ type: 'string',
+ required: true,
+ description: 'Start date for the statistics',
+ },
+ endDate: {
+ type: 'string',
+ required: true,
+ description: 'End date for the statistics',
+ },
+ },
+ },
+ })
+ async getCountByTypeIdAndStatus(
+ @Query('startDate') startDate: string,
+ @Query('endDate') endDate: string,
+ ) {
+ return this.applicationService.getApplicationCountByTypeIdAndStatus(
+ startDate,
+ endDate,
+ )
+ }
+
@Scopes(AdminPortalScope.applicationSystemAdmin)
@BypassDelegation()
@Get('admin/:nationalId/applications')
@@ -94,6 +129,7 @@ export class AdminController {
true, // Show pruned applications
)
}
+
@Scopes(AdminPortalScope.applicationSystemInstitution)
@BypassDelegation()
@Get('admin/institution/:nationalId/applications/:page/:count')
diff --git a/apps/application-system/api/src/app/modules/application/dto/applicationAdmin.response.dto.ts b/apps/application-system/api/src/app/modules/application/dto/applicationAdmin.response.dto.ts
index 4da2f71de4f6..2249a4aec957 100644
--- a/apps/application-system/api/src/app/modules/application/dto/applicationAdmin.response.dto.ts
+++ b/apps/application-system/api/src/app/modules/application/dto/applicationAdmin.response.dto.ts
@@ -41,3 +41,40 @@ export class ApplicationAdminPaginatedResponse {
@IsNumber()
count!: number
}
+
+export class ApplicationStatistics {
+ @ApiProperty()
+ @Expose()
+ @IsString()
+ typeid!: string
+
+ @ApiProperty()
+ @Expose()
+ @IsNumber()
+ count!: number
+
+ @ApiProperty()
+ @Expose()
+ @IsNumber()
+ draft!: number
+
+ @ApiProperty()
+ @Expose()
+ @IsNumber()
+ inprogress!: number
+
+ @ApiProperty()
+ @Expose()
+ @IsNumber()
+ completed!: number
+
+ @ApiProperty()
+ @Expose()
+ @IsNumber()
+ rejected!: number
+
+ @ApiProperty()
+ @Expose()
+ @IsNumber()
+ approved!: number
+}
diff --git a/apps/auth-admin-web/components/Client/form/ClientCreateForm.tsx b/apps/auth-admin-web/components/Client/form/ClientCreateForm.tsx
index f5e56ff7a53e..17871cbbbdda 100644
--- a/apps/auth-admin-web/components/Client/form/ClientCreateForm.tsx
+++ b/apps/auth-admin-web/components/Client/form/ClientCreateForm.tsx
@@ -712,149 +712,16 @@ const ClientCreateForm: React.FC> = (
diff --git a/apps/auth-admin-web/components/Resource/forms/ApiScopeCreateForm.tsx b/apps/auth-admin-web/components/Resource/forms/ApiScopeCreateForm.tsx
index dde72376e27b..8db1effd7312 100644
--- a/apps/auth-admin-web/components/Resource/forms/ApiScopeCreateForm.tsx
+++ b/apps/auth-admin-web/components/Resource/forms/ApiScopeCreateForm.tsx
@@ -449,186 +449,16 @@ const ApiScopeCreateForm: React.FC
> = (
diff --git a/apps/financial-aid/api/src/app/modules/application/dto/updateApplication.input.ts b/apps/financial-aid/api/src/app/modules/application/dto/updateApplication.input.ts
index b8094e23e5f0..d69d731f9512 100644
--- a/apps/financial-aid/api/src/app/modules/application/dto/updateApplication.input.ts
+++ b/apps/financial-aid/api/src/app/modules/application/dto/updateApplication.input.ts
@@ -24,6 +24,10 @@ export class UpdateApplicationInput implements UpdateApplication {
@Field(() => String)
readonly event!: ApplicationEventType
+ @Allow()
+ @Field({ nullable: true })
+ readonly appliedDate?: string
+
@Allow()
@Field({ nullable: true })
readonly rejection?: string
@@ -58,7 +62,7 @@ export class UpdateApplicationInput implements UpdateApplication {
@Allow()
@Field({ nullable: true })
- readonly spouseHasFetchedDirectTaxPayment!: boolean
+ readonly spouseHasFetchedDirectTaxPayment?: boolean
@Allow()
@Field(() => [DirectTaxPaymentInput], { nullable: true })
diff --git a/apps/financial-aid/api/src/app/modules/application/models/application.model.ts b/apps/financial-aid/api/src/app/modules/application/models/application.model.ts
index a5c584c9008a..6ab17b7b2692 100644
--- a/apps/financial-aid/api/src/app/modules/application/models/application.model.ts
+++ b/apps/financial-aid/api/src/app/modules/application/models/application.model.ts
@@ -26,6 +26,9 @@ export class ApplicationModel implements Application {
@Field()
readonly modified!: string
+ @Field()
+ readonly appliedDate!: string
+
@Field()
readonly nationalId!: string
diff --git a/apps/financial-aid/backend/migrations/20240528122439-update-application-add-applied.js b/apps/financial-aid/backend/migrations/20240528122439-update-application-add-applied.js
new file mode 100644
index 000000000000..ebecf13be552
--- /dev/null
+++ b/apps/financial-aid/backend/migrations/20240528122439-update-application-add-applied.js
@@ -0,0 +1,48 @@
+'use strict'
+
+module.exports = {
+ async up(queryInterface, Sequelize) {
+ return queryInterface.sequelize.transaction(async (t) => {
+ // Add the new column
+ await queryInterface.addColumn(
+ 'applications',
+ 'applied',
+ {
+ type: Sequelize.DATE,
+ allowNull: true,
+ },
+ { transaction: t },
+ )
+
+ // Update the applied column with the createdAt date
+ await queryInterface.sequelize.query(
+ `
+ UPDATE applications
+ SET applied = created;
+ `,
+ { transaction: t },
+ )
+
+ // Change the column to not allow null values if necessary
+ await queryInterface.changeColumn(
+ 'applications',
+ 'applied',
+ {
+ type: Sequelize.DATE,
+ allowNull: false,
+ },
+ { transaction: t },
+ )
+ })
+ },
+
+ async down(queryInterface, Sequelize) {
+ return queryInterface.sequelize.transaction((t) =>
+ Promise.all([
+ queryInterface.removeColumn('applications', 'applied', {
+ transaction: t,
+ }),
+ ]),
+ )
+ },
+}
diff --git a/apps/financial-aid/backend/migrations/20240529131132-application_event-add-to-event_type.js b/apps/financial-aid/backend/migrations/20240529131132-application_event-add-to-event_type.js
new file mode 100644
index 000000000000..7edcca5591b0
--- /dev/null
+++ b/apps/financial-aid/backend/migrations/20240529131132-application_event-add-to-event_type.js
@@ -0,0 +1,33 @@
+'use strict'
+
+const replaceEnum = require('sequelize-replace-enum-postgres').default
+
+module.exports = {
+ up: async (queryInterface, Sequelize) => {
+ return replaceEnum({
+ queryInterface,
+ tableName: 'application_events',
+ columnName: 'event_type',
+ defaultValue: 'New',
+ newValues: [
+ 'SpouseFileUpload',
+ 'New',
+ 'InProgress',
+ 'DataNeeded',
+ 'Rejected',
+ 'Approved',
+ 'StaffComment',
+ 'UserComment',
+ 'FileUpload',
+ 'AssignCase',
+ 'DateChanged',
+ ],
+ enumName: 'enum_application_events_event_type',
+ })
+ },
+
+ down: async (queryInterface, Sequelize) => {
+ // no need to roll back
+ return
+ },
+}
diff --git a/apps/financial-aid/backend/migrations/20240531145432-update-application-applied_date.js b/apps/financial-aid/backend/migrations/20240531145432-update-application-applied_date.js
new file mode 100644
index 000000000000..720d46d2103a
--- /dev/null
+++ b/apps/financial-aid/backend/migrations/20240531145432-update-application-applied_date.js
@@ -0,0 +1,23 @@
+'use strict'
+
+module.exports = {
+ up: (queryInterface, Sequelize) => {
+ return queryInterface.sequelize.transaction((t) =>
+ Promise.all([
+ queryInterface.renameColumn('applications', 'applied', 'applied_date', {
+ transaction: t,
+ }),
+ ]),
+ )
+ },
+
+ down: (queryInterface, Sequelize) => {
+ return queryInterface.sequelize.transaction((t) =>
+ Promise.all([
+ queryInterface.renameColumn('applications', 'applied_date', 'applied', {
+ transaction: t,
+ }),
+ ]),
+ )
+ },
+}
diff --git a/apps/financial-aid/backend/src/app/modules/application/application.controller.ts b/apps/financial-aid/backend/src/app/modules/application/application.controller.ts
index 3f15a3de9bdf..6600fb51675a 100644
--- a/apps/financial-aid/backend/src/app/modules/application/application.controller.ts
+++ b/apps/financial-aid/backend/src/app/modules/application/application.controller.ts
@@ -246,6 +246,7 @@ export class ApplicationController {
ApplicationEventType.INPROGRESS,
ApplicationEventType.ASSIGNCASE,
ApplicationEventType.NEW,
+ ApplicationEventType.DATECHANGED,
]
const applicantUpdateEvents = [
diff --git a/apps/financial-aid/backend/src/app/modules/application/application.service.ts b/apps/financial-aid/backend/src/app/modules/application/application.service.ts
index ba8d96c9f803..d5b7b7b63d2d 100644
--- a/apps/financial-aid/backend/src/app/modules/application/application.service.ts
+++ b/apps/financial-aid/backend/src/app/modules/application/application.service.ts
@@ -55,6 +55,7 @@ import { DeductionFactorsModel } from '../deductionFactors'
import { DirectTaxPaymentService } from '../directTaxPayment'
import { DirectTaxPaymentModel } from '../directTaxPayment/models'
import { ChildrenModel, ChildrenService } from '../children'
+import { nowFactory } from './factories/date.factory'
interface Recipient {
name: string
@@ -146,7 +147,7 @@ export class ApplicationService {
spouseNationalId: nationalId,
},
],
- created: { [Op.gte]: firstDateOfMonth() },
+ appliedDate: { [Op.gte]: firstDateOfMonth() },
},
})
@@ -294,6 +295,7 @@ export class ApplicationService {
const appModel = await this.applicationModel.create({
...application,
+ appliedDate: nowFactory(),
nationalId: application.nationalId || user.nationalId,
})
diff --git a/apps/financial-aid/backend/src/app/modules/application/dto/updateApplication.dto.ts b/apps/financial-aid/backend/src/app/modules/application/dto/updateApplication.dto.ts
index 7d86c57379f7..75b7f3f3047e 100644
--- a/apps/financial-aid/backend/src/app/modules/application/dto/updateApplication.dto.ts
+++ b/apps/financial-aid/backend/src/app/modules/application/dto/updateApplication.dto.ts
@@ -19,13 +19,18 @@ export class UpdateApplicationDto {
@IsOptional()
@IsString()
@ApiProperty()
- readonly state: ApplicationState
+ readonly state?: ApplicationState
@IsNotEmpty()
@IsString()
@ApiProperty()
readonly event: ApplicationEventType
+ @IsOptional()
+ @IsString()
+ @ApiProperty()
+ readonly appliedDate?: string
+
@IsOptional()
@IsString()
@ApiProperty()
diff --git a/apps/financial-aid/backend/src/app/modules/application/factories/date.factory.ts b/apps/financial-aid/backend/src/app/modules/application/factories/date.factory.ts
new file mode 100644
index 000000000000..d6805340f92e
--- /dev/null
+++ b/apps/financial-aid/backend/src/app/modules/application/factories/date.factory.ts
@@ -0,0 +1 @@
+export const nowFactory = () => new Date()
diff --git a/apps/financial-aid/backend/src/app/modules/application/models/application.model.ts b/apps/financial-aid/backend/src/app/modules/application/models/application.model.ts
index 049dd7656769..9bbbbda00889 100644
--- a/apps/financial-aid/backend/src/app/modules/application/models/application.model.ts
+++ b/apps/financial-aid/backend/src/app/modules/application/models/application.model.ts
@@ -49,6 +49,13 @@ export class ApplicationModel extends Model {
@ApiProperty()
modified: Date
+ @Column({
+ type: DataType.DATE,
+ allowNull: false,
+ })
+ @ApiProperty()
+ appliedDate: Date
+
@Column({
type: DataType.STRING,
allowNull: false,
diff --git a/apps/financial-aid/backend/src/app/modules/application/test/create.spec.ts b/apps/financial-aid/backend/src/app/modules/application/test/create.spec.ts
index 90509e2f2711..832cef72cd5a 100644
--- a/apps/financial-aid/backend/src/app/modules/application/test/create.spec.ts
+++ b/apps/financial-aid/backend/src/app/modules/application/test/create.spec.ts
@@ -1,6 +1,7 @@
import { EmailService } from '@island.is/email-service'
import {
ApplicationState,
+ ChildrenAid,
Employment,
FamilyStatus,
FileType,
@@ -21,6 +22,8 @@ import { ApplicationModel } from '../models/application.model'
import { createTestingApplicationModule } from './createTestingApplicationModule'
import { DirectTaxPaymentService } from '../../directTaxPayment'
import { ChildrenService } from '../../children'
+import { nowFactory } from '../factories/date.factory'
+jest.mock('../factories/date.factory')
interface Then {
result: ApplicationModel
@@ -80,6 +83,7 @@ describe('ApplicationController - Create', () => {
describe('database query', () => {
let mockCreate: jest.Mock
let mockFindOne: jest.Mock
+ const date = new Date()
const user: User = {
nationalId: '0000000000',
@@ -121,6 +125,8 @@ describe('ApplicationController - Create', () => {
applicationSystemId: '',
nationalId: user.nationalId,
spouseHasFetchedDirectTaxPayment: false,
+ children: [],
+ childrenComment: '',
}
beforeEach(async () => {
@@ -129,6 +135,9 @@ describe('ApplicationController - Create', () => {
const mockFindApplication = mockApplicationModel.findOne as jest.Mock
mockFindApplication.mockReturnValueOnce(null)
+ const mockToday = nowFactory as jest.Mock
+ mockToday.mockReturnValueOnce(date)
+
await givenWhenThen(user, application)
})
@@ -136,6 +145,7 @@ describe('ApplicationController - Create', () => {
expect(mockCreate).toHaveBeenCalledWith({
nationalId: user.nationalId,
...application,
+ appliedDate: date,
})
})
@@ -150,7 +160,7 @@ describe('ApplicationController - Create', () => {
spouseNationalId: user.nationalId,
},
],
- created: { [Op.gte]: firstDateOfMonth() },
+ appliedDate: { [Op.gte]: firstDateOfMonth() },
},
})
})
@@ -201,6 +211,8 @@ describe('ApplicationController - Create', () => {
applicationSystemId: '',
nationalId: user.nationalId,
spouseHasFetchedDirectTaxPayment: false,
+ children: [],
+ childrenComment: '',
}
const municipality: Municipality = {
@@ -213,12 +225,14 @@ describe('ApplicationController - Create', () => {
individualAid: undefined,
cohabitationAid: undefined,
usingNav: false,
+ childrenAid: ChildrenAid.NOTDEFINED,
}
const appModel = {
id,
state: application.state,
created: new Date(),
+ appliedDate: new Date(),
email: application.email,
}
@@ -323,6 +337,8 @@ describe('ApplicationController - Create', () => {
applicationSystemId: null,
nationalId: user.nationalId,
spouseHasFetchedDirectTaxPayment: false,
+ children: [],
+ childrenComment: '',
}
const municipality: Municipality = {
@@ -335,12 +351,14 @@ describe('ApplicationController - Create', () => {
individualAid: undefined,
cohabitationAid: undefined,
usingNav: false,
+ childrenAid: ChildrenAid.NOTDEFINED,
}
const appModel = {
id,
state: application.state,
created: new Date(),
+ appliedDate: new Date(),
email: application.email,
spouseEmail: application.spouseEmail,
spouseName: application.spouseName,
@@ -437,12 +455,15 @@ describe('ApplicationController - Create', () => {
applicationSystemId: '',
nationalId: user.nationalId,
spouseHasFetchedDirectTaxPayment: false,
+ children: [],
+ childrenComment: '',
}
const appModel = {
id,
state: application.state,
created: new Date(),
+ appliedDate: new Date(),
email: application.email,
}
@@ -532,6 +553,8 @@ describe('ApplicationController - Create', () => {
applicationSystemId: '',
nationalId: '',
spouseHasFetchedDirectTaxPayment: false,
+ children: [],
+ childrenComment: '',
}
const user: User = {
nationalId: '0000000000',
@@ -542,6 +565,7 @@ describe('ApplicationController - Create', () => {
id,
state: application.state,
created: new Date(),
+ appliedDate: new Date(),
email: application.email,
}
@@ -575,7 +599,7 @@ describe('ApplicationController - Create', () => {
})
})
- describe('applicant has applied for period', () => {
+ describe('applicant has appliedDate for period', () => {
let then: Then
const id = uuid()
@@ -623,13 +647,8 @@ describe('ApplicationController - Create', () => {
applicationSystemId: '',
nationalId: user.nationalId,
spouseHasFetchedDirectTaxPayment: false,
- }
-
- const appModel = {
- id,
- state: application.state,
- created: new Date(),
- email: application.email,
+ children: [],
+ childrenComment: '',
}
beforeEach(async () => {
@@ -687,6 +706,8 @@ describe('ApplicationController - Create', () => {
applicationSystemId: '',
nationalId: user.nationalId,
spouseHasFetchedDirectTaxPayment: false,
+ children: [],
+ childrenComment: '',
}
beforeEach(async () => {
diff --git a/apps/financial-aid/backend/src/app/modules/application/test/getCurrentApplication.spec.ts b/apps/financial-aid/backend/src/app/modules/application/test/getCurrentApplication.spec.ts
index 83fb32bb64f3..92416fbb4080 100644
--- a/apps/financial-aid/backend/src/app/modules/application/test/getCurrentApplication.spec.ts
+++ b/apps/financial-aid/backend/src/app/modules/application/test/getCurrentApplication.spec.ts
@@ -56,7 +56,7 @@ describe('ApplicationController - Get current application', () => {
spouseNationalId: user.nationalId,
},
],
- created: { [Op.gte]: firstDateOfMonth() },
+ appliedDate: { [Op.gte]: firstDateOfMonth() },
},
})
})
diff --git a/apps/financial-aid/backend/src/app/modules/application/test/update.spec.ts b/apps/financial-aid/backend/src/app/modules/application/test/update.spec.ts
index b39f4d3679d4..a00390154dd4 100644
--- a/apps/financial-aid/backend/src/app/modules/application/test/update.spec.ts
+++ b/apps/financial-aid/backend/src/app/modules/application/test/update.spec.ts
@@ -316,6 +316,7 @@ describe('ApplicationController - Update', () => {
${ApplicationEventType.APPROVED}
${ApplicationEventType.STAFFCOMMENT}
${ApplicationEventType.ASSIGNCASE}
+ ${ApplicationEventType.DATECHANGED}
${ApplicationEventType.NEW}
${ApplicationEventType.INPROGRESS}
`.describe('$event', ({ event }) => {
@@ -423,6 +424,7 @@ describe('ApplicationController - Update', () => {
${ApplicationEventType.APPROVED}
${ApplicationEventType.STAFFCOMMENT}
${ApplicationEventType.ASSIGNCASE}
+ ${ApplicationEventType.DATECHANGED}
${ApplicationEventType.NEW}
${ApplicationEventType.INPROGRESS}
`.describe('$event', ({ event }) => {
diff --git a/apps/financial-aid/web-veita/graphql/sharedGql.ts b/apps/financial-aid/web-veita/graphql/sharedGql.ts
index ce29ec6a418e..482dd0b7a15b 100644
--- a/apps/financial-aid/web-veita/graphql/sharedGql.ts
+++ b/apps/financial-aid/web-veita/graphql/sharedGql.ts
@@ -7,6 +7,7 @@ export const ApplicationQuery = gql`
applicationSystemId
nationalId
created
+ appliedDate
modified
name
phoneNumber
@@ -150,6 +151,7 @@ export const UpdateApplicationTableMutation = gql`
email
modified
created
+ appliedDate
state
staff {
name
@@ -178,6 +180,7 @@ export const ApplicationsQuery = gql`
email
modified
created
+ appliedDate
state
staff {
name
@@ -208,6 +211,7 @@ export const ApplicationEventMutation = gql`
nationalId
created
modified
+ appliedDate
name
phoneNumber
email
@@ -328,6 +332,7 @@ export const UpdateApplicationMutation = gql`
nationalId
created
modified
+ appliedDate
name
phoneNumber
email
diff --git a/apps/financial-aid/web-veita/src/components/ApplicationHeader/ApplicationHeader.tsx b/apps/financial-aid/web-veita/src/components/ApplicationHeader/ApplicationHeader.tsx
index 49402de5718e..99098905f673 100644
--- a/apps/financial-aid/web-veita/src/components/ApplicationHeader/ApplicationHeader.tsx
+++ b/apps/financial-aid/web-veita/src/components/ApplicationHeader/ApplicationHeader.tsx
@@ -47,10 +47,10 @@ const ApplicationHeader = ({
await changeApplicationState(
application.id,
+ ApplicationEventType.ASSIGNCASE,
application.state === ApplicationState.NEW
? ApplicationState.INPROGRESS
: application.state,
- ApplicationEventType.ASSIGNCASE,
)
.then((updatedApplication) => {
setApplication(updatedApplication)
diff --git a/apps/financial-aid/web-veita/src/components/ApplicationsTable/ApplicationsTable.tsx b/apps/financial-aid/web-veita/src/components/ApplicationsTable/ApplicationsTable.tsx
index 488baf81489b..490b4dc66a40 100644
--- a/apps/financial-aid/web-veita/src/components/ApplicationsTable/ApplicationsTable.tsx
+++ b/apps/financial-aid/web-veita/src/components/ApplicationsTable/ApplicationsTable.tsx
@@ -22,6 +22,7 @@ import {
getStateUrlFromRoute,
Routes,
SortableTableHeaderProps,
+ truncateString,
} from '@island.is/financial-aid/shared/lib'
import { useAllApplications } from '@island.is/financial-aid-web/veita/src/utils/useAllApplications'
@@ -81,7 +82,7 @@ const ApplicationsTable = ({
<>
{application.staff?.name ? (
- {application.staff?.name}
+ {truncateString(application.staff?.name, 13)}
) : (
@@ -145,7 +146,7 @@ const ApplicationsTable = ({
),
TextTableItem(
'default',
- getMonth(new Date(item.created).getMonth()),
+ getMonth(new Date(item.appliedDate).getMonth()),
),
assignButton(item),
]}
diff --git a/apps/financial-aid/web-veita/src/components/AppliedMonthModal/AppliedMonthModal.css.ts b/apps/financial-aid/web-veita/src/components/AppliedMonthModal/AppliedMonthModal.css.ts
new file mode 100644
index 000000000000..120820488ccb
--- /dev/null
+++ b/apps/financial-aid/web-veita/src/components/AppliedMonthModal/AppliedMonthModal.css.ts
@@ -0,0 +1,9 @@
+import { style } from '@vanilla-extract/css'
+
+export const modal = style({
+ display: 'block',
+ width: '100%',
+ maxWidth: '440px',
+ boxShadow: '0px 8px 32px rgba(0, 0, 0, 0.08)',
+ borderRadius: '12px',
+})
diff --git a/apps/financial-aid/web-veita/src/components/AppliedMonthModal/AppliedMonthModal.tsx b/apps/financial-aid/web-veita/src/components/AppliedMonthModal/AppliedMonthModal.tsx
new file mode 100644
index 000000000000..c9611cc7bdc1
--- /dev/null
+++ b/apps/financial-aid/web-veita/src/components/AppliedMonthModal/AppliedMonthModal.tsx
@@ -0,0 +1,151 @@
+import React, { useState } from 'react'
+import { ModalBase, Text, Box } from '@island.is/island-ui/core'
+import format from 'date-fns/format'
+
+import * as styles from './AppliedMonthModal.css'
+import * as modalButtonStyles from '../ModalTypes/ModalTypes.css'
+import cn from 'classnames'
+
+import * as modalStyles from '../StateModal/StateModal.css'
+
+import {
+ Application,
+ ApplicationEventType,
+ getMonth,
+} from '@island.is/financial-aid/shared/lib'
+import { useApplicationState } from '@island.is/financial-aid-web/veita/src/utils/useApplicationState'
+
+interface Props {
+ headline: string
+ isVisible: boolean
+ onVisibilityChange: React.Dispatch>
+ appliedDate: string
+ createdDate: string
+ applicationId: string
+ setApplication: React.Dispatch>
+}
+
+const AppliedMonthModal = ({
+ headline,
+ isVisible,
+ onVisibilityChange,
+ appliedDate,
+ createdDate,
+ applicationId,
+ setApplication,
+}: Props) => {
+ const closeModal = (): void => {
+ onVisibilityChange(false)
+ }
+ const currentAppliedMonth = new Date(appliedDate).getMonth()
+
+ const [error, setError] = useState(false)
+
+ const getSurroundingMonths = (createdDate: string): Date[] => {
+ const date = new Date(createdDate)
+
+ if (Number.isNaN(date.getTime())) {
+ throw new Error('Invalid date')
+ }
+
+ const year = date.getFullYear()
+ const month = date.getMonth()
+
+ // Calculate the previous two months
+ const prevMonth1 = new Date(year, month - 1)
+ const prevMonth2 = new Date(year, month - 2)
+
+ // Calculate the next month
+ const nextMonth = new Date(year, month + 1)
+
+ return [prevMonth2, prevMonth1, date, nextMonth]
+ }
+
+ const updateApplication = useApplicationState()
+
+ const onClickUpdateAppliedMonth = async (newDate: Date) => {
+ await updateApplication(
+ applicationId,
+ ApplicationEventType.DATECHANGED,
+ undefined,
+ newDate,
+ undefined,
+ `Tímabilið var breytt frá ${getMonth(currentAppliedMonth)} í ${getMonth(
+ new Date(newDate).getMonth(),
+ )}`,
+ )
+ .then((updatedApplication) => {
+ setApplication(updatedApplication)
+ onVisibilityChange(false)
+ })
+ .catch(() => {
+ setError(true)
+ })
+ }
+
+ return (
+ {
+ if (visibility !== isVisible) {
+ onVisibilityChange(visibility)
+ }
+ }}
+ className={modalStyles.modalBase}
+ >
+
+
+
+
+
+
+ {headline}
+
+
+
+
+
+ {getSurroundingMonths(createdDate).map((surroundingMonth) => {
+ const date = new Date(surroundingMonth)
+ const isActive = date.getMonth() === currentAppliedMonth
+
+ return (
+
+ )
+ })}
+
+
+
+ Eitthvað misstókst, vinsamlegast reyndu aftur síðar
+
+
+
+
+
+
+ )
+}
+
+export default AppliedMonthModal
diff --git a/apps/financial-aid/web-veita/src/components/CommentSection/CommentSection.tsx b/apps/financial-aid/web-veita/src/components/CommentSection/CommentSection.tsx
index cd2528ca8e32..d59e52975cc5 100644
--- a/apps/financial-aid/web-veita/src/components/CommentSection/CommentSection.tsx
+++ b/apps/financial-aid/web-veita/src/components/CommentSection/CommentSection.tsx
@@ -1,54 +1,47 @@
import React, { useContext, useState } from 'react'
-import { useRouter } from 'next/router'
-
import { Box, Input, Button } from '@island.is/island-ui/core'
-import { useMutation } from '@apollo/client'
-import { ApplicationEventMutation } from '@island.is/financial-aid-web/veita/graphql/sharedGql'
import {
Application,
ApplicationEventType,
} from '@island.is/financial-aid/shared/lib'
-import { AdminContext } from '../AdminProvider/AdminProvider'
import AnimateHeight from 'react-animate-height'
+import { useApplicationEvent } from '@island.is/financial-aid-web/veita/src/utils/useApplicationEvent'
+import { AdminContext } from '@island.is/financial-aid-web/veita/src/components/AdminProvider/AdminProvider'
interface Props {
- className?: string
+ applicationId: string
setApplication: React.Dispatch>
+ className?: string
}
-const CommentSection = ({ className, setApplication }: Props) => {
- const router = useRouter()
-
+const CommentSection = ({
+ className,
+ setApplication,
+ applicationId,
+}: Props) => {
const { admin } = useContext(AdminContext)
+
const [showInput, setShowInput] = useState(false)
const [comment, setComment] = useState()
- const [
- createApplicationEventMutation,
- { loading: isCreatingApplicationEvent },
- ] = useMutation(ApplicationEventMutation)
+ const { isCreatingApplicationEvent, creatApplicationEvent } =
+ useApplicationEvent()
- const saveStaffComment = async (staffComment: string | undefined) => {
- if (staffComment) {
- const { data } = await createApplicationEventMutation({
- variables: {
- input: {
- applicationId: router.query.id,
- comment: staffComment,
- eventType: ApplicationEventType.STAFFCOMMENT,
- staffNationalId: admin?.nationalId,
- staffName: admin?.name,
- },
- },
- })
+ const onClickComment = async () => {
+ const updatedApplication = await creatApplicationEvent(
+ applicationId,
+ ApplicationEventType.STAFFCOMMENT,
+ admin?.nationalId,
+ admin?.name,
+ comment,
+ )
- if (data) {
- setApplication(data.createApplicationEvent)
- setComment('')
- setShowInput(false)
- }
+ if (updatedApplication) {
+ setApplication(updatedApplication)
+ setComment('')
+ setShowInput(false)
}
}
@@ -84,9 +77,7 @@ const CommentSection = ({ className, setApplication }: Props) => {
icon="checkmark"
size="small"
iconType="outline"
- onClick={() => {
- saveStaffComment(comment)
- }}
+ onClick={onClickComment}
disabled={isCreatingApplicationEvent}
>
Vista athugasemd
diff --git a/apps/financial-aid/web-veita/src/components/ModalTypes/AcceptModal.tsx b/apps/financial-aid/web-veita/src/components/ModalTypes/AcceptModal.tsx
index 0c7acc67dcd7..87177926f496 100644
--- a/apps/financial-aid/web-veita/src/components/ModalTypes/AcceptModal.tsx
+++ b/apps/financial-aid/web-veita/src/components/ModalTypes/AcceptModal.tsx
@@ -34,7 +34,7 @@ interface Props {
interface calculationsState {
amount: number
income?: number
- childrenAidAmount: number
+ childrenAidAmount?: number
personalTaxCreditPercentage?: number
secondPersonalTaxCredit: number
showSecondPersonalTaxCredit: boolean
@@ -56,6 +56,10 @@ const AcceptModal = ({
const router = useRouter()
const maximumInputLength = 6
+ const hasChildrenAid =
+ applicationMunicipality.childrenAid === ChildrenAid.APPLICANT &&
+ hasApplicantChildren
+
const aidAmount = useMemo(() => {
if (applicationMunicipality && homeCircumstances) {
return aidCalculator(
@@ -81,7 +85,7 @@ const AcceptModal = ({
const [state, setState] = useState({
amount: aidAmount,
- childrenAidAmount: 0,
+ childrenAidAmount: undefined,
income: undefined,
personalTaxCreditPercentage: undefined,
secondPersonalTaxCredit: 0,
@@ -101,7 +105,7 @@ const AcceptModal = ({
const finalAmount = calculateAcceptedAidFinalAmount(
state.amount +
- state.childrenAidAmount -
+ (state.childrenAidAmount ?? 0) -
checkingValue(state.income) -
sumValues,
checkingValue(state.personalTaxCreditPercentage),
@@ -118,6 +122,7 @@ const AcceptModal = ({
const areRequiredFieldsFilled =
state.income === undefined ||
state.personalTaxCreditPercentage === undefined ||
+ (hasChildrenAid && state.childrenAidAmount === undefined) ||
!finalAmount ||
finalAmount === 0
@@ -167,26 +172,30 @@ const AcceptModal = ({
/>
- {applicationMunicipality.childrenAid === ChildrenAid.APPLICANT &&
- hasApplicantChildren && (
-
- {
- setState({
- ...state,
- childrenAidAmount: input,
- hasError: false,
- })
- }}
- maximumInputLength={maximumInputLength}
- />
-
- )}
+ {hasChildrenAid && (
+
+ {
+ setState({
+ ...state,
+ childrenAidAmount: input,
+ hasError: false,
+ })
+ }}
+ maximumInputLength={maximumInputLength}
+ hasError={state.hasError && state.childrenAidAmount === undefined}
+ />
+
+ )}
{
const [isStateModalVisible, setStateModalVisible] = useState(false)
+ const [appliedMonthModalVisible, setAppliedMonthModalVisible] =
+ useState(false)
const [isRejectedReasonModalVisible, setRejectedReasonModalVisible] =
useState(false)
@@ -87,15 +90,19 @@ const ApplicationProfile = ({
const applicationInfo: ApplicationProfileInfo[] = [
{
- title: 'Tímabil',
- content:
- getMonth(new Date(application.created).getMonth()) +
- format(new Date(application.created), ' y'),
+ title: 'Dagsetning umsóknar',
+ content: format(new Date(application.created), 'dd.MM.y · kk:mm'),
},
{
- title: 'Sótt um',
- content: format(new Date(application.created), 'dd.MM.y · kk:mm'),
+ title: 'Fyrir tímabilið',
+ content:
+ getMonth(new Date(application.appliedDate).getMonth()) +
+ format(new Date(application.appliedDate), ' y'),
+ onclick: () => {
+ setAppliedMonthModalVisible(true)
+ },
},
+
aidAmount
? {
title: 'Áætluð aðstoð',
@@ -274,6 +281,7 @@ const ApplicationProfile = ({
{!isPrint && (
@@ -322,6 +330,18 @@ const ApplicationProfile = ({
}}
reason={application.rejection ?? ''}
/>
+
+ {
+ setAppliedMonthModalVisible(isVisibleBoolean)
+ }}
+ appliedDate={application.appliedDate}
+ createdDate={application.created}
+ applicationId={application.id}
+ setApplication={setApplication}
+ />
>
)
}
diff --git a/apps/financial-aid/web-veita/src/components/StateModal/StateModal.tsx b/apps/financial-aid/web-veita/src/components/StateModal/StateModal.tsx
index ef36961a82f3..37039684f873 100644
--- a/apps/financial-aid/web-veita/src/components/StateModal/StateModal.tsx
+++ b/apps/financial-aid/web-veita/src/components/StateModal/StateModal.tsx
@@ -66,8 +66,9 @@ const StateModal = ({
await changeApplicationState(
applicationId,
- state,
eventTypeFromApplicationState[state],
+ state,
+ undefined,
rejection,
comment,
amount,
diff --git a/apps/financial-aid/web-veita/src/components/index.ts b/apps/financial-aid/web-veita/src/components/index.ts
index 35a2195d5574..25d39400edad 100644
--- a/apps/financial-aid/web-veita/src/components/index.ts
+++ b/apps/financial-aid/web-veita/src/components/index.ts
@@ -15,6 +15,8 @@ export { default as OptionsModal } from './ModalTypes/OptionsModal'
export { default as EmailFormatInputModal } from './ModalTypes/EmailFormatInputModal'
export { default as AcceptModal } from './ModalTypes/AcceptModal'
export { default as AidAmountModal } from './AidAmountModal/AidAmountModal'
+export { default as AppliedMonthModal } from './AppliedMonthModal/AppliedMonthModal'
+
export { default as TableHeaders } from './TableHeaders/TableHeaders'
export { default as SortableTableHeader } from './TableHeaders/SortableTableHeader'
diff --git a/apps/financial-aid/web-veita/src/utils/navigation.ts b/apps/financial-aid/web-veita/src/utils/navigation.ts
index 85738753fecd..f83331caefe6 100644
--- a/apps/financial-aid/web-veita/src/utils/navigation.ts
+++ b/apps/financial-aid/web-veita/src/utils/navigation.ts
@@ -13,10 +13,10 @@ export const navigationItems = [
{ title: 'Nafn', sortBy: ApplicationHeaderSortByEnum.NAME },
{ title: 'Staða', sortBy: ApplicationHeaderSortByEnum.STATE },
{ title: 'Tími án umsjár', sortBy: ApplicationHeaderSortByEnum.MODIFIED },
- { title: 'Tímabil', sortBy: ApplicationHeaderSortByEnum.CREATED },
+ { title: 'Tímabil', sortBy: ApplicationHeaderSortByEnum.APPLIEDDATE },
{ title: 'Umsjá', sortBy: ApplicationHeaderSortByEnum.STAFF },
],
- defaultHeaderSort: ApplicationHeaderSortByEnum.CREATED,
+ defaultHeaderSort: ApplicationHeaderSortByEnum.APPLIEDDATE,
},
{
group: 'Mitt',
@@ -27,7 +27,7 @@ export const navigationItems = [
{ title: 'Nafn', sortBy: ApplicationHeaderSortByEnum.NAME },
{ title: 'Staða', sortBy: ApplicationHeaderSortByEnum.STATE },
{ title: 'Síðast uppfært', sortBy: ApplicationHeaderSortByEnum.MODIFIED },
- { title: 'Tímabil', sortBy: ApplicationHeaderSortByEnum.CREATED },
+ { title: 'Tímabil', sortBy: ApplicationHeaderSortByEnum.APPLIEDDATE },
{ title: 'Unnið af', sortBy: ApplicationHeaderSortByEnum.STAFF },
],
defaultHeaderSort: ApplicationHeaderSortByEnum.MODIFIED,
@@ -44,7 +44,7 @@ export const navigationItems = [
{ title: 'Nafn', sortBy: ApplicationHeaderSortByEnum.NAME },
{ title: 'Staða', sortBy: ApplicationHeaderSortByEnum.STATE },
{ title: 'Úrlausnartími', sortBy: ApplicationHeaderSortByEnum.MODIFIED },
- { title: 'Tímabil', sortBy: ApplicationHeaderSortByEnum.CREATED },
+ { title: 'Sótt um', sortBy: ApplicationHeaderSortByEnum.CREATED },
{ title: 'Unnið af', sortBy: ApplicationHeaderSortByEnum.STAFF },
],
defaultHeaderSort: ApplicationHeaderSortByEnum.MODIFIED,
@@ -60,7 +60,7 @@ export const navigationItems = [
{ title: 'Nafn', sortBy: ApplicationHeaderSortByEnum.NAME },
{ title: 'Staða', sortBy: ApplicationHeaderSortByEnum.STATE },
{ title: 'Úrlausnartími', sortBy: ApplicationHeaderSortByEnum.MODIFIED },
- { title: 'Tímabil', sortBy: ApplicationHeaderSortByEnum.CREATED },
+ { title: 'Sótt um', sortBy: ApplicationHeaderSortByEnum.CREATED },
{ title: 'Unnið af', sortBy: ApplicationHeaderSortByEnum.STAFF },
],
defaultHeaderSort: ApplicationHeaderSortByEnum.MODIFIED,
diff --git a/apps/financial-aid/web-veita/src/utils/useApplicationEvent.ts b/apps/financial-aid/web-veita/src/utils/useApplicationEvent.ts
new file mode 100644
index 000000000000..437c6c0d1d8b
--- /dev/null
+++ b/apps/financial-aid/web-veita/src/utils/useApplicationEvent.ts
@@ -0,0 +1,39 @@
+import { useMutation } from '@apollo/client'
+import { ApplicationEventType } from '@island.is/financial-aid/shared/lib'
+import { ApplicationEventMutation } from '@island.is/financial-aid-web/veita/graphql/sharedGql'
+
+export const useApplicationEvent = () => {
+ const [
+ createApplicationEventMutation,
+ { loading: isCreatingApplicationEvent },
+ ] = useMutation(ApplicationEventMutation)
+
+ const creatApplicationEvent = async (
+ applicationId: string,
+ eventType: ApplicationEventType,
+ staffNationalId?: string,
+ staffName?: string,
+ staffComment?: string,
+ ) => {
+ const { data } = await createApplicationEventMutation({
+ variables: {
+ input: {
+ applicationId: applicationId,
+ comment: staffComment,
+ eventType: eventType,
+ staffNationalId: staffNationalId,
+ staffName: staffName,
+ },
+ },
+ })
+
+ if (data) {
+ return data.createApplicationEvent
+ }
+ }
+
+ return {
+ isCreatingApplicationEvent,
+ creatApplicationEvent,
+ }
+}
diff --git a/apps/financial-aid/web-veita/src/utils/useApplicationState.ts b/apps/financial-aid/web-veita/src/utils/useApplicationState.ts
index c279b4dc4a91..351b472e3343 100644
--- a/apps/financial-aid/web-veita/src/utils/useApplicationState.ts
+++ b/apps/financial-aid/web-veita/src/utils/useApplicationState.ts
@@ -34,10 +34,11 @@ export const useApplicationState = () => {
}
}
- const changeApplicationState = async (
+ const updateApplication = async (
applicationId: string,
- state: ApplicationState,
event: ApplicationEventType,
+ state?: ApplicationState,
+ appliedDate?: Date,
rejection?: string,
comment?: string,
amount?: Amount,
@@ -48,9 +49,10 @@ export const useApplicationState = () => {
input: {
id: applicationId,
state,
+ appliedDate,
rejection,
comment,
- staffId: admin?.staff?.id,
+ staffId: appliedDate ? undefined : admin?.staff?.id,
event,
amount,
},
@@ -64,5 +66,5 @@ export const useApplicationState = () => {
}
}
- return changeApplicationState
+ return updateApplication
}
diff --git a/apps/judicial-system/api/src/app/modules/auth/auth.service.ts b/apps/judicial-system/api/src/app/modules/auth/auth.service.ts
index 47dfeaf72b17..9f2ea5e7b278 100644
--- a/apps/judicial-system/api/src/app/modules/auth/auth.service.ts
+++ b/apps/judicial-system/api/src/app/modules/auth/auth.service.ts
@@ -160,7 +160,7 @@ export class AuthService {
nationalId: string
}
} catch (error) {
- console.error('Token verification failed:', error)
+ this.logger.error('Token verification failed:', error)
throw error
}
}
diff --git a/apps/judicial-system/api/src/app/modules/case-list/models/caseList.model.ts b/apps/judicial-system/api/src/app/modules/case-list/models/caseList.model.ts
index fda810ed9962..e087ad955c7f 100644
--- a/apps/judicial-system/api/src/app/modules/case-list/models/caseList.model.ts
+++ b/apps/judicial-system/api/src/app/modules/case-list/models/caseList.model.ts
@@ -5,6 +5,7 @@ import {
CaseAppealRulingDecision,
CaseAppealState,
CaseDecision,
+ CaseIndictmentRulingDecision,
CaseState,
CaseType,
IndictmentCaseReviewDecision,
@@ -120,4 +121,7 @@ export class CaseListEntry {
@Field(() => String, { nullable: true })
readonly indictmentVerdictAppealDeadline?: string
+
+ @Field(() => CaseIndictmentRulingDecision, { nullable: true })
+ readonly indictmentRulingDecision?: CaseIndictmentRulingDecision
}
diff --git a/apps/judicial-system/api/src/app/modules/defendant/dto/updateDefendant.input.ts b/apps/judicial-system/api/src/app/modules/defendant/dto/updateDefendant.input.ts
index 8b179cee53a5..a77d184629b5 100644
--- a/apps/judicial-system/api/src/app/modules/defendant/dto/updateDefendant.input.ts
+++ b/apps/judicial-system/api/src/app/modules/defendant/dto/updateDefendant.input.ts
@@ -4,6 +4,7 @@ import { Field, ID, InputType } from '@nestjs/graphql'
import {
DefendantPlea,
+ DefenderChoice,
Gender,
ServiceRequirement,
} from '@island.is/judicial-system/types'
@@ -68,11 +69,6 @@ export class UpdateDefendantInput {
@Field(() => String, { nullable: true })
readonly defenderPhoneNumber?: string
- @Allow()
- @IsOptional()
- @Field(() => Boolean, { nullable: true })
- readonly defendantWaivesRightToCounsel?: boolean
-
@Allow()
@IsOptional()
@Field(() => DefendantPlea, { nullable: true })
@@ -87,4 +83,9 @@ export class UpdateDefendantInput {
@IsOptional()
@Field(() => String, { nullable: true })
readonly verdictViewDate?: string
+
+ @Allow()
+ @IsOptional()
+ @Field(() => DefenderChoice, { nullable: true })
+ readonly defenderChoice?: DefenderChoice
}
diff --git a/apps/judicial-system/api/src/app/modules/defendant/models/defendant.model.ts b/apps/judicial-system/api/src/app/modules/defendant/models/defendant.model.ts
index 50c1fed8da12..c52460c4125a 100644
--- a/apps/judicial-system/api/src/app/modules/defendant/models/defendant.model.ts
+++ b/apps/judicial-system/api/src/app/modules/defendant/models/defendant.model.ts
@@ -2,6 +2,7 @@ import { Field, ID, ObjectType, registerEnumType } from '@nestjs/graphql'
import {
DefendantPlea,
+ DefenderChoice,
Gender,
ServiceRequirement,
} from '@island.is/judicial-system/types'
@@ -9,6 +10,7 @@ import {
registerEnumType(Gender, { name: 'Gender' })
registerEnumType(DefendantPlea, { name: 'DefendantPlea' })
registerEnumType(ServiceRequirement, { name: 'ServiceRequirement' })
+registerEnumType(DefenderChoice, { name: 'DefenderChoice' })
@ObjectType()
export class Defendant {
@@ -54,9 +56,6 @@ export class Defendant {
@Field(() => String, { nullable: true })
readonly defenderPhoneNumber?: string
- @Field(() => Boolean, { nullable: true })
- readonly defendantWaivesRightToCounsel?: boolean
-
@Field(() => DefendantPlea, { nullable: true })
readonly defendantPlea?: DefendantPlea
@@ -68,4 +67,7 @@ export class Defendant {
@Field(() => String, { nullable: true })
readonly verdictAppealDeadline?: string
+
+ @Field(() => DefenderChoice, { nullable: true })
+ readonly defenderChoice?: DefenderChoice
}
diff --git a/apps/judicial-system/api/src/app/modules/file/models/presignedPost.model.ts b/apps/judicial-system/api/src/app/modules/file/models/presignedPost.model.ts
index b9bbc73f354e..c2d419bdee0e 100644
--- a/apps/judicial-system/api/src/app/modules/file/models/presignedPost.model.ts
+++ b/apps/judicial-system/api/src/app/modules/file/models/presignedPost.model.ts
@@ -9,4 +9,7 @@ export class PresignedPost {
@Field(() => graphqlTypeJson)
readonly fields!: { [key: string]: string }
+
+ @Field(() => String)
+ readonly key!: string
}
diff --git a/apps/judicial-system/backend/migrations/20240522092213-update-file-keys.js b/apps/judicial-system/backend/migrations/20240522092213-update-file-keys.js
new file mode 100644
index 000000000000..49b35862d68a
--- /dev/null
+++ b/apps/judicial-system/backend/migrations/20240522092213-update-file-keys.js
@@ -0,0 +1,67 @@
+'use strict'
+
+module.exports = {
+ async up(queryInterface, Sequelize) {
+ return queryInterface.sequelize.transaction((transaction) =>
+ Promise.all([
+ queryInterface.sequelize.query(
+ `UPDATE case_file
+ SET key = SUBSTRING(key FROM 9)
+ WHERE key LIKE 'uploads/' || '%';
+ UPDATE case_file
+ SET key = SUBSTRING(key FROM 23)
+ WHERE key LIKE 'indictments/completed/' || '%';
+ UPDATE case_file
+ SET key = SUBSTRING(key FROM 13)
+ WHERE key LIKE 'indictments/' || '%';`,
+ { transaction },
+ ),
+ queryInterface.addColumn(
+ 'case',
+ 'indictment_hash',
+ { type: Sequelize.STRING, allowNull: true },
+ { transaction },
+ ),
+ queryInterface.addColumn(
+ 'case_file',
+ 'hash',
+ { type: Sequelize.STRING, allowNull: true },
+ { transaction },
+ ),
+ ]),
+ )
+ },
+
+ async down(queryInterface) {
+ return queryInterface.sequelize.transaction((transaction) =>
+ Promise.all([
+ queryInterface.sequelize.query(
+ `UPDATE case_file
+ SET key = 'uploads/' || key
+ WHERE key IS NOT NULL AND case_id IN (
+ SELECT id
+ FROM "case"
+ WHERE type != 'INDICTMENT'
+ );
+ UPDATE case_file
+ SET key = 'indictments/completed/' || key
+ WHERE key IS NOT NULL AND case_id IN (
+ SELECT id
+ FROM "case"
+ WHERE type = 'INDICTMENT' AND state = 'COMPLETED'
+ );
+ UPDATE case_file
+ SET key = 'indictments/' || key
+ WHERE key IS NOT NULL AND case_id IN (
+ SELECT id
+ FROM "case"
+ WHERE type = 'INDICTMENT' AND state != 'COMPLETED'
+ );`,
+ { transaction },
+ ),
+ queryInterface.removeColumn('case', 'indictment_hash', { transaction }),
+ queryInterface.removeColumn('case_file', 'hash', { transaction }),
+ ]),
+ )
+ },
+}
diff --git a/apps/judicial-system/backend/migrations/20240530132001-update-defendant.js b/apps/judicial-system/backend/migrations/20240530132001-update-defendant.js
new file mode 100644
index 000000000000..2e08613fd81e
--- /dev/null
+++ b/apps/judicial-system/backend/migrations/20240530132001-update-defendant.js
@@ -0,0 +1,56 @@
+module.exports = {
+ up: (queryInterface, Sequelize) => {
+ return queryInterface.sequelize.transaction((t) =>
+ queryInterface
+ .addColumn(
+ 'defendant',
+ 'defender_choice',
+ {
+ type: Sequelize.STRING,
+ allowNull: true,
+ },
+ { transaction: t },
+ )
+ .then(() =>
+ queryInterface.sequelize.query(
+ `UPDATE "defendant" SET defender_choice = 'WAIVE' WHERE defendant_waives_right_to_counsel = true;`,
+ { transaction: t },
+ ),
+ )
+ .then(() =>
+ queryInterface.removeColumn(
+ 'defendant',
+ 'defendant_waives_right_to_counsel',
+ { transaction: t },
+ ),
+ ),
+ )
+ },
+
+ down: (queryInterface, Sequelize) => {
+ return queryInterface.sequelize.transaction((t) =>
+ queryInterface
+ .addColumn(
+ 'defendant',
+ 'defendant_waives_right_to_counsel',
+ {
+ type: Sequelize.BOOLEAN,
+ defaultValue: false,
+ allowNull: false,
+ },
+ { transaction: t },
+ )
+ .then(() =>
+ queryInterface.sequelize.query(
+ `UPDATE "defendant" SET defendant_waives_right_to_counsel = true WHERE defender_choice = 'WAIVE';`,
+ { transaction: t },
+ ),
+ )
+ .then(() =>
+ queryInterface.removeColumn('defendant', 'defender_choice', {
+ transaction: t,
+ }),
+ ),
+ )
+ },
+}
diff --git a/apps/judicial-system/backend/src/app/formatters/confirmedIndictmentPdf.ts b/apps/judicial-system/backend/src/app/formatters/confirmedIndictmentPdf.ts
index 2f896a54141a..341797e8cfea 100644
--- a/apps/judicial-system/backend/src/app/formatters/confirmedIndictmentPdf.ts
+++ b/apps/judicial-system/backend/src/app/formatters/confirmedIndictmentPdf.ts
@@ -2,8 +2,8 @@ import { applyCase } from 'beygla'
import { PDFDocument, rgb, StandardFonts } from 'pdf-lib'
import { formatDate } from '@island.is/judicial-system/formatters'
-import { IndictmentConfirmation } from '@island.is/judicial-system/types'
+import { IndictmentConfirmation } from './indictmentPdf'
import { drawTextWithEllipsisPDFKit } from './pdfHelpers'
import { PDFKitCoatOfArms } from './PDFKitCoatOfArms'
diff --git a/apps/judicial-system/backend/src/app/formatters/formatters.spec.ts b/apps/judicial-system/backend/src/app/formatters/formatters.spec.ts
index 31042ac3dba0..1e481e244591 100644
--- a/apps/judicial-system/backend/src/app/formatters/formatters.spec.ts
+++ b/apps/judicial-system/backend/src/app/formatters/formatters.spec.ts
@@ -46,6 +46,7 @@ export const makeProsecutor = (): User => {
role: UserRole.PROSECUTOR,
active: true,
title: 'aðstoðarsaksóknari',
+ canConfirmIndictment: true,
institution: {
id: '',
created: '',
diff --git a/apps/judicial-system/backend/src/app/formatters/index.ts b/apps/judicial-system/backend/src/app/formatters/index.ts
index b69fe76741ef..3807e29bbac5 100644
--- a/apps/judicial-system/backend/src/app/formatters/index.ts
+++ b/apps/judicial-system/backend/src/app/formatters/index.ts
@@ -32,4 +32,5 @@ export {
export { getRequestPdfAsBuffer, getRequestPdfAsString } from './requestPdf'
export { getRulingPdfAsBuffer, getRulingPdfAsString } from './rulingPdf'
export { createCaseFilesRecord } from './caseFilesRecordPdf'
-export { createIndictment } from './indictmentPdf'
+export { createIndictment, IndictmentConfirmation } from './indictmentPdf'
+export { createConfirmedIndictment } from './confirmedIndictmentPdf'
diff --git a/apps/judicial-system/backend/src/app/formatters/indictmentPdf.ts b/apps/judicial-system/backend/src/app/formatters/indictmentPdf.ts
index 750b82bd7ce0..19716636f770 100644
--- a/apps/judicial-system/backend/src/app/formatters/indictmentPdf.ts
+++ b/apps/judicial-system/backend/src/app/formatters/indictmentPdf.ts
@@ -8,10 +8,6 @@ import {
formatDate,
lowercase,
} from '@island.is/judicial-system/formatters'
-import {
- CaseState,
- type IndictmentConfirmation,
-} from '@island.is/judicial-system/types'
import { nowFactory } from '../factories'
import { indictment } from '../messages'
@@ -56,6 +52,12 @@ const roman = (num: number) => {
return str
}
+export interface IndictmentConfirmation {
+ actor: string
+ institution: string
+ date: Date
+}
+
export const createIndictment = async (
theCase: Case,
formatMessage: FormatMessage,
@@ -81,7 +83,7 @@ export const createIndictment = async (
setTitle(doc, title)
- if (theCase.state === CaseState.SUBMITTED && confirmation) {
+ if (confirmation) {
addIndictmentConfirmation(
doc,
confirmation.actor,
diff --git a/apps/judicial-system/backend/src/app/messages/courtUpload.ts b/apps/judicial-system/backend/src/app/messages/courtUpload.ts
index c14ed09b7836..060c9cd8cb05 100644
--- a/apps/judicial-system/backend/src/app/messages/courtUpload.ts
+++ b/apps/judicial-system/backend/src/app/messages/courtUpload.ts
@@ -6,6 +6,11 @@ export const courtUpload = defineMessages({
defaultMessage: 'Krafa um {caseType} {date}',
description: 'Notaður sem nafn á kröfuskjali í Auði.',
},
+ indictment: {
+ id: 'judicial.system.backend:court_upload.indictment',
+ defaultMessage: 'Ákæra',
+ description: 'Notaður sem nafn á ákæru í Auði.',
+ },
caseFilesRecord: {
id: 'judicial.system.backend:court_upload.case_files_record',
defaultMessage: 'Skjalaskrá {policeCaseNumber}',
diff --git a/apps/judicial-system/backend/src/app/modules/aws-s3/awsS3.service.ts b/apps/judicial-system/backend/src/app/modules/aws-s3/awsS3.service.ts
index 3ffe4f13c044..1b781380af3b 100644
--- a/apps/judicial-system/backend/src/app/modules/aws-s3/awsS3.service.ts
+++ b/apps/judicial-system/backend/src/app/modules/aws-s3/awsS3.service.ts
@@ -2,10 +2,36 @@ import { S3 } from 'aws-sdk'
import { Inject, Injectable } from '@nestjs/common'
+import { type Logger, LOGGER_PROVIDER } from '@island.is/logging'
import type { ConfigType } from '@island.is/nest/config'
+import {
+ CaseState,
+ CaseType,
+ isCompletedCase,
+ isIndictmentCase,
+} from '@island.is/judicial-system/types'
+
import { awsS3ModuleConfig } from './awsS3.config'
+const requestPrefix = 'uploads/'
+const generatedPrefix = 'generated/'
+const indictmentPrefix = 'indictments/'
+const completedIndictmentPrefix = 'indictments/completed/'
+
+const formatConfirmedKey = (key: string) =>
+ key.replace(/\/([^/]*)$/, '/confirmed/$1')
+const formatS3RequestKey = (key: string) => `${requestPrefix}${key}`
+const formatS3IndictmentKey = (key: string) => `${indictmentPrefix}${key}`
+const formatS3CompletedIndictmentKey = (key: string) =>
+ `${completedIndictmentPrefix}${key}`
+const formatS3Key = (caseType: CaseType, caseState: CaseState, key: string) =>
+ isIndictmentCase(caseType)
+ ? isCompletedCase(caseState)
+ ? formatS3CompletedIndictmentKey(key)
+ : formatS3IndictmentKey(key)
+ : formatS3RequestKey(key)
+
@Injectable()
export class AwsS3Service {
private readonly s3: S3
@@ -13,18 +39,24 @@ export class AwsS3Service {
constructor(
@Inject(awsS3ModuleConfig.KEY)
private readonly config: ConfigType,
+ @Inject(LOGGER_PROVIDER) private readonly logger: Logger,
) {
this.s3 = new S3({ region: this.config.region })
}
- createPresignedPost(key: string, type: string): Promise {
+ createPresignedPost(
+ caseType: CaseType,
+ caseState: CaseState,
+ key: string,
+ type: string,
+ ): Promise {
return new Promise((resolve, reject) => {
this.s3.createPresignedPost(
{
Bucket: this.config.bucket,
Expires: this.config.timeToLivePost,
Fields: {
- key,
+ key: formatS3Key(caseType, caseState, key),
'content-type': type,
'Content-Disposition': 'inline',
},
@@ -40,7 +72,56 @@ export class AwsS3Service {
})
}
- getSignedUrl(key: string, timeToLive?: number): Promise {
+ private objectExistsInS3(key: string): Promise {
+ return this.s3
+ .headObject({
+ Bucket: this.config.bucket,
+ Key: key,
+ })
+ .promise()
+ .then(
+ () => true,
+ () => {
+ // The error is either 404 Not Found or 403 Forbidden.
+ // Normally, we would check if the error is 404 Not Found.
+ // However, to avoid granting the service ListBucket permissions,
+ // we also allow 403 Forbidden.
+ return false
+ },
+ )
+ }
+
+ private requestObjectExists(key: string): Promise {
+ return this.objectExistsInS3(formatS3RequestKey(key))
+ }
+
+ private async indictmentObjectExists(
+ caseSate: CaseState,
+ key: string,
+ ): Promise {
+ if (isCompletedCase(caseSate)) {
+ if (await this.objectExistsInS3(formatS3CompletedIndictmentKey(key))) {
+ return true
+ }
+ }
+
+ return this.objectExistsInS3(formatS3IndictmentKey(key))
+ }
+
+ objectExists(
+ caseType: CaseType,
+ caseState: CaseState,
+ key: string,
+ ): Promise {
+ return isIndictmentCase(caseType)
+ ? this.indictmentObjectExists(caseState, key)
+ : this.requestObjectExists(key)
+ }
+
+ private getSignedUrlFromS3(
+ key: string,
+ timeToLive?: number,
+ ): Promise {
return new Promise((resolve, reject) => {
this.s3.getSignedUrl(
'getObject',
@@ -60,36 +141,89 @@ export class AwsS3Service {
})
}
- async deleteObject(key: string): Promise {
- return this.s3
- .deleteObject({
- Bucket: this.config.bucket,
- Key: key,
- })
- .promise()
- .then(() => true)
+ private getRequestSignedUrl(
+ key: string,
+ timeToLive?: number,
+ ): Promise {
+ return this.getSignedUrlFromS3(formatS3RequestKey(key), timeToLive)
}
- objectExists(key: string): Promise {
- return this.s3
- .headObject({
- Bucket: this.config.bucket,
- Key: key,
- })
- .promise()
- .then(
- () => true,
- () => {
- // The error is either 404 Not Found or 403 Forbidden.
- // Normally, we would check if the error is 404 Not Found.
- // However, to avoid granting the service ListBucket permissions,
- // we also allow 403 Forbidden.
- return false
- },
- )
+ private async getIndictmentSignedUrl(
+ caseSate: CaseState,
+ key: string,
+ timeToLive?: number,
+ ): Promise {
+ if (isCompletedCase(caseSate)) {
+ const completedKey = formatS3CompletedIndictmentKey(key)
+
+ if (await this.objectExistsInS3(completedKey)) {
+ return await this.getSignedUrlFromS3(completedKey, timeToLive)
+ }
+ }
+
+ return this.getSignedUrlFromS3(formatS3IndictmentKey(key), timeToLive)
+ }
+
+ getSignedUrl(
+ caseType: CaseType,
+ caseState: CaseState,
+ key?: string,
+ timeToLive?: number,
+ ): Promise {
+ if (!key) {
+ throw new Error('Key is required')
+ }
+
+ return isIndictmentCase(caseType)
+ ? this.getIndictmentSignedUrl(caseState, key, timeToLive)
+ : this.getRequestSignedUrl(key, timeToLive)
}
- async getObject(key: string): Promise {
+ async getConfirmedSignedUrl(
+ caseType: CaseType,
+ caseState: CaseState,
+ key: string | undefined,
+ force: boolean,
+ confirmContent: (content: Buffer) => Promise,
+ timeToLive?: number,
+ ): Promise {
+ if (!key) {
+ throw new Error('Key is required')
+ }
+
+ if (!isIndictmentCase(caseType)) {
+ throw new Error('Only indictment case objects can be confirmed')
+ }
+
+ const confirmedKey = formatConfirmedKey(key)
+
+ if (
+ !force &&
+ (await this.indictmentObjectExists(caseState, confirmedKey))
+ ) {
+ return this.getIndictmentSignedUrl(caseState, confirmedKey, timeToLive)
+ }
+
+ const confirmedContent = await this.getIndictmentObject(
+ caseState,
+ key,
+ ).then((content) => confirmContent(content))
+
+ if (!confirmedContent) {
+ return this.getIndictmentSignedUrl(caseState, key, timeToLive)
+ }
+
+ return this.putConfirmedObject(
+ caseType,
+ caseState,
+ key,
+ confirmedContent,
+ ).then(() =>
+ this.getIndictmentSignedUrl(caseState, confirmedKey, timeToLive),
+ )
+ }
+
+ private async getObjectFromS3(key: string): Promise {
return this.s3
.getObject({
Bucket: this.config.bucket,
@@ -99,7 +233,88 @@ export class AwsS3Service {
.then((data) => data.Body as Buffer)
}
- async putObject(key: string, content: string): Promise {
+ private getRequestObject(key: string): Promise {
+ return this.getObjectFromS3(formatS3RequestKey(key))
+ }
+
+ private async getIndictmentObject(
+ caseSate: CaseState,
+ key: string,
+ ): Promise {
+ if (isCompletedCase(caseSate)) {
+ const completedKey = formatS3CompletedIndictmentKey(key)
+
+ if (await this.objectExistsInS3(completedKey)) {
+ return await this.getObjectFromS3(completedKey)
+ }
+ }
+
+ return this.getObjectFromS3(formatS3IndictmentKey(key))
+ }
+
+ async getObject(
+ caseType: CaseType,
+ caseState: CaseState,
+ key?: string,
+ ): Promise {
+ if (!key) {
+ throw new Error('Key is required')
+ }
+
+ return isIndictmentCase(caseType)
+ ? this.getIndictmentObject(caseState, key)
+ : this.getRequestObject(key)
+ }
+
+ getGeneratedObject(caseType: CaseType, key: string): Promise {
+ if (isIndictmentCase(caseType)) {
+ throw new Error('Only request case objects can be generated')
+ }
+
+ return this.getObjectFromS3(`${generatedPrefix}${key}`)
+ }
+
+ async getConfirmedObject(
+ caseType: CaseType,
+ caseState: CaseState,
+ key: string | undefined,
+ force: boolean,
+ confirmContent: (content: Buffer) => Promise,
+ ): Promise {
+ if (!key) {
+ throw new Error('Key is required')
+ }
+
+ if (!isIndictmentCase(caseType)) {
+ throw new Error('Only indictment case objects can be confirmed')
+ }
+
+ const confirmedKey = formatConfirmedKey(key)
+
+ if (
+ !force &&
+ (await this.indictmentObjectExists(caseState, confirmedKey))
+ ) {
+ return this.getIndictmentObject(caseState, confirmedKey)
+ }
+
+ const content = await this.getIndictmentObject(caseState, key)
+
+ const confirmedContent = await confirmContent(content)
+
+ if (!confirmedContent) {
+ return this.getIndictmentObject(caseState, key)
+ }
+
+ return this.putConfirmedObject(
+ caseType,
+ caseState,
+ key,
+ confirmedContent,
+ ).then(() => this.getIndictmentObject(caseState, confirmedKey))
+ }
+
+ private async putObjectToS3(key: string, content: string): Promise {
return this.s3
.putObject({
Bucket: this.config.bucket,
@@ -111,7 +326,95 @@ export class AwsS3Service {
.then(() => key)
}
- async copyObject(key: string, newKey: string): Promise {
+ async putObject(
+ caseType: CaseType,
+ caseState: CaseState,
+ key: string,
+ content: string,
+ ): Promise {
+ return this.putObjectToS3(formatS3Key(caseType, caseState, key), content)
+ }
+
+ putGeneratedObject(
+ caseType: CaseType,
+ key: string,
+ content: string,
+ ): Promise {
+ if (isIndictmentCase(caseType)) {
+ throw new Error('Only request case objects can be generated')
+ }
+
+ return this.putObjectToS3(`${generatedPrefix}${key}`, content)
+ }
+
+ putConfirmedObject(
+ caseType: CaseType,
+ caseState: CaseState,
+ key: string,
+ content: string,
+ ): Promise {
+ if (!isIndictmentCase(caseType)) {
+ throw new Error('Only indictment case objects can be confirmed')
+ }
+
+ return this.putObject(caseType, caseState, formatConfirmedKey(key), content)
+ }
+
+ private async deleteObjectFromS3(key: string): Promise {
+ return this.s3
+ .deleteObject({
+ Bucket: this.config.bucket,
+ Key: key,
+ })
+ .promise()
+ .then(() => true)
+ .catch((reason) => {
+ // Tolerate failure, but log error
+ this.logger.error(`Failed to delete object ${key} from AWS S3`, {
+ reason,
+ })
+ return false
+ })
+ }
+
+ private deleteRequestObject(key: string): Promise {
+ return this.deleteObjectFromS3(formatS3RequestKey(key))
+ }
+
+ private async deleteIndictmentObject(
+ caseSate: CaseState,
+ key: string,
+ ): Promise {
+ if (isCompletedCase(caseSate)) {
+ const completedKey = formatS3CompletedIndictmentKey(key)
+
+ if (await this.objectExistsInS3(completedKey)) {
+ // No need to wait for the delete to finish
+ this.deleteObjectFromS3(formatConfirmedKey(completedKey))
+
+ return await this.deleteObjectFromS3(completedKey)
+ }
+ }
+
+ const originalKey = formatS3IndictmentKey(key)
+
+ // No need to wait for the delete to finish
+ this.deleteObjectFromS3(formatConfirmedKey(originalKey))
+
+ return this.deleteObjectFromS3(originalKey)
+ }
+
+ async deleteObject(
+ caseType: CaseType,
+ caseState: CaseState,
+ key: string,
+ ): Promise {
+ return isIndictmentCase(caseType)
+ ? this.deleteIndictmentObject(caseState, key)
+ : this.deleteRequestObject(key)
+ }
+
+ private async copyObject(key: string, newKey: string): Promise {
return this.s3
.copyObject({
Bucket: this.config.bucket,
@@ -121,4 +424,43 @@ export class AwsS3Service {
.promise()
.then(() => newKey)
}
+
+ async archiveObject(
+ caseType: CaseType,
+ caseState: CaseState,
+ key: string,
+ ): Promise {
+ if (!isIndictmentCase(caseType)) {
+ throw new Error('Only indictment case objects can be archived')
+ }
+
+ if (!isCompletedCase(caseState)) {
+ throw new Error('Only completed indictment case objects can be archived')
+ }
+
+ const oldKey = formatS3IndictmentKey(key)
+ const newKey = formatS3CompletedIndictmentKey(key)
+
+ const oldConfirmedKey = formatConfirmedKey(oldKey)
+
+ if (await this.objectExistsInS3(oldConfirmedKey)) {
+ const newConfirmedKey = formatConfirmedKey(newKey)
+
+ if (!(await this.objectExistsInS3(newConfirmedKey))) {
+ await this.copyObject(oldConfirmedKey, newConfirmedKey)
+ }
+
+ // No need to wait for the delete to finish
+ this.deleteObjectFromS3(oldConfirmedKey)
+ }
+
+ if (!(await this.objectExistsInS3(newKey))) {
+ await this.copyObject(oldKey, newKey)
+ }
+
+ // No need to wait for the delete to finish
+ this.deleteObjectFromS3(oldKey)
+
+ return newKey
+ }
}
diff --git a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts
index a06ffadef641..d055e66339fb 100644
--- a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts
@@ -96,7 +96,6 @@ import {
} from './guards/rolesRules'
import { CaseInterceptor } from './interceptors/case.interceptor'
import { CaseListInterceptor } from './interceptors/caseList.interceptor'
-import { TransitionInterceptor } from './interceptors/transition.interceptor'
import { Case } from './models/case.model'
import { SignatureConfirmationResponse } from './models/signatureConfirmation.response'
import { transitionCase } from './state/case.state'
@@ -260,7 +259,6 @@ export class CaseController {
}
@UseGuards(JwtAuthGuard, CaseExistsGuard, RolesGuard, CaseWriteGuard)
- @UseInterceptors(TransitionInterceptor)
@RolesRules(
prosecutorTransitionRule,
prosecutorRepresentativeTransitionRule,
@@ -304,9 +302,8 @@ export class CaseController {
`User ${user.id} does not have permission to confirm indictments`,
)
}
- if (theCase.indictmentDeniedExplanation) {
- update.indictmentDeniedExplanation = ''
- }
+
+ update.indictmentDeniedExplanation = null
}
break
case CaseTransition.ACCEPT:
@@ -339,7 +336,6 @@ export class CaseController {
),
}
}
-
break
case CaseTransition.REOPEN:
update.rulingDate = null
@@ -399,12 +395,11 @@ export class CaseController {
}
break
case CaseTransition.ASK_FOR_CONFIRMATION:
- if (theCase.indictmentReturnedExplanation) {
- update.indictmentReturnedExplanation = ''
- }
+ update.indictmentReturnedExplanation = null
break
case CaseTransition.RETURN_INDICTMENT:
- update.courtCaseNumber = ''
+ update.courtCaseNumber = null
+ update.indictmentHash = null
break
case CaseTransition.REDISTRIBUTE:
update.judgeId = null
diff --git a/apps/judicial-system/backend/src/app/modules/case/case.service.ts b/apps/judicial-system/backend/src/app/modules/case/case.service.ts
index e01cb4ae6e1e..408ad4b46bd6 100644
--- a/apps/judicial-system/backend/src/app/modules/case/case.service.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/case.service.ts
@@ -41,6 +41,7 @@ import {
isCompletedCase,
isIndictmentCase,
isRestrictionCase,
+ isTrafficViolationCase,
NotificationType,
prosecutorCanSelectDefenderForInvestigationCase,
UserRole,
@@ -55,7 +56,7 @@ import { AwsS3Service } from '../aws-s3'
import { CourtService } from '../court'
import { Defendant, DefendantService } from '../defendant'
import { CaseEvent, EventService } from '../event'
-import { EventLog } from '../event-log'
+import { EventLog, EventLogService } from '../event-log'
import { CaseFile, FileService } from '../file'
import { IndictmentCount } from '../indictment-count'
import { Institution } from '../institution'
@@ -104,7 +105,6 @@ export interface UpdateCase
| 'caseFilesComments'
| 'prosecutorId'
| 'sharedWithProsecutorsOfficeId'
- | 'courtCaseNumber'
| 'sessionArrangements'
| 'courtLocation'
| 'courtStartDate'
@@ -153,8 +153,6 @@ export interface UpdateCase
| 'appealValidToDate'
| 'isAppealCustodyIsolation'
| 'appealIsolationToDate'
- | 'indictmentDeniedExplanation'
- | 'indictmentReturnedExplanation'
| 'indictmentRulingDecision'
| 'indictmentReviewerId'
> {
@@ -164,6 +162,7 @@ export interface UpdateCase
defendantWaivesRightToCounsel?: boolean
rulingDate?: Date | null
rulingSignatureDate?: Date | null
+ courtCaseNumber?: string | null
courtRecordSignatoryId?: string | null
courtRecordSignatureDate?: Date | null
parentCaseId?: string | null
@@ -171,6 +170,9 @@ export interface UpdateCase
courtDate?: UpdateDateLog | null
postponedIndefinitelyExplanation?: string | null
judgeId?: string | null
+ indictmentReturnedExplanation?: string | null
+ indictmentDeniedExplanation?: string | null
+ indictmentHash?: string | null
}
type DateLogKeys = keyof Pick
@@ -243,6 +245,11 @@ export const include: Includeable[] = [
as: 'appealJudge3',
include: [{ model: Institution, as: 'institution' }],
},
+ {
+ model: User,
+ as: 'indictmentReviewer',
+ include: [{ model: Institution, as: 'institution' }],
+ },
{
model: Case,
as: 'parentCase',
@@ -271,6 +278,7 @@ export const include: Includeable[] = [
as: 'eventLogs',
required: false,
where: { eventType: { [Op.in]: eventTypes } },
+ order: [['created', 'ASC']],
separate: true,
},
{
@@ -286,11 +294,6 @@ export const include: Includeable[] = [
where: { commentType: { [Op.in]: commentTypes } },
},
{ model: Notification, as: 'notifications' },
- {
- model: User,
- as: 'indictmentReviewer',
- include: [{ model: Institution, as: 'institution' }],
- },
]
export const order: OrderItem[] = [
@@ -364,6 +367,7 @@ export class CaseService {
private readonly signingService: SigningService,
private readonly intlService: IntlService,
private readonly eventService: EventService,
+ private readonly eventLogService: EventLogService,
private readonly messageService: MessageService,
@Inject(LOGGER_PROVIDER) private readonly logger: Logger,
) {}
@@ -388,7 +392,7 @@ export class CaseService {
pdf: string,
): Promise {
return this.awsS3Service
- .putObject(`generated/${theCase.id}/ruling.pdf`, pdf)
+ .putGeneratedObject(theCase.type, `${theCase.id}/ruling.pdf`, pdf)
.then(() => true)
.catch((reason) => {
this.logger.error(
@@ -405,7 +409,7 @@ export class CaseService {
pdf: string,
): Promise {
return this.awsS3Service
- .putObject(`generated/${theCase.id}/courtRecord.pdf`, pdf)
+ .putGeneratedObject(theCase.type, `${theCase.id}/courtRecord.pdf`, pdf)
.then(() => true)
.catch((reason) => {
this.logger.error(
@@ -465,7 +469,11 @@ export class CaseService {
) {
for (const caseFile of theCase.caseFiles) {
if (caseFile.policeCaseNumber === oldPoliceCaseNumbers[i]) {
- await this.fileService.deleteCaseFile(caseFile, transaction)
+ await this.fileService.deleteCaseFile(
+ theCase,
+ caseFile,
+ transaction,
+ )
}
}
@@ -642,6 +650,19 @@ export class CaseService {
elementId: policeCaseNumber,
}))
+ const caseFilesCategories = isTrafficViolationCase(theCase)
+ ? [
+ CaseFileCategory.COVER_LETTER,
+ CaseFileCategory.CRIMINAL_RECORD,
+ CaseFileCategory.COST_BREAKDOWN,
+ ]
+ : [
+ CaseFileCategory.COVER_LETTER,
+ CaseFileCategory.INDICTMENT,
+ CaseFileCategory.CRIMINAL_RECORD,
+ CaseFileCategory.COST_BREAKDOWN,
+ ]
+
const deliverCaseFileToCourtMessages =
theCase.caseFiles
?.filter(
@@ -649,12 +670,7 @@ export class CaseService {
caseFile.state === CaseFileState.STORED_IN_RVG &&
caseFile.key &&
((caseFile.category &&
- [
- CaseFileCategory.COVER_LETTER,
- CaseFileCategory.INDICTMENT,
- CaseFileCategory.CRIMINAL_RECORD,
- CaseFileCategory.COST_BREAKDOWN,
- ].includes(caseFile.category)) ||
+ caseFilesCategories.includes(caseFile.category)) ||
(caseFile.category === CaseFileCategory.CASE_FILE &&
!caseFile.policeCaseNumber)),
)
@@ -665,12 +681,20 @@ export class CaseService {
elementId: caseFile.id,
})) ?? []
- return this.messageService.sendMessagesToQueue(
- this.getDeliverProsecutorToCourtMessages(theCase, user)
- .concat(this.getDeliverDefendantToCourtMessages(theCase, user))
- .concat(deliverCaseFilesRecordToCourtMessages)
- .concat(deliverCaseFileToCourtMessages),
- )
+ const messages = this.getDeliverProsecutorToCourtMessages(theCase, user)
+ .concat(this.getDeliverDefendantToCourtMessages(theCase, user))
+ .concat(deliverCaseFilesRecordToCourtMessages)
+ .concat(deliverCaseFileToCourtMessages)
+
+ if (isTrafficViolationCase(theCase)) {
+ messages.push({
+ type: MessageType.DELIVERY_TO_COURT_INDICTMENT,
+ user,
+ caseId: theCase.id,
+ })
+ }
+
+ return this.messageService.sendMessagesToQueue(messages)
}
private addMessagesForDefenderEmailChangeToQueue(
@@ -1343,6 +1367,29 @@ export class CaseService {
}
}
+ handleEventLogs(
+ theCase: Case,
+ update: UpdateCase,
+ user: TUser,
+ transaction: Transaction,
+ ) {
+ if (
+ isIndictmentCase(theCase.type) &&
+ update.state === CaseState.SUBMITTED &&
+ theCase.state === CaseState.WAITING_FOR_CONFIRMATION
+ ) {
+ return this.eventLogService.create(
+ {
+ eventType: EventType.INDICTMENT_CONFIRMED,
+ caseId: theCase.id,
+ nationalId: user.nationalId,
+ userRole: user.role,
+ },
+ transaction,
+ )
+ }
+ }
+
async update(
theCase: Case,
update: UpdateCase,
@@ -1351,6 +1398,8 @@ export class CaseService {
): Promise {
const receivingCase =
update.courtCaseNumber && theCase.state === CaseState.SUBMITTED
+ const returningIndictmentCase =
+ update.state === CaseState.DRAFT && theCase.state === CaseState.RECEIVED
return this.sequelize
.transaction(async (transaction) => {
@@ -1365,6 +1414,7 @@ export class CaseService {
await this.handleDateUpdates(theCase, update, transaction)
await this.handleCommentUpdates(theCase, update, transaction)
+ await this.handleEventLogs(theCase, update, user, transaction)
if (Object.keys(update).length === 0) {
return
@@ -1401,6 +1451,13 @@ export class CaseService {
) {
await this.fileService.resetCaseFileStates(theCase.id, transaction)
}
+
+ if (returningIndictmentCase) {
+ await this.fileService.resetIndictmentCaseFileHashes(
+ theCase.id,
+ transaction,
+ )
+ }
})
.then(async () => {
const updatedCase = await this.findById(theCase.id, true)
diff --git a/apps/judicial-system/backend/src/app/modules/case/filters/cases.filter.ts b/apps/judicial-system/backend/src/app/modules/case/filters/cases.filter.ts
index f4c2d84aaa4c..838275876e6d 100644
--- a/apps/judicial-system/backend/src/app/modules/case/filters/cases.filter.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/filters/cases.filter.ts
@@ -263,7 +263,6 @@ const getDefenceUserCasesQueryFilter = (user: User): WhereOptions => {
}
export const getCasesQueryFilter = (user: User): WhereOptions => {
- // TODO: Convert to switch
if (isProsecutionUser(user)) {
return getProsecutionUserCasesQueryFilter(user)
}
diff --git a/apps/judicial-system/backend/src/app/modules/case/interceptors/caseList.interceptor.ts b/apps/judicial-system/backend/src/app/modules/case/interceptors/caseList.interceptor.ts
index af0efc03b223..940d763221d6 100644
--- a/apps/judicial-system/backend/src/app/modules/case/interceptors/caseList.interceptor.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/interceptors/caseList.interceptor.ts
@@ -58,6 +58,7 @@ export class CaseListInterceptor implements NestInterceptor {
)?.comment,
indictmentReviewer: theCase.indictmentReviewer,
indictmentReviewDecision: theCase.indictmentReviewDecision,
+ indictmentRulingDecision: theCase.indictmentRulingDecision,
}
}),
),
diff --git a/apps/judicial-system/backend/src/app/modules/case/interceptors/transition.interceptor.ts b/apps/judicial-system/backend/src/app/modules/case/interceptors/transition.interceptor.ts
deleted file mode 100644
index d95e51de802e..000000000000
--- a/apps/judicial-system/backend/src/app/modules/case/interceptors/transition.interceptor.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import { Observable } from 'rxjs'
-import { map } from 'rxjs/operators'
-
-import {
- CallHandler,
- ExecutionContext,
- Injectable,
- NestInterceptor,
-} from '@nestjs/common'
-
-import {
- CaseFileCategory,
- CaseState,
- CaseTransition,
- EventType,
- isIndictmentCase,
- isTrafficViolationCase,
- User,
-} from '@island.is/judicial-system/types'
-
-import { nowFactory } from '../../../factories'
-import { formatConfirmedIndictmentKey } from '../../../formatters/formatters'
-import { AwsS3Service } from '../../aws-s3'
-import { EventLogService } from '../../event-log'
-import { TransitionCaseDto } from '../dto/transitionCase.dto'
-import { Case } from '../models/case.model'
-import { PDFService } from '../pdf.service'
-
-@Injectable()
-export class TransitionInterceptor implements NestInterceptor {
- constructor(
- private readonly eventLogService: EventLogService,
- private readonly awsService: AwsS3Service,
- private readonly pdfService: PDFService,
- ) {}
-
- async intercept(
- context: ExecutionContext,
- next: CallHandler,
- ): Promise> {
- const request = context.switchToHttp().getRequest()
- const theCase: Case = request.case
- const dto: TransitionCaseDto = request.body
- const user: User = request.user
-
- if (
- isIndictmentCase(theCase.type) &&
- !isTrafficViolationCase(theCase.indictmentSubtypes, theCase.type) &&
- theCase.state === CaseState.WAITING_FOR_CONFIRMATION &&
- dto.transition === CaseTransition.SUBMIT
- ) {
- for (const indictment of theCase.caseFiles?.filter(
- (cf) => cf.category === CaseFileCategory.INDICTMENT && cf.key,
- ) ?? []) {
- // Get indictment PDF from S3
- const file = await this.awsService.getObject(indictment.key ?? '')
-
- // Create a stamped indictment PDF
- const confirmedIndictment =
- await this.pdfService.getConfirmedIndictmentPdf(
- {
- actor: user.name,
- institution: user.institution?.name ?? '',
- date: nowFactory(),
- },
- file,
- )
-
- // Save the PDF to S3
- await this.awsService.putObject(
- formatConfirmedIndictmentKey(indictment.key),
- confirmedIndictment.toString('binary'),
- )
- }
- }
-
- return next.handle().pipe(
- map((data: Case) => {
- if (isIndictmentCase(data.type) && data.state === CaseState.SUBMITTED) {
- this.eventLogService.create({
- eventType: EventType.INDICTMENT_CONFIRMED,
- caseId: data.id,
- nationalId: user.nationalId,
- userRole: user.role,
- })
- }
-
- return data
- }),
- )
- }
-}
diff --git a/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts b/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts
index 585a3e7c04f4..bc47e8277231 100644
--- a/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts
@@ -80,9 +80,27 @@ export class InternalCaseController {
@Body() internalCasesDto: InternalCasesDto,
): Promise {
this.logger.debug('Getting all indictment cases')
- const nationalId = formatNationalId(internalCasesDto.nationalId)
- return this.internalCaseService.getIndictmentCases(nationalId)
+ return this.internalCaseService.getIndictmentCases(
+ internalCasesDto.nationalId,
+ )
+ }
+
+ @Post('cases/indictment/:caseId')
+ @ApiOkResponse({
+ type: Case,
+ description: 'Gets indictment case by id',
+ })
+ getIndictmentCase(
+ @Param('caseId') caseId: string,
+ @Body() internalCasesDto: InternalCasesDto,
+ ): Promise {
+ this.logger.debug(`Getting indictment case ${caseId}`)
+
+ return this.internalCaseService.getIndictmentCase(
+ caseId,
+ internalCasesDto.nationalId,
+ )
}
@UseGuards(CaseExistsGuard)
@@ -106,6 +124,27 @@ export class InternalCaseController {
)
}
+ @UseGuards(CaseExistsGuard, new CaseTypeGuard(indictmentCases))
+ @Post(
+ `case/:caseId/${messageEndpoint[MessageType.DELIVERY_TO_COURT_INDICTMENT]}`,
+ )
+ @ApiOkResponse({
+ type: DeliverResponse,
+ description: 'Delivers an indictment to court',
+ })
+ deliverIndictmentToCourt(
+ @Param('caseId') caseId: string,
+ @CurrentCase() theCase: Case,
+ @Body() deliverDto: DeliverDto,
+ ): Promise {
+ this.logger.debug(`Delivering the indictment for case ${caseId} to court`)
+
+ return this.internalCaseService.deliverIndictmentToCourt(
+ theCase,
+ deliverDto.user,
+ )
+ }
+
@UseGuards(CaseExistsGuard, new CaseTypeGuard(indictmentCases))
@Post(
`case/:caseId/${
diff --git a/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts b/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts
index 690b48e5e69e..028c0a158b3f 100644
--- a/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts
@@ -10,6 +10,7 @@ import {
Inject,
Injectable,
InternalServerErrorException,
+ NotFoundException,
} from '@nestjs/common'
import { InjectConnection, InjectModel } from '@nestjs/sequelize'
@@ -17,18 +18,19 @@ import { FormatMessage, IntlService } from '@island.is/cms-translations'
import { type Logger, LOGGER_PROVIDER } from '@island.is/logging'
import type { ConfigType } from '@island.is/nest/config'
-import { formatCaseType } from '@island.is/judicial-system/formatters'
+import {
+ formatCaseType,
+ formatNationalId,
+} from '@island.is/judicial-system/formatters'
import {
CaseFileCategory,
CaseOrigin,
CaseState,
CaseType,
- EventType,
- type IndictmentConfirmation,
- isCompletedCase,
isIndictmentCase,
isProsecutionUser,
isRestrictionCase,
+ isTrafficViolationCase,
NotificationType,
restrictionCases,
type User as TUser,
@@ -37,8 +39,6 @@ import {
import { nowFactory, uuidFactory } from '../../factories'
import {
- createCaseFilesRecord,
- createIndictment,
getCourtRecordPdfAsBuffer,
getCourtRecordPdfAsString,
getCustodyNoticePdfAsString,
@@ -53,8 +53,9 @@ import { CaseEvent, EventService } from '../event'
import { EventLogService } from '../event-log'
import { CaseFile, FileService } from '../file'
import { IndictmentCount, IndictmentCountService } from '../indictment-count'
-import { CourtDocumentType, PoliceService } from '../police'
-import { UserService } from '../user'
+import { Institution } from '../institution'
+import { PoliceDocument, PoliceDocumentType, PoliceService } from '../police'
+import { User, UserService } from '../user'
import { InternalCreateCaseDto } from './dto/internalCreateCase.dto'
import { archiveFilter } from './filters/case.archiveFilter'
import { ArchiveResponse } from './models/archive.response'
@@ -63,6 +64,7 @@ import { CaseArchive } from './models/caseArchive.model'
import { DateLog } from './models/dateLog.model'
import { DeliverResponse } from './models/deliver.response'
import { caseModuleConfig } from './case.config'
+import { PDFService } from './pdf.service'
const caseEncryptionProperties: (keyof Case)[] = [
'description',
@@ -160,6 +162,7 @@ export class InternalCaseService {
private readonly defendantService: DefendantService,
@Inject(forwardRef(() => EventLogService))
private readonly eventLogService: EventLogService,
+ private readonly pdfService: PDFService,
@Inject(LOGGER_PROVIDER) private readonly logger: Logger,
) {}
@@ -284,135 +287,11 @@ export class InternalCaseService {
})
}
- private async getCaseFilesRecordPdf(
- theCase: Case,
- policeCaseNumber: string,
- ): Promise {
- if (isCompletedCase(theCase.state)) {
- try {
- return await this.awsS3Service.getObject(
- `indictments/completed/${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
- )
- } catch {
- // Ignore the error and try the original key
- }
- }
-
- try {
- return await this.awsS3Service.getObject(
- `indictments/${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
- )
- } catch {
- // Ignore the error and generate the pdf
- }
-
- const caseFiles = theCase.caseFiles
- ?.filter(
- (caseFile) =>
- caseFile.policeCaseNumber === policeCaseNumber &&
- caseFile.category === CaseFileCategory.CASE_FILE &&
- caseFile.type === 'application/pdf' &&
- caseFile.key &&
- caseFile.chapter !== null &&
- caseFile.orderWithinChapter !== null,
- )
- ?.sort(
- (caseFile1, caseFile2) =>
- (caseFile1.chapter ?? 0) - (caseFile2.chapter ?? 0) ||
- (caseFile1.orderWithinChapter ?? 0) -
- (caseFile2.orderWithinChapter ?? 0),
- )
- ?.map((caseFile) => async () => {
- const buffer = await this.awsS3Service
- .getObject(caseFile.key ?? '')
- .catch((reason) => {
- // Tolerate failure, but log error
- this.logger.error(
- `Unable to get file ${caseFile.id} of case ${theCase.id} from AWS S3`,
- { reason },
- )
- })
-
- return {
- chapter: caseFile.chapter as number,
- date: caseFile.displayDate ?? caseFile.created,
- name: caseFile.userGeneratedFilename ?? caseFile.name,
- buffer: buffer ?? undefined,
- }
- })
-
- const pdf = await createCaseFilesRecord(
- theCase,
- policeCaseNumber,
- caseFiles ?? [],
- this.formatMessage,
- )
-
- await this.awsS3Service
- .putObject(
- `indictments/${isCompletedCase(theCase.state) ? 'completed/' : ''}${
- theCase.id
- }/${policeCaseNumber}/caseFilesRecord.pdf`,
- pdf.toString('binary'),
- )
- .catch((reason) => {
- this.logger.error(
- `Failed to upload case files record pdf to AWS S3 for case ${theCase.id} and police case ${policeCaseNumber}`,
- { reason },
- )
- })
-
- return pdf
- }
-
- private async throttleUploadCaseFilesRecordPdfToCourt(
- theCase: Case,
- policeCaseNumber: string,
- user: TUser,
- ): Promise {
- // Serialize all case files record pdf deliveries in this process
- await this.throttle.catch((reason) => {
- this.logger.info('Previous case files record pdf delivery failed', {
- reason,
- })
- })
-
- await this.refreshFormatMessage()
-
- return this.getCaseFilesRecordPdf(theCase, policeCaseNumber)
- .then((pdf) => {
- const fileName = this.formatMessage(courtUpload.caseFilesRecord, {
- policeCaseNumber,
- })
-
- return this.courtService.createDocument(
- user,
- theCase.id,
- theCase.courtId,
- theCase.courtCaseNumber,
- CourtDocumentFolder.CASE_DOCUMENTS,
- fileName,
- `${fileName}.pdf`,
- 'application/pdf',
- pdf,
- )
- })
- .then(() => {
- return true
- })
- .catch((error) => {
- // Tolerate failure, but log error
- this.logger.warn(
- `Failed to upload case files record pdf to court for case ${theCase.id}`,
- { error },
- )
-
- return false
- })
- }
-
private getSignedRulingPdf(theCase: Case) {
- return this.awsS3Service.getObject(`generated/${theCase.id}/ruling.pdf`)
+ return this.awsS3Service.getGeneratedObject(
+ theCase.type,
+ `${theCase.id}/ruling.pdf`,
+ )
}
private async deliverSignedRulingPdfToCourt(
@@ -628,20 +507,77 @@ export class InternalCaseService {
})
}
+ async deliverIndictmentToCourt(
+ theCase: Case,
+ user: TUser,
+ ): Promise {
+ return this.pdfService
+ .getIndictmentPdf(theCase)
+ .then(async (pdf) => {
+ await this.refreshFormatMessage()
+
+ const fileName = this.formatMessage(courtUpload.indictment)
+
+ return this.courtService.createDocument(
+ user,
+ theCase.id,
+ theCase.courtId,
+ theCase.courtCaseNumber,
+ CourtDocumentFolder.INDICTMENT_DOCUMENTS,
+ fileName,
+ `${fileName}.pdf`,
+ 'application/pdf',
+ pdf,
+ )
+ })
+ .then(() => ({ delivered: true }))
+ .catch((reason) => {
+ // Tolerate failure, but log error
+ this.logger.warn(
+ `Failed to upload indictment pdf to court for case ${theCase.id}`,
+ { reason },
+ )
+
+ return { delivered: false }
+ })
+ }
+
async deliverCaseFilesRecordToCourt(
theCase: Case,
policeCaseNumber: string,
user: TUser,
): Promise {
- this.throttle = this.throttleUploadCaseFilesRecordPdfToCourt(
- theCase,
- policeCaseNumber,
- user,
- )
+ return this.pdfService
+ .getCaseFilesRecordPdf(theCase, policeCaseNumber)
+ .then(async (pdf) => {
+ await this.refreshFormatMessage()
- const delivered = await this.throttle
+ const fileName = this.formatMessage(courtUpload.caseFilesRecord, {
+ policeCaseNumber,
+ })
- return { delivered }
+ return this.courtService.createDocument(
+ user,
+ theCase.id,
+ theCase.courtId,
+ theCase.courtCaseNumber,
+ CourtDocumentFolder.CASE_DOCUMENTS,
+ fileName,
+ `${fileName}.pdf`,
+ 'application/pdf',
+ pdf,
+ )
+ })
+ .then(() => ({ delivered: true }))
+ .catch((reason) => {
+ // Tolerate failure, but log reason
+ this.logger.warn(
+ `Failed to upload case files record pdf to court for case ${theCase.id}`,
+ { reason },
+ )
+
+ return { delivered: false }
+ })
}
async archiveCaseFilesRecord(
@@ -649,26 +585,12 @@ export class InternalCaseService {
policeCaseNumber: string,
): Promise {
return this.awsS3Service
- .copyObject(
- `indictments/${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
- `indictments/completed/${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
+ .archiveObject(
+ theCase.type,
+ theCase.state,
+ `${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
)
- .then(() => {
- // Fire and forget, no need to wait for the result
- this.awsS3Service
- .deleteObject(
- `indictments/${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
- )
- .catch((reason) => {
- // Tolerate failure, but log what happened
- this.logger.error(
- `Could not delete case files record for case ${theCase.id} and police case ${policeCaseNumber} from AWS S3`,
- { reason },
- )
- })
-
- return { delivered: true }
- })
+ .then(() => ({ delivered: true }))
.catch((reason) => {
this.logger.error(
`Failed to archive case files record for case ${theCase.id} and police case ${policeCaseNumber}`,
@@ -685,9 +607,9 @@ export class InternalCaseService {
): Promise {
await this.refreshFormatMessage()
- const delivered = await this.upploadRequestPdfToCourt(theCase, user)
-
- return { delivered }
+ return this.upploadRequestPdfToCourt(theCase, user).then((delivered) => ({
+ delivered,
+ }))
}
async deliverCourtRecordToCourt(
@@ -696,9 +618,9 @@ export class InternalCaseService {
): Promise {
await this.refreshFormatMessage()
- const delivered = await this.uploadCourtRecordPdfToCourt(theCase, user)
-
- return { delivered }
+ return this.uploadCourtRecordPdfToCourt(theCase, user).then(
+ (delivered) => ({ delivered }),
+ )
}
async deliverSignedRulingToCourt(
@@ -707,9 +629,9 @@ export class InternalCaseService {
): Promise {
await this.refreshFormatMessage()
- const delivered = await this.deliverSignedRulingPdfToCourt(theCase, user)
-
- return { delivered }
+ return this.deliverSignedRulingPdfToCourt(theCase, user).then(
+ (delivered) => ({ delivered }),
+ )
}
async deliverCaseConclusionToCourt(
@@ -830,7 +752,7 @@ export class InternalCaseService {
private async deliverCaseToPoliceWithFiles(
theCase: Case,
user: TUser,
- courtDocuments: { type: CourtDocumentType; courtDocument: string }[],
+ courtDocuments: PoliceDocument[],
): Promise {
const originalAncestor = await this.findOriginalAncestor(theCase)
@@ -872,13 +794,13 @@ export class InternalCaseService {
.then(async () => {
const courtDocuments = [
{
- type: CourtDocumentType.RVKR,
+ type: PoliceDocumentType.RVKR,
courtDocument: Base64.btoa(
await getRequestPdfAsString(theCase, this.formatMessage),
),
},
{
- type: CourtDocumentType.RVTB,
+ type: PoliceDocumentType.RVTB,
courtDocument: Base64.btoa(
await getCourtRecordPdfAsString(theCase, this.formatMessage),
),
@@ -888,7 +810,7 @@ export class InternalCaseService {
) && theCase.state === CaseState.ACCEPTED
? [
{
- type: CourtDocumentType.RVVI,
+ type: PoliceDocumentType.RVVI,
courtDocument: Base64.btoa(
await getCustodyNoticePdfAsString(
theCase,
@@ -929,13 +851,18 @@ export class InternalCaseService {
caseFile.key,
)
.map(async (caseFile) => {
- const file = await this.awsS3Service.getObject(caseFile.key ?? '')
+ // TODO: Tolerate failure, but log error
+ const file = await this.awsS3Service.getObject(
+ theCase.type,
+ theCase.state,
+ caseFile.key,
+ )
return {
type:
caseFile.category === CaseFileCategory.COURT_RECORD
- ? CourtDocumentType.RVTB
- : CourtDocumentType.RVDO,
+ ? PoliceDocumentType.RVTB
+ : PoliceDocumentType.RVDO,
courtDocument: Base64.btoa(file.toString('binary')),
}
}) ?? [],
@@ -959,91 +886,70 @@ export class InternalCaseService {
theCase: Case,
user: TUser,
): Promise {
- const delivered = await Promise.all(
- theCase.caseFiles
- ?.filter(
- (caseFile) =>
- caseFile.category === CaseFileCategory.INDICTMENT && caseFile.key,
- )
- .map(async (caseFile) => {
- const file = await this.awsS3Service.getObject(caseFile.key ?? '')
-
- return {
- type: CourtDocumentType.RVAS,
- courtDocument: Base64.btoa(file.toString('binary')),
- }
- }) ?? [],
- )
- .then(async (indictmentDocuments) => {
- if (indictmentDocuments.length === 0) {
- let confirmation: IndictmentConfirmation = undefined
- const confirmationEvent =
- await this.eventLogService.findEventTypeByCaseId(
- EventType.INDICTMENT_CONFIRMED,
- theCase.id,
- )
-
- if (confirmationEvent && confirmationEvent.nationalId) {
- const actor = await this.userService.findByNationalId(
- confirmationEvent.nationalId,
- )
-
- confirmation = {
- actor: actor.name,
- institution: actor.institution?.name ?? '',
- date: confirmationEvent.created,
- }
- }
-
- const file = await this.refreshFormatMessage().then(async () =>
- createIndictment(theCase, this.formatMessage, confirmation),
- )
+ try {
+ let policeDocuments: PoliceDocument[]
- indictmentDocuments.push({
- type: CourtDocumentType.RVAS,
- courtDocument: Base64.btoa(file.toString('binary')),
- })
- }
+ if (isTrafficViolationCase(theCase)) {
+ const file = await this.pdfService.getIndictmentPdf(theCase)
- return this.deliverCaseToPoliceWithFiles(
- theCase,
- user,
- indictmentDocuments,
- )
- })
- .catch((reason) => {
- // Tolerate failure, but log error
- this.logger.error(
- `Failed to deliver indictment for case ${theCase.id} to police`,
+ policeDocuments = [
{
- reason,
+ type: PoliceDocumentType.RVAS,
+ courtDocument: Base64.btoa(file.toString('binary')),
},
+ ]
+ } else {
+ policeDocuments = await Promise.all(
+ theCase.caseFiles
+ ?.filter(
+ (caseFile) =>
+ caseFile.category === CaseFileCategory.INDICTMENT &&
+ caseFile.key,
+ )
+ .map(async (caseFile) => {
+ // TODO: Tolerate failure, but log error
+ const file = await this.fileService.getCaseFileFromS3(
+ theCase,
+ caseFile,
+ )
+
+ return {
+ type: PoliceDocumentType.RVAS,
+ courtDocument: Base64.btoa(file.toString('binary')),
+ }
+ }) ?? [],
)
+ }
- return false
- })
+ const delivered = await this.deliverCaseToPoliceWithFiles(
+ theCase,
+ user,
+ policeDocuments,
+ )
- return { delivered }
+ return { delivered }
+ } catch (error) {
+ // Tolerate failure, but log error
+ this.logger.error(
+ `Failed to deliver indictment for case ${theCase.id} to police`,
+ { error },
+ )
+
+ return { delivered: false }
+ }
}
- private async throttleUploadCaseFilesRecordPdfToPolice(
+ async deliverCaseFilesRecordToPolice(
theCase: Case,
policeCaseNumber: string,
user: TUser,
- ): Promise {
- // Serialize all case files record pdf deliveries in this process
- await this.throttle.catch((reason) => {
- this.logger.info('Previous case files record pdf delivery failed', {
- reason,
- })
- })
-
- return this.refreshFormatMessage()
- .then(() => this.getCaseFilesRecordPdf(theCase, policeCaseNumber))
+ ): Promise {
+ const delivered = await this.pdfService
+ .getCaseFilesRecordPdf(theCase, policeCaseNumber)
.then((pdf) =>
this.deliverCaseToPoliceWithFiles(theCase, user, [
{
- type: CourtDocumentType.RVMG,
+ type: PoliceDocumentType.RVMG,
courtDocument: Base64.btoa(pdf.toString('binary')),
},
]),
@@ -1057,20 +963,6 @@ export class InternalCaseService {
return false
})
- }
-
- async deliverCaseFilesRecordToPolice(
- theCase: Case,
- policeCaseNumber: string,
- user: TUser,
- ): Promise {
- this.throttle = this.throttleUploadCaseFilesRecordPdfToPolice(
- theCase,
- policeCaseNumber,
- user,
- )
-
- const delivered = await this.throttle
return { delivered }
}
@@ -1083,7 +975,7 @@ export class InternalCaseService {
.then((pdf) =>
this.deliverCaseToPoliceWithFiles(theCase, user, [
{
- type: CourtDocumentType.RVUR,
+ type: PoliceDocumentType.RVUR,
courtDocument: Base64.btoa(pdf.toString('binary')),
},
]),
@@ -1109,10 +1001,15 @@ export class InternalCaseService {
theCase.caseFiles
?.filter((file) => file.category === CaseFileCategory.APPEAL_RULING)
.map(async (caseFile) => {
- const file = await this.awsS3Service.getObject(caseFile.key ?? '')
+ // TODO: Tolerate failure, but log error
+ const file = await this.awsS3Service.getObject(
+ theCase.type,
+ theCase.state,
+ caseFile.key,
+ )
return {
- type: CourtDocumentType.RVUL,
+ type: PoliceDocumentType.RVUL,
courtDocument: Base64.btoa(file.toString('binary')),
}
}) ?? [],
@@ -1154,6 +1051,8 @@ export class InternalCaseService {
}
async getIndictmentCases(nationalId: string): Promise {
+ const formattedNationalId = formatNationalId(nationalId)
+
return this.caseModel.findAll({
include: [
{ model: Defendant, as: 'defendants' },
@@ -1163,8 +1062,45 @@ export class InternalCaseService {
attributes: ['id', 'courtCaseNumber', 'type', 'state'],
where: {
type: CaseType.INDICTMENT,
- '$defendants.national_id$': nationalId,
+ [Op.or]: [
+ { '$defendants.national_id$': nationalId },
+ { '$defendants.national_id$': formattedNationalId },
+ ],
},
})
}
+
+ async getIndictmentCase(
+ caseId: string,
+ nationalId: string,
+ ): Promise {
+ // The national id could be without a hyphen or with a hyphen so we need to
+ // search for both
+ const formattedNationalId = formatNationalId(nationalId)
+
+ const caseById = await this.caseModel.findOne({
+ include: [
+ { model: Defendant, as: 'defendants' },
+ { model: Institution, as: 'court' },
+ { model: Institution, as: 'prosecutorsOffice' },
+ { model: User, as: 'judge' },
+ { model: User, as: 'prosecutor' },
+ ],
+ attributes: ['courtCaseNumber'],
+ where: {
+ type: CaseType.INDICTMENT,
+ id: caseId,
+ [Op.or]: [
+ { '$defendants.national_id$': nationalId },
+ { '$defendants.national_id$': formattedNationalId },
+ ],
+ },
+ })
+
+ if (!caseById) {
+ throw new NotFoundException(`Case ${caseId} not found`)
+ }
+
+ return caseById
+ }
}
diff --git a/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.service.ts b/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.service.ts
index a0e44ad60792..bf2410586094 100644
--- a/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.service.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.service.ts
@@ -26,6 +26,7 @@ import {
CaseState,
CommentType,
DateType,
+ EventType,
NotificationType,
UserRole,
} from '@island.is/judicial-system/types'
@@ -33,6 +34,7 @@ import {
import { nowFactory, uuidFactory } from '../../factories'
import { AwsS3Service } from '../aws-s3'
import { Defendant, DefendantService } from '../defendant'
+import { EventLog } from '../event-log'
import {
CaseFile,
defenderCaseFileCategoriesForRestrictionAndInvestigationCases,
@@ -97,6 +99,8 @@ export const attributes: (keyof Case)[] = [
'appealRulingModifiedHistory',
'requestAppealRulingNotToBePublished',
'prosecutorsOfficeId',
+ 'indictmentRulingDecision',
+ 'indictmentHash',
]
export interface LimitedAccessUpdateCase
@@ -109,6 +113,7 @@ export interface LimitedAccessUpdateCase
| 'appealRulingDecision'
> {}
+const eventTypes = Object.values(EventType)
const dateTypes = Object.values(DateType)
const commentTypes = Object.values(CommentType)
@@ -184,6 +189,14 @@ export const include: Includeable[] = [
],
},
},
+ {
+ model: EventLog,
+ as: 'eventLogs',
+ required: false,
+ where: { eventType: { [Op.in]: eventTypes } },
+ order: [['created', 'ASC']],
+ separate: true,
+ },
{
model: DateLog,
as: 'dateLogs',
@@ -377,9 +390,7 @@ export class LimitedAccessCaseService {
})
}
- private zipFiles(
- files: Array<{ data: Buffer; name: string }>,
- ): Promise {
+ private zipFiles(files: { data: Buffer; name: string }[]): Promise {
return new Promise((resolve, reject) => {
const buffs: Buffer[] = []
const converter = new Writable()
@@ -410,7 +421,7 @@ export class LimitedAccessCaseService {
}
async getAllFilesZip(theCase: Case, user: TUser): Promise {
- const filesToZip: Array<{ data: Buffer; name: string }> = []
+ const filesToZip: { data: Buffer; name: string }[] = []
const caseFilesByCategory =
theCase.caseFiles?.filter(
@@ -422,9 +433,10 @@ export class LimitedAccessCaseService {
),
) ?? []
+ // TODO: speed this up by fetching all files in parallel
for (const file of caseFilesByCategory) {
await this.awsS3Service
- .getObject(file.key ?? '')
+ .getObject(theCase.type, theCase.state, file.key)
.then((content) => filesToZip.push({ data: content, name: file.name }))
.catch((reason) =>
// Tolerate failure, but log what happened
diff --git a/apps/judicial-system/backend/src/app/modules/case/models/case.model.ts b/apps/judicial-system/backend/src/app/modules/case/models/case.model.ts
index 81117d9db4cf..fb6261333a6a 100644
--- a/apps/judicial-system/backend/src/app/modules/case/models/case.model.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/models/case.model.ts
@@ -1013,4 +1013,12 @@ export class Case extends Model {
})
@ApiPropertyOptional({ enum: IndictmentCaseReviewDecision })
indictmentReviewDecision?: IndictmentCaseReviewDecision
+
+ /**********
+ * The md5 hash of the confirmed generated indictment
+ * Only used for traffic violation cases
+ **********/
+ @Column({ type: DataType.STRING, allowNull: true })
+ @ApiPropertyOptional({ type: String })
+ indictmentHash?: string
}
diff --git a/apps/judicial-system/backend/src/app/modules/case/pdf.service.ts b/apps/judicial-system/backend/src/app/modules/case/pdf.service.ts
index 1f9f9c5fa26a..d1b1d966820b 100644
--- a/apps/judicial-system/backend/src/app/modules/case/pdf.service.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/pdf.service.ts
@@ -1,8 +1,12 @@
+import CryptoJS from 'crypto-js'
+
import {
+ BadRequestException,
Inject,
Injectable,
InternalServerErrorException,
} from '@nestjs/common'
+import { InjectModel } from '@nestjs/sequelize'
import { FormatMessage, IntlService } from '@island.is/cms-translations'
import type { Logger } from '@island.is/logging'
@@ -10,10 +14,9 @@ import { LOGGER_PROVIDER } from '@island.is/logging'
import {
CaseFileCategory,
- CaseState,
EventType,
- type IndictmentConfirmation,
- isCompletedCase,
+ hasIndictmentCaseBeenSubmittedToCourt,
+ isTrafficViolationCase,
type User as TUser,
} from '@island.is/judicial-system/types'
@@ -24,10 +27,9 @@ import {
getCustodyNoticePdfAsBuffer,
getRequestPdfAsBuffer,
getRulingPdfAsBuffer,
+ IndictmentConfirmation,
} from '../../formatters'
-import { createConfirmedIndictment } from '../../formatters/confirmedIndictmentPdf'
import { AwsS3Service } from '../aws-s3'
-import { EventLogService } from '../event-log'
import { UserService } from '../user'
import { Case } from './models/case.model'
@@ -38,8 +40,8 @@ export class PDFService {
constructor(
private readonly awsS3Service: AwsS3Service,
private readonly intlService: IntlService,
- private readonly eventLogService: EventLogService,
private readonly userService: UserService,
+ @InjectModel(Case) private readonly caseModel: typeof Case,
@Inject(LOGGER_PROVIDER) private readonly logger: Logger,
) {}
@@ -87,36 +89,50 @@ export class PDFService {
)
?.map((caseFile) => async () => {
const buffer = await this.awsS3Service
- .getObject(caseFile.key ?? '')
+ .getObject(theCase.type, theCase.state, caseFile.key)
.catch((reason) => {
// Tolerate failure, but log error
this.logger.error(
`Unable to get file ${caseFile.id} of case ${theCase.id} from AWS S3`,
{ reason },
)
+
+ return undefined
})
return {
chapter: caseFile.chapter as number,
date: caseFile.displayDate ?? caseFile.created,
name: caseFile.userGeneratedFilename ?? caseFile.name,
- buffer: buffer ?? undefined,
+ buffer: buffer,
}
})
- return createCaseFilesRecord(
+ const generatedPdf = await createCaseFilesRecord(
theCase,
policeCaseNumber,
caseFiles ?? [],
this.formatMessage,
)
+
+ if (hasIndictmentCaseBeenSubmittedToCourt(theCase.state)) {
+ // No need to wait for the upload to finish
+ this.tryUploadPdfToS3(
+ theCase,
+ `${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
+ generatedPdf,
+ )
+ }
+
+ return generatedPdf
}
async getCourtRecordPdf(theCase: Case, user: TUser): Promise {
if (theCase.courtRecordSignatureDate) {
try {
- return await this.awsS3Service.getObject(
- `generated/${theCase.id}/courtRecord.pdf`,
+ return await this.awsS3Service.getGeneratedObject(
+ theCase.type,
+ `${theCase.id}/courtRecord.pdf`,
)
} catch (error) {
this.logger.info(
@@ -140,8 +156,9 @@ export class PDFService {
async getRulingPdf(theCase: Case): Promise {
if (theCase.rulingSignatureDate) {
try {
- return await this.awsS3Service.getObject(
- `generated/${theCase.id}/ruling.pdf`,
+ return await this.awsS3Service.getGeneratedObject(
+ theCase.type,
+ `${theCase.id}/ruling.pdf`,
)
} catch (error) {
this.logger.info(
@@ -162,62 +179,101 @@ export class PDFService {
return getCustodyNoticePdfAsBuffer(theCase, this.formatMessage)
}
+ private async tryGetPdfFromS3(
+ theCase: Case,
+ key: string,
+ ): Promise {
+ return await this.awsS3Service
+ .getObject(theCase.type, theCase.state, key)
+ .catch(() => undefined) // Ignore errors and return undefined
+ }
+
+ private tryUploadPdfToS3(theCase: Case, key: string, pdf: Buffer) {
+ this.awsS3Service
+ .putObject(theCase.type, theCase.state, key, pdf.toString('binary'))
+ .catch((reason) => {
+ this.logger.error(`Failed to upload pdf ${key} to AWS S3`, { reason })
+ })
+ }
+
async getIndictmentPdf(theCase: Case): Promise {
- await this.refreshFormatMessage()
+ if (!isTrafficViolationCase(theCase)) {
+ throw new BadRequestException(
+ `Case ${theCase.id} is not a traffic violation case`,
+ )
+ }
- let confirmation: IndictmentConfirmation = undefined
- const confirmationEvent = await this.eventLogService.findEventTypeByCaseId(
- EventType.INDICTMENT_CONFIRMED,
- theCase.id,
- )
+ let confirmation: IndictmentConfirmation | undefined = undefined
- if (confirmationEvent && confirmationEvent.nationalId) {
- const actor = await this.userService.findByNationalId(
- confirmationEvent.nationalId,
+ if (hasIndictmentCaseBeenSubmittedToCourt(theCase.state)) {
+ if (theCase.indictmentHash) {
+ const existingPdf = await this.tryGetPdfFromS3(
+ theCase,
+ `${theCase.id}/indictment.pdf`,
+ )
+
+ if (existingPdf) {
+ return existingPdf
+ }
+ }
+
+ const confirmationEvent = theCase.eventLogs?.find(
+ (event) => event.eventType === EventType.INDICTMENT_CONFIRMED,
)
- confirmation = {
- actor: actor.name,
- institution: actor.institution?.name ?? '',
- date: confirmationEvent.created,
+ if (confirmationEvent && confirmationEvent.nationalId) {
+ const actor = await this.userService.findByNationalId(
+ confirmationEvent.nationalId,
+ )
+
+ confirmation = {
+ actor: actor.name,
+ institution: actor.institution?.name ?? '',
+ date: confirmationEvent.created,
+ }
}
}
- return createIndictment(theCase, this.formatMessage, confirmation)
- }
+ await this.refreshFormatMessage()
- async getConfirmedIndictmentPdf(
- confirmation: IndictmentConfirmation,
- indictmentPDF: Buffer,
- ): Promise {
- return createConfirmedIndictment(confirmation, indictmentPDF)
+ const generatedPdf = await createIndictment(
+ theCase,
+ this.formatMessage,
+ confirmation,
+ )
+
+ if (hasIndictmentCaseBeenSubmittedToCourt(theCase.state) && confirmation) {
+ const indictmentHash = CryptoJS.MD5(
+ generatedPdf.toString('binary'),
+ ).toString(CryptoJS.enc.Hex)
+
+ // No need to wait for this to finish
+ this.caseModel
+ .update({ indictmentHash }, { where: { id: theCase.id } })
+ .then(() =>
+ this.tryUploadPdfToS3(
+ theCase,
+ `${theCase.id}/indictment.pdf`,
+ generatedPdf,
+ ),
+ )
+ }
+
+ return generatedPdf
}
async getCaseFilesRecordPdf(
theCase: Case,
policeCaseNumber: string,
): Promise {
- if (
- ![CaseState.NEW, CaseState.DRAFT, CaseState.SUBMITTED].includes(
- theCase.state,
+ if (hasIndictmentCaseBeenSubmittedToCourt(theCase.state)) {
+ const existingPdf = await this.tryGetPdfFromS3(
+ theCase,
+ `${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
)
- ) {
- if (isCompletedCase(theCase.state)) {
- try {
- return await this.awsS3Service.getObject(
- `indictments/completed/${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
- )
- } catch {
- // Ignore the error and try the original key
- }
- }
- try {
- return await this.awsS3Service.getObject(
- `indictments/${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
- )
- } catch {
- // Ignore the error and generate the pdf
+ if (existingPdf) {
+ return existingPdf
}
}
@@ -226,6 +282,6 @@ export class PDFService {
policeCaseNumber,
)
- return this.throttle
+ return await this.throttle
}
}
diff --git a/apps/judicial-system/backend/src/app/modules/case/state/case.state.spec.ts b/apps/judicial-system/backend/src/app/modules/case/state/case.state.spec.ts
index 9231ba71d22e..8114e399c09c 100644
--- a/apps/judicial-system/backend/src/app/modules/case/state/case.state.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/state/case.state.spec.ts
@@ -748,8 +748,6 @@ describe('Transition Case', () => {
const allowedFromStates = [
CaseState.DRAFT,
CaseState.WAITING_FOR_CONFIRMATION,
- CaseState.SUBMITTED,
- CaseState.RECEIVED,
]
describe.each(allowedFromStates)(
diff --git a/apps/judicial-system/backend/src/app/modules/case/state/case.state.ts b/apps/judicial-system/backend/src/app/modules/case/state/case.state.ts
index 8115f7da33a4..fbae47a66af0 100644
--- a/apps/judicial-system/backend/src/app/modules/case/state/case.state.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/state/case.state.ts
@@ -101,8 +101,6 @@ const indictmentCaseStateMachine: Map<
fromStates: [
IndictmentCaseState.DRAFT,
IndictmentCaseState.WAITING_FOR_CONFIRMATION,
- IndictmentCaseState.SUBMITTED,
- IndictmentCaseState.RECEIVED,
],
to: { state: IndictmentCaseState.DELETED },
},
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/getAll.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/getAll.spec.ts
index bc96214b669d..9abf249fb601 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/getAll.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/getAll.spec.ts
@@ -49,7 +49,7 @@ describe('CaseController - Get all', () => {
const mockGetCasesQueryFilter = getCasesQueryFilter as jest.Mock
mockGetCasesQueryFilter.mockReturnValueOnce(filter)
const mockFindAll = mockCaseModel.findAll as jest.Mock
- mockFindAll.mockReturnValueOnce(cases)
+ mockFindAll.mockResolvedValueOnce(cases)
then = await givenWhenThen()
})
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/getCaseFilesRecordPdf.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/getCaseFilesRecordPdf.spec.ts
index 79e03ac601df..b982b0dfba58 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/getCaseFilesRecordPdf.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/getCaseFilesRecordPdf.spec.ts
@@ -3,7 +3,11 @@ import { uuid } from 'uuidv4'
import { BadRequestException } from '@nestjs/common'
-import { CaseFileCategory, CaseState } from '@island.is/judicial-system/types'
+import {
+ CaseFileCategory,
+ CaseState,
+ CaseType,
+} from '@island.is/judicial-system/types'
import { createTestingCaseModule } from '../createTestingCaseModule'
@@ -38,11 +42,12 @@ describe('CaseController - Get case files record pdf', () => {
] as CaseFile[]
const theCase = {
id: caseId,
- state: CaseState.ACCEPTED,
+ type: CaseType.INDICTMENT,
+ state: CaseState.COMPLETED,
policeCaseNumbers: [uuid(), policeCaseNumber, uuid()],
caseFiles,
} as Case
- const pdf = uuid()
+ const pdf = Buffer.from(uuid())
const res = { end: jest.fn() } as unknown as Response
let mockawsS3Service: AwsS3Service
@@ -54,6 +59,8 @@ describe('CaseController - Get case files record pdf', () => {
mockawsS3Service = awsS3Service
const mockGetObject = mockawsS3Service.getObject as jest.Mock
mockGetObject.mockRejectedValue(new Error('Some error'))
+ const mockPutObject = mockawsS3Service.putObject as jest.Mock
+ mockPutObject.mockRejectedValue(new Error('Some error'))
givenWhenThen = async (policeCaseNumber: string) => {
const then = {} as Then
@@ -82,13 +89,10 @@ describe('CaseController - Get case files record pdf', () => {
})
it('should generate pdf after failing to get it from AWS S3', () => {
- expect(mockawsS3Service.getObject).toHaveBeenNthCalledWith(
- 1,
- `indictments/completed/${caseId}/${policeCaseNumber}/caseFilesRecord.pdf`,
- )
- expect(mockawsS3Service.getObject).toHaveBeenNthCalledWith(
- 2,
- `indictments/${caseId}/${policeCaseNumber}/caseFilesRecord.pdf`,
+ expect(mockawsS3Service.getObject).toHaveBeenCalledWith(
+ theCase.type,
+ theCase.state,
+ `${caseId}/${policeCaseNumber}/caseFilesRecord.pdf`,
)
expect(createCaseFilesRecord).toHaveBeenCalledWith(
theCase,
@@ -96,28 +100,20 @@ describe('CaseController - Get case files record pdf', () => {
expect.any(Array),
expect.any(Function),
)
+ expect(mockawsS3Service.putObject).toHaveBeenCalledWith(
+ theCase.type,
+ theCase.state,
+ `${caseId}/${policeCaseNumber}/caseFilesRecord.pdf`,
+ pdf.toString('binary'),
+ )
expect(res.end).toHaveBeenCalledWith(pdf)
})
})
- describe('pdf returned from AWS S3 indictment completed folder', () => {
- beforeEach(async () => {
- const mockGetObject = mockawsS3Service.getObject as jest.Mock
- mockGetObject.mockReturnValueOnce(pdf)
-
- await givenWhenThen(policeCaseNumber)
- })
-
- it('should return pdf', () => {
- expect(res.end).toHaveBeenCalledWith(pdf)
- })
- })
-
- describe('pdf returned from AWS S3 indictment folder', () => {
+ describe('pdf returned from AWS S3', () => {
beforeEach(async () => {
const mockGetObject = mockawsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(new Error('Some error'))
- mockGetObject.mockReturnValueOnce(pdf)
+ mockGetObject.mockResolvedValueOnce(pdf)
await givenWhenThen(policeCaseNumber)
})
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/getCourtRecordPdf.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/getCourtRecordPdf.spec.ts
index b2e92770f532..143d68c2ab8b 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/getCourtRecordPdf.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/getCourtRecordPdf.spec.ts
@@ -3,7 +3,7 @@ import { uuid } from 'uuidv4'
import { Logger } from '@island.is/logging'
-import { User } from '@island.is/judicial-system/types'
+import { CaseState, CaseType, User } from '@island.is/judicial-system/types'
import { createTestingCaseModule } from '../createTestingCaseModule'
@@ -33,9 +33,16 @@ describe('CaseController - Get court record pdf', () => {
beforeEach(async () => {
const { awsS3Service, logger, caseController } =
await createTestingCaseModule()
+
mockAwsS3Service = awsS3Service
mockLogger = logger
+ const mockGetGeneratedObject =
+ mockAwsS3Service.getGeneratedObject as jest.Mock
+ mockGetGeneratedObject.mockRejectedValue(new Error('Some error'))
+ const getMock = getCourtRecordPdfAsBuffer as jest.Mock
+ getMock.mockRejectedValue(new Error('Some error'))
+
givenWhenThen = async (
caseId: string,
user: User,
@@ -57,23 +64,29 @@ describe('CaseController - Get court record pdf', () => {
describe('AWS S3 pdf returned', () => {
const user = { id: uuid() } as User
const caseId = uuid()
+ const caseType = CaseType.PAROLE_REVOCATION
+ const caseSate = CaseState.ACCEPTED
const theCase = {
id: caseId,
+ type: caseType,
+ state: caseSate,
courtRecordSignatureDate: nowFactory(),
} as Case
const res = { end: jest.fn() } as unknown as Response
const pdf = uuid()
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockResolvedValueOnce(pdf)
+ const mockGetGeneratedObject =
+ mockAwsS3Service.getGeneratedObject as jest.Mock
+ mockGetGeneratedObject.mockResolvedValueOnce(pdf)
await givenWhenThen(caseId, user, theCase, res)
})
it('should lookup pdf', () => {
- expect(mockAwsS3Service.getObject).toHaveBeenCalledWith(
- `generated/${caseId}/courtRecord.pdf`,
+ expect(mockAwsS3Service.getGeneratedObject).toHaveBeenCalledWith(
+ caseType,
+ `${caseId}/courtRecord.pdf`,
)
expect(res.end).toHaveBeenCalledWith(pdf)
})
@@ -86,13 +99,11 @@ describe('CaseController - Get court record pdf', () => {
id: caseId,
courtRecordSignatureDate: nowFactory(),
} as Case
- const error = new Error('Some ignored error')
+ const error = new Error('Some error')
const res = { end: jest.fn() } as unknown as Response
const pdf = uuid()
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(error)
const getMock = getCourtRecordPdfAsBuffer as jest.Mock
getMock.mockResolvedValueOnce(pdf)
@@ -124,8 +135,6 @@ describe('CaseController - Get court record pdf', () => {
const res = {} as Response
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(new Error('Some ignored error'))
const getMock = getCourtRecordPdfAsBuffer as jest.Mock
getMock.mockRejectedValueOnce(new Error('Some error'))
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/getCourtRecordSignatureConfirmation.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/getCourtRecordSignatureConfirmation.spec.ts
index 1edc06628158..59bc8a99c6a4 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/getCourtRecordSignatureConfirmation.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/getCourtRecordSignatureConfirmation.spec.ts
@@ -38,6 +38,14 @@ describe('CaseController - Get court record signature confirmation', () => {
mockAwsS3Service = awsS3Service
mockCaseModel = caseModel
+ const mockPutGeneratedObject =
+ mockAwsS3Service.putGeneratedObject as jest.Mock
+ mockPutGeneratedObject.mockRejectedValue(new Error('Some error'))
+ const mockUpdate = mockCaseModel.update as jest.Mock
+ mockUpdate.mockRejectedValue(new Error('Some error'))
+ const mockFindOne = mockCaseModel.findOne as jest.Mock
+ mockFindOne.mockRejectedValue(new Error('Some error'))
+
const mockTransaction = sequelize.transaction as jest.Mock
transaction = {} as Transaction
mockTransaction.mockImplementationOnce(
@@ -92,8 +100,9 @@ describe('CaseController - Get court record signature confirmation', () => {
let then: Then
beforeEach(async () => {
- const mockPutObject = mockAwsS3Service.putObject as jest.Mock
- mockPutObject.mockResolvedValueOnce(Promise.resolve())
+ const mockPutGeneratedObject =
+ mockAwsS3Service.putGeneratedObject as jest.Mock
+ mockPutGeneratedObject.mockResolvedValueOnce(Promise.resolve())
const mockUpdate = mockCaseModel.update as jest.Mock
mockUpdate.mockResolvedValueOnce([1, [theCase]])
const mockFindOne = mockCaseModel.findOne as jest.Mock
@@ -120,9 +129,6 @@ describe('CaseController - Get court record signature confirmation', () => {
let then: Then
beforeEach(async () => {
- const mockPutObject = mockAwsS3Service.putObject as jest.Mock
- mockPutObject.mockRejectedValueOnce(new Error('Some error'))
-
then = await givenWhenThen(caseId, user, theCase, documentToken)
})
@@ -138,10 +144,9 @@ describe('CaseController - Get court record signature confirmation', () => {
let then: Then
beforeEach(async () => {
- const mockPutObject = mockAwsS3Service.putObject as jest.Mock
- mockPutObject.mockResolvedValueOnce(Promise.resolve())
- const mockUpdate = mockCaseModel.update as jest.Mock
- mockUpdate.mockRejectedValueOnce(new Error('Some error'))
+ const mockPutGeneratedObject =
+ mockAwsS3Service.putGeneratedObject as jest.Mock
+ mockPutGeneratedObject.mockResolvedValueOnce(Promise.resolve())
then = await givenWhenThen(caseId, user, theCase, documentToken)
})
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/getIndictmentPdf.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/getIndictmentPdf.spec.ts
index 5cc6729c4027..015d1f3c01ff 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/getIndictmentPdf.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/getIndictmentPdf.spec.ts
@@ -1,9 +1,16 @@
import { Response } from 'express'
import { uuid } from 'uuidv4'
+import {
+ CaseState,
+ CaseType,
+ IndictmentSubtype,
+} from '@island.is/judicial-system/types'
+
import { createTestingCaseModule } from '../createTestingCaseModule'
import { createIndictment } from '../../../../formatters'
+import { AwsS3Service } from '../../../aws-s3'
import { Case } from '../../models/case.model'
jest.mock('../../../../formatters/indictmentPdf')
@@ -16,16 +23,29 @@ type GivenWhenThen = () => Promise
describe('CaseController - Get indictment pdf', () => {
const caseId = uuid()
+ const policeCaseNumber = uuid()
const theCase = {
id: caseId,
+ type: CaseType.INDICTMENT,
+ state: CaseState.COMPLETED,
+ policeCaseNumbers: [policeCaseNumber],
+ indictmentSubtypes: {
+ [policeCaseNumber]: [IndictmentSubtype.TRAFFIC_VIOLATION],
+ },
+ indictmentHash: uuid(),
} as Case
- const pdf = uuid()
+ const pdf = Buffer.from(uuid())
const res = { end: jest.fn() } as unknown as Response
+ let mockAwsS3Service: AwsS3Service
let givenWhenThen: GivenWhenThen
beforeEach(async () => {
- const { caseController } = await createTestingCaseModule()
+ const { awsS3Service, caseController } = await createTestingCaseModule()
+
+ mockAwsS3Service = awsS3Service
+ const mockGetObject = mockAwsS3Service.getObject as jest.Mock
+ mockGetObject.mockRejectedValue(new Error('Some error'))
givenWhenThen = async () => {
const then = {} as Then
@@ -48,7 +68,12 @@ describe('CaseController - Get indictment pdf', () => {
await givenWhenThen()
})
- it('should generate pdf', () => {
+ it('should generate pdf after failing to get it from AWS S3', () => {
+ expect(mockAwsS3Service.getObject).toHaveBeenCalledWith(
+ theCase.type,
+ theCase.state,
+ `${caseId}/indictment.pdf`,
+ )
expect(createIndictment).toHaveBeenCalledWith(
theCase,
expect.any(Function),
@@ -57,4 +82,17 @@ describe('CaseController - Get indictment pdf', () => {
expect(res.end).toHaveBeenCalledWith(pdf)
})
})
+
+ describe('pdf returned from AWS S3', () => {
+ beforeEach(async () => {
+ const mockGetObject = mockAwsS3Service.getObject as jest.Mock
+ mockGetObject.mockResolvedValueOnce(pdf)
+
+ await givenWhenThen()
+ })
+
+ it('should return pdf', () => {
+ expect(res.end).toHaveBeenCalledWith(pdf)
+ })
+ })
})
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/getRulingPdf.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/getRulingPdf.spec.ts
index 4be69ad5d793..0f081c9a98ed 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/getRulingPdf.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/getRulingPdf.spec.ts
@@ -3,6 +3,8 @@ import { uuid } from 'uuidv4'
import { Logger } from '@island.is/logging'
+import { CaseState, CaseType } from '@island.is/judicial-system/types'
+
import { createTestingCaseModule } from '../createTestingCaseModule'
import { nowFactory } from '../../../../factories'
@@ -30,9 +32,16 @@ describe('CaseController - Get ruling pdf', () => {
beforeEach(async () => {
const { awsS3Service, logger, caseController } =
await createTestingCaseModule()
+
mockAwsS3Service = awsS3Service
mockLogger = logger
+ const mockGetGeneratedObject =
+ mockAwsS3Service.getGeneratedObject as jest.Mock
+ mockGetGeneratedObject.mockRejectedValue(new Error('Some error'))
+ const getMock = getRulingPdfAsBuffer as jest.Mock
+ getMock.mockRejectedValue(new Error('Some error'))
+
givenWhenThen = async (caseId: string, theCase: Case, res: Response) => {
const then = {} as Then
@@ -46,36 +55,32 @@ describe('CaseController - Get ruling pdf', () => {
}
})
- describe('AWS S3 lookup', () => {
- const caseId = uuid()
- const theCase = { id: caseId, rulingSignatureDate: nowFactory() } as Case
- const res = {} as Response
-
- beforeEach(async () => {
- await givenWhenThen(caseId, theCase, res)
- })
-
- it('should lookup pdf', () => {
- expect(mockAwsS3Service.getObject).toHaveBeenCalledWith(
- `generated/${caseId}/ruling.pdf`,
- )
- })
- })
-
describe('AWS S3 pdf returned', () => {
const caseId = uuid()
- const theCase = { id: caseId, rulingSignatureDate: nowFactory() } as Case
+ const caseType = CaseType.EXPULSION_FROM_HOME
+ const caseState = CaseState.REJECTED
+ const theCase = {
+ id: caseId,
+ type: caseType,
+ state: caseState,
+ rulingSignatureDate: nowFactory(),
+ } as Case
const res = { end: jest.fn() } as unknown as Response
const pdf = {}
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockResolvedValueOnce(pdf)
+ const mockGetGeneratedObject =
+ mockAwsS3Service.getGeneratedObject as jest.Mock
+ mockGetGeneratedObject.mockResolvedValueOnce(pdf)
await givenWhenThen(caseId, theCase, res)
})
it('should return pdf', () => {
+ expect(mockAwsS3Service.getGeneratedObject).toHaveBeenCalledWith(
+ caseType,
+ `${caseId}/ruling.pdf`,
+ )
expect(res.end).toHaveBeenCalledWith(pdf)
})
})
@@ -84,12 +89,9 @@ describe('CaseController - Get ruling pdf', () => {
const caseId = uuid()
const theCase = { id: caseId, rulingSignatureDate: nowFactory() } as Case
const res = {} as Response
- const error = new Error('Some ignored error')
+ const error = new Error('Some error')
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(error)
-
await givenWhenThen(caseId, theCase, res)
})
@@ -101,43 +103,25 @@ describe('CaseController - Get ruling pdf', () => {
})
})
- describe('pdf generated', () => {
- const caseId = uuid()
- const theCase = { id: caseId, rulingSignatureDate: nowFactory() } as Case
- const res = {} as Response
-
- beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(new Error('Some ignored error'))
-
- await givenWhenThen(caseId, theCase, res)
- })
-
- it('should generate pdf', () => {
- expect(getRulingPdfAsBuffer).toHaveBeenCalledWith(
- theCase,
- expect.any(Function),
- )
- })
- })
-
- describe('pdf generated', () => {
+ describe('generated pdf returned', () => {
const caseId = uuid()
const theCase = { id: caseId, rulingSignatureDate: nowFactory() } as Case
- const res = {} as Response
+ const res = { end: jest.fn() } as unknown as Response
+ const pdf = {}
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(new Error('Some ignored error'))
+ const getMock = getRulingPdfAsBuffer as jest.Mock
+ getMock.mockResolvedValueOnce(pdf)
await givenWhenThen(caseId, theCase, res)
})
- it('should generate pdf', () => {
+ it('should return pdf', () => {
expect(getRulingPdfAsBuffer).toHaveBeenCalledWith(
theCase,
expect.any(Function),
)
+ expect(res.end).toHaveBeenCalledWith(pdf)
})
})
@@ -158,26 +142,6 @@ describe('CaseController - Get ruling pdf', () => {
})
})
- describe('generated pdf returned', () => {
- const caseId = uuid()
- const theCase = { id: caseId, rulingSignatureDate: nowFactory() } as Case
- const res = { end: jest.fn() } as unknown as Response
- const pdf = {}
-
- beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(new Error('Some ignored error'))
- const getMock = getRulingPdfAsBuffer as jest.Mock
- getMock.mockResolvedValueOnce(pdf)
-
- await givenWhenThen(caseId, theCase, res)
- })
-
- it('should return pdf', () => {
- expect(res.end).toHaveBeenCalledWith(pdf)
- })
- })
-
describe('pdf generation fails', () => {
const caseId = uuid()
const theCase = { id: caseId, rulingSignatureDate: nowFactory() } as Case
@@ -185,11 +149,6 @@ describe('CaseController - Get ruling pdf', () => {
const res = {} as Response
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(new Error('Some ignored error'))
- const getMock = getRulingPdfAsBuffer as jest.Mock
- getMock.mockRejectedValueOnce(new Error('Some error'))
-
then = await givenWhenThen(caseId, theCase, res)
})
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/getRulingSignatureConfirmation.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/getRulingSignatureConfirmation.spec.ts
index 6ebe21d948ae..c05595c977b2 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/getRulingSignatureConfirmation.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/getRulingSignatureConfirmation.spec.ts
@@ -65,8 +65,9 @@ describe('CaseController - Get ruling signature confirmation', () => {
const mockToday = nowFactory as jest.Mock
mockToday.mockReturnValueOnce(date)
- const mockPutObject = mockAwsS3Service.putObject as jest.Mock
- mockPutObject.mockResolvedValue(uuid())
+ const mockPutGeneratedObject =
+ mockAwsS3Service.putGeneratedObject as jest.Mock
+ mockPutGeneratedObject.mockResolvedValue(uuid())
const mockUpdate = mockCaseModel.update as jest.Mock
mockUpdate.mockResolvedValue([1])
const mockPostMessageToQueue =
@@ -125,15 +126,12 @@ describe('CaseController - Get ruling signature confirmation', () => {
then = await givenWhenThen(caseId, user, theCase, documentToken)
})
- it('should set the ruling signature date', () => {
+ it('should return success', () => {
expect(mockCaseModel.update).toHaveBeenCalledWith(
{ rulingSignatureDate: date },
{ where: { id: caseId }, transaction },
)
- })
-
- it('should return success', () => {
- expect(mockAwsS3Service.putObject).toHaveBeenCalled()
+ expect(mockAwsS3Service.putGeneratedObject).toHaveBeenCalled()
expect(mockMessageService.sendMessagesToQueue).toHaveBeenCalledWith([
{ type: MessageType.DELIVERY_TO_COURT_SIGNED_RULING, user, caseId },
{
@@ -169,10 +167,7 @@ describe('CaseController - Get ruling signature confirmation', () => {
{ rulingSignatureDate: date },
{ where: { id: caseId }, transaction },
)
- })
-
- it('should return success', () => {
- expect(mockAwsS3Service.putObject).toHaveBeenCalled()
+ expect(mockAwsS3Service.putGeneratedObject).toHaveBeenCalled()
expect(mockMessageService.sendMessagesToQueue).toHaveBeenCalledWith([
{ type: MessageType.DELIVERY_TO_COURT_SIGNED_RULING, user, caseId },
{
@@ -269,8 +264,9 @@ describe('CaseController - Get ruling signature confirmation', () => {
let then: Then
beforeEach(async () => {
- const mockPutObject = mockAwsS3Service.putObject as jest.Mock
- mockPutObject.mockRejectedValueOnce(new Error('Some error'))
+ const mockPutGeneratedObject =
+ mockAwsS3Service.putGeneratedObject as jest.Mock
+ mockPutGeneratedObject.mockRejectedValueOnce(new Error('Some error'))
then = await givenWhenThen(caseId, user, theCase, documentToken)
})
@@ -279,7 +275,6 @@ describe('CaseController - Get ruling signature confirmation', () => {
expect(then.result.documentSigned).toBe(false)
expect(then.result.message).toBeTruthy()
expect(then.result.code).toBeUndefined()
-
expect(mockCaseModel.update).not.toHaveBeenCalled()
expect(mockMessageService.sendMessagesToQueue).not.toHaveBeenCalled()
})
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/transition.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/transition.spec.ts
index ac0f883e4f10..5a3ff83919e4 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/transition.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/transition.spec.ts
@@ -301,8 +301,6 @@ describe('CaseController - Transition', () => {
${CaseTransition.COMPLETE} | ${CaseState.RECEIVED} | ${CaseState.COMPLETED}
${CaseTransition.DELETE} | ${CaseState.DRAFT} | ${CaseState.DELETED}
${CaseTransition.DELETE} | ${CaseState.WAITING_FOR_CONFIRMATION} | ${CaseState.DELETED}
- ${CaseTransition.DELETE} | ${CaseState.SUBMITTED} | ${CaseState.DELETED}
- ${CaseTransition.DELETE} | ${CaseState.RECEIVED} | ${CaseState.DELETED}
`.describe(
'$transition $oldState case transitioning to $newState case',
({ transition, oldState, newState }) => {
@@ -359,12 +357,22 @@ describe('CaseController - Transition', () => {
transition === CaseTransition.DELETE ? null : undefined,
courtCaseNumber:
transition === CaseTransition.RETURN_INDICTMENT
- ? ''
+ ? null
+ : undefined,
+ indictmentHash:
+ transition === CaseTransition.RETURN_INDICTMENT
+ ? null
: undefined,
rulingDate:
transition === CaseTransition.COMPLETE ? date : undefined,
judgeId:
transition === CaseTransition.REDISTRIBUTE ? null : undefined,
+ indictmentDeniedExplanation:
+ transition === CaseTransition.SUBMIT ? null : undefined,
+ indictmentReturnedExplanation:
+ transition === CaseTransition.ASK_FOR_CONFIRMATION
+ ? null
+ : undefined,
},
{ where: { id: caseId }, transaction },
)
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/update.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/update.spec.ts
index 117142cff466..3d4b3d6fba78 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/update.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/update.spec.ts
@@ -190,6 +190,7 @@ describe('CaseController - Update', () => {
it('should delete a case file', () => {
expect(mockFileService.deleteCaseFile).toHaveBeenCalledWith(
+ theCase,
caseFile,
transaction,
)
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/archiveCaseFilesRecord.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/archiveCaseFilesRecord.spec.ts
index 11ed25dccfda..9ae4785ad11c 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/archiveCaseFilesRecord.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/archiveCaseFilesRecord.spec.ts
@@ -2,6 +2,8 @@ import { uuid } from 'uuidv4'
import { BadRequestException } from '@nestjs/common'
+import { CaseState, CaseType } from '@island.is/judicial-system/types'
+
import { createTestingCaseModule } from '../createTestingCaseModule'
import { AwsS3Service } from '../../../aws-s3'
@@ -20,6 +22,8 @@ type GivenWhenThen = (
describe('InternalCaseController - Archive case files record', () => {
const caseId = uuid()
+ const caseType = CaseType.INDICTMENT
+ const caseState = CaseState.COMPLETED
const policeCaseNumber = uuid()
let mockawsS3Service: AwsS3Service
@@ -30,10 +34,8 @@ describe('InternalCaseController - Archive case files record', () => {
await createTestingCaseModule()
mockawsS3Service = awsS3Service
- const mockCopyObject = mockawsS3Service.copyObject as jest.Mock
- mockCopyObject.mockRejectedValue(new Error('Some error'))
- const mockDeleteObject = mockawsS3Service.deleteObject as jest.Mock
- mockDeleteObject.mockRejectedValue(new Error('Some error'))
+ const mockArchiveObject = mockawsS3Service.archiveObject as jest.Mock
+ mockArchiveObject.mockRejectedValue(new Error('Some error'))
givenWhenThen = async (
policeCaseNumber: string,
@@ -44,6 +46,8 @@ describe('InternalCaseController - Archive case files record', () => {
await internalCaseController
.archiveCaseFilesRecord(caseId, policeCaseNumber, {
id: caseId,
+ type: caseType,
+ state: caseState,
policeCaseNumbers,
} as Case)
.then((result) => (then.result = result))
@@ -57,8 +61,8 @@ describe('InternalCaseController - Archive case files record', () => {
let then: Then
beforeEach(async () => {
- const mockCopyObject = mockawsS3Service.copyObject as jest.Mock
- mockCopyObject.mockResolvedValueOnce(uuid())
+ const mockArchiveObject = mockawsS3Service.archiveObject as jest.Mock
+ mockArchiveObject.mockResolvedValueOnce(uuid())
then = await givenWhenThen(policeCaseNumber, [
uuid(),
@@ -67,20 +71,12 @@ describe('InternalCaseController - Archive case files record', () => {
])
})
- it('should copy the case files record to the AWS S3 indictment completed folder', () => {
- expect(mockawsS3Service.copyObject).toHaveBeenCalledWith(
- `indictments/${caseId}/${policeCaseNumber}/caseFilesRecord.pdf`,
- `indictments/completed/${caseId}/${policeCaseNumber}/caseFilesRecord.pdf`,
- )
- })
-
- it('should delete the case files record from the AWS S3 indictment folder', () => {
- expect(mockawsS3Service.deleteObject).toHaveBeenCalledWith(
- `indictments/${caseId}/${policeCaseNumber}/caseFilesRecord.pdf`,
+ it('should archive the case files record', () => {
+ expect(mockawsS3Service.archiveObject).toHaveBeenCalledWith(
+ caseType,
+ caseState,
+ `${caseId}/${policeCaseNumber}/caseFilesRecord.pdf`,
)
- })
-
- it('should return a success response', () => {
expect(then.result).toEqual({ delivered: true })
})
})
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverAppealToPolice.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverAppealToPolice.spec.ts
index e00a9c2cf05a..fe04e77ebbf0 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverAppealToPolice.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverAppealToPolice.spec.ts
@@ -14,7 +14,7 @@ import { createTestingCaseModule } from '../createTestingCaseModule'
import { randomDate } from '../../../../test'
import { AwsS3Service } from '../../../aws-s3'
-import { CourtDocumentType, PoliceService } from '../../../police'
+import { PoliceDocumentType, PoliceService } from '../../../police'
import { Case } from '../../models/case.model'
import { DeliverResponse } from '../../models/deliver.response'
@@ -40,8 +40,8 @@ describe('InternalCaseController - Deliver appeal to police', () => {
mockAwsS3Service = awsS3Service
mockPoliceService = policeService
- const mockGetObject = awsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValue(new Error('Some error'))
+ const mockGetGeneratedObject = awsS3Service.getObject as jest.Mock
+ mockGetGeneratedObject.mockRejectedValue(new Error('Some error'))
const mockUpdatePoliceCase = mockPoliceService.updatePoliceCase as jest.Mock
mockUpdatePoliceCase.mockRejectedValue(new Error('Some error'))
@@ -87,8 +87,8 @@ describe('InternalCaseController - Deliver appeal to police', () => {
let then: Then
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockResolvedValueOnce(appealRulingPdf)
+ const mockGetGeneratedObject = mockAwsS3Service.getObject as jest.Mock
+ mockGetGeneratedObject.mockResolvedValueOnce(appealRulingPdf)
const mockUpdatePoliceCase =
mockPoliceService.updatePoliceCase as jest.Mock
mockUpdatePoliceCase.mockResolvedValueOnce(true)
@@ -96,7 +96,11 @@ describe('InternalCaseController - Deliver appeal to police', () => {
then = await givenWhenThen(caseId, theCase)
})
it('should update the police case', async () => {
- expect(mockAwsS3Service.getObject).toHaveBeenCalledWith(appealRulingKey)
+ expect(mockAwsS3Service.getObject).toHaveBeenCalledWith(
+ caseType,
+ caseState,
+ appealRulingKey,
+ )
expect(mockPoliceService.updatePoliceCase).toHaveBeenCalledWith(
user,
caseId,
@@ -109,7 +113,7 @@ describe('InternalCaseController - Deliver appeal to police', () => {
caseConclusion,
[
{
- type: CourtDocumentType.RVUL,
+ type: PoliceDocumentType.RVUL,
courtDocument: Base64.btoa(appealRulingPdf),
},
],
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverCaseFilesRecordToCourt.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverCaseFilesRecordToCourt.spec.ts
index ee2a309ee87c..f8275ed779fc 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverCaseFilesRecordToCourt.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverCaseFilesRecordToCourt.spec.ts
@@ -2,7 +2,7 @@ import { uuid } from 'uuidv4'
import { BadRequestException } from '@nestjs/common'
-import { CaseState, User } from '@island.is/judicial-system/types'
+import { CaseState, CaseType, User } from '@island.is/judicial-system/types'
import { createTestingCaseModule } from '../createTestingCaseModule'
@@ -33,7 +33,8 @@ describe('InternalCaseController - Deliver case files record to court', () => {
const courtCaseNumber = uuid()
const theCase = {
id: caseId,
- state: CaseState.ACCEPTED,
+ type: CaseType.INDICTMENT,
+ state: CaseState.COMPLETED,
policeCaseNumbers: [policeCaseNumber],
courtId,
courtCaseNumber,
@@ -45,9 +46,6 @@ describe('InternalCaseController - Deliver case files record to court', () => {
let givenWhenThen: GivenWhenThen
beforeEach(async () => {
- const mockGet = createCaseFilesRecord as jest.Mock
- mockGet.mockRejectedValue(new Error('Some error'))
-
const { awsS3Service, courtService, internalCaseController } =
await createTestingCaseModule()
@@ -94,37 +92,24 @@ describe('InternalCaseController - Deliver case files record to court', () => {
then = await givenWhenThen(caseId, policeCaseNumber, theCase)
})
- it('should try to get the pdf from AWS S3 indictment completed folder', () => {
- expect(mockAwsS3Service.getObject).toHaveBeenNthCalledWith(
- 1,
- `indictments/completed/${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
+ it('should deliver the case files record', () => {
+ expect(mockAwsS3Service.getObject).toHaveBeenCalledWith(
+ theCase.type,
+ theCase.state,
+ `${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
)
- })
-
- it('should try to get the pdf from AWS S3 indictment folder', () => {
- expect(mockAwsS3Service.getObject).toHaveBeenNthCalledWith(
- 2,
- `indictments/${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
- )
- })
-
- it('should generate the case files record', async () => {
expect(createCaseFilesRecord).toHaveBeenCalledWith(
theCase,
policeCaseNumber,
[],
expect.any(Function),
)
- })
-
- it('should store the case files record in AWS S3', async () => {
expect(mockAwsS3Service.putObject).toHaveBeenCalledWith(
- `indictments/completed/${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
+ theCase.type,
+ theCase.state,
+ `${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
pdf.toString(),
)
- })
-
- it('should create a case files record at court', async () => {
expect(mockCourtService.createDocument).toHaveBeenCalledWith(
user,
caseId,
@@ -136,41 +121,14 @@ describe('InternalCaseController - Deliver case files record to court', () => {
'application/pdf',
pdf,
)
- })
-
- it('should return a success response', async () => {
expect(then.result).toEqual({ delivered: true })
})
})
- describe('pdf returned from AWS S3 indictment completed folder', () => {
- beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockReturnValueOnce(pdf)
-
- await givenWhenThen(caseId, policeCaseNumber, theCase)
- })
-
- it('should use the AWS S3 pdf', () => {
- expect(mockCourtService.createDocument).toHaveBeenCalledWith(
- user,
- caseId,
- courtId,
- courtCaseNumber,
- CourtDocumentFolder.CASE_DOCUMENTS,
- `Skjalaskrá ${policeCaseNumber}`,
- `Skjalaskrá ${policeCaseNumber}.pdf`,
- 'application/pdf',
- pdf,
- )
- })
- })
-
- describe('pdf returned from AWS S3 indictment folder', () => {
+ describe('pdf returned from AWS S3', () => {
beforeEach(async () => {
const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(new Error('Some error'))
- mockGetObject.mockReturnValueOnce(pdf)
+ mockGetObject.mockResolvedValueOnce(pdf)
await givenWhenThen(caseId, policeCaseNumber, theCase)
})
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverCaseFilesRecordToCourtGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverCaseFilesRecordToCourtGuards.spec.ts
index e0b918dcb4c3..328109144d6c 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverCaseFilesRecordToCourtGuards.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverCaseFilesRecordToCourtGuards.spec.ts
@@ -1,5 +1,3 @@
-import { CanActivate } from '@nestjs/common'
-
import { indictmentCases } from '@island.is/judicial-system/types'
import { CaseExistsGuard } from '../../guards/caseExists.guard'
@@ -17,34 +15,12 @@ describe('InternalCaseController - Deliver case files record to court guards', (
)
})
- it('should have two guards', () => {
+ it('should have the right guard configuration', () => {
expect(guards).toHaveLength(2)
- })
-
- describe('CaseExistsGuard', () => {
- let guard: CanActivate
-
- beforeEach(() => {
- guard = new guards[0]()
- })
-
- it('should have CaseExistsGuard as guard 1', () => {
- expect(guard).toBeInstanceOf(CaseExistsGuard)
- })
- })
-
- describe('CaseTypeGuard', () => {
- let guard: CanActivate
-
- beforeEach(() => {
- guard = guards[1]
- })
-
- it('should have CaseTypeGuard as guard 2', () => {
- expect(guard).toBeInstanceOf(CaseTypeGuard)
- expect(guard).toEqual({
- allowedCaseTypes: indictmentCases,
- })
+ expect(new guards[0]()).toBeInstanceOf(CaseExistsGuard)
+ expect(guards[1]).toBeInstanceOf(CaseTypeGuard)
+ expect(guards[1]).toEqual({
+ allowedCaseTypes: indictmentCases,
})
})
})
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverCaseFilesRecordToPolice.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverCaseFilesRecordToPolice.spec.ts
index 57d1eb6f335b..860e0e59dfd1 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverCaseFilesRecordToPolice.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverCaseFilesRecordToPolice.spec.ts
@@ -11,7 +11,7 @@ import { nowFactory } from '../../../../factories'
import { createCaseFilesRecord } from '../../../../formatters'
import { randomDate } from '../../../../test'
import { AwsS3Service } from '../../../aws-s3'
-import { CourtDocumentType, PoliceService } from '../../../police'
+import { PoliceDocumentType, PoliceService } from '../../../police'
import { Case } from '../../models/case.model'
import { DeliverResponse } from '../../models/deliver.response'
@@ -34,7 +34,7 @@ describe('InternalCaseController - Deliver case files record to police', () => {
const user = { id: uuid() } as User
const caseId = uuid()
const caseType = CaseType.INDICTMENT
- const caseState = CaseState.ACCEPTED
+ const caseState = CaseState.COMPLETED
const policeCaseNumber = uuid()
const defendantNationalId = '0123456789'
const courtId = uuid()
@@ -107,13 +107,10 @@ describe('InternalCaseController - Deliver case files record to police', () => {
})
it('should update the police case', () => {
- expect(mockAwsS3Service.getObject).toHaveBeenNthCalledWith(
- 1,
- `indictments/completed/${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
- )
- expect(mockAwsS3Service.getObject).toHaveBeenNthCalledWith(
- 2,
- `indictments/${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
+ expect(mockAwsS3Service.getObject).toHaveBeenCalledWith(
+ caseType,
+ caseState,
+ `${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
)
expect(createCaseFilesRecord).toHaveBeenCalledWith(
theCase,
@@ -122,7 +119,9 @@ describe('InternalCaseController - Deliver case files record to police', () => {
expect.any(Function),
)
expect(mockAwsS3Service.putObject).toHaveBeenCalledWith(
- `indictments/completed/${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
+ theCase.type,
+ theCase.state,
+ `${theCase.id}/${policeCaseNumber}/caseFilesRecord.pdf`,
pdf.toString(),
)
expect(mockPoliceService.updatePoliceCase).toHaveBeenCalledWith(
@@ -137,7 +136,7 @@ describe('InternalCaseController - Deliver case files record to police', () => {
'',
[
{
- type: CourtDocumentType.RVMG,
+ type: PoliceDocumentType.RVMG,
courtDocument: Base64.btoa(pdf.toString('binary')),
},
],
@@ -146,40 +145,10 @@ describe('InternalCaseController - Deliver case files record to police', () => {
})
})
- describe('pdf returned from AWS S3 indictment completed folder', () => {
- beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockReturnValueOnce(pdf)
-
- await givenWhenThen(caseId, policeCaseNumber, theCase)
- })
-
- it('should use the AWS S3 pdf', () => {
- expect(mockPoliceService.updatePoliceCase).toHaveBeenCalledWith(
- user,
- caseId,
- caseType,
- caseState,
- policeCaseNumber,
- courtCaseNumber,
- defendantNationalId,
- date,
- '',
- [
- {
- type: CourtDocumentType.RVMG,
- courtDocument: Base64.btoa(pdf.toString('binary')),
- },
- ],
- )
- })
- })
-
- describe('pdf returned from AWS S3 indictment folder', () => {
+ describe('pdf returned from AWS S3', () => {
beforeEach(async () => {
const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(new Error('Some error'))
- mockGetObject.mockReturnValueOnce(pdf)
+ mockGetObject.mockResolvedValueOnce(pdf)
await givenWhenThen(caseId, policeCaseNumber, theCase)
})
@@ -197,7 +166,7 @@ describe('InternalCaseController - Deliver case files record to police', () => {
'',
[
{
- type: CourtDocumentType.RVMG,
+ type: PoliceDocumentType.RVMG,
courtDocument: Base64.btoa(pdf.toString('binary')),
},
],
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverCaseToPolice.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverCaseToPolice.spec.ts
index d26aa17ec415..65ad3532e525 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverCaseToPolice.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverCaseToPolice.spec.ts
@@ -16,7 +16,7 @@ import {
getRequestPdfAsString,
} from '../../../../formatters'
import { randomDate } from '../../../../test'
-import { CourtDocumentType, PoliceService } from '../../../police'
+import { PoliceDocumentType, PoliceService } from '../../../police'
import { Case } from '../../models/case.model'
import { DeliverResponse } from '../../models/deliver.response'
@@ -130,15 +130,15 @@ describe('InternalCaseController - Deliver case to police', () => {
caseConclusion,
[
{
- type: CourtDocumentType.RVKR,
+ type: PoliceDocumentType.RVKR,
courtDocument: Base64.btoa(requestPdf),
},
{
- type: CourtDocumentType.RVTB,
+ type: PoliceDocumentType.RVTB,
courtDocument: Base64.btoa(courtRecordPdf),
},
{
- type: CourtDocumentType.RVVI,
+ type: PoliceDocumentType.RVVI,
courtDocument: Base64.btoa(custodyNoticePdf),
},
],
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentCaseToPolice.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentCaseToPolice.spec.ts
index 8bc07564f842..9897732717a4 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentCaseToPolice.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentCaseToPolice.spec.ts
@@ -14,7 +14,7 @@ import { createTestingCaseModule } from '../createTestingCaseModule'
import { nowFactory } from '../../../../factories'
import { randomDate } from '../../../../test'
import { AwsS3Service } from '../../../aws-s3'
-import { CourtDocumentType, PoliceService } from '../../../police'
+import { PoliceDocumentType, PoliceService } from '../../../police'
import { Case } from '../../models/case.model'
import { DeliverResponse } from '../../models/deliver.response'
@@ -101,8 +101,16 @@ describe('InternalCaseController - Deliver indictment case to police', () => {
})
it('should update the police case', async () => {
- expect(mockAwsS3Service.getObject).toHaveBeenCalledWith(courtRecordKey)
- expect(mockAwsS3Service.getObject).toHaveBeenCalledWith(rulingKey)
+ expect(mockAwsS3Service.getObject).toHaveBeenCalledWith(
+ caseType,
+ caseState,
+ courtRecordKey,
+ )
+ expect(mockAwsS3Service.getObject).toHaveBeenCalledWith(
+ caseType,
+ caseState,
+ rulingKey,
+ )
expect(mockPoliceService.updatePoliceCase).toHaveBeenCalledWith(
user,
caseId,
@@ -115,11 +123,11 @@ describe('InternalCaseController - Deliver indictment case to police', () => {
'',
[
{
- type: CourtDocumentType.RVTB,
+ type: PoliceDocumentType.RVTB,
courtDocument: Base64.btoa(courtRecordPdf),
},
{
- type: CourtDocumentType.RVDO,
+ type: PoliceDocumentType.RVDO,
courtDocument: Base64.btoa(rulingPdf),
},
],
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentToCourt.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentToCourt.spec.ts
new file mode 100644
index 000000000000..0bcb6dcd14ba
--- /dev/null
+++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentToCourt.spec.ts
@@ -0,0 +1,166 @@
+import { uuid } from 'uuidv4'
+
+import {
+ CaseState,
+ CaseType,
+ IndictmentSubtype,
+ User,
+} from '@island.is/judicial-system/types'
+
+import { createTestingCaseModule } from '../createTestingCaseModule'
+
+import { createIndictment } from '../../../../formatters'
+import { AwsS3Service } from '../../../aws-s3'
+import { CourtDocumentFolder, CourtService } from '../../../court'
+import { Case } from '../../models/case.model'
+import { DeliverResponse } from '../../models/deliver.response'
+
+jest.mock('../../../../formatters/indictmentPdf')
+
+interface Then {
+ result: DeliverResponse
+ error: Error
+}
+
+type GivenWhenThen = (caseId: string, theCase: Case) => Promise
+
+describe('InternalCaseController - Deliver indictment to court', () => {
+ const user = { id: uuid() } as User
+ const caseId = uuid()
+ const policeCaseNumber = uuid()
+ const courtId = uuid()
+ const courtCaseNumber = uuid()
+ const theCase = {
+ id: caseId,
+ type: CaseType.INDICTMENT,
+ state: CaseState.COMPLETED,
+ policeCaseNumbers: [policeCaseNumber],
+ indictmentSubtypes: {
+ [policeCaseNumber]: [IndictmentSubtype.TRAFFIC_VIOLATION],
+ },
+ courtId,
+ courtCaseNumber,
+ indictmentHash: uuid(),
+ } as Case
+ const pdf = Buffer.from('test indictment')
+
+ let mockAwsS3Service: AwsS3Service
+ let mockCourtService: CourtService
+ let givenWhenThen: GivenWhenThen
+
+ beforeEach(async () => {
+ const { awsS3Service, courtService, internalCaseController } =
+ await createTestingCaseModule()
+
+ mockAwsS3Service = awsS3Service
+ const mockGetObject = mockAwsS3Service.getObject as jest.Mock
+ mockGetObject.mockRejectedValue(new Error('Some error'))
+
+ const mockCreateIndictment = createIndictment as jest.Mock
+ mockCreateIndictment.mockRejectedValue(new Error('Some error'))
+
+ mockCourtService = courtService
+ const mockCreateDocument = mockCourtService.createDocument as jest.Mock
+ mockCreateDocument.mockRejectedValue(new Error('Some error'))
+
+ givenWhenThen = async (caseId: string, theCase: Case) => {
+ const then = {} as Then
+
+ await internalCaseController
+ .deliverIndictmentToCourt(caseId, theCase, { user })
+ .then((result) => (then.result = result))
+ .catch((error) => (then.error = error))
+
+ return then
+ }
+ })
+
+ describe('deliver generated indictment pdf to court', () => {
+ let then: Then
+
+ beforeEach(async () => {
+ const mockCreateIndictment = createIndictment as jest.Mock
+ mockCreateIndictment.mockResolvedValueOnce(pdf)
+ const mockCreateDocument = mockCourtService.createDocument as jest.Mock
+ mockCreateDocument.mockResolvedValueOnce(uuid())
+
+ then = await givenWhenThen(caseId, theCase)
+ })
+
+ it('should deliver the indictment', () => {
+ expect(mockAwsS3Service.getObject).toHaveBeenCalledWith(
+ theCase.type,
+ theCase.state,
+ `${theCase.id}/indictment.pdf`,
+ )
+ expect(createIndictment).toHaveBeenCalledWith(
+ theCase,
+ expect.any(Function),
+ undefined,
+ )
+ expect(mockCourtService.createDocument).toHaveBeenCalledWith(
+ user,
+ caseId,
+ courtId,
+ courtCaseNumber,
+ CourtDocumentFolder.INDICTMENT_DOCUMENTS,
+ `Ákæra`,
+ `Ákæra.pdf`,
+ 'application/pdf',
+ pdf,
+ )
+ expect(then.result).toEqual({ delivered: true })
+ })
+ })
+
+ describe('deliver indictment pdf from AWS S3 to court', () => {
+ beforeEach(async () => {
+ const mockGetGeneratedIndictmentCaseObject =
+ mockAwsS3Service.getObject as jest.Mock
+ mockGetGeneratedIndictmentCaseObject.mockResolvedValueOnce(pdf)
+
+ await givenWhenThen(caseId, theCase)
+ })
+
+ it('should use the AWS S3 pdf', () => {
+ expect(mockCourtService.createDocument).toHaveBeenCalledWith(
+ user,
+ caseId,
+ courtId,
+ courtCaseNumber,
+ CourtDocumentFolder.INDICTMENT_DOCUMENTS,
+ `Ákæra`,
+ `Ákæra.pdf`,
+ 'application/pdf',
+ pdf,
+ )
+ })
+ })
+
+ describe('delivery to court fails', () => {
+ let then: Then
+
+ beforeEach(async () => {
+ const mockCreateIndictment = createIndictment as jest.Mock
+ mockCreateIndictment.mockResolvedValueOnce(pdf)
+
+ then = await givenWhenThen(caseId, theCase)
+ })
+
+ it('should return a failure response', async () => {
+ expect(then.result.delivered).toEqual(false)
+ })
+ })
+
+ describe('pdf generation fails', () => {
+ let then: Then
+
+ beforeEach(async () => {
+ then = await givenWhenThen(caseId, theCase)
+ })
+
+ it('should return a failure response', async () => {
+ expect(then.result.delivered).toEqual(false)
+ })
+ })
+})
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentToCourtGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentToCourtGuards.spec.ts
new file mode 100644
index 000000000000..897190bc36a4
--- /dev/null
+++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentToCourtGuards.spec.ts
@@ -0,0 +1,26 @@
+import { indictmentCases } from '@island.is/judicial-system/types'
+
+import { CaseExistsGuard } from '../../guards/caseExists.guard'
+import { CaseTypeGuard } from '../../guards/caseType.guard'
+import { InternalCaseController } from '../../internalCase.controller'
+
+describe('InternalCaseController - Deliver indictment to court guards', () => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let guards: any[]
+
+ beforeEach(() => {
+ guards = Reflect.getMetadata(
+ '__guards__',
+ InternalCaseController.prototype.deliverIndictmentToCourt,
+ )
+ })
+
+ it('should have the right guard configuration', () => {
+ expect(guards).toHaveLength(2)
+ expect(new guards[0]()).toBeInstanceOf(CaseExistsGuard)
+ expect(guards[1]).toBeInstanceOf(CaseTypeGuard)
+ expect(guards[1]).toEqual({
+ allowedCaseTypes: indictmentCases,
+ })
+ })
+})
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentToPolice.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentToPolice.spec.ts
index 6eab6494afed..a9bf45ec1fb2 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentToPolice.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentToPolice.spec.ts
@@ -6,6 +6,7 @@ import {
CaseOrigin,
CaseState,
CaseType,
+ IndictmentSubtype,
User,
} from '@island.is/judicial-system/types'
@@ -15,7 +16,8 @@ import { nowFactory } from '../../../../factories'
import { createIndictment } from '../../../../formatters'
import { randomDate } from '../../../../test'
import { AwsS3Service } from '../../../aws-s3'
-import { CourtDocumentType, PoliceService } from '../../../police'
+import { FileService } from '../../../file'
+import { PoliceDocumentType, PoliceService } from '../../../police'
import { Case } from '../../models/case.model'
import { DeliverResponse } from '../../models/deliver.response'
@@ -35,20 +37,24 @@ describe('InternalCaseController - Deliver indictment to police', () => {
const user = { id: userId } as User
let mockAwsS3Service: AwsS3Service
+ let mockFileService: FileService
let mockPoliceService: PoliceService
let givenWhenThen: GivenWhenThen
beforeEach(async () => {
- const { awsS3Service, policeService, internalCaseController } =
+ const { awsS3Service, fileService, policeService, internalCaseController } =
await createTestingCaseModule()
mockAwsS3Service = awsS3Service
+ mockFileService = fileService
mockPoliceService = policeService
const mockToday = nowFactory as jest.Mock
mockToday.mockReturnValueOnce(date)
- const mockGetObject = awsS3Service.getObject as jest.Mock
+ const mockGetObject = mockAwsS3Service.getObject as jest.Mock
mockGetObject.mockRejectedValue(new Error('Some error'))
+ const mockGetCaseFileFromS3 = mockFileService.getCaseFileFromS3 as jest.Mock
+ mockGetCaseFileFromS3.mockRejectedValue(new Error('Some error'))
const mockCreateIndictment = createIndictment as jest.Mock
mockCreateIndictment.mockRejectedValue(new Error('Some error'))
const mockUpdatePoliceCase = mockPoliceService.updatePoliceCase as jest.Mock
@@ -71,12 +77,17 @@ describe('InternalCaseController - Deliver indictment to police', () => {
describe('deliver indictment case files to police', () => {
const caseId = uuid()
const caseType = CaseType.INDICTMENT
- const caseState = CaseState.ACCEPTED
+ const caseState = CaseState.WAITING_FOR_CONFIRMATION
const policeCaseNumber = uuid()
const courtCaseNumber = uuid()
const defendantNationalId = '0123456789'
const indictmentKey = uuid()
const indictmentPdf = 'test indictment'
+ const caseFile = {
+ id: uuid(),
+ key: indictmentKey,
+ category: CaseFileCategory.INDICTMENT,
+ }
const theCase = {
id: caseId,
origin: CaseOrigin.LOKE,
@@ -85,16 +96,15 @@ describe('InternalCaseController - Deliver indictment to police', () => {
policeCaseNumbers: [policeCaseNumber],
courtCaseNumber,
defendants: [{ nationalId: defendantNationalId }],
- caseFiles: [
- { key: indictmentKey, category: CaseFileCategory.INDICTMENT },
- ],
+ caseFiles: [caseFile],
} as Case
let then: Then
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockResolvedValueOnce(indictmentPdf)
+ const mockGetCaseFileFromS3 =
+ mockFileService.getCaseFileFromS3 as jest.Mock
+ mockGetCaseFileFromS3.mockResolvedValueOnce(indictmentPdf)
const mockUpdatePoliceCase =
mockPoliceService.updatePoliceCase as jest.Mock
mockUpdatePoliceCase.mockResolvedValueOnce(true)
@@ -103,7 +113,10 @@ describe('InternalCaseController - Deliver indictment to police', () => {
})
it('should update the police case', async () => {
- expect(mockAwsS3Service.getObject).toHaveBeenCalledWith(indictmentKey)
+ expect(mockFileService.getCaseFileFromS3).toHaveBeenCalledWith(
+ theCase,
+ caseFile,
+ )
expect(mockPoliceService.updatePoliceCase).toHaveBeenCalledWith(
user,
caseId,
@@ -116,7 +129,7 @@ describe('InternalCaseController - Deliver indictment to police', () => {
'',
[
{
- type: CourtDocumentType.RVAS,
+ type: PoliceDocumentType.RVAS,
courtDocument: Base64.btoa(indictmentPdf),
},
],
@@ -128,7 +141,7 @@ describe('InternalCaseController - Deliver indictment to police', () => {
describe('deliver generated indictment pdf to police', () => {
const caseId = uuid()
const caseType = CaseType.INDICTMENT
- const caseState = CaseState.ACCEPTED
+ const caseState = CaseState.COMPLETED
const policeCaseNumber = uuid()
const courtCaseNumber = uuid()
const defendantNationalId = '0123456789'
@@ -141,6 +154,9 @@ describe('InternalCaseController - Deliver indictment to police', () => {
policeCaseNumbers: [policeCaseNumber],
courtCaseNumber,
defendants: [{ nationalId: defendantNationalId }],
+ indictmentSubtypes: {
+ [policeCaseNumber]: [IndictmentSubtype.TRAFFIC_VIOLATION],
+ },
} as Case
let then: Then
@@ -173,7 +189,7 @@ describe('InternalCaseController - Deliver indictment to police', () => {
'',
[
{
- type: CourtDocumentType.RVAS,
+ type: PoliceDocumentType.RVAS,
courtDocument: Base64.btoa(indictmentPdf),
},
],
@@ -181,4 +197,55 @@ describe('InternalCaseController - Deliver indictment to police', () => {
expect(then.result.delivered).toEqual(true)
})
})
+
+ describe('deliver indictment pdf from AWS S3 to police', () => {
+ const caseId = uuid()
+ const caseType = CaseType.INDICTMENT
+ const caseState = CaseState.COMPLETED
+ const policeCaseNumber = uuid()
+ const courtCaseNumber = uuid()
+ const defendantNationalId = '0123456789'
+ const indictmentPdf = 'test indictment'
+ const theCase = {
+ id: caseId,
+ origin: CaseOrigin.LOKE,
+ type: caseType,
+ state: caseState,
+ policeCaseNumbers: [policeCaseNumber],
+ courtCaseNumber,
+ defendants: [{ nationalId: defendantNationalId }],
+ indictmentSubtypes: {
+ [policeCaseNumber]: [IndictmentSubtype.TRAFFIC_VIOLATION],
+ },
+ indictmentHash: uuid(),
+ } as Case
+
+ beforeEach(async () => {
+ const mockGetGeneratedIndictmentCaseObject =
+ mockAwsS3Service.getObject as jest.Mock
+ mockGetGeneratedIndictmentCaseObject.mockResolvedValueOnce(indictmentPdf)
+
+ await givenWhenThen(caseId, theCase)
+ })
+
+ it('should update the police case', async () => {
+ expect(mockPoliceService.updatePoliceCase).toHaveBeenCalledWith(
+ user,
+ caseId,
+ caseType,
+ caseState,
+ policeCaseNumber,
+ courtCaseNumber,
+ defendantNationalId,
+ date,
+ '',
+ [
+ {
+ type: PoliceDocumentType.RVAS,
+ courtDocument: Base64.btoa(indictmentPdf),
+ },
+ ],
+ )
+ })
+ })
})
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentToPoliceGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentToPoliceGuards.spec.ts
index 3d389c82009d..454c56553c2b 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentToPoliceGuards.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentToPoliceGuards.spec.ts
@@ -1,5 +1,3 @@
-import { CanActivate } from '@nestjs/common'
-
import { indictmentCases } from '@island.is/judicial-system/types'
import { CaseExistsGuard } from '../../guards/caseExists.guard'
@@ -17,34 +15,12 @@ describe('InternalCaseController - Deliver indictment to police guards', () => {
)
})
- it('should have two guards', () => {
+ it('should have the right guard configuration', () => {
expect(guards).toHaveLength(2)
- })
-
- describe('CaseExistsGuard', () => {
- let guard: CanActivate
-
- beforeEach(() => {
- guard = new guards[0]()
- })
-
- it('should have CaseExistsGuard as guard 1', () => {
- expect(guard).toBeInstanceOf(CaseExistsGuard)
- })
- })
-
- describe('CaseTypeGuard', () => {
- let guard: CanActivate
-
- beforeEach(() => {
- guard = guards[1]
- })
-
- it('should have CaseTypeGuard as guard 2', () => {
- expect(guard).toBeInstanceOf(CaseTypeGuard)
- expect(guard).toEqual({
- allowedCaseTypes: indictmentCases,
- })
+ expect(new guards[0]()).toBeInstanceOf(CaseExistsGuard)
+ expect(guards[1]).toBeInstanceOf(CaseTypeGuard)
+ expect(guards[1]).toEqual({
+ allowedCaseTypes: indictmentCases,
})
})
})
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverSignedRulingToCourt.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverSignedRulingToCourt.spec.ts
index 30df480ec2c0..b0d1b3e91c19 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverSignedRulingToCourt.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverSignedRulingToCourt.spec.ts
@@ -1,7 +1,7 @@
import format from 'date-fns/format'
import { uuid } from 'uuidv4'
-import { User } from '@island.is/judicial-system/types'
+import { CaseState, CaseType, User } from '@island.is/judicial-system/types'
import { createTestingCaseModule } from '../createTestingCaseModule'
@@ -38,8 +38,9 @@ describe('InternalCaseController - Deliver signed ruling to court', () => {
mockCreateDocument.mockRejectedValue(new Error('Some error'))
mockAwsS3Service = awsS3Service
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValue(new Error('Some error'))
+ const mockGetGeneratedObject =
+ mockAwsS3Service.getGeneratedObject as jest.Mock
+ mockGetGeneratedObject.mockRejectedValue(new Error('Some error'))
givenWhenThen = async (caseId: string, theCase: Case) => {
const then = {} as Then
@@ -56,8 +57,17 @@ describe('InternalCaseController - Deliver signed ruling to court', () => {
describe('signed ruling delivered', () => {
const caseId = uuid()
const courtId = uuid()
+ const caseType = CaseType.BODY_SEARCH
+ const caseState = CaseState.ACCEPTED
+
const courtCaseNumber = uuid()
- const theCase = { id: caseId, courtId, courtCaseNumber } as Case
+ const theCase = {
+ id: caseId,
+ type: caseType,
+ state: caseState,
+ courtId,
+ courtCaseNumber,
+ } as Case
const pdf = Buffer.from('test ruling')
const now = randomDate()
@@ -66,21 +76,20 @@ describe('InternalCaseController - Deliver signed ruling to court', () => {
beforeEach(async () => {
const mockNowFactory = nowFactory as jest.Mock
mockNowFactory.mockReturnValue(now)
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockResolvedValueOnce(pdf)
+ const mockGetGeneratedObject =
+ mockAwsS3Service.getGeneratedObject as jest.Mock
+ mockGetGeneratedObject.mockResolvedValueOnce(pdf)
const mockCreateDocument = mockCourtService.createDocument as jest.Mock
mockCreateDocument.mockResolvedValueOnce(uuid())
then = await givenWhenThen(caseId, theCase)
})
- it('should get the signed ruling from S3', async () => {
- expect(mockAwsS3Service.getObject).toHaveBeenCalledWith(
- `generated/${caseId}/ruling.pdf`,
+ it('should deliver the signed ruling to court', async () => {
+ expect(mockAwsS3Service.getGeneratedObject).toHaveBeenCalledWith(
+ caseType,
+ `${caseId}/ruling.pdf`,
)
- })
-
- it('should create a ruling at court', async () => {
expect(mockCourtService.createDocument).toHaveBeenCalledWith(
user,
caseId,
@@ -92,9 +101,6 @@ describe('InternalCaseController - Deliver signed ruling to court', () => {
'application/pdf',
pdf,
)
- })
-
- it('should return a success response', async () => {
expect(then.result.delivered).toEqual(true)
})
})
@@ -108,8 +114,9 @@ describe('InternalCaseController - Deliver signed ruling to court', () => {
let then: Then
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockResolvedValueOnce(pdf)
+ const mockGetGeneratedObject =
+ mockAwsS3Service.getGeneratedObject as jest.Mock
+ mockGetGeneratedObject.mockResolvedValueOnce(pdf)
then = await givenWhenThen(caseId, theCase)
})
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverSignedRulingToPolice.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverSignedRulingToPolice.spec.ts
index bfc2c4634e90..6085395e9d23 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverSignedRulingToPolice.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverSignedRulingToPolice.spec.ts
@@ -12,7 +12,7 @@ import { createTestingCaseModule } from '../createTestingCaseModule'
import { randomDate } from '../../../../test'
import { AwsS3Service } from '../../../aws-s3'
-import { CourtDocumentType, PoliceService } from '../../../police'
+import { PoliceDocumentType, PoliceService } from '../../../police'
import { Case } from '../../models/case.model'
import { DeliverResponse } from '../../models/deliver.response'
@@ -38,8 +38,8 @@ describe('InternalCaseController - Deliver signed ruling to police', () => {
mockAwsS3Service = awsS3Service
mockPoliceService = policeService
- const mockGetObject = awsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValue(new Error('Some error'))
+ const mockGetGeneratedObject = awsS3Service.getGeneratedObject as jest.Mock
+ mockGetGeneratedObject.mockRejectedValue(new Error('Some error'))
const mockUpdatePoliceCase = mockPoliceService.updatePoliceCase as jest.Mock
mockUpdatePoliceCase.mockRejectedValue(new Error('Some error'))
@@ -80,8 +80,9 @@ describe('InternalCaseController - Deliver signed ruling to police', () => {
let then: Then
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockResolvedValueOnce(rulingPdf)
+ const mockGetGeneratedObject =
+ mockAwsS3Service.getGeneratedObject as jest.Mock
+ mockGetGeneratedObject.mockResolvedValueOnce(rulingPdf)
const mockUpdatePoliceCase =
mockPoliceService.updatePoliceCase as jest.Mock
mockUpdatePoliceCase.mockResolvedValueOnce(true)
@@ -90,8 +91,9 @@ describe('InternalCaseController - Deliver signed ruling to police', () => {
})
it('should update the police case', async () => {
- expect(mockAwsS3Service.getObject).toHaveBeenCalledWith(
- `generated/${caseId}/ruling.pdf`,
+ expect(mockAwsS3Service.getGeneratedObject).toHaveBeenCalledWith(
+ caseType,
+ `${caseId}/ruling.pdf`,
)
expect(mockPoliceService.updatePoliceCase).toHaveBeenCalledWith(
user,
@@ -105,7 +107,7 @@ describe('InternalCaseController - Deliver signed ruling to police', () => {
caseConclusion,
[
{
- type: CourtDocumentType.RVUR,
+ type: PoliceDocumentType.RVUR,
courtDocument: Base64.btoa(rulingPdf),
},
],
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getCaseFilesRecordPdf.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getCaseFilesRecordPdf.spec.ts
index 6690c916730c..5a8fbdb69ce0 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getCaseFilesRecordPdf.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getCaseFilesRecordPdf.spec.ts
@@ -3,7 +3,11 @@ import { uuid } from 'uuidv4'
import { BadRequestException } from '@nestjs/common'
-import { CaseFileCategory, CaseState } from '@island.is/judicial-system/types'
+import {
+ CaseFileCategory,
+ CaseState,
+ CaseType,
+} from '@island.is/judicial-system/types'
import { createTestingCaseModule } from '../createTestingCaseModule'
@@ -38,11 +42,12 @@ describe('LimitedAccessCaseController - Get case files record pdf', () => {
] as CaseFile[]
const theCase = {
id: caseId,
- state: CaseState.ACCEPTED,
+ type: CaseType.INDICTMENT,
+ state: CaseState.COMPLETED,
policeCaseNumbers: [uuid(), policeCaseNumber, uuid()],
caseFiles,
} as Case
- const pdf = uuid()
+ const pdf = Buffer.from(uuid())
const res = { end: jest.fn() } as unknown as Response
let mockawsS3Service: AwsS3Service
@@ -55,6 +60,8 @@ describe('LimitedAccessCaseController - Get case files record pdf', () => {
mockawsS3Service = awsS3Service
const mockGetObject = mockawsS3Service.getObject as jest.Mock
mockGetObject.mockRejectedValue(new Error('Some error'))
+ const mockPutObject = mockawsS3Service.putObject as jest.Mock
+ mockPutObject.mockRejectedValue(new Error('Some error'))
givenWhenThen = async (policeCaseNumber: string) => {
const then = {} as Then
@@ -83,13 +90,10 @@ describe('LimitedAccessCaseController - Get case files record pdf', () => {
})
it('should generate pdf after failing to get it from AWS S3', () => {
- expect(mockawsS3Service.getObject).toHaveBeenNthCalledWith(
- 1,
- `indictments/completed/${caseId}/${policeCaseNumber}/caseFilesRecord.pdf`,
- )
- expect(mockawsS3Service.getObject).toHaveBeenNthCalledWith(
- 2,
- `indictments/${caseId}/${policeCaseNumber}/caseFilesRecord.pdf`,
+ expect(mockawsS3Service.getObject).toHaveBeenCalledWith(
+ theCase.type,
+ theCase.state,
+ `${caseId}/${policeCaseNumber}/caseFilesRecord.pdf`,
)
expect(createCaseFilesRecord).toHaveBeenCalledWith(
theCase,
@@ -97,28 +101,20 @@ describe('LimitedAccessCaseController - Get case files record pdf', () => {
expect.any(Array),
expect.any(Function),
)
+ expect(mockawsS3Service.putObject).toHaveBeenCalledWith(
+ theCase.type,
+ theCase.state,
+ `${caseId}/${policeCaseNumber}/caseFilesRecord.pdf`,
+ pdf.toString('binary'),
+ )
expect(res.end).toHaveBeenCalledWith(pdf)
})
})
- describe('pdf returned from AWS S3 indictment completed folder', () => {
- beforeEach(async () => {
- const mockGetObject = mockawsS3Service.getObject as jest.Mock
- mockGetObject.mockReturnValueOnce(pdf)
-
- await givenWhenThen(policeCaseNumber)
- })
-
- it('should return pdf', () => {
- expect(res.end).toHaveBeenCalledWith(pdf)
- })
- })
-
- describe('pdf returned from AWS S3 indictment folder', () => {
+ describe('pdf returned from AWS S3', () => {
beforeEach(async () => {
const mockGetObject = mockawsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(new Error('Some error'))
- mockGetObject.mockReturnValueOnce(pdf)
+ mockGetObject.mockResolvedValueOnce(pdf)
await givenWhenThen(policeCaseNumber)
})
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getCourtRecordPdf.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getCourtRecordPdf.spec.ts
index d624584fd8ea..2d642cd9d34a 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getCourtRecordPdf.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getCourtRecordPdf.spec.ts
@@ -3,7 +3,7 @@ import { uuid } from 'uuidv4'
import { Logger } from '@island.is/logging'
-import { User } from '@island.is/judicial-system/types'
+import { CaseState, CaseType, User } from '@island.is/judicial-system/types'
import { createTestingCaseModule } from '../createTestingCaseModule'
@@ -33,9 +33,16 @@ describe('LimitedAccessCaseController - Get court record pdf', () => {
beforeEach(async () => {
const { awsS3Service, logger, limitedAccessCaseController } =
await createTestingCaseModule()
+
mockAwsS3Service = awsS3Service
mockLogger = logger
+ const mockGetGeneratedObject =
+ mockAwsS3Service.getGeneratedObject as jest.Mock
+ mockGetGeneratedObject.mockRejectedValue(new Error('Some error'))
+ const getMock = getCourtRecordPdfAsBuffer as jest.Mock
+ getMock.mockRejectedValue(new Error('Some error'))
+
givenWhenThen = async (
caseId: string,
user: User,
@@ -59,44 +66,33 @@ describe('LimitedAccessCaseController - Get court record pdf', () => {
}
})
- describe('AWS S3 lookup', () => {
- const user = {} as User
- const caseId = uuid()
- const theCase = {
- id: caseId,
- courtRecordSignatureDate: nowFactory(),
- } as Case
- const res = {} as Response
-
- beforeEach(async () => {
- await givenWhenThen(caseId, user, theCase, res)
- })
-
- it('should lookup pdf', () => {
- expect(mockAwsS3Service.getObject).toHaveBeenCalledWith(
- `generated/${caseId}/courtRecord.pdf`,
- )
- })
- })
-
describe('AWS S3 pdf returned', () => {
const user = {} as User
const caseId = uuid()
+ const caseType = CaseType.EXPULSION_FROM_HOME
+ const caseState = CaseState.DISMISSED
const theCase = {
id: caseId,
+ type: caseType,
+ state: caseState,
courtRecordSignatureDate: nowFactory(),
} as Case
const res = { end: jest.fn() } as unknown as Response
const pdf = {}
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockResolvedValueOnce(pdf)
+ const mockGetGeneratedObject =
+ mockAwsS3Service.getGeneratedObject as jest.Mock
+ mockGetGeneratedObject.mockResolvedValueOnce(pdf)
await givenWhenThen(caseId, user, theCase, res)
})
it('should return pdf', () => {
+ expect(mockAwsS3Service.getGeneratedObject).toHaveBeenCalledWith(
+ caseType,
+ `${caseId}/courtRecord.pdf`,
+ )
expect(res.end).toHaveBeenCalledWith(pdf)
})
})
@@ -109,12 +105,9 @@ describe('LimitedAccessCaseController - Get court record pdf', () => {
courtRecordSignatureDate: nowFactory(),
} as Case
const res = {} as Response
- const error = new Error('Some ignored error')
+ const error = new Error('Some error')
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(error)
-
await givenWhenThen(caseId, user, theCase, res)
})
@@ -126,31 +119,6 @@ describe('LimitedAccessCaseController - Get court record pdf', () => {
})
})
- describe('pdf generated', () => {
- const user = {} as User
- const caseId = uuid()
- const theCase = {
- id: caseId,
- courtRecordSignatureDate: nowFactory(),
- } as Case
- const res = {} as Response
-
- beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(new Error('Some ignored error'))
-
- await givenWhenThen(caseId, user, theCase, res)
- })
-
- it('should generate pdf', () => {
- expect(getCourtRecordPdfAsBuffer).toHaveBeenCalledWith(
- theCase,
- expect.any(Function),
- user,
- )
- })
- })
-
describe('generated pdf returned', () => {
const user = {} as User
const caseId = uuid()
@@ -162,8 +130,6 @@ describe('LimitedAccessCaseController - Get court record pdf', () => {
const pdf = {}
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(new Error('Some ignored error'))
const getMock = getCourtRecordPdfAsBuffer as jest.Mock
getMock.mockResolvedValueOnce(pdf)
@@ -171,6 +137,11 @@ describe('LimitedAccessCaseController - Get court record pdf', () => {
})
it('should return pdf', () => {
+ expect(getCourtRecordPdfAsBuffer).toHaveBeenCalledWith(
+ theCase,
+ expect.any(Function),
+ user,
+ )
expect(res.end).toHaveBeenCalledWith(pdf)
})
})
@@ -186,11 +157,6 @@ describe('LimitedAccessCaseController - Get court record pdf', () => {
const res = {} as Response
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(new Error('Some ignored error'))
- const getMock = getCourtRecordPdfAsBuffer as jest.Mock
- getMock.mockRejectedValueOnce(new Error('Some error'))
-
then = await givenWhenThen(caseId, user, theCase, res)
})
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getIndictmentPdf.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getIndictmentPdf.spec.ts
index 6e40f0b0e114..026b70c71758 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getIndictmentPdf.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getIndictmentPdf.spec.ts
@@ -1,9 +1,16 @@
import { Response } from 'express'
import { uuid } from 'uuidv4'
+import {
+ CaseState,
+ CaseType,
+ IndictmentSubtype,
+} from '@island.is/judicial-system/types'
+
import { createTestingCaseModule } from '../createTestingCaseModule'
import { createIndictment } from '../../../../formatters'
+import { AwsS3Service } from '../../../aws-s3'
import { Case } from '../../models/case.model'
jest.mock('../../../../formatters/indictmentPdf')
@@ -16,16 +23,30 @@ type GivenWhenThen = () => Promise
describe('LimitedCaseController - Get indictment pdf', () => {
const caseId = uuid()
+ const policeCaseNumber = uuid()
const theCase = {
id: caseId,
+ type: CaseType.INDICTMENT,
+ state: CaseState.COMPLETED,
+ policeCaseNumbers: [policeCaseNumber],
+ indictmentSubtypes: {
+ [policeCaseNumber]: [IndictmentSubtype.TRAFFIC_VIOLATION],
+ },
+ indictmentHash: uuid(),
} as Case
- const pdf = uuid()
+ const pdf = Buffer.from(uuid())
const res = { end: jest.fn() } as unknown as Response
+ let mockawsS3Service: AwsS3Service
let givenWhenThen: GivenWhenThen
beforeEach(async () => {
- const { limitedAccessCaseController } = await createTestingCaseModule()
+ const { awsS3Service, limitedAccessCaseController } =
+ await createTestingCaseModule()
+
+ mockawsS3Service = awsS3Service
+ const mockGetObject = mockawsS3Service.getObject as jest.Mock
+ mockGetObject.mockRejectedValue(new Error('Some error'))
givenWhenThen = async () => {
const then = {} as Then
@@ -49,6 +70,11 @@ describe('LimitedCaseController - Get indictment pdf', () => {
})
it('should generate pdf', () => {
+ expect(mockawsS3Service.getObject).toHaveBeenCalledWith(
+ theCase.type,
+ theCase.state,
+ `${caseId}/indictment.pdf`,
+ )
expect(createIndictment).toHaveBeenCalledWith(
theCase,
expect.any(Function),
@@ -57,4 +83,17 @@ describe('LimitedCaseController - Get indictment pdf', () => {
expect(res.end).toHaveBeenCalledWith(pdf)
})
})
+
+ describe('pdf returned from AWS S3', () => {
+ beforeEach(async () => {
+ const mockGetObject = mockawsS3Service.getObject as jest.Mock
+ mockGetObject.mockResolvedValueOnce(pdf)
+
+ await givenWhenThen()
+ })
+
+ it('should return pdf', () => {
+ expect(res.end).toHaveBeenCalledWith(pdf)
+ })
+ })
})
diff --git a/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getRulingPdf.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getRulingPdf.spec.ts
index 3b19de1e2299..d7e5b8e6c89c 100644
--- a/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getRulingPdf.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getRulingPdf.spec.ts
@@ -3,6 +3,8 @@ import { uuid } from 'uuidv4'
import { Logger } from '@island.is/logging'
+import { CaseState, CaseType } from '@island.is/judicial-system/types'
+
import { createTestingCaseModule } from '../createTestingCaseModule'
import { nowFactory } from '../../../../factories'
@@ -30,9 +32,16 @@ describe('LimitedAccessCaseController - Get ruling pdf', () => {
beforeEach(async () => {
const { awsS3Service, logger, limitedAccessCaseController } =
await createTestingCaseModule()
+
mockAwsS3Service = awsS3Service
mockLogger = logger
+ const mockGetGeneratedObject =
+ mockAwsS3Service.getGeneratedObject as jest.Mock
+ mockGetGeneratedObject.mockRejectedValue(new Error('Some error'))
+ const getMock = getRulingPdfAsBuffer as jest.Mock
+ getMock.mockRejectedValue(new Error('Some error'))
+
givenWhenThen = async (caseId: string, theCase: Case, res: Response) => {
const then = {} as Then
@@ -46,36 +55,32 @@ describe('LimitedAccessCaseController - Get ruling pdf', () => {
}
})
- describe('AWS S3 lookup', () => {
- const caseId = uuid()
- const theCase = { id: caseId, rulingSignatureDate: nowFactory() } as Case
- const res = {} as Response
-
- beforeEach(async () => {
- await givenWhenThen(caseId, theCase, res)
- })
-
- it('should lookup pdf', () => {
- expect(mockAwsS3Service.getObject).toHaveBeenCalledWith(
- `generated/${caseId}/ruling.pdf`,
- )
- })
- })
-
describe('AWS S3 pdf returned', () => {
const caseId = uuid()
- const theCase = { id: caseId, rulingSignatureDate: nowFactory() } as Case
+ const caseType = CaseType.AUTOPSY
+ const caseState = CaseState.REJECTED
+ const theCase = {
+ id: caseId,
+ type: caseType,
+ state: caseState,
+ rulingSignatureDate: nowFactory(),
+ } as Case
const res = { end: jest.fn() } as unknown as Response
const pdf = {}
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockResolvedValueOnce(pdf)
+ const mockGetGeneratedObject =
+ mockAwsS3Service.getGeneratedObject as jest.Mock
+ mockGetGeneratedObject.mockResolvedValueOnce(pdf)
await givenWhenThen(caseId, theCase, res)
})
it('should return pdf', () => {
+ expect(mockAwsS3Service.getGeneratedObject).toHaveBeenCalledWith(
+ caseType,
+ `${caseId}/ruling.pdf`,
+ )
expect(res.end).toHaveBeenCalledWith(pdf)
})
})
@@ -84,12 +89,9 @@ describe('LimitedAccessCaseController - Get ruling pdf', () => {
const caseId = uuid()
const theCase = { id: caseId, rulingSignatureDate: nowFactory() } as Case
const res = {} as Response
- const error = new Error('Some ignored error')
+ const error = new Error('Some error')
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(error)
-
await givenWhenThen(caseId, theCase, res)
})
@@ -101,26 +103,6 @@ describe('LimitedAccessCaseController - Get ruling pdf', () => {
})
})
- describe('pdf generated', () => {
- const caseId = uuid()
- const theCase = { id: caseId, rulingSignatureDate: nowFactory() } as Case
- const res = {} as Response
-
- beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(new Error('Some ignored error'))
-
- await givenWhenThen(caseId, theCase, res)
- })
-
- it('should generate pdf', () => {
- expect(getRulingPdfAsBuffer).toHaveBeenCalledWith(
- theCase,
- expect.any(Function),
- )
- })
- })
-
describe('generated pdf returned', () => {
const caseId = uuid()
const theCase = { id: caseId, rulingSignatureDate: nowFactory() } as Case
@@ -128,8 +110,6 @@ describe('LimitedAccessCaseController - Get ruling pdf', () => {
const pdf = {}
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(new Error('Some ignored error'))
const getMock = getRulingPdfAsBuffer as jest.Mock
getMock.mockResolvedValueOnce(pdf)
@@ -137,6 +117,10 @@ describe('LimitedAccessCaseController - Get ruling pdf', () => {
})
it('should return pdf', () => {
+ expect(getRulingPdfAsBuffer).toHaveBeenCalledWith(
+ theCase,
+ expect.any(Function),
+ )
expect(res.end).toHaveBeenCalledWith(pdf)
})
})
@@ -148,11 +132,6 @@ describe('LimitedAccessCaseController - Get ruling pdf', () => {
const res = {} as Response
beforeEach(async () => {
- const mockGetObject = mockAwsS3Service.getObject as jest.Mock
- mockGetObject.mockRejectedValueOnce(new Error('Some ignored error'))
- const getMock = getRulingPdfAsBuffer as jest.Mock
- getMock.mockRejectedValueOnce(new Error('Some error'))
-
then = await givenWhenThen(caseId, theCase, res)
})
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/defendant.service.ts b/apps/judicial-system/backend/src/app/modules/defendant/defendant.service.ts
index 30fdd92b23af..22e881540a50 100644
--- a/apps/judicial-system/backend/src/app/modules/defendant/defendant.service.ts
+++ b/apps/judicial-system/backend/src/app/modules/defendant/defendant.service.ts
@@ -5,12 +5,14 @@ import {
Inject,
Injectable,
InternalServerErrorException,
+ NotFoundException,
} from '@nestjs/common'
import { InjectModel } from '@nestjs/sequelize'
import type { Logger } from '@island.is/logging'
import { LOGGER_PROVIDER } from '@island.is/logging'
+import { formatNationalId } from '@island.is/judicial-system/formatters'
import {
CaseMessage,
MessageService,
@@ -195,6 +197,37 @@ export class DefendantService {
return updatedDefendant
}
+ async updateByNationalId(
+ caseId: string,
+ defendantNationalId: string,
+ update: UpdateDefendantDto,
+ ): Promise {
+ const formattedNationalId = formatNationalId(defendantNationalId)
+
+ const [numberOfAffectedRows, defendants] = await this.defendantModel.update(
+ update,
+ {
+ where: {
+ caseId,
+ [Op.or]: [
+ { national_id: formattedNationalId },
+ { national_id: defendantNationalId },
+ ],
+ },
+ returning: true,
+ },
+ )
+
+ const updatedDefendant = this.getUpdatedDefendant(
+ numberOfAffectedRows,
+ defendants,
+ defendants[0].id,
+ caseId,
+ )
+
+ return updatedDefendant
+ }
+
async delete(
theCase: Case,
defendantId: string,
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/dto/createDefendant.dto.ts b/apps/judicial-system/backend/src/app/modules/defendant/dto/createDefendant.dto.ts
index 31e4109c16d8..eee5ea58da99 100644
--- a/apps/judicial-system/backend/src/app/modules/defendant/dto/createDefendant.dto.ts
+++ b/apps/judicial-system/backend/src/app/modules/defendant/dto/createDefendant.dto.ts
@@ -2,7 +2,7 @@ import { IsBoolean, IsEnum, IsOptional, IsString } from 'class-validator'
import { ApiPropertyOptional } from '@nestjs/swagger'
-import { Gender } from '@island.is/judicial-system/types'
+import { DefenderChoice, Gender } from '@island.is/judicial-system/types'
export class CreateDefendantDto {
@IsOptional()
@@ -56,7 +56,7 @@ export class CreateDefendantDto {
readonly defenderPhoneNumber?: string
@IsOptional()
- @IsBoolean()
- @ApiPropertyOptional({ type: Boolean })
- readonly defendantWaivesRightToCounsel?: boolean
+ @IsEnum(DefenderChoice)
+ @ApiPropertyOptional({ enum: DefenderChoice })
+ readonly defenderChoice?: DefenderChoice
}
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/dto/updateDefendant.dto.ts b/apps/judicial-system/backend/src/app/modules/defendant/dto/updateDefendant.dto.ts
index 922535aab96e..e9887e658203 100644
--- a/apps/judicial-system/backend/src/app/modules/defendant/dto/updateDefendant.dto.ts
+++ b/apps/judicial-system/backend/src/app/modules/defendant/dto/updateDefendant.dto.ts
@@ -4,6 +4,7 @@ import { ApiPropertyOptional } from '@nestjs/swagger'
import {
DefendantPlea,
+ DefenderChoice,
Gender,
ServiceRequirement,
} from '@island.is/judicial-system/types'
@@ -60,9 +61,9 @@ export class UpdateDefendantDto {
readonly defenderPhoneNumber?: string
@IsOptional()
- @IsBoolean()
- @ApiPropertyOptional({ type: Boolean })
- readonly defendantWaivesRightToCounsel?: boolean
+ @IsEnum(DefenderChoice)
+ @ApiPropertyOptional({ enum: DefenderChoice })
+ readonly defenderChoice?: DefenderChoice
@IsOptional()
@IsEnum(DefendantPlea)
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/internalDefendant.controller.ts b/apps/judicial-system/backend/src/app/modules/defendant/internalDefendant.controller.ts
index e4736064af61..1fbefa6b9af2 100644
--- a/apps/judicial-system/backend/src/app/modules/defendant/internalDefendant.controller.ts
+++ b/apps/judicial-system/backend/src/app/modules/defendant/internalDefendant.controller.ts
@@ -3,10 +3,11 @@ import {
Controller,
Inject,
Param,
+ Patch,
Post,
UseGuards,
} from '@nestjs/common'
-import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'
+import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'
import type { Logger } from '@island.is/logging'
import { LOGGER_PROVIDER } from '@island.is/logging'
@@ -19,26 +20,26 @@ import {
import { Case, CaseExistsGuard, CurrentCase } from '../case'
import { DeliverDefendantToCourtDto } from './dto/deliverDefendantToCourt.dto'
+import { UpdateDefendantDto } from './dto/updateDefendant.dto'
import { CurrentDefendant } from './guards/defendant.decorator'
import { DefendantExistsGuard } from './guards/defendantExists.guard'
import { Defendant } from './models/defendant.model'
import { DeliverResponse } from './models/deliver.response'
import { DefendantService } from './defendant.service'
-@Controller(
- `api/internal/case/:caseId/${
- messageEndpoint[MessageType.DELIVERY_TO_COURT_DEFENDANT]
- }/:defendantId`,
-)
+@Controller('api/internal/case/:caseId')
@ApiTags('internal defendants')
-@UseGuards(TokenGuard, CaseExistsGuard, DefendantExistsGuard)
+@UseGuards(TokenGuard, CaseExistsGuard)
export class InternalDefendantController {
constructor(
private readonly defendantService: DefendantService,
@Inject(LOGGER_PROVIDER) private readonly logger: Logger,
) {}
- @Post()
+ @UseGuards(DefendantExistsGuard)
+ @Post(
+ `${messageEndpoint[MessageType.DELIVERY_TO_COURT_DEFENDANT]}/:defendantId`,
+ )
@ApiCreatedResponse({
type: DeliverResponse,
description: 'Delivers a case file to court',
@@ -60,4 +61,26 @@ export class InternalDefendantController {
deliverDefendantToCourtDto.user,
)
}
+
+ @Patch('defense/:defendantNationalId')
+ @ApiOkResponse({
+ type: Defendant,
+ description: 'Assigns defense choice to defendant',
+ })
+ async assignDefender(
+ @Param('caseId') caseId: string,
+ @Param('defendantNationalId') defendantNationalId: string,
+ @CurrentCase() theCase: Case,
+ @Body() updatedDefendantChoice: UpdateDefendantDto,
+ ): Promise {
+ this.logger.debug(`Assigning defense choice to defendant in case ${caseId}`)
+
+ const updatedDefendant = await this.defendantService.updateByNationalId(
+ theCase.id,
+ defendantNationalId,
+ updatedDefendantChoice,
+ )
+
+ return updatedDefendant
+ }
}
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/models/defendant.model.ts b/apps/judicial-system/backend/src/app/modules/defendant/models/defendant.model.ts
index 053d19ff4bf2..1d6b1e0f471e 100644
--- a/apps/judicial-system/backend/src/app/modules/defendant/models/defendant.model.ts
+++ b/apps/judicial-system/backend/src/app/modules/defendant/models/defendant.model.ts
@@ -13,6 +13,7 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
import {
DefendantPlea,
+ DefenderChoice,
Gender,
ServiceRequirement,
} from '@island.is/judicial-system/types'
@@ -94,9 +95,13 @@ export class Defendant extends Model {
@ApiPropertyOptional({ type: String })
defenderPhoneNumber?: string
- @Column({ type: DataType.BOOLEAN, allowNull: false, defaultValue: false })
- @ApiProperty({ type: Boolean })
- defendantWaivesRightToCounsel!: boolean
+ @Column({
+ type: DataType.ENUM,
+ allowNull: true,
+ values: Object.values(DefenderChoice),
+ })
+ @ApiPropertyOptional({ enum: DefenderChoice })
+ defenderChoice?: DefenderChoice
@Column({
type: DataType.ENUM,
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/internalDefendantController/internalDefendantControllerGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/internalDefendantController/internalDefendantControllerGuards.spec.ts
index 6e88a771a9f5..ae0ad03605fb 100644
--- a/apps/judicial-system/backend/src/app/modules/defendant/test/internalDefendantController/internalDefendantControllerGuards.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/defendant/test/internalDefendantController/internalDefendantControllerGuards.spec.ts
@@ -14,8 +14,8 @@ describe('InternalDefendantController - guards', () => {
guards = Reflect.getMetadata('__guards__', InternalDefendantController)
})
- it('should have three guards', () => {
- expect(guards).toHaveLength(3)
+ it('should have two guards', () => {
+ expect(guards).toHaveLength(2)
})
describe('TokenGuard', () => {
@@ -42,14 +42,14 @@ describe('InternalDefendantController - guards', () => {
})
})
- describe('DefendantExistsGuard', () => {
- let guard: CanActivate
-
- beforeEach(() => {
- guard = new guards[2]()
- })
-
- it('should have DefendantExistsGuard as guard 3', () => {
+ describe('Method level guards', () => {
+ it('should have DefendantExistsGuard on deliverDefendantToCourt method', () => {
+ const methodGuards = Reflect.getMetadata(
+ '__guards__',
+ InternalDefendantController.prototype.deliverDefendantToCourt,
+ )
+ expect(methodGuards).toHaveLength(1)
+ const guard = new methodGuards[0]()
expect(guard).toBeInstanceOf(DefendantExistsGuard)
})
})
diff --git a/apps/judicial-system/backend/src/app/modules/event-log/eventLog.service.ts b/apps/judicial-system/backend/src/app/modules/event-log/eventLog.service.ts
index 044af37f6c89..c1ee4450f943 100644
--- a/apps/judicial-system/backend/src/app/modules/event-log/eventLog.service.ts
+++ b/apps/judicial-system/backend/src/app/modules/event-log/eventLog.service.ts
@@ -1,3 +1,4 @@
+import { Transaction } from 'sequelize/types'
import { Sequelize } from 'sequelize-typescript'
import { Inject, Injectable } from '@nestjs/common'
@@ -11,11 +12,12 @@ import { EventType } from '@island.is/judicial-system/types'
import { CreateEventLogDto } from './dto/createEventLog.dto'
import { EventLog } from './models/eventLog.model'
-const allowMultiple = [
- 'LOGIN',
- 'LOGIN_UNAUTHORIZED',
- 'LOGIN_BYPASS',
- 'LOGIN_BYPASS_UNAUTHORIZED',
+const allowMultiple: EventType[] = [
+ EventType.LOGIN,
+ EventType.LOGIN_UNAUTHORIZED,
+ EventType.LOGIN_BYPASS,
+ EventType.LOGIN_BYPASS_UNAUTHORIZED,
+ EventType.INDICTMENT_CONFIRMED,
]
@Injectable()
@@ -27,7 +29,10 @@ export class EventLogService {
private readonly logger: Logger,
) {}
- async create(event: CreateEventLogDto): Promise {
+ async create(
+ event: CreateEventLogDto,
+ transaction?: Transaction,
+ ): Promise {
const { eventType, caseId, userRole, nationalId } = event
if (!allowMultiple.includes(event.eventType)) {
@@ -45,28 +50,16 @@ export class EventLogService {
}
try {
- await this.eventLogModel.create({
- eventType,
- caseId,
- nationalId,
- userRole,
- })
+ await this.eventLogModel.create(
+ { eventType, caseId, nationalId, userRole },
+ { transaction },
+ )
} catch (error) {
// Tolerate failure but log error
this.logger.error('Failed to create event log', error)
}
}
- async findEventTypeByCaseId(eventType: EventType, caseId: string) {
- return this.eventLogModel.findOne({
- where: {
- eventType,
- caseId,
- },
- order: [['created', 'DESC']],
- })
- }
-
async loginMap(
nationalIds: string[],
): Promise
{column.appealState && (
diff --git a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.spec.ts b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.spec.ts
index 2dce52f5335b..d742df360bc2 100644
--- a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.spec.ts
+++ b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.spec.ts
@@ -78,7 +78,7 @@ describe('mapCaseStateToTagVariant', () => {
text: strings.active.defaultMessage,
})
- expect(fn(CaseState.ACCEPTED, false, CaseType.INDICTMENT)).toEqual({
+ expect(fn(CaseState.COMPLETED, false, CaseType.INDICTMENT)).toEqual({
color: 'darkerBlue',
text: strings.inactive.defaultMessage,
})
diff --git a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.strings.ts b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.strings.ts
index 2bcb77190931..23440548181d 100644
--- a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.strings.ts
+++ b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.strings.ts
@@ -67,4 +67,10 @@ export const strings = defineMessages({
defaultMessage: 'Móttekið',
description: 'Notað sem merki þegar mál í stöðu "Móttekið" í málalista',
},
+ completed: {
+ id: 'judicial.system.core:tag_case_state.completed',
+ defaultMessage:
+ '{indictmentRulingDecision, select, RULING {Dómur} FINE {Viðurlagaákvörðun} DISMISSAL {Frávísun} CANCELLATION {Niðurfelling} other {Lokið}}',
+ description: 'Notað sem merki þegar mál í stöðu "Dómþulur" í málalista',
+ },
})
diff --git a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx
index e920234c4642..8cb3498d30bc 100644
--- a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx
+++ b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx
@@ -2,11 +2,9 @@ import React from 'react'
import { IntlShape, useIntl } from 'react-intl'
import { Tag, TagVariant } from '@island.is/island-ui/core'
+import { isInvestigationCase } from '@island.is/judicial-system/types'
import {
- isIndictmentCase,
- isInvestigationCase,
-} from '@island.is/judicial-system/types'
-import {
+ CaseIndictmentRulingDecision,
CaseState,
CaseType,
User,
@@ -21,6 +19,7 @@ interface Props {
isValidToDateInThePast?: boolean | null
courtDate?: string | null
indictmentReviewer?: User | null
+ indictmentRulingDecision?: CaseIndictmentRulingDecision | null
customMapCaseStateToTag?: (
formatMessage: IntlShape['formatMessage'],
state?: CaseState | null,
@@ -53,6 +52,7 @@ export const mapCaseStateToTagVariant = (
isValidToDateInThePast?: boolean | null,
scheduledDate?: string | null,
isCourtRole?: boolean,
+ indictmentRulingDecision?: CaseIndictmentRulingDecision | null,
): { color: TagVariant; text: string } => {
switch (state) {
case CaseState.NEW:
@@ -71,8 +71,7 @@ export const mapCaseStateToTagVariant = (
case CaseState.MAIN_HEARING:
return { color: 'blue', text: formatMessage(strings.reassignment) }
case CaseState.ACCEPTED:
- case CaseState.COMPLETED:
- return isIndictmentCase(caseType) || isValidToDateInThePast
+ return isValidToDateInThePast
? { color: 'darkerBlue', text: formatMessage(strings.inactive) }
: {
color: 'blue',
@@ -84,6 +83,11 @@ export const mapCaseStateToTagVariant = (
return { color: 'rose', text: formatMessage(strings.rejected) }
case CaseState.DISMISSED:
return { color: 'dark', text: formatMessage(strings.dismissed) }
+ case CaseState.COMPLETED:
+ return {
+ color: 'darkerBlue',
+ text: formatMessage(strings.completed, { indictmentRulingDecision }),
+ }
default:
return { color: 'white', text: formatMessage(strings.unknown) }
}
@@ -98,6 +102,7 @@ const TagCaseState: React.FC
> = (Props) => {
isValidToDateInThePast,
courtDate,
indictmentReviewer,
+ indictmentRulingDecision,
customMapCaseStateToTag,
} = Props
@@ -110,6 +115,7 @@ const TagCaseState: React.FC> = (Props) => {
isValidToDateInThePast,
courtDate,
isCourtRole,
+ indictmentRulingDecision,
)
if (!tagVariant) return null
diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Completed/Completed.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Completed/Completed.tsx
index b74b6ad4cb23..37e9619dec96 100644
--- a/apps/judicial-system/web/src/routes/Court/Indictments/Completed/Completed.tsx
+++ b/apps/judicial-system/web/src/routes/Court/Indictments/Completed/Completed.tsx
@@ -83,6 +83,13 @@ const Completed: FC = () => {
[workingCase.id],
)
+ const isRulingOrFine =
+ workingCase.indictmentRulingDecision &&
+ [
+ CaseIndictmentRulingDecision.RULING,
+ CaseIndictmentRulingDecision.FINE,
+ ].includes(workingCase.indictmentRulingDecision)
+
const stepIsValid = () =>
workingCase.indictmentRulingDecision === CaseIndictmentRulingDecision.RULING
? workingCase.defendants?.every(
@@ -112,30 +119,32 @@ const Completed: FC = () => {
-
-
-
- file.category === CaseFileCategory.CRIMINAL_RECORD_UPDATE,
- )}
- accept="application/pdf"
- header={formatMessage(core.uploadBoxTitle)}
- buttonLabel={formatMessage(core.uploadBoxButtonLabel)}
- description={formatMessage(core.uploadBoxDescription, {
- fileEndings: '.pdf',
- })}
- onChange={(files) =>
- handleCriminalRecordUpdateUpload(
- files,
- CaseFileCategory.CRIMINAL_RECORD_UPDATE,
- )
- }
- onRemove={(file) => handleRemoveFile(file)}
- />
-
+ {isRulingOrFine && (
+
+
+
+ file.category === CaseFileCategory.CRIMINAL_RECORD_UPDATE,
+ )}
+ accept="application/pdf"
+ header={formatMessage(core.uploadBoxTitle)}
+ buttonLabel={formatMessage(core.uploadBoxButtonLabel)}
+ description={formatMessage(core.uploadBoxDescription, {
+ fileEndings: '.pdf',
+ })}
+ onChange={(files) =>
+ handleCriminalRecordUpdateUpload(
+ files,
+ CaseFileCategory.CRIMINAL_RECORD_UPDATE,
+ )
+ }
+ onRemove={(file) => handleRemoveFile(file)}
+ />
+
+ )}
{workingCase.indictmentRulingDecision ===
CaseIndictmentRulingDecision.RULING && (
@@ -239,6 +248,7 @@ const Completed: FC = () => {
{
])
const stepIsValid = () => {
- if (!selectedAction) {
+ if (!allFilesDoneOrError) {
return false
}
- if (selectedAction === 'REDISTRIBUTE') {
- return uploadFiles.find(
- (file) => file.category === CaseFileCategory.COURT_RECORD,
- )
- } else if (selectedAction === 'POSTPONE') {
- return (
- Boolean(
+ switch (selectedAction) {
+ case 'POSTPONE':
+ return Boolean(
postponement?.postponedIndefinitely
? postponement.reason
: courtDate?.date,
- ) && allFilesDoneOrError
- )
- } else if (selectedAction === 'COMPLETE') {
- return selectedDecision !== undefined && allFilesDoneOrError
+ )
+ case 'REDISTRIBUTE':
+ return uploadFiles.some(
+ (file) =>
+ file.category === CaseFileCategory.COURT_RECORD &&
+ file.status === 'done',
+ )
+ case 'COMPLETE':
+ switch (selectedDecision) {
+ case CaseIndictmentRulingDecision.RULING:
+ case CaseIndictmentRulingDecision.DISMISSAL:
+ return (
+ uploadFiles.some(
+ (file) =>
+ file.category === CaseFileCategory.COURT_RECORD &&
+ file.status === 'done',
+ ) &&
+ uploadFiles.some(
+ (file) =>
+ file.category === CaseFileCategory.RULING &&
+ file.status === 'done',
+ )
+ )
+ case CaseIndictmentRulingDecision.FINE:
+ case CaseIndictmentRulingDecision.CANCELLATION:
+ return uploadFiles.some(
+ (file) =>
+ file.category === CaseFileCategory.COURT_RECORD &&
+ file.status === 'done',
+ )
+ default:
+ return false
+ }
+ default:
+ return false
}
}
@@ -191,7 +220,7 @@ const Conclusion: React.FC = () => {
workingCase={workingCase}
isLoading={isLoadingWorkingCase}
notFound={caseNotFound}
- isValid={allFilesDoneOrError}
+ isValid={stepIsValid()}
onNavigationTo={handleNavigationTo}
>
@@ -317,16 +346,48 @@ const Conclusion: React.FC = () => {
label={formatMessage(strings.ruling)}
/>
+
+ {
+ setSelectedDecision(CaseIndictmentRulingDecision.FINE)
+ }}
+ large
+ backgroundColor="white"
+ label={formatMessage(strings.fine)}
+ />
+
+
+ {
+ setSelectedDecision(CaseIndictmentRulingDecision.DISMISSAL)
+ }}
+ large
+ backgroundColor="white"
+ label={formatMessage(strings.dismissal)}
+ />
+
{
- setSelectedDecision(CaseIndictmentRulingDecision.FINE)
+ setSelectedDecision(CaseIndictmentRulingDecision.CANCELLATION)
}}
large
backgroundColor="white"
- label={formatMessage(strings.fine)}
+ label={formatMessage(strings.cancellation)}
/>
@@ -335,7 +396,7 @@ const Conclusion: React.FC = () => {
{
/>
)}
- {selectedDecision === 'RULING' && (
-
-
- file.category === CaseFileCategory.RULING,
- )}
- accept="application/pdf"
- header={formatMessage(strings.inputFieldLabel)}
- description={formatMessage(core.uploadBoxDescription, {
- fileEndings: '.pdf',
- })}
- buttonLabel={formatMessage(strings.uploadButtonText)}
- onChange={(files) => {
- handleUpload(
- addUploadFiles(files, CaseFileCategory.RULING),
- updateUploadFile,
- )
- }}
- onRemove={(file) => handleRemove(file, removeUploadFile)}
- onRetry={(file) => handleRetry(file, updateUploadFile)}
- />
-
- )}
+ {selectedAction === 'COMPLETE' &&
+ (selectedDecision === CaseIndictmentRulingDecision.RULING ||
+ selectedDecision === CaseIndictmentRulingDecision.DISMISSAL) && (
+
+
+ file.category === CaseFileCategory.RULING,
+ )}
+ accept="application/pdf"
+ header={formatMessage(strings.inputFieldLabel)}
+ description={formatMessage(core.uploadBoxDescription, {
+ fileEndings: '.pdf',
+ })}
+ buttonLabel={formatMessage(strings.uploadButtonText)}
+ onChange={(files) => {
+ handleUpload(
+ addUploadFiles(files, CaseFileCategory.RULING),
+ updateUploadFile,
+ )
+ }}
+ onRemove={(file) => handleRemove(file, removeUploadFile)}
+ onRetry={(file) => handleRetry(file, updateUploadFile)}
+ />
+
+ )}
> = (props) => {
defenderPhoneNumber: defendantWaivesRightToCounsel
? ''
: defendant.defenderPhoneNumber,
- defendantWaivesRightToCounsel,
+ defenderChoice:
+ defendantWaivesRightToCounsel === true
+ ? DefenderChoice.WAIVE
+ : undefined,
}
setAndSendDefendantToServer(updateDefendantInput, setWorkingCase)
@@ -79,7 +85,7 @@ const SelectDefender: React.FC> = (props) => {
accused: formatMessage(core.indictmentDefendant, { gender }),
}),
)}
- checked={Boolean(defendant.defendantWaivesRightToCounsel)}
+ checked={Boolean(defendant.defenderChoice === DefenderChoice.WAIVE)}
onChange={(event: React.ChangeEvent) => {
toggleDefendantWaivesRightToCounsel(
workingCase.id,
@@ -92,7 +98,7 @@ const SelectDefender: React.FC> = (props) => {
/>
diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.strings.ts b/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.strings.ts
index 86819bf38a17..b479674bf746 100644
--- a/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.strings.ts
+++ b/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.strings.ts
@@ -7,32 +7,9 @@ export const strings = defineMessages({
description:
'Notaður sem titill á yfirliti ákæru þegar máli er ekki lokið.',
},
- completedTitle: {
- id: 'judicial.system.core:indictment_overview.completed_title',
- defaultMessage: 'Máli lokið',
- description: 'Notaður sem titill á yfirliti ákæru þegar máli er lokið.',
- },
returnIndictmentButtonText: {
id: 'judicial.system.core:indictment_overview.return_indictment_button_text',
defaultMessage: 'Endursenda',
description: 'Notaður sem texti á takka til að endursenda ákæru.',
},
- sendToPublicProsecutorModalTitle: {
- id: 'judicial.system.core:indictment_overview.send_to_public_prosecutor_modal_title',
- defaultMessage: 'Mál hefur verið sent til Ríkissaksóknara',
- description:
- 'Notaður sem titill á staðfestingarglugga um að mál hafi verið sent til Ríkissaksóknara.',
- },
- sendToPublicProsecutorModalText: {
- id: 'judicial.system.core:indictment_overview.send_to_public_prosecutor_modal_text',
- defaultMessage: 'Gögn hafa verið send til Ríkissaksóknara til yfirlesturs',
- description:
- 'Notaður sem texti í staðfestingarglugga um að mál hafi verið sent til Ríkissaksóknara.',
- },
- sendToPublicProsecutorModalNextButtonText: {
- id: 'judicial.system.core:indictment_overview.send_to_public_prosecutor_modal_next_button_text',
- defaultMessage: 'Senda til ákæruvalds',
- description:
- 'Notaður sem texti á takka í staðfestingarglugga um að mál hafi verið sent til Ríkissaksóknara.',
- },
})
diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx
index d47a9bace956..2e67664769a1 100644
--- a/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx
+++ b/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx
@@ -2,9 +2,9 @@ import React, { useCallback, useContext, useState } from 'react'
import { useIntl } from 'react-intl'
import { useRouter } from 'next/router'
-import { Box, toast } from '@island.is/island-ui/core'
+import { Box } from '@island.is/island-ui/core'
import * as constants from '@island.is/judicial-system/consts'
-import { core, errors, titles } from '@island.is/judicial-system-web/messages'
+import { core, titles } from '@island.is/judicial-system-web/messages'
import {
CourtCaseInfo,
FormContentContainer,
@@ -14,15 +14,12 @@ import {
IndictmentsLawsBrokenAccordionItem,
InfoCardActiveIndictment,
InfoCardCaseScheduledIndictment,
- InfoCardClosedIndictment,
- Modal,
PageHeader,
PageLayout,
PageTitle,
useIndictmentsLawsBroken,
} from '@island.is/judicial-system-web/src/components'
import { CaseState } from '@island.is/judicial-system-web/src/graphql/schema'
-import { useDefendants } from '@island.is/judicial-system-web/src/utils/hooks'
import ReturnIndictmentModal from '../ReturnIndictmentCaseModal/ReturnIndictmentCaseModal'
import { strings } from './Overview.strings'
@@ -33,14 +30,10 @@ const IndictmentOverview = () => {
useContext(FormContext)
const { formatMessage } = useIntl()
const lawsBroken = useIndictmentsLawsBroken(workingCase)
- const { updateDefendant } = useDefendants()
- const [modalVisible, setModalVisible] = useState<
- 'RETURN_INDICTMENT' | 'SEND_TO_PUBLIC_PROSECUTOR'
- >()
+ const [modalVisible, setModalVisible] = useState<'RETURN_INDICTMENT'>()
const caseHasBeenReceivedByCourt = workingCase.state === CaseState.RECEIVED
const latestDate = workingCase.courtDate ?? workingCase.arraignmentDate
- const caseIsClosed = workingCase.state === CaseState.COMPLETED
const handleNavigationTo = useCallback(
(destination: string) => router.push(`${destination}/${workingCase.id}`),
@@ -55,21 +48,9 @@ const IndictmentOverview = () => {
isValid={true}
onNavigationTo={handleNavigationTo}
>
-
+
-
- {caseIsClosed
- ? formatMessage(strings.completedTitle)
- : formatMessage(strings.inProgressTitle)}
-
+ {formatMessage(strings.inProgressTitle)}
{caseHasBeenReceivedByCourt && workingCase.court && latestDate?.date && (
@@ -84,11 +65,7 @@ const IndictmentOverview = () => {
)}
- {caseIsClosed ? (
-
- ) : (
-
- )}
+
{lawsBroken.size > 0 && (
@@ -96,66 +73,28 @@ const IndictmentOverview = () => {
)}
{workingCase.caseFiles && (
-
+
)}
- {caseIsClosed && (
-
- {
- const promises = workingCase.defendants
- ? workingCase.defendants.map(async (defendant) => {
- const updatedDefendant = await updateDefendant({
- caseId: workingCase.id,
- defendantId: defendant.id,
- serviceRequirement: defendant.serviceRequirement,
- })
-
- return updatedDefendant
- })
- : []
-
- const allDefendantsUpdated = await Promise.all(promises)
-
- if (allDefendantsUpdated.length > 0) {
- setModalVisible('SEND_TO_PUBLIC_PROSECUTOR')
- } else {
- toast.error(formatMessage(errors.updateDefendant))
- }
- }}
- nextButtonText={formatMessage(
- strings.sendToPublicProsecutorModalNextButtonText,
- )}
- nextIsDisabled={workingCase.defendants?.some(
- (defendant) => !defendant.serviceRequirement,
- )}
- />
-
- )}
- {!caseIsClosed && (
-
-
- handleNavigationTo(
- constants.INDICTMENTS_RECEPTION_AND_ASSIGNMENT_ROUTE,
- )
- }
- nextButtonText={formatMessage(core.continue)}
- actionButtonText={formatMessage(strings.returnIndictmentButtonText)}
- actionButtonColorScheme={'destructive'}
- actionButtonIsDisabled={!workingCase.courtCaseNumber}
- onActionButtonClick={() => setModalVisible('RETURN_INDICTMENT')}
- />
-
- )}
+
+
+ handleNavigationTo(
+ constants.INDICTMENTS_RECEPTION_AND_ASSIGNMENT_ROUTE,
+ )
+ }
+ nextButtonText={formatMessage(core.continue)}
+ actionButtonText={formatMessage(strings.returnIndictmentButtonText)}
+ actionButtonColorScheme={'destructive'}
+ actionButtonIsDisabled={!caseHasBeenReceivedByCourt}
+ onActionButtonClick={() => setModalVisible('RETURN_INDICTMENT')}
+ />
+
{modalVisible === 'RETURN_INDICTMENT' && (
{
onComplete={() => router.push(constants.CASES_ROUTE)}
/>
)}
- {modalVisible === 'SEND_TO_PUBLIC_PROSECUTOR' && (
- setModalVisible(undefined)}
- />
- )}
)
}
diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts
index fc389fc02139..8bfd7f9404ce 100644
--- a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts
+++ b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts
@@ -40,9 +40,8 @@ export const strings = defineMessages({
description: 'Notaður sem titill á staðfestingarglugga um að mál sé lokið.',
},
completedCaseModalBody: {
- id: 'judicial.system.core:indictments.summary.completed_case_modal_body',
- defaultMessage:
- 'Gögn hafa verið send á ákæranda og verjanda hafi þeim verið hlaðið upp.',
+ id: 'judicial.system.core:indictments.summary.completed_case_modal_body_v2',
+ defaultMessage: 'Gögn hafa verið send ákæranda og verjanda.',
description: 'Notaður sem texti í staðfestingarglugga um að mál sé lokið.',
},
})
diff --git a/apps/judicial-system/web/src/routes/CourtOfAppeal/Cases/appealdCases.graphql b/apps/judicial-system/web/src/routes/CourtOfAppeal/Cases/appealdCases.graphql
index 8d869557e9a0..06656feca08e 100644
--- a/apps/judicial-system/web/src/routes/CourtOfAppeal/Cases/appealdCases.graphql
+++ b/apps/judicial-system/web/src/routes/CourtOfAppeal/Cases/appealdCases.graphql
@@ -25,7 +25,7 @@ query AppealedCases($input: CaseListQueryInput) {
nationalId
name
noNationalId
- defendantWaivesRightToCounsel
+ defenderChoice
}
appealedDate
initialRulingDate
diff --git a/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseOverviewHeader/CaseOverviewHeader.tsx b/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseOverviewHeader/CaseOverviewHeader.tsx
index fa35c4f32bb1..194f9abe61db 100644
--- a/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseOverviewHeader/CaseOverviewHeader.tsx
+++ b/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseOverviewHeader/CaseOverviewHeader.tsx
@@ -87,8 +87,8 @@ const CaseOverviewHeader: React.FC = (props) => {
)}
{workingCase.appealRulingDecision &&
- workingCase.eventLogs &&
- workingCase.eventLogs.length > 0 && (
+ filteredEvents &&
+ filteredEvents.length > 0 && (
{filteredEvents?.map((event, index) => (
diff --git a/apps/judicial-system/web/src/routes/Defender/CaseOverview.tsx b/apps/judicial-system/web/src/routes/Defender/CaseOverview.tsx
index 3752308f7c7e..4fcc37593d60 100644
--- a/apps/judicial-system/web/src/routes/Defender/CaseOverview.tsx
+++ b/apps/judicial-system/web/src/routes/Defender/CaseOverview.tsx
@@ -364,7 +364,9 @@ export const CaseOverview: React.FC> = () => {
strings.confirmAppealAfterDeadlineModalSecondaryButtonText,
)}
onPrimaryButtonClick={() => {
- router.push(`${constants.APPEAL_ROUTE}/${workingCase.id}`)
+ router.push(
+ `${constants.DEFENDER_APPEAL_ROUTE}/${workingCase.id}`,
+ )
}}
onSecondaryButtonClick={() => {
setModalVisible('NoModal')
diff --git a/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx b/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx
index 0e6e2b411cf6..3e44b9026c9c 100644
--- a/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx
+++ b/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx
@@ -158,6 +158,7 @@ export const DefenderCasesTable: React.FC> = (
caseType={column.type}
isValidToDateInThePast={column.isValidToDateInThePast}
courtDate={column.courtDate}
+ indictmentRulingDecision={column.indictmentRulingDecision}
/>
{column.appealState && (
diff --git a/apps/judicial-system/web/src/routes/Defender/Cases/defenderCases.graphql b/apps/judicial-system/web/src/routes/Defender/Cases/defenderCases.graphql
index 230b576c0c32..df7b8ee18f12 100644
--- a/apps/judicial-system/web/src/routes/Defender/Cases/defenderCases.graphql
+++ b/apps/judicial-system/web/src/routes/Defender/Cases/defenderCases.graphql
@@ -22,10 +22,11 @@ query DefenderCases($input: CaseListQueryInput) {
nationalId
name
noNationalId
- defendantWaivesRightToCounsel
+ defenderChoice
}
initialRulingDate
rulingDate
postponedIndefinitelyExplanation
+ indictmentRulingDecision
}
}
diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFiles/CaseFiles.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFiles/CaseFiles.tsx
index e0fd7ef80491..9e1593cde7a5 100644
--- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFiles/CaseFiles.tsx
+++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFiles/CaseFiles.tsx
@@ -5,6 +5,7 @@ import router from 'next/router'
import { Box, InputFileUpload, Text } from '@island.is/island-ui/core'
import { fileExtensionWhitelist } from '@island.is/island-ui/core/types'
import * as constants from '@island.is/judicial-system/consts'
+import { isTrafficViolationCase } from '@island.is/judicial-system/types'
import { titles } from '@island.is/judicial-system-web/messages'
import {
FormContentContainer,
@@ -21,7 +22,6 @@ import {
useS3Upload,
useUploadFiles,
} from '@island.is/judicial-system-web/src/utils/hooks'
-import { isTrafficViolationIndictment } from '@island.is/judicial-system-web/src/utils/stepHelper'
import * as strings from './CaseFiles.strings'
@@ -40,7 +40,7 @@ const CaseFiles: React.FC> = () => {
workingCase.id,
)
- const isTrafficViolationCaseCheck = isTrafficViolationIndictment(workingCase)
+ const isTrafficViolationCaseCheck = isTrafficViolationCase(workingCase)
const stepIsValid =
uploadFiles.some(
diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx
index 52911b704d7c..21f06fa7cff9 100644
--- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx
+++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx
@@ -4,6 +4,7 @@ import { useRouter } from 'next/router'
import { Box, RadioButton, Text } from '@island.is/island-ui/core'
import * as constants from '@island.is/judicial-system/consts'
+import { isTrafficViolationCase } from '@island.is/judicial-system/types'
import { titles } from '@island.is/judicial-system-web/messages'
import {
BlueBox,
@@ -27,7 +28,6 @@ import {
useCase,
useDefendants,
} from '@island.is/judicial-system-web/src/utils/hooks'
-import { isTrafficViolationIndictment } from '@island.is/judicial-system-web/src/utils/stepHelper'
import { isProcessingStepValidIndictments } from '@island.is/judicial-system-web/src/utils/validate'
import { ProsecutorSection, SelectCourt } from '../../components'
@@ -41,7 +41,7 @@ const Processing: React.FC = () => {
const { formatMessage } = useIntl()
const { updateDefendant, updateDefendantState } = useDefendants()
const router = useRouter()
- const isTrafficViolationCaseCheck = isTrafficViolationIndictment(workingCase)
+ const isTrafficViolationCaseCheck = isTrafficViolationCase(workingCase)
const handleNavigationTo = useCallback(
async (destination: string) => {
diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesForReview.tsx b/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesForReview.tsx
index 197b5222d399..ac8a40e59f23 100644
--- a/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesForReview.tsx
+++ b/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesForReview.tsx
@@ -80,6 +80,7 @@ const CasesForReview: React.FC = ({
mapIndictmentCaseStateToTagVariant
}
indictmentReviewer={row.indictmentReviewer}
+ indictmentRulingDecision={row.indictmentRulingDecision}
/>
),
},
diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx
index 1c52098aa36d..7f2a9d6253e7 100644
--- a/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx
+++ b/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx
@@ -16,6 +16,7 @@ import {
import {
isDistrictCourtUser,
isProsecutionUser,
+ isRequestCase,
} from '@island.is/judicial-system/types'
import { core, tables } from '@island.is/judicial-system-web/messages'
import {
@@ -326,6 +327,7 @@ const ActiveCases: React.FC> = (props) => {
isCourtRole={isDistrictCourtUser(user)}
isValidToDateInThePast={c.isValidToDateInThePast}
courtDate={c.courtDate}
+ indictmentRulingDecision={c.indictmentRulingDecision}
/>
{c.appealState && (
@@ -381,7 +383,10 @@ const ActiveCases: React.FC> = (props) => {
onClick: () => handleOpenCase(c.id, true),
icon: 'open',
},
- ...(isProsecutionUser(user)
+ ...(isProsecutionUser(user) &&
+ (isRequestCase(c.type) ||
+ c.state === CaseState.DRAFT ||
+ c.state === CaseState.WAITING_FOR_CONFIRMATION)
? [
{
title: formatMessage(
diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/MobileCase.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/MobileCase.tsx
index fe5104b2d64d..7ebc39545481 100644
--- a/apps/judicial-system/web/src/routes/Shared/Cases/MobileCase.tsx
+++ b/apps/judicial-system/web/src/routes/Shared/Cases/MobileCase.tsx
@@ -78,6 +78,7 @@ const MobileCase: React.FC> = ({
isCourtRole={isCourtRole}
isValidToDateInThePast={theCase.isValidToDateInThePast}
courtDate={theCase.courtDate}
+ indictmentRulingDecision={theCase.indictmentRulingDecision}
/>,
]}
isLoading={isLoading}
diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql b/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql
index 2fd62e99ff51..0fe071daaaa2 100644
--- a/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql
+++ b/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql
@@ -25,7 +25,7 @@ query Cases {
nationalId
name
noNationalId
- defendantWaivesRightToCounsel
+ defenderChoice
verdictViewDate
}
courtDate
@@ -92,5 +92,6 @@ query Cases {
indictmentAppealDeadline
indictmentVerdictViewedByAll
indictmentVerdictAppealDeadline
+ indictmentRulingDecision
}
}
diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/prisonCases.graphql b/apps/judicial-system/web/src/routes/Shared/Cases/prisonCases.graphql
index 7466a685cf10..a7f734cc0b13 100644
--- a/apps/judicial-system/web/src/routes/Shared/Cases/prisonCases.graphql
+++ b/apps/judicial-system/web/src/routes/Shared/Cases/prisonCases.graphql
@@ -25,7 +25,7 @@ query PrisonCases {
nationalId
name
noNationalId
- defendantWaivesRightToCounsel
+ defenderChoice
}
courtDate
isValidToDateInThePast
@@ -81,5 +81,6 @@ query PrisonCases {
active
}
postponedIndefinitelyExplanation
+ indictmentRulingDecision
}
}
diff --git a/apps/judicial-system/web/src/utils/formHelper.ts b/apps/judicial-system/web/src/utils/formHelper.ts
index bc47dc522288..8cc151880683 100644
--- a/apps/judicial-system/web/src/utils/formHelper.ts
+++ b/apps/judicial-system/web/src/utils/formHelper.ts
@@ -190,7 +190,7 @@ export type stepValidationsType = {
) => boolean
[constants.INDICTMENTS_SUBPOENA_ROUTE]: (theCase: Case) => boolean
[constants.INDICTMENTS_DEFENDER_ROUTE]: (theCase: Case) => boolean
- [constants.INDICTMENTS_CONCLUSION_ROUTE]: () => boolean
+ [constants.INDICTMENTS_CONCLUSION_ROUTE]: (theCase: Case) => boolean
[constants.INDICTMENTS_COURT_OVERVIEW_ROUTE]: () => boolean
[constants.INDICTMENTS_SUMMARY_ROUTE]: () => boolean
[constants.COURT_OF_APPEAL_OVERVIEW_ROUTE]: () => boolean
@@ -279,7 +279,8 @@ export const stepValidations = (): stepValidationsType => {
validations.isSubpoenaStepValid(theCase),
[constants.INDICTMENTS_DEFENDER_ROUTE]: (theCase: Case) =>
validations.isDefenderStepValid(theCase),
- [constants.INDICTMENTS_CONCLUSION_ROUTE]: () => true,
+ [constants.INDICTMENTS_CONCLUSION_ROUTE]: (theCase: Case) =>
+ validations.isConclusionStepValid(theCase),
[constants.INDICTMENTS_COURT_OVERVIEW_ROUTE]: () => true,
[constants.COURT_OF_APPEAL_OVERVIEW_ROUTE]: () => true,
[constants.COURT_OF_APPEAL_CASE_ROUTE]: (theCase: Case) =>
@@ -299,15 +300,12 @@ export const findFirstInvalidStep = (steps: string[], theCase: Case) => {
steps.includes(key),
)
- if (
- stepsToCheck.every(([, validationFn]) => validationFn(theCase) === true)
- ) {
+ if (stepsToCheck.every(([, validationFn]) => validationFn(theCase))) {
return steps[steps.length - 1]
}
const [key] =
- stepsToCheck.find(([, validationFn]) => validationFn(theCase) === false) ||
- []
+ stepsToCheck.find(([, validationFn]) => !validationFn(theCase)) ?? []
return key
}
diff --git a/apps/judicial-system/web/src/utils/hooks/useCase/index.ts b/apps/judicial-system/web/src/utils/hooks/useCase/index.ts
index 8a2d6e6e4ab0..55cea551369b 100644
--- a/apps/judicial-system/web/src/utils/hooks/useCase/index.ts
+++ b/apps/judicial-system/web/src/utils/hooks/useCase/index.ts
@@ -109,10 +109,7 @@ export const update = (update: UpdateCase, workingCase: Case): UpdateCase => {
return validUpdates
}
-export const formatUpdates = (
- updates: Array,
- workingCase: Case,
-) => {
+export const formatUpdates = (updates: UpdateCase[], workingCase: Case) => {
const changes: UpdateCase[] = updates.map((entry) => {
if (entry.force) {
return overwrite(entry)
diff --git a/apps/judicial-system/web/src/utils/hooks/useCase/limitedAccessUpdateCase.graphql b/apps/judicial-system/web/src/utils/hooks/useCase/limitedAccessUpdateCase.graphql
index ac23d6071e46..8b63cf9881b4 100644
--- a/apps/judicial-system/web/src/utils/hooks/useCase/limitedAccessUpdateCase.graphql
+++ b/apps/judicial-system/web/src/utils/hooks/useCase/limitedAccessUpdateCase.graphql
@@ -27,7 +27,7 @@ mutation LimitedAccessUpdateCase($input: UpdateCaseInput!) {
defenderNationalId
defenderEmail
defenderPhoneNumber
- defendantWaivesRightToCounsel
+ defenderChoice
}
defenderName
defenderNationalId
diff --git a/apps/judicial-system/web/src/utils/hooks/useCase/updateCase.graphql b/apps/judicial-system/web/src/utils/hooks/useCase/updateCase.graphql
index f9f3a850d7d6..5d88783a029f 100644
--- a/apps/judicial-system/web/src/utils/hooks/useCase/updateCase.graphql
+++ b/apps/judicial-system/web/src/utils/hooks/useCase/updateCase.graphql
@@ -20,7 +20,7 @@ mutation UpdateCase($input: UpdateCaseInput!) {
defenderNationalId
defenderEmail
defenderPhoneNumber
- defendantWaivesRightToCounsel
+ defenderChoice
defendantPlea
}
defenderName
diff --git a/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx b/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx
index 6b8899dcae03..edd04e4b7511 100644
--- a/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx
+++ b/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx
@@ -14,10 +14,11 @@ import {
isCourtOfAppealsUser,
isDefenceUser,
isDistrictCourtUser,
- isIndictmentCase,
isInvestigationCase,
isPublicProsecutorUser,
+ isRequestCase,
isRestrictionCase,
+ isTrafficViolationCase,
} from '@island.is/judicial-system/types'
import { errors } from '@island.is/judicial-system-web/messages'
import { UserContext } from '@island.is/judicial-system-web/src/components'
@@ -30,7 +31,6 @@ import {
import { TempCase as Case } from '@island.is/judicial-system-web/src/types'
import { findFirstInvalidStep } from '../../formHelper'
-import { isTrafficViolationIndictment } from '../../stepHelper'
import useCase from '../useCase'
const useCaseList = () => {
@@ -74,60 +74,81 @@ const useCaseList = () => {
const openCase = (caseToOpen: Case, user: User) => {
let routeTo = null
- const isTrafficViolation = isTrafficViolationIndictment(caseToOpen)
+ const isTrafficViolation = isTrafficViolationCase(caseToOpen)
if (isDefenceUser(user)) {
- if (isIndictmentCase(caseToOpen.type)) {
- routeTo = DEFENDER_INDICTMENT_ROUTE
- } else {
+ if (isRequestCase(caseToOpen.type)) {
routeTo = DEFENDER_ROUTE
+ } else {
+ routeTo = DEFENDER_INDICTMENT_ROUTE
}
} else if (isPublicProsecutorUser(user)) {
+ // Public prosecutor users can only see completed indictments
routeTo = constants.PUBLIC_PROSECUTOR_STAFF_INDICTMENT_OVERVIEW_ROUTE
- } else if (isCompletedCase(caseToOpen.state)) {
- if (isIndictmentCase(caseToOpen.type)) {
- routeTo = constants.CLOSED_INDICTMENT_OVERVIEW_ROUTE
- } else if (isCourtOfAppealsUser(user)) {
- if (caseToOpen.appealState === CaseAppealState.COMPLETED) {
- routeTo = constants.COURT_OF_APPEAL_RESULT_ROUTE
- } else {
- routeTo = constants.COURT_OF_APPEAL_OVERVIEW_ROUTE
- }
+ } else if (isCourtOfAppealsUser(user)) {
+ // Court of appeals users can only see appealed request cases
+ if (caseToOpen.appealState === CaseAppealState.COMPLETED) {
+ routeTo = constants.COURT_OF_APPEAL_RESULT_ROUTE
} else {
- routeTo = constants.SIGNED_VERDICT_OVERVIEW_ROUTE
+ routeTo = constants.COURT_OF_APPEAL_OVERVIEW_ROUTE
}
} else if (isDistrictCourtUser(user)) {
if (isRestrictionCase(caseToOpen.type)) {
- routeTo = findFirstInvalidStep(
- constants.courtRestrictionCasesRoutes,
- caseToOpen,
- )
+ if (isCompletedCase(caseToOpen.state)) {
+ routeTo = constants.SIGNED_VERDICT_OVERVIEW_ROUTE
+ } else {
+ routeTo = findFirstInvalidStep(
+ constants.courtRestrictionCasesRoutes,
+ caseToOpen,
+ )
+ }
} else if (isInvestigationCase(caseToOpen.type)) {
- routeTo = findFirstInvalidStep(
- constants.courtInvestigationCasesRoutes,
- caseToOpen,
- )
+ if (isCompletedCase(caseToOpen.state)) {
+ routeTo = constants.SIGNED_VERDICT_OVERVIEW_ROUTE
+ } else {
+ routeTo = findFirstInvalidStep(
+ constants.courtInvestigationCasesRoutes,
+ caseToOpen,
+ )
+ }
} else {
- // Route to Indictment Overview section since it always a valid step and
- // would be skipped if we route to the last valid step
- routeTo = constants.INDICTMENTS_COURT_OVERVIEW_ROUTE
+ if (isCompletedCase(caseToOpen.state)) {
+ routeTo = constants.INDICTMENTS_COMPLETED_ROUTE
+ } else {
+ // Route to Indictment Overview section since it always a valid step and
+ // would be skipped if we route to the last valid step
+ routeTo = constants.INDICTMENTS_COURT_OVERVIEW_ROUTE
+ }
}
} else {
+ // The user is a prosecution user
if (isRestrictionCase(caseToOpen.type)) {
- routeTo = findFirstInvalidStep(
- constants.prosecutorRestrictionCasesRoutes,
- caseToOpen,
- )
+ if (isCompletedCase(caseToOpen.state)) {
+ routeTo = constants.SIGNED_VERDICT_OVERVIEW_ROUTE
+ } else {
+ routeTo = findFirstInvalidStep(
+ constants.prosecutorRestrictionCasesRoutes,
+ caseToOpen,
+ )
+ }
} else if (isInvestigationCase(caseToOpen.type)) {
- routeTo = findFirstInvalidStep(
- constants.prosecutorInvestigationCasesRoutes,
- caseToOpen,
- )
+ if (isCompletedCase(caseToOpen.state)) {
+ routeTo = constants.SIGNED_VERDICT_OVERVIEW_ROUTE
+ } else {
+ routeTo = findFirstInvalidStep(
+ constants.prosecutorInvestigationCasesRoutes,
+ caseToOpen,
+ )
+ }
} else {
- routeTo = findFirstInvalidStep(
- constants.prosecutorIndictmentRoutes(isTrafficViolation),
- caseToOpen,
- )
+ if (isCompletedCase(caseToOpen.state)) {
+ routeTo = constants.CLOSED_INDICTMENT_OVERVIEW_ROUTE
+ } else {
+ routeTo = findFirstInvalidStep(
+ constants.prosecutorIndictmentRoutes(isTrafficViolation),
+ caseToOpen,
+ )
+ }
}
}
@@ -162,6 +183,7 @@ const useCaseList = () => {
? getLimitedAccessCase({ variables: { input: { id } } })
: getCase({ variables: { input: { id } } })
}
+
if (
isTransitioningCase ||
isSendingNotification ||
diff --git a/apps/judicial-system/web/src/utils/hooks/useDeb/index.tsx b/apps/judicial-system/web/src/utils/hooks/useDeb/index.tsx
index 0e926219eea2..47a2352c4878 100644
--- a/apps/judicial-system/web/src/utils/hooks/useDeb/index.tsx
+++ b/apps/judicial-system/web/src/utils/hooks/useDeb/index.tsx
@@ -4,7 +4,7 @@ import { TempCase as Case } from '@island.is/judicial-system-web/src/types'
import useCase from '../useCase'
-const useDeb = (workingCase: Case, keys: Array | keyof Case) => {
+const useDeb = (workingCase: Case, keys: (keyof Case)[] | keyof Case) => {
const { updateCase } = useCase()
const newKeys = Array.isArray(keys) ? keys : [keys]
diff --git a/apps/judicial-system/web/src/utils/hooks/useS3Upload/createPresignedPost.graphql b/apps/judicial-system/web/src/utils/hooks/useS3Upload/createPresignedPost.graphql
index 7bb7c7702cea..4f3ded8599bc 100644
--- a/apps/judicial-system/web/src/utils/hooks/useS3Upload/createPresignedPost.graphql
+++ b/apps/judicial-system/web/src/utils/hooks/useS3Upload/createPresignedPost.graphql
@@ -2,5 +2,6 @@ mutation CreatePresignedPost($input: CreatePresignedPostInput!) {
createPresignedPost(input: $input) {
url
fields
+ key
}
}
diff --git a/apps/judicial-system/web/src/utils/hooks/useS3Upload/limitedAccessCreatePresignedPost.graphql b/apps/judicial-system/web/src/utils/hooks/useS3Upload/limitedAccessCreatePresignedPost.graphql
index d578f36b3aaa..b334cc70d5ac 100644
--- a/apps/judicial-system/web/src/utils/hooks/useS3Upload/limitedAccessCreatePresignedPost.graphql
+++ b/apps/judicial-system/web/src/utils/hooks/useS3Upload/limitedAccessCreatePresignedPost.graphql
@@ -2,5 +2,6 @@ mutation LimitedAccessCreatePresignedPost($input: CreatePresignedPostInput!) {
limitedAccessCreatePresignedPost(input: $input) {
url
fields
+ key
}
}
diff --git a/apps/judicial-system/web/src/utils/hooks/useS3Upload/useS3Upload.ts b/apps/judicial-system/web/src/utils/hooks/useS3Upload/useS3Upload.ts
index 572bc62bd1b8..9580d95bfe8a 100644
--- a/apps/judicial-system/web/src/utils/hooks/useS3Upload/useS3Upload.ts
+++ b/apps/judicial-system/web/src/utils/hooks/useS3Upload/useS3Upload.ts
@@ -267,7 +267,7 @@ const useS3Upload = (caseId: string) => {
return presignedPost
}
- const promises = files.map(async (file, idx) => {
+ const promises = files.map(async (file) => {
try {
updateFile({ ...file, status: 'uploading' })
@@ -279,13 +279,13 @@ const useS3Upload = (caseId: string) => {
const newFileId = await addFileToCaseState({
...file,
- key: presignedPost.fields.key,
+ key: presignedPost.key,
})
updateFile(
{
...file,
- key: presignedPost.fields.key,
+ key: presignedPost.key,
percent: 100,
status: 'done',
},
diff --git a/apps/judicial-system/web/src/utils/hooks/useSections/index.ts b/apps/judicial-system/web/src/utils/hooks/useSections/index.ts
index 7ee6ca835c9c..8d0c0f1363ff 100644
--- a/apps/judicial-system/web/src/utils/hooks/useSections/index.ts
+++ b/apps/judicial-system/web/src/utils/hooks/useSections/index.ts
@@ -15,6 +15,7 @@ import {
isInvestigationCase,
isProsecutionUser,
isRestrictionCase,
+ isTrafficViolationCase,
} from '@island.is/judicial-system/types'
import { core, sections } from '@island.is/judicial-system-web/messages'
import { RouteSection } from '@island.is/judicial-system-web/src/components/PageLayout/PageLayout'
@@ -30,10 +31,7 @@ import {
import { TempCase as Case } from '@island.is/judicial-system-web/src/types'
import { stepValidations, stepValidationsType } from '../../formHelper'
-import {
- isTrafficViolationIndictment,
- shouldUseAppealWithdrawnRoutes,
-} from '../../stepHelper'
+import { shouldUseAppealWithdrawnRoutes } from '../../stepHelper'
const validateFormStepper = (
isActiveSubSectionValid: boolean,
@@ -401,7 +399,7 @@ const useSections = (
const { id, type, state } = workingCase
const caseHasBeenReceivedByCourt =
state === CaseState.RECEIVED || state === CaseState.MAIN_HEARING
- const isTrafficViolation = isTrafficViolationIndictment(workingCase)
+ const isTrafficViolation = isTrafficViolationCase(workingCase)
return {
name: formatMessage(sections.indictmentCaseProsecutorSection.title),
diff --git a/apps/judicial-system/web/src/utils/mocks.ts b/apps/judicial-system/web/src/utils/mocks.ts
index 5a1e4d803f32..83b75c15eb91 100644
--- a/apps/judicial-system/web/src/utils/mocks.ts
+++ b/apps/judicial-system/web/src/utils/mocks.ts
@@ -167,7 +167,7 @@ export const mockCase = (caseType: CaseType): Case => {
name: 'Donald Duck',
gender: Gender.MALE,
address: 'Batcave 1337',
- defendantWaivesRightToCounsel: false,
+ defenderChoice: null,
},
],
defendantWaivesRightToCounsel: false,
diff --git a/apps/judicial-system/web/src/utils/stepHelper.ts b/apps/judicial-system/web/src/utils/stepHelper.ts
index 50f0a8bb8abe..e410eac190bb 100644
--- a/apps/judicial-system/web/src/utils/stepHelper.ts
+++ b/apps/judicial-system/web/src/utils/stepHelper.ts
@@ -3,12 +3,9 @@ import parseISO from 'date-fns/parseISO'
import { TagVariant } from '@island.is/island-ui/core'
import { formatDate } from '@island.is/judicial-system/formatters'
-import { isTrafficViolationCase } from '@island.is/judicial-system/types'
import {
CaseAppealState,
CaseCustodyRestrictions,
- CaseFileCategory,
- CaseType,
DefendantPlea,
Gender,
Notification,
@@ -89,23 +86,6 @@ export const createCaseResentExplanation = (
}Krafa endursend ${formatDate(now, 'PPPp')} - ${explanation}`
}
-export const isTrafficViolationIndictment = (workingCase: Case): boolean => {
- const isTrafficViolation = isTrafficViolationCase(
- workingCase.indictmentSubtypes,
- workingCase.type as CaseType,
- )
-
- return Boolean(
- isTrafficViolation &&
- !(
- workingCase.caseFiles &&
- workingCase.caseFiles.find(
- (file) => file.category === CaseFileCategory.INDICTMENT,
- )
- ),
- )
-}
-
export const hasSentNotification = (
notificationType: NotificationType,
notifications?: Notification[] | null,
diff --git a/apps/judicial-system/web/src/utils/validate.ts b/apps/judicial-system/web/src/utils/validate.ts
index 13e371364c45..8cba7111952a 100644
--- a/apps/judicial-system/web/src/utils/validate.ts
+++ b/apps/judicial-system/web/src/utils/validate.ts
@@ -1,6 +1,7 @@
// TODO: Add tests
import {
isIndictmentCase,
+ isTrafficViolationCase,
prosecutorCanSelectDefenderForInvestigationCase,
} from '@island.is/judicial-system/types'
import {
@@ -8,12 +9,13 @@ import {
CaseAppealState,
CaseFileCategory,
CaseType,
+ DefenderChoice,
SessionArrangements,
User,
} from '@island.is/judicial-system-web/src/graphql/schema'
import { TempCase as Case } from '@island.is/judicial-system-web/src/types'
-import { isBusiness, isTrafficViolationIndictment } from './stepHelper'
+import { isBusiness } from './stepHelper'
export type Validation =
| 'empty'
@@ -421,7 +423,7 @@ export const isDefenderStepValid = (workingCase: Case): boolean => {
const defendantsAreValid = () =>
workingCase.defendants?.every((defendant) => {
return (
- defendant.defendantWaivesRightToCounsel ||
+ defendant.defenderChoice === DefenderChoice.WAIVE ||
validate([
[defendant.defenderName, ['empty']],
[defendant.defenderEmail, ['email-format']],
@@ -433,6 +435,11 @@ export const isDefenderStepValid = (workingCase: Case): boolean => {
return Boolean(workingCase.prosecutor && defendantsAreValid())
}
+export const isConclusionStepValid = (workingCase: Case): boolean => {
+ // TODO: Implement after selected action has been added as a field to the case
+ return true
+}
+
export const isAdminUserFormValid = (user: User): boolean => {
return Boolean(
user.institution &&
@@ -495,7 +502,7 @@ export const isCaseFilesStepValidIndictments = (workingCase: Case): boolean => {
workingCase.caseFiles?.some(
(file) => file.category === CaseFileCategory.COVER_LETTER,
) &&
- (isTrafficViolationIndictment(workingCase) ||
+ (isTrafficViolationCase(workingCase) ||
workingCase.caseFiles?.some(
(file) => file.category === CaseFileCategory.INDICTMENT,
)) &&
diff --git a/apps/native/app/src/graphql/client.ts b/apps/native/app/src/graphql/client.ts
index 096adb246d21..97b98e2d2f51 100644
--- a/apps/native/app/src/graphql/client.ts
+++ b/apps/native/app/src/graphql/client.ts
@@ -157,13 +157,7 @@ const cache = new InMemoryCache({
Query: {
fields: {
userNotifications: {
- merge(existing, incoming) {
- return {
- ...existing,
- ...incoming,
- data: incoming.data || existing.data,
- }
- },
+ merge: true,
},
},
},
diff --git a/apps/native/app/src/screens/notifications/notifications.tsx b/apps/native/app/src/screens/notifications/notifications.tsx
index 208655e1a3cf..f496cab9a3e5 100644
--- a/apps/native/app/src/screens/notifications/notifications.tsx
+++ b/apps/native/app/src/screens/notifications/notifications.tsx
@@ -5,7 +5,7 @@ import {
Skeleton,
Problem,
} from '@ui'
-import { Reference, useApolloClient } from '@apollo/client'
+import { useApolloClient } from '@apollo/client'
import { dismissAllNotificationsAsync } from 'expo-notifications'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
@@ -87,40 +87,22 @@ export const NotificationsScreen: NavigationFunctionComponent = ({
const [markAllUserNotificationsAsRead] =
useMarkAllNotificationsAsReadMutation({
- onCompleted: (data) => {
- if (data.markAllNotificationsRead?.success) {
+ onCompleted: (d) => {
+ if (d.markAllNotificationsRead?.success) {
// If all notifications are marked as read, update cache to reflect that
- client.cache.modify({
- fields: {
- userNotifications(existingNotifications = {}) {
- const existingDataRefs = existingNotifications.data || []
-
- const updatedData = existingDataRefs.forEach(
- (ref: Reference | NotificationItem) => {
- const id = client.cache.identify(ref)
- client.cache.modify({
- id,
- fields: {
- metadata(existingMetadata) {
- return {
- ...existingMetadata,
- read: false,
- }
- },
- },
- })
- return ref
- },
- )
-
- return {
- ...existingNotifications,
- data: updatedData,
- unreadCount: 0,
- }
+ for (const notification of data?.userNotifications?.data || []) {
+ client.cache.modify({
+ id: client.cache.identify(notification),
+ fields: {
+ metadata(existingMetadata) {
+ return {
+ ...existingMetadata,
+ read: true,
+ }
+ },
},
- },
- })
+ })
+ }
}
},
})
@@ -251,6 +233,7 @@ export const NotificationsScreen: NavigationFunctionComponent = ({
title={intl.formatMessage({ id: 'notifications.screenTitle' })}
onClosePress={() => Navigation.dismissModal(componentId)}
style={{ marginHorizontal: 16 }}
+ showLoading={loading && !!data}
/>
@@ -281,7 +264,10 @@ export const NotificationsScreen: NavigationFunctionComponent = ({
style={{
maxWidth: 145,
}}
- iconStyle={{ tintColor: theme.color.blue400 }}
+ iconStyle={{
+ tintColor: theme.color.blue400,
+ resizeMode: 'contain',
+ }}
/>
{showError ? (
diff --git a/apps/native/app/src/screens/settings/settings.tsx b/apps/native/app/src/screens/settings/settings.tsx
index 59473d9c2035..c63bcadacf9d 100644
--- a/apps/native/app/src/screens/settings/settings.tsx
+++ b/apps/native/app/src/screens/settings/settings.tsx
@@ -39,7 +39,6 @@ import { createNavigationOptionHooks } from '../../hooks/create-navigation-optio
import { navigateTo } from '../../lib/deep-linking'
import { showPicker } from '../../lib/show-picker'
import { authStore } from '../../stores/auth-store'
-import { useNotificationsStore } from '../../stores/notifications-store'
import {
preferencesStore,
usePreferencesStore,
diff --git a/apps/native/app/src/ui/lib/button/button.tsx b/apps/native/app/src/ui/lib/button/button.tsx
index 98764be7a21e..db882a0c1a7e 100644
--- a/apps/native/app/src/ui/lib/button/button.tsx
+++ b/apps/native/app/src/ui/lib/button/button.tsx
@@ -92,7 +92,7 @@ const Text = styled.Text<{
const Icon = styled.Image`
width: 16px;
height: 16px;
- margin-left: 10px;
+ margin-left: 8px;
`
export function Button({
diff --git a/apps/service-portal/src/components/DocumentsEmpty/DocumentsEmpty.css.ts b/apps/service-portal/src/components/DocumentsEmpty/DocumentsEmpty.css.ts
new file mode 100644
index 000000000000..3ae765b21300
--- /dev/null
+++ b/apps/service-portal/src/components/DocumentsEmpty/DocumentsEmpty.css.ts
@@ -0,0 +1,13 @@
+import { theme } from '@island.is/island-ui/theme'
+import { style } from '@vanilla-extract/css'
+
+export const lock = style({
+ position: 'absolute',
+ zIndex: 1,
+ top: theme.spacing[2],
+ right: theme.spacing[3],
+})
+
+export const img = style({
+ height: 180,
+})
diff --git a/apps/service-portal/src/components/DocumentsEmpty/DocumentsEmpty.tsx b/apps/service-portal/src/components/DocumentsEmpty/DocumentsEmpty.tsx
new file mode 100644
index 000000000000..516d77d1db4b
--- /dev/null
+++ b/apps/service-portal/src/components/DocumentsEmpty/DocumentsEmpty.tsx
@@ -0,0 +1,59 @@
+import { Box, Icon, Text } from '@island.is/island-ui/core'
+import { useLocale } from '@island.is/localization'
+import { m } from '@island.is/service-portal/core'
+import * as styles from './DocumentsEmpty.css'
+
+interface Props {
+ hasDelegationAccess: boolean
+}
+
+export const DocumentsEmpty = ({ hasDelegationAccess }: Props) => {
+ const { formatMessage } = useLocale()
+
+ return (
+
+
+ {hasDelegationAccess
+ ? formatMessage(m.emptyDocumentsList)
+ : formatMessage(m.accessNeeded)}
+
+ {!hasDelegationAccess && (
+
+ {formatMessage(m.accessDeniedText)}
+
+ )}
+
+ {
+
+ }
+
+ {!hasDelegationAccess && (
+
+ )}
+
+ )
+}
+
+export default DocumentsEmpty
diff --git a/apps/service-portal/src/components/Header/Header.tsx b/apps/service-portal/src/components/Header/Header.tsx
index 5bd6d74d1eab..21c913b99da0 100644
--- a/apps/service-portal/src/components/Header/Header.tsx
+++ b/apps/service-portal/src/components/Header/Header.tsx
@@ -26,6 +26,7 @@ import { useWindowSize } from 'react-use'
import NotificationButton from '../Notifications/NotificationButton'
import Sidemenu from '../Sidemenu/Sidemenu'
import * as styles from './Header.css'
+import { DocumentsScope } from '@island.is/auth/scopes'
export type MenuTypes = 'side' | 'user' | 'notifications' | undefined
interface Props {
@@ -57,6 +58,10 @@ export const Header = ({ position }: Props) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
+ const hasNotificationsDelegationAccess = user?.scopes?.includes(
+ DocumentsScope.main,
+ )
+
return (
@@ -96,7 +101,6 @@ export const Header = ({ position }: Props) => {
flexWrap="nowrap"
marginLeft={[1, 1, 2]}
>
- {user &&
}
{
setMenuOpen(val)}
showMenu={menuOpen === 'notifications'}
+ disabled={!hasNotificationsDelegationAccess}
/>
)}
+ {user && }
+