Skip to content

Commit

Permalink
feat: change workitem state API
Browse files Browse the repository at this point in the history
  • Loading branch information
Chinlinlee committed May 24, 2023
1 parent 3481b62 commit eb6ed91
Show file tree
Hide file tree
Showing 8 changed files with 421 additions and 0 deletions.
57 changes: 57 additions & 0 deletions api/dicom-web/controller/UPS-RS/change-workItem-state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const {
ChangeWorkItemStateService
} = require("./service/change-workItem-state.service");
const { ApiLogger } = require("../../../../utils/logs/api-logger");
const { Controller } = require("../../../controller.class");
const { DicomWebServiceError } = require("@error/dicom-web-service");

class ChangeWorkItemStateController extends Controller {
constructor(req, res) {
super(req, res);
}

async mainProcess() {
let apiLogger = new ApiLogger(this.request, "UPS-RS");

apiLogger.addTokenValue();
apiLogger.logger.info(`Update workItem, params: ${this.paramsToString()}`);

try {
let service = new ChangeWorkItemStateService(this.request, this.response);
let workItems = await service.changeWorkItemState();
return this.response
.set("Content-Type", "application/dicom+json")
.status(200)
.end();
} catch (e) {
let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e));
apiLogger.logger.error(errorStr);

if (e instanceof DicomWebServiceError) {
return this.response.status(e.code).json({
status: e.status,
message: e.message
});
}

this.response.writeHead(500, {
"Content-Type": "application/dicom+json"
});
this.response.end(JSON.stringify({
code: 500,
message: "An Server Exception Occurred"
}));
}
}
}

/**
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
module.exports = async function (req, res) {
let controller = new ChangeWorkItemStateController(req, res);

await controller.doPipeline();
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
const _ = require("lodash");
const moment = require("moment");
const { DicomJsonModel } = require("@models/DICOM/dicom-json-model");
const { DicomCode } = require("@models/DICOM/code");
const workItemModel = require("@models/mongodb/models/workItems");
const {
DicomWebServiceError,
DicomWebStatusCodes
} = require("@error/dicom-web-service");

class ChangeWorkItemStateService {
/**
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
constructor(req, res) {
this.request = req;
this.response = res;
this.requestState = /** @type {Object[]} */(this.request.body).pop();
/** @type {DicomJsonModel} */
this.requestState = new DicomJsonModel(this.requestState);
this.workItem = null;
this.workItemState = "";
}

async changeWorkItemState() {
await this.findOneWorkItem();

this.workItemState = this.workItem.getString("00741000");
let requestState = this.requestState.getString("00741000");

if (requestState === "IN PROGRESS") {
this.inProgressChange();
} else if (requestState === "CANCELED") {
this.cancelChange();
} else if (requestState === "COMPLETED") {
this.completeChange();
}

await workItemModel.findOneAndUpdate({
upsInstanceUID: this.request.params.workItem
}, {
...this.requestState.dicomJson
});
}

async findOneWorkItem() {

let workItem = await workItemModel.findOne({
upsInstanceUID: this.request.params.workItem
});

if (!workItem) {
throw new DicomWebServiceError(
DicomWebStatusCodes.UPSDoesNotExist,
"The UPS instance not exist",
404
);
}

this.workItem = new DicomJsonModel(workItem);

}

inProgressChange() {
if (this.workItemState === "IN PROGRESS") {
throw new DicomWebServiceError(
DicomWebStatusCodes.UPSAlreadyInProgress,
"The request is inconsistent with the current state of the Target Workitem",
409
);
} else if (this.workItemState === "COMPLETED" || this.workItemState === "CANCELED") {
throw new DicomWebServiceError(
DicomWebStatusCodes.UPSMayNoLongerBeUpdated,
"The request is inconsistent with the current state of the Target Workitem",
409
);
}
}

cancelChange() {
if (this.workItemState === "SCHEDULED") {
throw new DicomWebServiceError(
DicomWebStatusCodes.UPSNotYetInProgress,
"The request is inconsistent with the current state of the Target Workitem",
409
);
} else if (this.workItemState === "COMPLETED") {
throw new DicomWebServiceError(
DicomWebStatusCodes.UPSMayNoLongerBeUpdated,
"The request is inconsistent with the current state of the Target Workitem",
409
);
} else if (this.workItemState === "CANCELED") {
this.response.set("Warning", "299 Raccoon: The UPS is already in the requested state of CANCELED.");
}

let transactionUID = this.requestState.getString("00081195");
let workItemTransactionUID = this.requestState.getString("00081195");
if (transactionUID !== workItemTransactionUID) {
throw new DicomWebServiceError(
DicomWebStatusCodes.UPSTransactionUIDNotCorrect,
"Refused: The correct Transaction UID was not provided",
400
);
}
this.supplementDiscontinuationReasonCode();
}

