diff --git a/packages/distribution/addon/abilities/distribution.js b/packages/distribution/addon/abilities/distribution.js index d2e4d36b6..9525845ee 100644 --- a/packages/distribution/addon/abilities/distribution.js +++ b/packages/distribution/addon/abilities/distribution.js @@ -38,4 +38,13 @@ export default class DistributionAbility extends Ability { ).length > 0 ); } + + get canReopen() { + return ( + !this.config.ui.readonly && + (this.config.permissions.reopenDistribution?.() ?? true) && + this.distribution.controls.value?.case.edges[0]?.node.parentWorkItem + .isRedoable + ); + } } diff --git a/packages/distribution/addon/components/cd-navigation/controls.hbs b/packages/distribution/addon/components/cd-navigation/controls.hbs index dcc96f225..bd31a4b65 100644 --- a/packages/distribution/addon/components/cd-navigation/controls.hbs +++ b/packages/distribution/addon/components/cd-navigation/controls.hbs @@ -4,6 +4,7 @@ @route="new" class="uk-icon-button" title={{t "caluma.distribution.new.title"}} + data-test-new-inquiry > @@ -38,4 +39,19 @@ {{/if}} {{/if}} + {{#if (can "reopen distribution")}} + + {{/if}} \ No newline at end of file diff --git a/packages/distribution/addon/components/cd-navigation/controls.js b/packages/distribution/addon/components/cd-navigation/controls.js index 17f181401..44f69194d 100644 --- a/packages/distribution/addon/components/cd-navigation/controls.js +++ b/packages/distribution/addon/components/cd-navigation/controls.js @@ -8,6 +8,7 @@ import { gql } from "graphql-tag"; import { decodeId } from "@projectcaluma/ember-core/helpers/decode-id"; import config from "@projectcaluma/ember-distribution/config"; import completeWorkItemMutation from "@projectcaluma/ember-distribution/gql/mutations/complete-work-item.graphql"; +import redoWorkItemMutation from "@projectcaluma/ember-distribution/gql/mutations/redo-work-item.graphql"; import incompleteInquiriesQuery from "@projectcaluma/ember-distribution/gql/queries/incomplete-inquiries.graphql"; export default class CdNavigationControlsComponent extends Component { @@ -63,6 +64,30 @@ export default class CdNavigationControlsComponent extends Component { } } + @dropTask + *reopenDistribution() { + try { + if (!(yield confirm(this.intl.t("caluma.distribution.reopen-confirm")))) { + return; + } + + const distributionWorkItemId = decodeId( + this.distribution.controls.value?.case.edges[0]?.node.parentWorkItem.id + ); + + yield this.apollo.mutate({ + mutation: redoWorkItemMutation, + variables: { + workItem: distributionWorkItemId, + }, + }); + + yield this.distribution.refetchControls(); + } catch (e) { + this.notification.danger(this.intl.t("caluma.distribution.reopen-error")); + } + } + @dropTask *sendInquiries() { if (!(yield confirm(this.intl.t("caluma.distribution.send-confirm")))) { diff --git a/packages/distribution/addon/gql/mutations/redo-work-item.graphql b/packages/distribution/addon/gql/mutations/redo-work-item.graphql new file mode 100644 index 000000000..d0559a4c7 --- /dev/null +++ b/packages/distribution/addon/gql/mutations/redo-work-item.graphql @@ -0,0 +1,8 @@ +mutation RedoWorkItem($workItem: ID!) { + redoWorkItem(input: { id: $workItem }) { + workItem { + id + isRedoable + } + } +} diff --git a/packages/distribution/addon/gql/queries/control-work-items.graphql b/packages/distribution/addon/gql/queries/control-work-items.graphql index 05b71e1c0..dd0961490 100644 --- a/packages/distribution/addon/gql/queries/control-work-items.graphql +++ b/packages/distribution/addon/gql/queries/control-work-items.graphql @@ -50,4 +50,15 @@ query ControlWorkItems( } } } + case: allCases(filter: [{ id: $caseId }]) { + edges { + node { + id + parentWorkItem { + id + isRedoable + } + } + } + } } diff --git a/packages/distribution/addon/services/distribution.js b/packages/distribution/addon/services/distribution.js index 158558778..782f965f5 100644 --- a/packages/distribution/addon/services/distribution.js +++ b/packages/distribution/addon/services/distribution.js @@ -29,10 +29,18 @@ export default class DistributionService extends Service { navigation = trackedTask(this, this.fetchNavigation, () => [this.caseId]); async refetch() { - await getObservable(this.controls.value)?.refetch(); + await this.refetchControls(); + await this.refetchNavigation(); + } + + async refetchNavigation() { await getObservable(this.navigation.value)?.refetch(); } + async refetchControls() { + await getObservable(this.controls.value)?.refetch(); + } + @dropTask *fetchControls(caseId) { return yield this.apollo.watchQuery({ diff --git a/packages/distribution/tests/integration/components/cd-navigation-test.js b/packages/distribution/tests/integration/components/cd-navigation-test.js index 9c4d156c3..60c75bdec 100644 --- a/packages/distribution/tests/integration/components/cd-navigation-test.js +++ b/packages/distribution/tests/integration/components/cd-navigation-test.js @@ -13,7 +13,7 @@ module("Integration | Component | cd-navigation", function (hooks) { setupIntl(hooks); hooks.beforeEach(function () { - distribution(this.server, [ + const distributionCase = distribution(this.server, [ { id: "group1" }, { id: "group2" }, { id: "group3" }, @@ -21,7 +21,7 @@ module("Integration | Component | cd-navigation", function (hooks) { { id: "group5" }, ]); - this.caseId = this.server.db.cases[0].id; + this.caseId = distributionCase.id; this.owner.lookup("service:caluma-options").currentGroupId = "group1"; this.owner.lookup("service:router").isActive = () => true; diff --git a/packages/distribution/tests/integration/components/cd-navigation/controls-test.js b/packages/distribution/tests/integration/components/cd-navigation/controls-test.js index 8018e6af5..97e1c4989 100644 --- a/packages/distribution/tests/integration/components/cd-navigation/controls-test.js +++ b/packages/distribution/tests/integration/components/cd-navigation/controls-test.js @@ -14,7 +14,7 @@ module("Integration | Component | cd-navigation/controls", function (hooks) { setupIntl(hooks); hooks.beforeEach(function () { - distribution(this.server, [ + const distributionCase = distribution(this.server, [ { id: "group1" }, { id: "group2" }, { id: "group3" }, @@ -22,7 +22,7 @@ module("Integration | Component | cd-navigation/controls", function (hooks) { { id: "group5" }, ]); - this.caseId = this.server.db.cases[0].id; + this.caseId = distributionCase.id; this.owner.lookup("service:caluma-options").currentGroupId = "group1"; Object.defineProperty(this.owner.lookup("service:distribution"), "caseId", { value: this.caseId, @@ -94,4 +94,21 @@ module("Integration | Component | cd-navigation/controls", function (hooks) { ) ); }); + + test("it can redo the current distribution", async function (assert) { + await assert.expect(3); + + await render(hbs``); + + await click("[data-test-complete-distribution]"); + await confirm(); + + assert.dom("[data-test-reopen-distribution]").exists(); + + await click("[data-test-reopen-distribution]"); + await confirm(); + + assert.dom("[data-test-complete-distribution]").exists(); + assert.dom("[data-test-new-inquiry]").exists(); + }); }); diff --git a/packages/distribution/translations/de.yaml b/packages/distribution/translations/de.yaml index e285b0321..bf3c744f5 100644 --- a/packages/distribution/translations/de.yaml +++ b/packages/distribution/translations/de.yaml @@ -4,14 +4,17 @@ caluma: start: "Starten" send: "Offene Anfragen versenden" complete: "Zirkulation abschliessen" + reopen: "Zirkulation wiedereröffnen" send-confirm: "Wollen Sie wirklich alle offenen Anfragen versenden?" complete-confirm: "Es gibt noch {count} offene {count, plural, =1 {Anfrage} other {Anfragen}} in der aktuellen Zirkulation. Wenn Sie die Zirkulation abschliessen, werden alle verbleibenden offenen Anfragen abgebrochen. Möchten Sie fortfahren?" complete-confirm-empty: "Möchten Sie die Zirkulation wirklich abschliessen?" + reopen-confirm: "Wollen Sie die Zirkulation wirklich wiedereröffnen?" send-error: "Fehler beim Versenden der offenen Anfragen" complete-error: "Fehler beim Abschliessen der Zirkulation" + reopen-error: "Fehler beim Wiedereröffnen der Zirkulation" more: "mehr" less: "weniger" diff --git a/packages/distribution/translations/en.yaml b/packages/distribution/translations/en.yaml index 763588fe6..e2900b843 100644 --- a/packages/distribution/translations/en.yaml +++ b/packages/distribution/translations/en.yaml @@ -5,14 +5,17 @@ caluma: send: "Send pending inquiries" complete: "Complete circulation" send-confirm: "Do you really want to send all pending inquiries?" + reopen: "Reopen circulation" complete-confirm: "There {count, plural, =1 {is} other {are}} {count} pending {count, plural, =1 {inquiry} other {inquiries}} on the current distribution. Completing the distribution will cause all remaining pending inquiries to be canceled. Would you like to continue?" complete-confirm-empty: "Do you really want to complete the distribution?" + reopen-confirm: "Do you really want to reopen the distribution?" send-error: "Error while sending pending inquiries" - complete-error: "Error while completing distribution" + complete-error: "Error while completing the distribution" + reopen-error: "Error while reopening the distribution" more: "more" less: "less" diff --git a/packages/distribution/translations/fr.yaml b/packages/distribution/translations/fr.yaml index c03656f4c..ba9949a56 100644 --- a/packages/distribution/translations/fr.yaml +++ b/packages/distribution/translations/fr.yaml @@ -5,13 +5,16 @@ caluma: send: "Envoyer les demandes ouvertes" complete: "Terminer la circulation" send-confirm: "Voulez-vous vraiment envoyer toutes les demandes ouvertes ?" + reopen: "Rouvrir la procédure de circulation" complete-confirm: "Il y a {count} {count, plural, =1 {demande} other {demandes}} en attente sur la distribution actuelle. Si vous terminez la distribution, toutes les demandes en attente seront annulées. Voulez-vous continuer ?" complete-confirm-empty: "Vous voulez vraiment fermer la circulation ?" + reopen-confirm: "Vous voulez vraiment rouvrir la circulation ?" send-error: "Erreur lors de l'envoi des demandes ouvertes" - complete-error: "Erreur lors de la terminaison de la distribution" + complete-error: "Erreur lors de la terminaison de la circulation" + reopen-error: "Erreur lors de la réouverture de la circulation" more: "plus" less: "moins" diff --git a/packages/testing/addon-mirage-support/factories/work-item.js b/packages/testing/addon-mirage-support/factories/work-item.js index 6997cdc89..c9e8db2b1 100644 --- a/packages/testing/addon-mirage-support/factories/work-item.js +++ b/packages/testing/addon-mirage-support/factories/work-item.js @@ -16,4 +16,5 @@ export default Factory.extend({ ? faker.date.past() : null; }, + redoable: () => false, }); diff --git a/packages/testing/addon/mirage-graphql/mocks/work-item.js b/packages/testing/addon/mirage-graphql/mocks/work-item.js index bb86090ee..cbdc0b4ba 100644 --- a/packages/testing/addon/mirage-graphql/mocks/work-item.js +++ b/packages/testing/addon/mirage-graphql/mocks/work-item.js @@ -27,6 +27,27 @@ export default class WorkItemMock extends BaseMock { }); } + @register("RedoWorkItemPayload") + handleRedoWorkItem(_, { input }) { + const { id } = deserialize(input); + const workItem = this.collection.find(id); + + if (workItem.taskId === "distribution") { + const caseId = workItem.childCaseId; + + this.collection + .where({ caseId, taskId: "complete-distribution" }) + .update({ status: "READY" }); + this.collection + .where({ caseId, taskId: "create-inquiry" }) + .update({ status: "READY" }); + } + + return this.handleSavePayload.fn.call(this, _, { + input: { id, redoable: false, status: "READY" }, + }); + } + @register("CompleteWorkItemPayload") handleCompleteWorkItem(_, { input }) { const { id } = deserialize(input); @@ -102,6 +123,10 @@ export default class WorkItemMock extends BaseMock { addressedGroups: workItem.addressedGroups, }); } else if (taskId === "complete-distribution") { + this.collection + .where({ childCaseId: caseId, status: "READY", taskId: "distribution" }) + .update({ status: "COMPLETED", redoable: true }); + this.collection .where({ caseId, status: "READY", taskId: "inquiry" }) .update({ status: "SKIPPED" }); @@ -116,7 +141,7 @@ export default class WorkItemMock extends BaseMock { } return this.handleSavePayload.fn.call(this, _, { - input: { id: input.id, status: "COMPLETED" }, + input: { id, status: "COMPLETED" }, }); } } diff --git a/packages/testing/addon/mirage-graphql/schema.graphql b/packages/testing/addon/mirage-graphql/schema.graphql index cc7fcc899..df380b3cd 100644 --- a/packages/testing/addon/mirage-graphql/schema.graphql +++ b/packages/testing/addon/mirage-graphql/schema.graphql @@ -4048,6 +4048,13 @@ type WorkItem implements Node { first: Int last: Int ): WorkItemConnection! + + """ + This property potentially performs poorly if used in a large setof entries, as + the evaluation of the redoable jexl configurationcannot be performed on the + database level. Please use carefully. + """ + isRedoable: Boolean } type WorkItemConnection { diff --git a/packages/testing/addon/scenarios/distribution.js b/packages/testing/addon/scenarios/distribution.js index e40e5fcf0..6e92f2516 100644 --- a/packages/testing/addon/scenarios/distribution.js +++ b/packages/testing/addon/scenarios/distribution.js @@ -73,6 +73,7 @@ export function createBlueprint(server) { server.create("workflow", { slug: "distribution" }); server.create("workflow", { slug: "inquiry" }); + server.create("task", { slug: "distribution" }); server.create("task", { slug: "create-inquiry" }); server.create("task", { slug: "complete-distribution" }); server.create("task", { slug: "inquiry" }); @@ -225,10 +226,17 @@ export function reviseInquiry(server, { inquiry }) { } export function createCase(server, { group }) { + const distributionWorkItem = server.create("work-item", { + taskId: "distribution", + status: "READY", + case: server.create("case"), + }); + const distributionCase = server.create("case", { id: "4222ab21-9c89-47de-98be-d62a8ed0ebeb", status: "RUNNING", workflowId: "distribution", + parentWorkItem: distributionWorkItem, }); server.create("work-item", { @@ -389,4 +397,6 @@ export default function (server, groups) { status: "inquiry-answer-status-positive", }), }); + + return distributionCase; }