diff --git a/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/SearchComponent.js b/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/SearchComponent.js index 78af5d3c9..dc3233ca6 100644 --- a/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/SearchComponent.js +++ b/forms-flow-web/src/components/FOI/Dashboard/IAO/AdvancedSearch/SearchComponent.js @@ -143,7 +143,8 @@ const AdvancedSearch = ({ userDetail }) => { [StateEnum.review.label]: false, [StateEnum.signoff.label]: false, [StateEnum.closed.label]: false, - [StateEnum.callforrecordsoverdue.label]: false + [StateEnum.callforrecordsoverdue.label]: false, + [StateEnum.onholdother.label]: false }; const [requestState, setRequestState] = useState(() => { diff --git a/forms-flow-web/src/components/FOI/Dashboard/Ministry/AdvancedSearch/SearchComponent.js b/forms-flow-web/src/components/FOI/Dashboard/Ministry/AdvancedSearch/SearchComponent.js index 5058944d6..93eacfa3d 100644 --- a/forms-flow-web/src/components/FOI/Dashboard/Ministry/AdvancedSearch/SearchComponent.js +++ b/forms-flow-web/src/components/FOI/Dashboard/Ministry/AdvancedSearch/SearchComponent.js @@ -131,7 +131,8 @@ const AdvancedSearch = ({ userDetail }) => { [StateEnum.review.label]: false, [StateEnum.signoff.label]: false, [StateEnum.closed.label]: false, - [StateEnum.callforrecordsoverdue.label]: false + [StateEnum.callforrecordsoverdue.label]: false, + [StateEnum.onholdother.label]: false }; const [requestState, setRequestState] = useState(() => { if (Object.keys(advancedSearchParams).length > 0 && advancedSearchParams.requestState.length > 0) { diff --git a/forms-flow-web/src/components/FOI/Dashboard/Ministry/Queue.js b/forms-flow-web/src/components/FOI/Dashboard/Ministry/Queue.js index a8b92a7ac..277e5168f 100644 --- a/forms-flow-web/src/components/FOI/Dashboard/Ministry/Queue.js +++ b/forms-flow-web/src/components/FOI/Dashboard/Ministry/Queue.js @@ -90,7 +90,7 @@ const Queue = ({ userDetail, tableInfo }) => { function getRecordsDue(params) { let receivedDateString = params.row.cfrduedate; const currentStatus = params.row.currentState; - if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase()) { + if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.onholdother.name.toLowerCase()) { return "N/A"; } else if(!receivedDateString) { return ""; @@ -102,7 +102,7 @@ const Queue = ({ userDetail, tableInfo }) => { function getLDD(params) { let receivedDateString = params.row.duedate; const currentStatus = params.row.currentState; - if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase()) { + if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.onholdother.name.toLowerCase()) { return "N/A"; } else if(!receivedDateString) { return ""; diff --git a/forms-flow-web/src/components/FOI/Dashboard/utils.js b/forms-flow-web/src/components/FOI/Dashboard/utils.js index 086411d30..827e11488 100644 --- a/forms-flow-web/src/components/FOI/Dashboard/utils.js +++ b/forms-flow-web/src/components/FOI/Dashboard/utils.js @@ -133,7 +133,7 @@ export const onBehalfFullName = (params) => { export const getRecordsDue = (params) => { let receivedDateString = params.row.cfrduedate; const currentStatus = params.row.currentState; - if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase()) { + if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase() || currentStatus.toLowerCase() === StateEnum.onholdother.name.toLowerCase()) { return "N/A"; } else if(!receivedDateString) { return ""; @@ -145,7 +145,7 @@ export const getRecordsDue = (params) => { export const getLDD = (params) => { let receivedDateString = params.row.duedate; const currentStatus = params.row.currentState; - if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase()) { + if (currentStatus.toLowerCase() === StateEnum.onhold.name.toLowerCase()||currentStatus.toLowerCase() === StateEnum.onholdother.name.toLowerCase()) { return "N/A"; } else if(!receivedDateString) { return ""; @@ -158,7 +158,7 @@ export const getDaysLeft = (params) => { const receivedDateString = params.row.duedate; if ( - [StateEnum.onhold.name.toLowerCase(), StateEnum.closed.name.toLowerCase()].includes(params.row.currentState.toLowerCase()) + [StateEnum.onhold.name.toLowerCase(), StateEnum.closed.name.toLowerCase(), StateEnum.onholdother.name.toLowerCase()].includes(params.row.currentState.toLowerCase()) ) { return "N/A"; } else if(!receivedDateString) { diff --git a/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js b/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js index bbef03d0e..e3650b0bb 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/BottomButtonGroup/index.js @@ -369,12 +369,13 @@ const BottomButtonGroup = React.memo( case StateEnum.peerreview.name: case StateEnum.section5pending.name: case StateEnum.appfeeowing.name: + case StateEnum.onholdother.name: case StateEnum.recordsreadyforreview.name: const status = Object.values(StateEnum).find( (statusValue) => statusValue.name === currentSelectedStatus ); saveRequestObject.requeststatuslabel = status.label; - if (currentSelectedStatus === StateEnum.onhold.name && !saveRequestObject.paymentExpiryDate) { + if ((currentSelectedStatus === StateEnum.onhold.name || currentSelectedStatus === StateEnum.onholdother.name) && !saveRequestObject.paymentExpiryDate) { saveRequestObject.paymentExpiryDate = dueDateCalculation(new Date(), PAYMENT_EXPIRY_DAYS); } break; diff --git a/forms-flow-web/src/components/FOI/FOIRequest/ExtensionDetails/ExtensionDetailsBox.js b/forms-flow-web/src/components/FOI/FOIRequest/ExtensionDetails/ExtensionDetailsBox.js index 0e32914db..e512144e7 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/ExtensionDetails/ExtensionDetailsBox.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/ExtensionDetails/ExtensionDetailsBox.js @@ -67,7 +67,7 @@ const ExtensionDetailsBox = React.memo(() => { setExtensionId(null); }} disabled={pendingExtensionExists || requestState?.toLowerCase() === StateEnum.onhold.name.toLowerCase() || - requestState?.toLowerCase() === StateEnum.closed.name.toLowerCase()} + requestState?.toLowerCase() === StateEnum.onholdother.name.toLowerCase() || requestState?.toLowerCase() === StateEnum.closed.name.toLowerCase()} > New Extension diff --git a/forms-flow-web/src/components/FOI/FOIRequest/ExtensionDetails/ExtensionsTable.js b/forms-flow-web/src/components/FOI/FOIRequest/ExtensionDetails/ExtensionsTable.js index 08d1293e7..09f914cf4 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/ExtensionDetails/ExtensionsTable.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/ExtensionDetails/ExtensionsTable.js @@ -168,7 +168,7 @@ const ExtensionsTable = ({ showActions = true }) => { disabled={ (index > 0 && extension.extensionstatus !== extensionStatusId.pending) || requestState?.toLowerCase() === StateEnum.onhold.name.toLowerCase() || - requestState?.toLowerCase() === StateEnum.closed.name.toLowerCase() + requestState?.toLowerCase() === StateEnum.onholdother.name.toLowerCase() || requestState?.toLowerCase() === StateEnum.closed.name.toLowerCase() } > diff --git a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js index 23450bed3..61839a970 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequest.js @@ -373,6 +373,7 @@ const FOIRequest = React.memo(({ userDetail, openApplicantProfileModal }) => { currentState === StateEnum.peerreview.name || currentState === StateEnum.signoff.name || currentState === StateEnum.response.name || + currentState === StateEnum.onholdother.name || currentState === StateEnum.closed.name ); } @@ -893,7 +894,6 @@ const FOIRequest = React.memo(({ userDetail, openApplicantProfileModal }) => { const handleSaveRequest = (_state, _unSaved, id) => { setHeader(_state); - if (!_unSaved) { setUnSavedRequest(_unSaved); dispatch(fetchFOIRequestDetailsWrapper(id || requestId, ministryId)); diff --git a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequestHeader/index.js b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequestHeader/index.js index 2a76a6055..b0ed964ba 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/FOIRequestHeader/index.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/FOIRequestHeader/index.js @@ -226,6 +226,7 @@ const FOIRequestHeader = React.memo( status.toLowerCase() === StateEnum.onhold.name.toLowerCase() || status.toLowerCase() === StateEnum.response.name.toLowerCase() || status.toLowerCase() === StateEnum.recordsreadyforreview.name.toLowerCase() || + status.toLowerCase() === StateEnum.onholdother.name.toLowerCase() || (ministryId && status.toLowerCase() === StateEnum.peerreview.name.toLowerCase()); const getMinistryAssignedTo = () => { diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js index a89a289bf..9459d9a31 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReview.js @@ -263,6 +263,7 @@ const MinistryReview = React.memo(({ userDetail }) => { currentState === StateEnum.peerreview.name || currentState === StateEnum.signoff.name || currentState === StateEnum.response.name || + currentState === StateEnum.onholdother.name || currentState === StateEnum.closed.name ); } @@ -328,6 +329,7 @@ const MinistryReview = React.memo(({ userDetail }) => { const [CFRUnsaved, setCFRUnsaved] = React.useState(false); const hideBottomText = [ StateEnum.onhold.name.toLowerCase(), + StateEnum.onholdother.name.toLowerCase(), StateEnum.closed.name.toLowerCase(), ]; @@ -489,6 +491,9 @@ const MinistryReview = React.memo(({ userDetail }) => { break; case StateEnum.recordsreadyforreview.name: foitabheaderBG = "foitabheadercollection foitabheaderRecordsReadyForReviewBG"; + break; + case StateEnum.onholdother.name: + foitabheaderBG = "foitabheadercollection foitabheaderOnHoldOtherBG"; break; default: foitabheaderBG = "foitabheadercollection foitabheaderdefaultBG"; diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReviewTabbedContainer.scss b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReviewTabbedContainer.scss index fd1a57bdc..617a97784 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReviewTabbedContainer.scss +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/MinistryReviewTabbedContainer.scss @@ -214,7 +214,9 @@ .foitabheaderPeerreviewBG{ background-color: #096DD1; } - + .foitabheaderOnHoldOtherBG{ + background-color: #595959; + } .foileftpanelheader { padding-left: 12%; diff --git a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestDetails.js b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestDetails.js index 2e4c233a4..ec9a9472a 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestDetails.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/MinistryReview/RequestDetails.js @@ -57,8 +57,9 @@ const RequestDetails = React.memo((requestDetails) => { Records Due Date - {_requestDetails?.currentState?.toLowerCase() !== - StateEnum.onhold.name.toLowerCase() + {(_requestDetails?.currentState?.toLowerCase() !== + StateEnum.onhold.name.toLowerCase() && _requestDetails?.currentState?.toLowerCase() !== + StateEnum.onholdother.name.toLowerCase()) ? formatDate( _requestDetails.cfrDueDate, "MMM dd yyyy" @@ -71,8 +72,9 @@ const RequestDetails = React.memo((requestDetails) => { Legislated Due Date - {_requestDetails?.currentState?.toLowerCase() !== - StateEnum.onhold.name.toLowerCase() + {(_requestDetails?.currentState?.toLowerCase() !== + StateEnum.onhold.name.toLowerCase() && _requestDetails?.currentState?.toLowerCase() !== + StateEnum.onholdother.name.toLowerCase()) ? formatDate( _requestDetails.dueDate, "MMM dd yyyy" diff --git a/forms-flow-web/src/components/FOI/FOIRequest/RequestDetails.js b/forms-flow-web/src/components/FOI/FOIRequest/RequestDetails.js index 4ee9e5deb..c171c02c9 100644 --- a/forms-flow-web/src/components/FOI/FOIRequest/RequestDetails.js +++ b/forms-flow-web/src/components/FOI/FOIRequest/RequestDetails.js @@ -266,8 +266,8 @@ const RequestDetails = React.memo( { return "foitabheadercollection foitabheaderAppFeeOwingBG"; case StateEnum.recordsreadyforreview.name: return "foitabheadercollection foitabheaderRecordsReadyForReviewBG"; - + case StateEnum.onholdother.name: + return "foitabheadercollection foitabheaderOnholdOtherBG"; default: return "foitabheadercollection foitabheaderdefaultBG"; } diff --git a/forms-flow-web/src/components/FOI/customComponents/CFRForm/index.tsx b/forms-flow-web/src/components/FOI/customComponents/CFRForm/index.tsx index bb67e4ce7..3bacb9768 100644 --- a/forms-flow-web/src/components/FOI/customComponents/CFRForm/index.tsx +++ b/forms-flow-web/src/components/FOI/customComponents/CFRForm/index.tsx @@ -379,14 +379,14 @@ export const CFRForm = ({ if(requestState === StateEnum.peerreview.name){ return true; } - if (formHistory.length > 0 && [StateEnum.feeassessed.name, StateEnum.onhold.name, StateEnum.callforrecords.name].includes(requestState)) { + if (formHistory.length > 0 && [StateEnum.feeassessed.name, StateEnum.onhold.name, StateEnum.callforrecords.name, StateEnum.onholdother.name].includes(requestState)) { if (isMinistry) { return ['review', 'approved'].includes(initialFormData.formStatus) || isNewCFRForm; } else { return initialFormData.formStatus !== 'review'; } } - if (formData.balanceRemaining > 0 && [StateEnum.feeassessed.name, StateEnum.onhold.name].includes(requestState)) { + if (formData.balanceRemaining > 0 && [StateEnum.feeassessed.name, StateEnum.onhold.name, StateEnum.onholdother.name].includes(requestState)) { if (isMinistry) { return !['clarification', 'init'].includes(initialFormData.formStatus); } else { @@ -523,7 +523,8 @@ export const CFRForm = ({ const disableNewCfrFormBtn = () => { return(formData?.formStatus !== 'approved' || requestState === StateEnum.peerreview.name || (requestState !== StateEnum.callforrecords.name && - requestState !== StateEnum.feeassessed.name && requestState !== StateEnum.onhold.name) || (requestState === StateEnum.onhold.name && formData?.actualTotalDue > 0)); + requestState !== StateEnum.feeassessed.name && requestState !== StateEnum.onhold.name) || (requestState === StateEnum.onhold.name && formData?.actualTotalDue > 0) || + (requestState === StateEnum.onholdother.name && formData?.actualTotalDue > 0)); } const disableAmountPaid = () => { @@ -544,7 +545,7 @@ export const CFRForm = ({ } const isFeeWaiverDisabled = () => { - if(isMinistry || requestState === StateEnum.peerreview.name || (!isMinistry && (requestState !== StateEnum.onhold.name || formData?.formStatus !== 'approved'))) + if(isMinistry || requestState === StateEnum.peerreview.name || (!isMinistry && (requestState !== StateEnum.onhold.name || requestState !== StateEnum.onholdother.name || formData?.formStatus !== 'approved'))) return true; else return false; diff --git a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js index d3eacd84a..9ddc823c8 100644 --- a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js +++ b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/index.js @@ -169,7 +169,6 @@ export default function ConfirmationModal({requestId, openModal, handleModal, st else if (saveRequestObject.requeststatuslabel === StateEnum.signoff.label && state.toLowerCase() === StateEnum.review.name.toLowerCase()) fileStatusTransition = StateTransitionCategories.signoffreview.name; - fileInfoList = files.map(file => { return { ministrycode: requestNumber.split("-")[0], @@ -186,7 +185,7 @@ export default function ConfirmationModal({requestId, openModal, handleModal, st const attchmentFileNameList = attachmentsArray?.map(_file => _file.filename); const getDaysRemaining = () => { - if (currentState?.toLowerCase() === StateEnum.closed.name.toLowerCase() && state.toLowerCase() !== StateEnum.closed.name.toLowerCase() && state.toLowerCase() !== StateEnum.onhold.name.toLowerCase()) { + if (currentState?.toLowerCase() === StateEnum.closed.name.toLowerCase() && state.toLowerCase() !== StateEnum.closed.name.toLowerCase() && state.toLowerCase() !== StateEnum.onhold.name.toLowerCase() && state.toLowerCase() !== StateEnum.onholdother.name.toLowerCase()) { return ( {daysRemainingLDD} DAYS REMAINING ); @@ -194,7 +193,9 @@ export default function ConfirmationModal({requestId, openModal, handleModal, st } const addorUpdateConfirmationModal = () => { - if (state.toLowerCase() === StateEnum.closed.name.toLowerCase() && + if(currentState?.toLowerCase() === StateEnum.onholdother.name.toLowerCase()) + return null; + else if (state.toLowerCase() === StateEnum.closed.name.toLowerCase() && currentState?.toLowerCase() !== StateEnum.closed.name.toLowerCase()) { return ( @@ -261,7 +262,7 @@ export default function ConfirmationModal({requestId, openModal, handleModal, st : null } - {(currentState?.toLowerCase() !== StateEnum.closed.name.toLowerCase() && [StateEnum.callforrecords.name.toLowerCase(), StateEnum.consult.name.toLowerCase(), StateEnum.onhold.name.toLowerCase()].includes(state.toLowerCase())) ? + {(currentState?.toLowerCase() !== StateEnum.closed.name.toLowerCase() && [StateEnum.callforrecords.name.toLowerCase(), StateEnum.consult.name.toLowerCase(), StateEnum.onhold.name.toLowerCase(), StateEnum.onholdother.name.toLowerCase()].includes(state.toLowerCase())) ? diff --git a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js index a3058bcbe..7cb10eef7 100644 --- a/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js +++ b/forms-flow-web/src/components/FOI/customComponents/ConfirmationModal/util.js @@ -46,6 +46,9 @@ import FOI_COMPONENT_CONSTANTS from '../../../../constants/FOI/foiComponentConst _saveRequestObject.reopen = true; return {title: "Re-Open Request", body: <>Are you sure you want to re-open Request # {_requestNumber ? _requestNumber : `U-00${_requestId}`}?
The request will be re-opened to the previous state: {_state} }; } + if ((_currentState?.toLowerCase() === StateEnum.onholdother.name.toLowerCase())){ + return {title: "Taking Request off hold", body: "Are you sure you want to take this request off hold? The legislated due date will be recalculated"}; + } switch(_state.toLowerCase()) { case StateEnum.intakeinprogress.name.toLowerCase(): return {title: "Changing the state", body: "Are you sure you want to change the state to Intake in Progress?"}; @@ -134,6 +137,10 @@ import FOI_COMPONENT_CONSTANTS from '../../../../constants/FOI/foiComponentConst return {title: "Changing the state", body: `Are you sure you want to change Request #${_requestNumber} to ${StateEnum.response.name}?`}; case StateEnum.appfeeowing.name.toLowerCase(): return {title: "Changing the state", body: `Are you sure you want to change Request #${_requestNumber} to ${StateEnum.appfeeowing.name}?`}; + case StateEnum.onholdother.name.toLowerCase(): + return {title: "Change Request to On Hold - Other", + body: <>Are you sure you want to change Request #{_requestNumber} to {StateEnum.onholdother.name}? This should be used for scenarios + that are not fee related (such as Third Party notice). This will stop the clock.}; default: return {title: "", body: ""}; } diff --git a/forms-flow-web/src/components/FOI/customComponents/Records/util.js b/forms-flow-web/src/components/FOI/customComponents/Records/util.js index 44b2d1a33..e47225424 100644 --- a/forms-flow-web/src/components/FOI/customComponents/Records/util.js +++ b/forms-flow-web/src/components/FOI/customComponents/Records/util.js @@ -151,12 +151,7 @@ export const sortDivisionalFiles = (divisionMap) => { export const calculateTotalUploadedFileSizeInKB = (records) => { return records?.reduce((total, record) => { - return ( - total + - (record.attributes.convertedfilesize - ? record.attributes.convertedfilesize - : record.attributes.filesize) - ); + return (total + record.attributes.filesize); }, 0); }; diff --git a/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js b/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js index 632bf69fd..b05df147e 100644 --- a/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js +++ b/forms-flow-web/src/components/FOI/customComponents/StateDropDown.js @@ -111,7 +111,15 @@ const StateDropDown = ({ const appendRecordsReadyForReview = (stateList) => { const recordsreadyforreview = { status: "Records Ready for Review", isSelected: false }; let appendedList = stateList.slice(); - appendedList.splice(-1, 0, recordsreadyforreview); + if(previousState === StateEnum.open.name || previousState === StateEnum.response.name) + appendedList.splice(-2, 0, recordsreadyforreview); + else appendedList.splice(-1, 0, recordsreadyforreview); + return appendedList; + } + const appendPreviousStateForHoldOthers = (stateList, previousStateName) => { + const previousStateObject = { status: previousStateName, isSelected: false }; + let appendedList = stateList.slice(); + appendedList.splice(-1, 0, previousStateObject); return appendedList; } const isMCFMinistryTeam = userDetail?.groups?.some(str => str.includes("MCF Ministry Team")) @@ -225,7 +233,10 @@ const StateDropDown = ({ break; case StateEnum.appfeeowing.name.toLowerCase(): return _stateList.appfeeowing; - + case StateEnum.onholdother.name.toLowerCase(): + if (!isMinistryCoordinator) { + return appendPreviousStateForHoldOthers(_stateList.onholdother, previousState); + } else return _stateList.onholdother; default: return []; } diff --git a/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss b/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss index 68e5048ba..06ac4642c 100644 --- a/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss +++ b/forms-flow-web/src/components/FOI/customComponents/statedropdown.scss @@ -102,12 +102,13 @@ background-color: #096DD1; } - - .tagging { background-color: #9B1048; } .readytoscan{ background-color: #A2096C; +} +.onhold-other { + background-color: #595959; } \ No newline at end of file diff --git a/forms-flow-web/src/constants/FOI/statusEnum.js b/forms-flow-web/src/constants/FOI/statusEnum.js index 8b15ca80a..e9624e96a 100644 --- a/forms-flow-web/src/constants/FOI/statusEnum.js +++ b/forms-flow-web/src/constants/FOI/statusEnum.js @@ -28,11 +28,13 @@ const StateList = Object.freeze({ { status: "Open", isSelected: false }, { status: "Call For Records", isSelected: false }, { status: "Peer Review", isSelected: false }, + { status: "On Hold - Other", isSelected: false }, { status: "Closed", isSelected: false }, ], callforrecords: [ { status: "Call For Records", isSelected: false }, { status: "Open", isSelected: false }, + { status: "On Hold - Other", isSelected: false }, { status: "Closed", isSelected: false }, ], callforrecordscfdmsdpersonal: [ @@ -41,6 +43,7 @@ const StateList = Object.freeze({ { status: "Tagging", isSelected: false }, { status: "Ready to Scan", isSelected: false }, { status: "Records Ready for Review", isSelected: false }, + { status: "On Hold - Other", isSelected: false }, { status: "Closed", isSelected: false }, ], tagging: [ @@ -49,6 +52,7 @@ const StateList = Object.freeze({ { status: "Ready to Scan", isSelected: false }, { status: "Records Ready for Review", isSelected: false }, { status: "Records Review", isSelected: false }, + { status: "On Hold - Other", isSelected: false }, { status: "Closed", isSelected: false }, ], readytoscan: [ @@ -57,17 +61,20 @@ const StateList = Object.freeze({ { status: "Tagging", isSelected: false }, { status: "Records Ready for Review", isSelected: false }, { status: "Records Review", isSelected: false }, + { status: "On Hold - Other", isSelected: false }, { status: "Closed", isSelected: false }, ], feeassessed: [ { status: "Fee Estimate", isSelected: false }, { status: "On Hold", isSelected: false }, { status: "Call For Records", isSelected: false }, + { status: "On Hold - Other", isSelected: false }, { status: "Closed", isSelected: false }, ], feeassessedforpersonal: [ { status: "Fee Estimate", isSelected: false }, { status: "Call For Records", isSelected: false }, + { status: "On Hold - Other", isSelected: false }, { status: "Closed", isSelected: false }, ], onhold: [ @@ -80,10 +87,12 @@ const StateList = Object.freeze({ { status: "Deduplication", isSelected: false }, { status: "Harms Assessment", isSelected: false }, { status: "Records Review", isSelected: false }, + { status: "On Hold - Other", isSelected: false }, { status: "Closed", isSelected: false }, ], harms: [ { status: "Harms Assessment", isSelected: false }, + { status: "On Hold - Other", isSelected: false }, { status: "Closed", isSelected: false }, ], consult: [ @@ -92,6 +101,7 @@ const StateList = Object.freeze({ { status: "Records Review", isSelected: false }, { status: "Ministry Sign Off", isSelected: false }, { status: "Peer Review", isSelected: false }, + { status: "On Hold - Other", isSelected: false }, { status: "Closed", isSelected: false }, ], review: [ @@ -102,6 +112,7 @@ const StateList = Object.freeze({ { status: "Ministry Sign Off", isSelected: false }, { status: "Peer Review", isSelected: false }, { status: "Response", isSelected: false }, + { status: "On Hold - Other", isSelected: false }, { status: "Closed", isSelected: false }, ], reviewcfdmsdpersonal: [ @@ -113,10 +124,12 @@ const StateList = Object.freeze({ { status: "Ministry Sign Off", isSelected: false }, { status: "Peer Review", isSelected: false }, { status: "Response", isSelected: false }, + { status: "On Hold - Other", isSelected: false }, { status: "Closed", isSelected: false }, ], signoff: [ { status: "Ministry Sign Off", isSelected: false }, + { status: "On Hold - Other", isSelected: false }, { status: "Closed", isSelected: false }, ], response: [ @@ -124,18 +137,21 @@ const StateList = Object.freeze({ { status: "On Hold", isSelected: false }, { status: "Records Review", isSelected: false }, { status: "Peer Review", isSelected: false }, + { status: "On Hold - Other", isSelected: false }, { status: "Closed", isSelected: false }, ], responseforpersonal: [ { status: "Response", isSelected: false }, { status: "Records Review", isSelected: false }, { status: "Peer Review", isSelected: false }, + { status: "On Hold - Other", isSelected: false }, { status: "Closed", isSelected: false }, ], //peerreview: [{status:"Peer Review", isSelected: false},{status:"Intake in Progress", isSelected: false}, {status: "Open", isSelected: false},{status: "Records Review", isSelected: false},{status: "Consult", isSelected: false},{status: "Response", isSelected: false}], peerreview: [ { status: "Peer Review", isSelected: false }, - { status: "Records Ready for Review", isSelected: false } + { status: "Records Ready for Review", isSelected: false }, + { status: "On Hold - Other", isSelected: false }, ], section5pending: [ { status: "Section 5 Pending", isSelected: false }, @@ -157,8 +173,13 @@ const StateList = Object.freeze({ { status: "Tagging", isSelected: false }, { status: "Consult", isSelected: false }, { status: "Records Review", isSelected: false }, + { status: "On Hold - Other", isSelected: false }, { status: "Closed", isSelected: false } ], + onholdother: [ + { status: "On Hold - Other", isSelected: false }, + { status: "Closed", isSelected: false } + ] }); const MinistryStateList = Object.freeze({ @@ -205,6 +226,7 @@ const MinistryStateList = Object.freeze({ tagging: [{ status: "Tagging", isSelected: true }], readytoscan: [{ status: "Ready to Scan", isSelected: true }], recordsreadyforreview: [{ status: "Records Ready for Review", isSelected: false }], + onholdother:[{status: "On Hold - Other", isSelected: false }], }); // This corresponds to rows in the FOIRequestStatuses table on the backend @@ -231,6 +253,7 @@ const StateEnum = Object.freeze({ appfeeowing: { name: "App Fee Owing", label: "appfeeowing" }, section5pending: { name: "Section 5 Pending", label: "section5pending" }, recordsreadyforreview: { name: "Records Ready for Review", label: "recordsreadyforreview" }, + onholdother: { name: "On Hold - Other", label: "onholdother" }, }); const StateTransitionCategories = Object.freeze({ diff --git a/notification-manager/notification_api/dao/models/FOIRequestComments.py b/notification-manager/notification_api/dao/models/FOIRequestComments.py index bf06b1286..72fdfcd46 100644 --- a/notification-manager/notification_api/dao/models/FOIRequestComments.py +++ b/notification-manager/notification_api/dao/models/FOIRequestComments.py @@ -24,7 +24,7 @@ class Meta: # pylint: disable=too-few-public-methods @classmethod - def savecomment(cls, commenttypeid, foirequestcomment, userid): + def savecomment(cls, commenttypeid, foirequestcomment, userid, createdat): conn = None try: id_of_new_row = None @@ -39,7 +39,7 @@ def savecomment(cls, commenttypeid, foirequestcomment, userid): comment, taggedusers, isactive, createdby, created_at, commentsversion) \ VALUES(%s::integer,%s::integer, %s::integer, %s::integer,%s,%s,%s::boolean,%s,%s,%s::integer) RETURNING commentid', (parentcommentid, int(data["ministryrequestid"]), int(data["version"]), commenttypeid, - str(data["comment"]), taggedusers, True, userid, datetime.now(), data["commentsversion"])) + str(data["comment"]), taggedusers, True, userid, createdat or datetime.now(), data["commentsversion"])) conn.commit() id_of_new_row = cursor.fetchone()[0] cursor.close() diff --git a/notification-manager/notification_api/io/message/processor/notificationprocessor.py b/notification-manager/notification_api/io/message/processor/notificationprocessor.py index 01bfb9017..507fe68c0 100644 --- a/notification-manager/notification_api/io/message/processor/notificationprocessor.py +++ b/notification-manager/notification_api/io/message/processor/notificationprocessor.py @@ -88,6 +88,7 @@ def __createcomment(self, notification): comment, notification.createdby, 2, + notification.__dict__.get('createdat') ) def __notificationmessage(self, username): diff --git a/notification-manager/notification_api/io/message/schemas/notification.py b/notification-manager/notification_api/io/message/schemas/notification.py index 87b82a0b7..7094554c3 100644 --- a/notification-manager/notification_api/io/message/schemas/notification.py +++ b/notification-manager/notification_api/io/message/schemas/notification.py @@ -14,6 +14,7 @@ class NotificationPublishSchema(object): serviceid = fields.Str(data_key="serviceid", allow_none=False) errorflag = fields.Str(data_key="errorflag", allow_none=False) createdby = fields.Str(data_key="message", allow_none=False) + createdat = fields.Str(data_key="createdat", allow_none=True, required=False) class NotificationHarmsPDFStitchPublishSchema(object): diff --git a/notification-manager/notification_api/io/redis_stream/redisstreamdb.py b/notification-manager/notification_api/io/redis_stream/redisstreamdb.py index d43a16ee8..03341027a 100644 --- a/notification-manager/notification_api/io/redis_stream/redisstreamdb.py +++ b/notification-manager/notification_api/io/redis_stream/redisstreamdb.py @@ -2,4 +2,5 @@ from config import REDIS_HOST,REDIS_PORT,REDIS_PASSWORD, REDIS_HEALTH_CHECK_INTERVAL #streamdb = Database(host=str(REDIS_HOST), port=str(REDIS_PORT), db=0,password=str(REDIS_PASSWORD)) +print(REDIS_HOST) streamdb = Database(host=str(REDIS_HOST), port=str(REDIS_PORT), db=0,password=str(REDIS_PASSWORD), retry_on_timeout=True, health_check_interval=int(REDIS_HEALTH_CHECK_INTERVAL), socket_keepalive=True) diff --git a/notification-manager/notification_api/services/commentservice.py b/notification-manager/notification_api/services/commentservice.py index 5155ff1b8..0d8195353 100644 --- a/notification-manager/notification_api/services/commentservice.py +++ b/notification-manager/notification_api/services/commentservice.py @@ -16,10 +16,10 @@ class commentservice: """ - def createcomment(self, requesttype, requestid, comment, userid, type=1): + def createcomment(self, requesttype, requestid, comment, userid, type=1, createdat=datetime.now()): foirequest = self.getrequest(requestid, requesttype) _comment = self.__preparecomment(foirequest, comment) - FOIRequestComment().savecomment(type, _comment, userid) + FOIRequestComment().savecomment(type, _comment, userid, createdat) return DefaultMethodResult(True,'No change',requestid) def __preparecomment(self, foirequest, comment): diff --git a/request-management-api/migrations/versions/83cfc8047acf_.py b/request-management-api/migrations/versions/83cfc8047acf_.py new file mode 100644 index 000000000..ee65fd455 --- /dev/null +++ b/request-management-api/migrations/versions/83cfc8047acf_.py @@ -0,0 +1,41 @@ +"""empty message + +Revision ID: 83cfc8047acf +Revises: 77ef102e9823 +Create Date: 2024-10-31 12:47:04.379106 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.sql import column, table +from sqlalchemy.sql.sqltypes import Boolean, String + +# revision identifiers, used by Alembic. +revision = '83cfc8047acf' +down_revision = '77ef102e9823' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + requeststatus_table = table('FOIRequestStatuses', + column('name',String), + column('description',String), + column('isactive',Boolean), + column('statuslabel',String), + ) + op.bulk_insert( + requeststatus_table, + [ + {'name':'On Hold - Other','description':'On Hold for Other reasons','isactive':True, 'statuslabel':'onholdother'}, + ] + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.execute('delete from public."FOIRequestStatuses" where name = \'On Hold - Other\';') + # ### end Alembic commands ### + diff --git a/request-management-api/request_api/models/CloseReasons.py b/request-management-api/request_api/models/CloseReasons.py index 729bc779a..1fb9a0523 100644 --- a/request-management-api/request_api/models/CloseReasons.py +++ b/request-management-api/request_api/models/CloseReasons.py @@ -21,7 +21,7 @@ def getallclosereasons(cls): @classmethod def getclosereason(cls,closereasonid): - closereason_schema = CloseReasonSchema(many=True) + closereason_schema = CloseReasonSchema(many=False) query = db.session.query(CloseReason).filter_by(closereasonid=closereasonid).first() return closereason_schema.dump(query) diff --git a/request-management-api/request_api/models/FOIMinistryRequestDocuments.py b/request-management-api/request_api/models/FOIMinistryRequestDocuments.py index 2dc5b65e4..f49f53c14 100644 --- a/request-management-api/request_api/models/FOIMinistryRequestDocuments.py +++ b/request-management-api/request_api/models/FOIMinistryRequestDocuments.py @@ -48,14 +48,19 @@ def getdocuments(cls,ministryrequestid,ministryrequestversion): @classmethod def getactivedocuments(cls,ministryrequestid): sql = ''' - WITH document AS ( + WITH rawdocument AS ( + SELECT documentpath, min(foidocumentid) as foidocumentid + FROM "FOIRawRequestDocuments" + GROUP BY documentpath + ), + document AS ( SELECT documentpath, min(created_at) AS created_at FROM "FOIMinistryRequestDocuments" GROUP BY documentpath ) SELECT * FROM ( SELECT DISTINCT ON (foiministrydocumentid) - doc.created_at, + case when rrd.created_at is null then doc.created_at else rrd.created_at end, fmrd.foiministrydocumentid, fmrd.filename, fmrd.documentpath, @@ -67,7 +72,10 @@ def getactivedocuments(cls,ministryrequestid): FROM "FOIMinistryRequestDocuments" fmrd JOIN document doc on doc.documentpath = fmrd.documentpath - where fmrd.foiministryrequest_id =:ministryrequestid ORDER BY fmrd.foiministrydocumentid, version DESC) AS list + left JOIN rawdocument rawdoc + on rawdoc.documentpath = fmrd.documentpath + left join "FOIRawRequestDocuments" rrd on rrd.foidocumentid = rawdoc.foidocumentid + where fmrd.foiministryrequest_id = :ministryrequestid ORDER BY fmrd.foiministrydocumentid, version DESC) AS list ORDER BY created_at DESC ''' rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) diff --git a/request-management-api/request_api/models/FOIMinistryRequests.py b/request-management-api/request_api/models/FOIMinistryRequests.py index d1f0b86b8..be13ad168 100644 --- a/request-management-api/request_api/models/FOIMinistryRequests.py +++ b/request-management-api/request_api/models/FOIMinistryRequests.py @@ -175,11 +175,11 @@ def getrequests(cls, group = None): if group is None: _ministryrequestids = _session.query(distinct(FOIMinistryRequest.foiministryrequestid)).filter(FOIMinistryRequest.isactive == True).all() elif (group == IAOTeamWithKeycloackGroup.flex.value): - _ministryrequestids = _session.query(distinct(FOIMinistryRequest.foiministryrequestid)).filter(and_(FOIMinistryRequest.isactive == True), and_(and_(FOIMinistryRequest.assignedgroup == group),and_(FOIMinistryRequest.requeststatuslabel.in_([StateName.open.name,StateName.callforrecords.name,StateName.closed.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.value,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name])))).all() + _ministryrequestids = _session.query(distinct(FOIMinistryRequest.foiministryrequestid)).filter(and_(FOIMinistryRequest.isactive == True), and_(and_(FOIMinistryRequest.assignedgroup == group),and_(FOIMinistryRequest.requeststatuslabel.in_([StateName.open.name,StateName.callforrecords.name,StateName.closed.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.value,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name,StateName.onholdother.name])))).all() elif (group in ProcessingTeamWithKeycloackGroup.list()): - _ministryrequestids = _session.query(distinct(FOIMinistryRequest.foiministryrequestid)).filter(and_(FOIMinistryRequest.isactive == True), and_(and_(FOIMinistryRequest.assignedgroup == group),and_(FOIMinistryRequest.requeststatuslabel.in_([StateName.open.name,StateName.callforrecords.name,StateName.closed.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.value,StateName.onhold.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name])))).all() + _ministryrequestids = _session.query(distinct(FOIMinistryRequest.foiministryrequestid)).filter(and_(FOIMinistryRequest.isactive == True), and_(and_(FOIMinistryRequest.assignedgroup == group),and_(FOIMinistryRequest.requeststatuslabel.in_([StateName.open.name,StateName.callforrecords.name,StateName.closed.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.value,StateName.onhold.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name,StateName.onholdother.name])))).all() else: - _ministryrequestids = _session.query(distinct(FOIMinistryRequest.foiministryrequestid)).filter(and_(FOIMinistryRequest.isactive == True), or_(and_(FOIMinistryRequest.assignedgroup == group),and_(FOIMinistryRequest.assignedministrygroup == group,or_(FOIMinistryRequest.requeststatuslabel.in_([StateName.callforrecords.name,StateName.recordsreview.name,StateName.recordsreadyforreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.name,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name]))))).all() + _ministryrequestids = _session.query(distinct(FOIMinistryRequest.foiministryrequestid)).filter(and_(FOIMinistryRequest.isactive == True), or_(and_(FOIMinistryRequest.assignedgroup == group),and_(FOIMinistryRequest.assignedministrygroup == group,or_(FOIMinistryRequest.requeststatuslabel.in_([StateName.callforrecords.name,StateName.recordsreview.name,StateName.recordsreadyforreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.name,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name,StateName.onholdother.name]))))).all() _requests = [] ministryrequest_schema = FOIMinistryRequestSchema() @@ -332,7 +332,7 @@ def getstatesummary(cls, ministryrequestid): return transitions @classmethod - def getlastoffholddate(cls, ministryrequestid): + def getlastoffholddate(cls, ministryrequestid, currentstatus): transitions = [] try: sql = """select fm2.version, fs2."name" as status, fm2.created_at from "FOIMinistryRequests" fm2 inner join "FOIRequestStatuses" fs2 on fm2.requeststatusid = fs2.requeststatusid @@ -349,7 +349,7 @@ def getlastoffholddate(cls, ministryrequestid): recent_offhold_index = None offhold_indicator = False for entry in desc_transitions: - if entry["status"] == StateName.onhold.value: + if entry["status"] == currentstatus: onhold_occurance = onhold_occurance + 1 if onhold_occurance > 1: recent_offhold_index = index @@ -491,13 +491,13 @@ def getrequestssubquery(cls, groups, filterfields, keyword, additionalfilter, us else_ = FOIMinistryRequest.assignedministrygroup).label('ministryAssignedToFormatted') duedate = case([ - (FOIMinistryRequest.requeststatuslabel == StateName.onhold.name, # On Hold + (or_(FOIMinistryRequest.requeststatuslabel == StateName.onhold.name, FOIMinistryRequest.requeststatuslabel == StateName.onholdother.name), # On Hold literal(None)), ], else_ = cast(FOIMinistryRequest.duedate, String)).label('duedate') cfrduedate = case([ - (FOIMinistryRequest.requeststatuslabel == StateName.onhold.name, # On Hold + (or_(FOIMinistryRequest.requeststatuslabel == StateName.onhold.name, FOIMinistryRequest.requeststatuslabel == StateName.onholdother.name), # On Hold literal(None)), ], else_ = cast(FOIMinistryRequest.cfrduedate, String)).label('cfrduedate') @@ -846,7 +846,7 @@ def getgroupfilters(cls, groups): ) ) ) - statusfilter = FOIMinistryRequest.requeststatuslabel.in_([StateName.callforrecords.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.name,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name, StateName.recordsreadyforreview.name]) + statusfilter = FOIMinistryRequest.requeststatuslabel.in_([StateName.callforrecords.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.name,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name, StateName.recordsreadyforreview.name, StateName.onholdother.name]) ministryfilter = and_( FOIMinistryRequest.isactive == True, FOIRequestStatus.isactive == True, @@ -894,7 +894,7 @@ def getupcominglegislativeduerecords(cls): where isactive = true and duedate is not null and requeststatuslabel not in :requeststatuslabel and duedate between NOW() - INTERVAL '7 DAY' AND NOW() + INTERVAL '7 DAY' order by filenumber , version desc;""" - requeststatuslabel = tuple([StateName.closed.name,StateName.redirect.name,StateName.unopened.name,StateName.intakeinprogress.name,StateName.onhold.name,StateName.archived.name]) + requeststatuslabel = tuple([StateName.closed.name,StateName.redirect.name,StateName.unopened.name,StateName.intakeinprogress.name,StateName.onhold.name,StateName.archived.name, StateName.onholdother.name]) rs = db.session.execute(text(sql), {'requeststatuslabel': requeststatuslabel}) for row in rs: upcomingduerecords.append({"filenumber": row["filenumber"], "duedate": row["duedate"],"foiministryrequestid": row["foiministryrequestid"], "version": row["version"], "foirequest_id": row["foirequest_id"], "created_at": row["created_at"], "createdby": row["createdby"]}) @@ -920,7 +920,7 @@ def getupcomingdivisionduerecords(cls): inner join "ProgramAreaDivisionStages" pads on frd.stageid = pads.stageid and frd.stageid in (5, 7, 9) and frd.divisionduedate between NOW() - INTERVAL '7 DAY' AND NOW() + INTERVAL '7 DAY' order by frd.foiministryrequest_id , frd.foiministryrequestversion_id desc;""" - requeststatuslabel = tuple([StateName.closed.name,StateName.redirect.name,StateName.unopened.name,StateName.intakeinprogress.name,StateName.onhold.name,StateName.archived.name]) + requeststatuslabel = tuple([StateName.closed.name,StateName.redirect.name,StateName.unopened.name,StateName.intakeinprogress.name,StateName.onhold.name,StateName.archived.name,StateName.onholdother.name]) rs = db.session.execute(text(sql), {'requeststatuslabel': requeststatuslabel}) for row in rs: @@ -1113,13 +1113,13 @@ def getbasequery(cls, iaoassignee, ministryassignee, userid=None, requestby='IAO else_ = FOIMinistryRequest.assignedministrygroup).label('ministryAssignedToFormatted') duedate = case([ - (FOIMinistryRequest.requeststatuslabel == StateName.onhold.name, # On Hold + (or_(FOIMinistryRequest.requeststatuslabel == StateName.onhold.name, FOIMinistryRequest.requeststatuslabel == StateName.onholdother.name), # On Hold literal(None)), ], else_ = cast(FOIMinistryRequest.duedate, String)).label('duedate') cfrduedate = case([ - (FOIMinistryRequest.requeststatuslabel == StateName.onhold.name, # On Hold + (or_(FOIMinistryRequest.requeststatuslabel == StateName.onhold.name, FOIMinistryRequest.requeststatuslabel == StateName.onholdother.name), # On Hold literal(None)), ], else_ = cast(FOIMinistryRequest.cfrduedate, String)).label('cfrduedate') @@ -1328,7 +1328,7 @@ def advancedsearch(cls, params, userid, isministryrestrictedfilemanager = False) groupfilter.append(FOIMinistryRequest.assignedministrygroup == group) #ministry advanced search show cfr onwards - statefilter = FOIMinistryRequest.requeststatuslabel.in_([StateName.callforrecords.name,StateName.closed.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.name,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name,StateName.recordsreadyforreview.name]) + statefilter = FOIMinistryRequest.requeststatuslabel.in_([StateName.callforrecords.name,StateName.closed.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.name,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.peerreview.name,StateName.tagging.name,StateName.readytoscan.name,StateName.recordsreadyforreview.name,StateName.onholdother.name]) ministry_queue = FOIMinistryRequest.advancedsearchsubquery(params, iaoassignee, ministryassignee, userid, 'Ministry', False, isministryrestrictedfilemanager).filter(and_(or_(*groupfilter), statefilter)) @@ -1413,8 +1413,8 @@ def getfilterforrequeststatus(cls, params, iaoassignee, ministryassignee): #request status: overdue || on time if(params['requeststatus'][0] == 'overdue'): #exclude "on hold" for overdue - statelabel = StateName.onhold.name - return and_(FOIMinistryRequest.findfield('duedate', iaoassignee, ministryassignee) < datetime.now().date(), FOIMinistryRequest.requeststatuslabel != statelabel) + # statelabel = StateName.onhold.name + return and_(FOIMinistryRequest.findfield('duedate', iaoassignee, ministryassignee) < datetime.now().date(), and_(FOIMinistryRequest.requeststatuslabel != StateName.onhold.name, FOIMinistryRequest.requeststatuslabel != StateName.onholdother.name)) else: return FOIMinistryRequest.findfield('duedate', iaoassignee, ministryassignee) >= datetime.now().date() diff --git a/request-management-api/request_api/models/FOIRawRequestDocuments.py b/request-management-api/request_api/models/FOIRawRequestDocuments.py index 182cfcc69..7d75b4140 100644 --- a/request-management-api/request_api/models/FOIRawRequestDocuments.py +++ b/request-management-api/request_api/models/FOIRawRequestDocuments.py @@ -37,7 +37,21 @@ class FOIRawRequestDocument(db.Model): def getdocuments(cls,requestid, requestversion): documents = [] try: - sql = 'SELECT * FROM (SELECT DISTINCT ON (foidocumentid) raw2.created_at, raw.created_at as current_version_created_at, raw.foidocumentid, raw.filename, raw.documentpath, raw.category, raw.isactive, raw.createdby FROM "FOIRawRequestDocuments" raw join "FOIRawRequestDocuments" raw2 on (raw.foirequest_id = raw2.foirequest_id and raw2.version = 1) where raw.foirequest_id = :requestid and raw.foirequestversion_id = :requestversion and raw.isactive = true ORDER BY raw.foidocumentid DESC) AS list ORDER BY created_at DESC' + sql = ''' + SELECT * FROM ( + SELECT + DISTINCT ON (foidocumentid) + raw2.created_at, raw.created_at as current_version_created_at, raw.foidocumentid, raw.filename, raw.documentpath, raw.category, raw.isactive, raw.createdby + FROM "FOIRawRequestDocuments" raw + join "FOIRawRequestDocuments" raw2 + on (raw.documentpath = raw2.documentpath) + where raw.foirequest_id = :requestid + and raw.foirequestversion_id = :requestversion + and raw.isactive = true ORDER + BY raw.foidocumentid DESC, raw2.foidocumentid asc + ) AS list ORDER BY created_at DESC + ''' + rs = db.session.execute(text(sql), {'requestid': requestid, 'requestversion': requestversion}) for row in rs: diff --git a/request-management-api/request_api/models/FOIRequestNotificationUsers.py b/request-management-api/request_api/models/FOIRequestNotificationUsers.py index 9e80691ca..5be9219a2 100644 --- a/request-management-api/request_api/models/FOIRequestNotificationUsers.py +++ b/request-management-api/request_api/models/FOIRequestNotificationUsers.py @@ -274,7 +274,7 @@ def getgroupfilters(cls, groups): FOIRequests.assignedgroup == group, and_( FOIRequests.assignedministrygroup == group, - FOIRequests.requeststatuslabel.in_([StateName.callforrecords.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.name,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.tagging.name,StateName.readytoscan.name]) + FOIRequests.requeststatuslabel.in_([StateName.callforrecords.name,StateName.recordsreview.name,StateName.feeestimate.name,StateName.consult.name,StateName.ministrysignoff.name,StateName.onhold.name,StateName.deduplication.name,StateName.harmsassessment.name,StateName.response.name,StateName.tagging.name,StateName.readytoscan.name,StateName.onholdother.name]) ) ) ) diff --git a/request-management-api/request_api/resources/request.py b/request-management-api/request_api/resources/request.py index 8248453e2..d90a7098d 100644 --- a/request-management-api/request_api/resources/request.py +++ b/request-management-api/request_api/resources/request.py @@ -97,7 +97,7 @@ def post(requestid=None, actiontype=None): statuslabel = requestdata['requeststatuslabel'] if int(requestid) and str(requestid) != "-1" : status, statuslabel = rawrequestservice().getstatus(updaterequest) - if status not in [StateName.intakeinprogress.value, StateName.closed.value, StateName.redirect.value, StateName.peerreview.value, StateName.section5pending.value, StateName.appfeeowing.value]: + if status not in [StateName.intakeinprogress.value, StateName.closed.value, StateName.redirect.value, StateName.peerreview.value, StateName.section5pending.value, StateName.appfeeowing.value, StateName.onholdother.value]: raise ValueError('Invalid request state.') result = rawrequestservice().saverawrequestversion(updaterequest,requestid,assigneegroup,assignee,status,AuthHelper.getuserid(),assigneefirstname,assigneemiddlename,assigneelastname, statuslabel, actiontype) assignee = '' diff --git a/request-management-api/request_api/services/closereasonservice.py b/request-management-api/request_api/services/closereasonservice.py index 053aca842..5b799974c 100644 --- a/request-management-api/request_api/services/closereasonservice.py +++ b/request-management-api/request_api/services/closereasonservice.py @@ -5,4 +5,9 @@ class closereasonservice: def getclosereasons(self): """ Returns the active records """ - return CloseReason.getallclosereasons() \ No newline at end of file + return CloseReason.getallclosereasons() + + @staticmethod + def getclosereason(closereasonid): + """ Returns a specific close reason by ID """ + return CloseReason.getclosereason(closereasonid) \ No newline at end of file diff --git a/request-management-api/request_api/services/documentservice.py b/request-management-api/request_api/services/documentservice.py index b1e1c335d..7399abac1 100644 --- a/request-management-api/request_api/services/documentservice.py +++ b/request-management-api/request_api/services/documentservice.py @@ -110,15 +110,15 @@ def createministrydocumentversion(self, ministryrequestid, documentid, documents return FOIMinistryRequestDocument.createdocument(ministryrequestid, version, documentschema, userid) - def createrawdocumentversion(self, requestid, documentid, documentschema, userid): + def createrawdocumentversion(self, requestid, documentid, documentschema, userid): # updates existing raw request document with new attributes (i.e. name, category) version = self.__getversionforrequest(requestid, "rawrequest") document = FOIRawRequestDocument.getdocument(documentid) FOIRawRequestDocument.deActivaterawdocumentsversion(documentid, document['version'], userid) return FOIRawRequestDocument.createdocumentversion(requestid, version, self.__copydocumentproperties(document,documentschema,document['version']), userid) - def createrawrequestdocumentversion(self, requestid): + def createrawrequestdocumentversion(self, requestid): # creates new raw request document with new document id when request state changes newversion = self.__getversionforrequest(requestid,"rawrequest") - documents = self.getrequestdocuments(requestid, "rawrequest", newversion-1) + documents = FOIRawRequestDocument.getdocuments(requestid, newversion-1) documentarr = [] for document in documents: documentarr.append({"documentpath": document["documentpath"], "filename": document["filename"], "category": document['category'], "version": 1, "foirequest_id": requestid, "foirequestversion_id": newversion - 1, "createdby": document['createdby'], "created_at": document['created_at'] }) diff --git a/request-management-api/request_api/services/events/state.py b/request-management-api/request_api/services/events/state.py index d5b015d43..43a6fdd0a 100644 --- a/request-management-api/request_api/services/events/state.py +++ b/request-management-api/request_api/services/events/state.py @@ -11,6 +11,7 @@ import json from request_api.models.default_method_result import DefaultMethodResult from request_api.utils.enums import StateName +from request_api.services.closereasonservice import closereasonservice class stateevent: """ FOI Event management service @@ -63,7 +64,7 @@ def __createnotification(self, requestid, state, requesttype, userid): if state == StateName.callforrecords.value and requesttype == "ministryrequest": foirequest = notificationservice().getrequest(requestid, requesttype) _notificationtype = "Group Members" if foirequest['assignedministryperson'] is None else "State" - notification = self.__preparenotification(state) + notification = self.__preparenotification(state, requestid) if state == StateName.response.value and requesttype == "ministryrequest": signgoffapproval = FOIMinistryRequest().getrequest(requestid)['ministrysignoffapproval'] if signgoffapproval: @@ -90,8 +91,8 @@ def __createnotification(self, requestid, state, requesttype, userid): return DefaultMethodResult(True,'Notification added',requestid) return DefaultMethodResult(True,'No change',requestid) - def __preparenotification(self, state): - return self.__notificationmessage(state) + def __preparenotification(self, state, requestid): + return self.__notificationmessage(state, requestid) def __preparegroupmembernotification(self, state, requestid): if state == StateName.callforrecords.value: @@ -110,15 +111,32 @@ def __formatstate(self, state): return StateName.open.value if state == StateName.archived.value else state def __commentmessage(self, state, username, requesttype, requestid): - comment = username+' changed the state of the request to '+self.__formatstate(state) + comment = username + ' changed the state of the request to ' + self.__formatstate(state) + + if state == StateName.closed.value: + closereasonid = FOIMinistryRequest().getrequest(requestid).get('closereasonid') + close_reason = closereasonservice.getclosereason(closereasonid) + + if close_reason: + comment += f". The reason for the closure is {close_reason['name']}." + if state == StateName.response.value and requesttype == "ministryrequest": signgoffapproval = FOIMinistryRequest().getrequest(requestid)['ministrysignoffapproval'] if signgoffapproval: comment = comment + f". Approved by {signgoffapproval['approvername']}, {signgoffapproval['approvertitle']} on {signgoffapproval['approveddate']}" return comment - def __notificationmessage(self, state): - return 'Moved to '+self.__formatstate(state)+ ' State' + def __notificationmessage(self, state, requestid): + notification = 'Moved to ' + self.__formatstate(state) + ' State' + + if state == StateName.closed.value: + closereasonid = FOIMinistryRequest().getrequest(requestid).get('closereasonid') + close_reason = closereasonservice.getclosereason(closereasonid) + + if close_reason: + notification += f" as {close_reason['name']}." + + return notification def __notificationcfrmessage(self, requestid): metadata = FOIMinistryRequest.getmetadata(requestid) diff --git a/request-management-api/request_api/services/foirequest/requestservicegetter.py b/request-management-api/request_api/services/foirequest/requestservicegetter.py index b2dec36cc..2cc78f46c 100644 --- a/request-management-api/request_api/services/foirequest/requestservicegetter.py +++ b/request-management-api/request_api/services/foirequest/requestservicegetter.py @@ -268,7 +268,7 @@ def getonholdtransition(self, foiministryrequestid): onholddate = None transitions = FOIMinistryRequest.getrequeststatusById(foiministryrequestid) for entry in transitions: - if entry['requeststatuslabel'] == StateName.onhold.name: + if (entry['requeststatuslabel'] == StateName.onhold.name or entry['requeststatuslabel'] == StateName.onholdother.name): onholddate = datetimehandler().convert_to_pst(entry['created_at'],'%Y-%m-%d') else: if onholddate is not None: diff --git a/request-management-api/request_api/services/requestservice.py b/request-management-api/request_api/services/requestservice.py index 2bc99921a..8626742e8 100644 --- a/request-management-api/request_api/services/requestservice.py +++ b/request-management-api/request_api/services/requestservice.py @@ -119,7 +119,7 @@ def postpaymentstatetransition( ) def updateduedate( - self, requestid, ministryrequestid, offholddate, foirequestschema, nextstatename + self, requestid, ministryrequestid, offholddate, foirequestschema, nextstatename ): foirequest = self.getrequest(requestid, ministryrequestid) currentstatus = ( @@ -131,8 +131,10 @@ def updateduedate( # Check for Off Hold if ( currentstatus not in (None, "") - and currentstatus == StateName.onhold.value - and nextstatename != StateName.response.value + and ((currentstatus == StateName.onhold.value + and nextstatename != StateName.response.value) + or (currentstatus == StateName.onholdother.value + and nextstatename != StateName.open.value)) ): skipcalculation = self.__skipduedatecalculation( ministryrequestid, offholddate, currentstatus, nextstatename @@ -268,12 +270,12 @@ def calculateduedate(self, ministryrequestid, foirequest, paymentdate): def __skipduedatecalculation(self, ministryrequestid, offholddate, currentstatus="", nextstatename=""): - previousoffholddate = FOIMinistryRequest.getlastoffholddate(ministryrequestid) + previousoffholddate = FOIMinistryRequest.getlastoffholddate(ministryrequestid, currentstatus) if ( - currentstatus not in (None, "") - and currentstatus == StateName.onhold.value + (currentstatus not in (None, "") + and (currentstatus == StateName.onhold.value or currentstatus == StateName.onholdother.value) and nextstatename not in (None, "") - and currentstatus == nextstatename + and currentstatus == nextstatename) ): return True if previousoffholddate not in (None, ""): diff --git a/request-management-api/request_api/utils/enums.py b/request-management-api/request_api/utils/enums.py index e174ad3ba..8a87ab502 100644 --- a/request-management-api/request_api/utils/enums.py +++ b/request-management-api/request_api/utils/enums.py @@ -170,6 +170,7 @@ class StateName(Enum): readytoscan = "Ready to Scan" appfeeowing = "App Fee Owing" section5pending = "Section 5 Pending" + onholdother = "On Hold - Other" class CacheUrls(Enum): keycloakusers= "/api/foiassignees" programareas= "/api/foiflow/programareas"