completeChange() {
if (this.workItemState === "SCHEDULED") {
throw new DicomWebServiceError(
DicomWebStatusCodes.UPSNotYetInProgress,
"The request is inconsistent with the current state of the Target Workitem (UPS Not Yet In Progress)",
409
);
} else if (this.workItemState === "CANCELED") {
throw new DicomWebServiceError(
DicomWebStatusCodes.UPSMayNoLongerBeUpdated,
"The request is inconsistent with the current state of the Target Workitem (The CANCELED UPS can not change to COMPLETED)",
409
);
} else if (this.workItemState === "COMPLETED") {
this.response.set("Warning", "299 Raccoon: The UPS is already in the requested state of COMPLETED.");
}

let transactionUID = this.requestState.getString("00081195");
let workItemTransactionUID = this.requestState.getString("00081195");
if (transactionUID !== workItemTransactionUID) {
throw new DicomWebServiceError(
DicomWebStatusCodes.UPSTransactionUIDNotCorrect,
"Refused: The correct Transaction UID was not provided",
400
);
}
if (!this.meetFinalStateRequirementsOfCompleted()) {
throw new DicomWebServiceError(
DicomWebStatusCodes.UPSNotMetFinalStateRequirements,
"The request is inconsistent with the current state of the Target Workitem (The workitem is not meet final state requirements of completed)",
409
);
}
}

ensureProgressInformationSequence() {
let progressInformation = _.get(this.workItem.dicomJson, "00741002.Value");
if (!progressInformation) {
_.set(this.workItem.dicomJson, "00741002", {
vr: "SQ",
Value: []
});
}
}

supplementDiscontinuationReasonCode() {
this.ensureProgressInformationSequence();
let procedureStepCancellationDateTime = _.get(this.workItem.dicomJson, "00741002.Value.0.00404052");
if (!procedureStepCancellationDateTime) {
_.set(this.workItem.dicomJson, "00741002.Value.0.00404052", {
vr: "DT",
Value: [
moment().format("YYYYMMDDhhmmss.SSSSSSZZ")
]
});
}

let reasonCodeMeaning = _.get(this.workItem.dicomJson, "00741002.Value.0.0074100E.Value.0.00080104");
if (!reasonCodeMeaning) {
_.set(this.workItem.dicomJson, "00741002.Value.0.0074100E.Value.0", {
vr: "SQ",
Value: [
{
"00081000": {
"vr": "SH",
"Value": ["110513"]
},
"00080102": {
"vr": "SH",
"Value": ["DCM"]
},
"00080104": {
"vr": "LO",
"Value": ["Discontinued for unspecified reason"]
}
}
]
});
}
}

meetFinalStateRequirementsOfCompleted() {
let performedProcedure = _.get(this.workItem.dicomJson, "00741216");
if (performedProcedure &&
_.get(performedProcedure, "Value.0.00404050") &&
_.get(performedProcedure, "Value.0.00404051")) {

try {
let stationNameCode = new DicomCode(_.get(performedProcedure, "Value.0.00404028.Value.0"));
let workItemCode = new DicomCode(_.get(performedProcedure, "Value.0.00404019.Value.0"));

return true;
} catch(e) {
console.log(`Invalid Dicom Code ${e}`);
}
}
return false;
}
}

module.exports.ChangeWorkItemStateService = ChangeWorkItemStateService;
36 changes: 36 additions & 0 deletions api/dicom-web/ups-rs.route.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ router.get("/workitems",
* description: >
* This transaction retrieves a Workitem. It corresponds to the UPS DIMSE N-GET operation.
* See [Retrieve Workitem Transaction](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_11.5)
* parameters:
* - $ref: "#/components/parameters/workitemUID"
* responses:
* "200":
* description: Query successfully
Expand All @@ -91,6 +93,8 @@ router.get("/workitems/:workItem",
* description: >
* This transaction modifies Attributes of an existing Workitem. It corresponds to the UPS DIMSE N-SET operation.
* See [Update Workitem Transaction](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_11.6)
* parameters:
* - $ref: "#/components/parameters/workitemUID"
* responses:
* "200":
* description: modify successfully
Expand All @@ -99,6 +103,38 @@ router.post("/workitems/:workItem",
require("./controller/UPS-RS/update-workItem")
);

/**
* @openapi
* /dicom-web/workitems/{workitemUID}/state:
* put:
* tags:
* - UPS-RS
* description: >
* This transaction is used to change the state of a Workitem. It corresponds to the UPS DIMSE N-ACTION operation "Change UPS State".<br/>
* State changes are used to claim ownership, complete, or cancel a Workitem.<br/>
* See [Change Workitem State](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_11.7)
* parameters:
* - $ref: "#/components/parameters/workitemUID"
* responses:
* "200":
* description: The update was successful, and the response payload contains a Status Report document.
*/
router.put("/workitems/:workItem/state",
validateByJoi(Joi.array().items(
Joi.object({
"00741000": Joi.object({
vr: Joi.string().valid("CS"),
Value: Joi.array().items(Joi.string().valid("IN PROGRESS", "COMPLETED", "CANCELED")).min(1).max(1)
}),
"00081195": Joi.object({
vr: Joi.string().valid("UI"),
Value: Joi.array().items(Joi.string()).min(1).max(1)
})
})
).min(1).max(1), "body"),
require("./controller/UPS-RS/change-workItem-state")
);

//#endregion

module.exports = router;
Loading

0 comments on commit eb6ed91

Please sign in to comment.