Skip to content

Commit 8e96630

Browse files
authored
Merge pull request #6041 from bcgov/release/FOIMOD-release22.2-to-main
Release 22.2 DEV <> MAIN
2 parents 634531a + 838822b commit 8e96630

27 files changed

+450
-14
lines changed

forms-flow-web/src/apiManager/endpoints/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ const API = {
105105
FOI_GET_CFR_FORM: `${FOI_BASE_API_URL}/api/foicfrfee/ministryrequest/<ministryrequestid>`,
106106
FOI_POST_CFR_FORM: `${FOI_BASE_API_URL}/api/foicfrfee/foirequest/<requestid>/ministryrequest/<ministryrequestid>`,
107107
FOI_POST_CFR_FORM_IAO: `${FOI_BASE_API_URL}/api/foicfrfee/foirequest/<requestid>/ministryrequest/<ministryrequestid>/sanction`,
108+
109+
FOI_POST_CFR_INVOICE: `${FOI_BASE_API_URL}/api/foirequestinvoice/foicfrfee/<foicfrfeeid>`,
108110

109111
FOI_GET_APPLICATION_FEES_FORM: `${FOI_BASE_API_URL}/api/foiapplicationfee/foirequest/<requestid>/ministryrequest/<ministryrequestid>`,
110112
FOI_POST_APPLICATION_FEES_FORM: `${FOI_BASE_API_URL}/api/foiapplicationfee/foirequest/<requestid>/ministryrequest/<ministryrequestid>`,

forms-flow-web/src/apiManager/services/FOI/foiCFRFormServices.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,23 @@ export const saveCFRForm = (
8989
});
9090
};
9191

92+
export const saveInvoice = (invoiceData, isMinistry, dispatch) => {
93+
if (isMinistry) {
94+
dispatch(serviceActionError("Ministry User cannot generate invoice"));
95+
return Promise.reject(new Error("Ministry User cannot generate invoice"));
96+
}
97+
const baseUrl = API.FOI_POST_CFR_INVOICE;
98+
const apiUrl = replaceUrl(baseUrl, "<foicfrfeeid>", invoiceData.cfrFeeData.cfrfeeid);
99+
return httpPOSTRequest(apiUrl, invoiceData)
100+
.then((res) => {
101+
if (!res.data) {
102+
dispatch(serviceActionError("Empty response body"));
103+
throw new Error("Empty response body");
104+
}
105+
return res.data;
106+
})
107+
.catch((error) => {
108+
console.error("An error occured while trying to save and generate CFR Fee Invoice", error);
109+
catchError(error, dispatch);
110+
});
111+
}

forms-flow-web/src/components/FOI/customComponents/ContactApplicant/DocEditor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ export const DocEditor = ({
216216
container.documentEditor.editor.toggleTextAlignment('Left');
217217
container.documentEditor.selection.cellFormat.preferredWidthType = 'Point';
218218
container.documentEditor.selection.cellFormat.preferredWidth = 105;
219-
container.documentEditor.editor.insertText("Mailing Address:\nPO Box 9569 Stn Prov Govt\nVictoria, BC V8W 9K1");
219+
container.documentEditor.editor.insertText("Mailing Address:\nPO Box 9569 Stn Prov Govt\nVictoria, BC V8W 9V1");
220220

221221
container.documentEditor.selection.moveNextPosition();
222222
container.documentEditor.editor.applyBorders({type: 'NoBorder'});

forms-flow-web/src/components/FOI/customComponents/ContactApplicant/util.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ const displayApplicantConsentSection = (requestExtensions:any, requestDetails:an
379379
<p style="text-align: center;"><span style="font-size: 13px; ">&nbsp;</span></p>
380380
<p style="text-align: center;"><span style="font-size: 13px; ">&nbsp;</span></p>
381381
<p><span style='font-size:13px;font-family:"BC Sans";'></span><span style='font-size:13px;font-family:"BC Sans";'>${requestDetails.firstName}&nbsp;${requestDetails.lastName},&nbsp;</span><span style='font-size:13px;font-family:"BC Sans";'>IAO Position Title</span><span style="font-size:11px;">&nbsp;</span></p>
382-
<p><span style='font-size:13px;font-family:"BC Sans";'>Information Access Operations</span></p>
382+
<p><span style='font-size:13px;font-family:"BC Sans";'>FOI Operations</span></p>
383383
<p><span style='font-size:13px;font-family:"BC Sans";'>&nbsp;</span></p>
384384
<p><span style='font-size:13px;font-family:"BC Sans";'>Enclosure</span></p><strong><span style='font-size:13px;font-family:"BC Sans";'><br>&nbsp;</span></strong>
385385
<p><strong><span style='font-size:13px;font-family:"BC Sans";'>&nbsp;</span></strong></p>
@@ -408,7 +408,7 @@ const displayApplicantConsentSection = (requestExtensions:any, requestDetails:an
408408
<p style="text-align: center;"><span style="font-size: 13px; ">&nbsp;</span></p>
409409
<p style="text-align: center;"><span style="font-size: 13px; ">&nbsp;</span></p>
410410
<p><span style='font-size:13px;font-family:"BC Sans";'></span><span style='font-size:13px;font-family:"BC Sans";'>${requestDetails.firstName}&nbsp;${requestDetails.lastName},&nbsp;</span><span style='font-size:13px;font-family:"BC Sans";'>IAO Position Title</span><span style="font-size:11px;">&nbsp;</span></p>
411-
<p><span style='font-size:13px;font-family:"BC Sans";'>Information Access Operations</span></p>
411+
<p><span style='font-size:13px;font-family:"BC Sans";'>FOI Operations</span></p>
412412
<p><span style='font-size:13px;font-family:"BC Sans";'>&nbsp;</span></p>
413413
<p><span style='font-size:13px;font-family:"BC Sans";'>Enclosure</span></p><strong><span style='font-size:13px;font-family:"BC Sans";'><br>&nbsp;</span></strong>
414414
`;

forms-flow-web/src/components/FOI/customComponents/Fees/BottomButtonGroup.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import Tooltip from "@mui/material/Tooltip";
2+
13
export const BottomButtonGroup = ({
24
save,
35
validateFields,
@@ -7,8 +9,12 @@ export const BottomButtonGroup = ({
79
isNewCFRForm,
810
isMinistry,
911
setCreateModalOpen,
10-
disableNewCfrFormBtn
12+
disableNewCfrFormBtn,
13+
handleGenerateInvoice,
14+
cfrStatus,
15+
isProcessingFeeSubTab,
1116
}: any) => {
17+
const generateInvoiceDisabled: boolean = cfrStatus !== "approved";
1218
return (
1319
<div className="foi-bottom-button-group cfrform">
1420
<button
@@ -32,6 +38,29 @@ export const BottomButtonGroup = ({
3238
>
3339
+ Create New Processing Fee Form
3440
</button>}
41+
{isProcessingFeeSubTab && !isMinistry &&
42+
<Tooltip
43+
title={
44+
generateInvoiceDisabled &&
45+
<div style={{ fontSize: "10px" }}>
46+
Invoice generation disabled. Please change the 'Processing Fee Status' field to 'Approved'
47+
</div>
48+
}
49+
enterDelay={1000}
50+
leaveDelay={200}
51+
>
52+
<button
53+
type="button"
54+
className="col-lg-4 btn btn-bottom btn-save"
55+
id="btncfrinvoice"
56+
onClick={() => {handleGenerateInvoice()}}
57+
color="primary"
58+
disabled={generateInvoiceDisabled}
59+
>
60+
Generate Invoice
61+
</button>
62+
</Tooltip>
63+
}
3564
</div>
3665
)
3766
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import Dialog from '@material-ui/core/Dialog';
2+
import DialogActions from '@material-ui/core/DialogActions';
3+
import DialogContent from '@material-ui/core/DialogContent';
4+
import DialogContentText from '@material-ui/core/DialogContentText';
5+
import DialogTitle from '@material-ui/core/DialogTitle';
6+
import CloseIcon from '@material-ui/icons/Close';
7+
import IconButton from '@material-ui/core/IconButton';
8+
9+
export const GenerateInvoiceModal = ({
10+
modalOpen,
11+
handleClose,
12+
handleSave,
13+
cfrFees,
14+
}) => {
15+
const isAcutalFees = cfrFees?.actualtotaldue > 0;
16+
const modalTitle = isAcutalFees ? "Oustanding Fee Invoice" : "Fee Estimate Invoice";
17+
const modalMessage = `Would you like to generate an invoice? This will generate ${isAcutalFees ? 'an outstanding fee' : 'a fee estimate'} invoice, which will be based on your ${isAcutalFees ? "actual" : "estimated"} hours. Please ensure your ${isAcutalFees ? "actual" : "estimated"} hours are correct prior to creating the invoice.`;
18+
return (
19+
<>
20+
<div className="state-change-dialog">
21+
<Dialog
22+
open={modalOpen}
23+
onClose={handleClose}
24+
aria-labelledby="state-change-dialog-title"
25+
aria-describedby="state-change-dialog-description"
26+
maxWidth={'md'}
27+
fullWidth={true}
28+
>
29+
<DialogTitle disableTypography id="state-change-dialog-title">
30+
<h2 className="state-change-header">{modalTitle}</h2>
31+
<IconButton className="title-col3" onClick={handleClose}>
32+
<i className="dialog-close-button">Close</i>
33+
<CloseIcon />
34+
</IconButton>
35+
</DialogTitle>
36+
<DialogContent className={'dialog-content-nomargin'}>
37+
<DialogContentText id="state-change-dialog-description" component={'span'}>
38+
<span className="confirmation-message">
39+
{modalMessage}
40+
</span>
41+
</DialogContentText>
42+
</DialogContent>
43+
<DialogActions>
44+
<button
45+
className={`btn-bottom btn-save btn`}
46+
onClick={handleSave}
47+
>
48+
Continue
49+
</button>
50+
<button className="btn-bottom btn-cancel" onClick={handleClose}>
51+
Cancel
52+
</button>
53+
</DialogActions>
54+
</Dialog>
55+
</div>
56+
</>
57+
);
58+
}

forms-flow-web/src/components/FOI/customComponents/Fees/index.tsx

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Chip from '@mui/material/Chip';
66
import { errorToast, isMinistryLogin, readUploadedFileAsBytes } from "../../../../helper/FOI/helper";
77
import type { params, CFRFormData, ApplicationFeeFormData } from './types';
88
import foiFees from '../../../../constants/FOI/foiFees.json';
9-
import { fetchCFRForm, saveCFRForm } from "../../../../apiManager/services/FOI/foiCFRFormServices";
9+
import { fetchCFRForm, saveCFRForm, saveInvoice} from "../../../../apiManager/services/FOI/foiCFRFormServices";
1010
import _ from 'lodash';
1111
import { toast } from "react-toastify";
1212
import { StateEnum } from '../../../../constants/FOI/statusEnum';
@@ -19,10 +19,12 @@ import { BottomButtonGroup } from './BottomButtonGroup';
1919
import { CFRFormStatus } from './CFRFormStatus';
2020
import { FeesSubtabValues } from './types';
2121
import { fetchApplicationFeeForm, saveApplicationFeeForm } from '../../../../apiManager/services/FOI/foiApplicationFeeFormServices';
22-
import { completeMultiPartUpload, postFOIS3DocumentPreSignedUrl, saveFilesinS3 } from '../../../../apiManager/services/FOI/foiOSSServices';
22+
import { completeMultiPartUpload, postFOIS3DocumentPreSignedUrl, saveFilesinS3, downloadFileFromS3, getFOIS3DocumentPreSignedUrl } from '../../../../apiManager/services/FOI/foiOSSServices';
2323
import FOI_COMPONENT_CONSTANTS from '../../../../constants/FOI/foiComponentConstants';
2424
import { StatusChangeDialog } from './StatusChangeDialog';
2525
import { OSS_S3_CHUNK_SIZE } from "../../../../constants/constants";
26+
import { saveAs } from "file-saver";
27+
import { GenerateInvoiceModal } from './GenerateInvoiceModal';
2628

2729
export const Fees = ({
2830
requestNumber,
@@ -647,6 +649,11 @@ export const Fees = ({
647649
const handleStatusChangeModalClose = () => {
648650
setStatusChangeModalOpen(false);
649651
}
652+
653+
const [invoiceModalOpen, setInvoiceModalOpen] = useState(false);
654+
const handleInvoiceModalClose = () => {
655+
setInvoiceModalOpen(false);
656+
}
650657

651658
const disableNewCfrFormBtn = () => {
652659
return(CFRFormData?.formStatus !== 'approved' || requestState === StateEnum.peerreview.name || (requestState !== StateEnum.callforrecords.name &&
@@ -700,6 +707,106 @@ export const Fees = ({
700707
requestDetails?.requestType ===
701708
FOI_COMPONENT_CONSTANTS.REQUEST_TYPE_GENERAL)
702709
}
710+
const formatApplicantAddress = (requestDetails : any) => {
711+
if (!requestDetails) {
712+
console.error("requestDetails do not exist.");
713+
}
714+
const primaryAddress = requestDetails.address? requestDetails.address : "";
715+
const secondaryAddress = requestDetails.addressSecondary ? requestDetails.addressSecondary : "";
716+
const city = requestDetails.city ? requestDetails.city : "";
717+
const province = requestDetails.province ? requestDetails.province : "";
718+
const country = requestDetails.country ? requestDetails.country : "";
719+
const postalCode = requestDetails.postal ? requestDetails.postal : "";
720+
721+
if (secondaryAddress) {
722+
return `${primaryAddress}\n${secondaryAddress}\n${city} ${province} ${country} ${postalCode}`;
723+
}
724+
return `${primaryAddress}\n${city} ${province} ${country} ${postalCode}`;
725+
}
726+
727+
const handleGenerateInvoice = () => {
728+
setInvoiceModalOpen(true);
729+
}
730+
const handleInvoiceSave = () => {
731+
genreateInvoice();
732+
setInvoiceModalOpen(false);
733+
}
734+
const genreateInvoice = async () => {
735+
const toastID = toast.loading("Downloading Invoice (0%)");
736+
const invoiceData = {
737+
applicantName: `${requestDetails.firstName} ${requestDetails.lastName}`,
738+
applicantAddress: formatApplicantAddress(requestDetails),
739+
cfrFeeData: initialCFRState
740+
};
741+
const apiResponse = await saveInvoice(invoiceData, isMinistry, dispatch);
742+
if (apiResponse?.status === 201) {
743+
getFOIS3DocumentPreSignedUrl(
744+
apiResponse.invoice.split("/").slice(4).join("/"),
745+
ministryId,
746+
dispatch,
747+
(err: any, res: any) => {
748+
if (!err) {
749+
downloadFileFromS3(
750+
{filepath: res},
751+
(_err: any, response: any) => {
752+
const blob = new Blob([response.data], {type: "application/octet-stream"});
753+
saveAs(blob, `${requestDetails.axisRequestId} - Invoice.pdf`);
754+
toast.update(toastID, {
755+
render: "Download complete",
756+
type: "success",
757+
className: "file-upload-toast",
758+
isLoading: false,
759+
autoClose: 3000,
760+
hideProgressBar: true,
761+
closeOnClick: true,
762+
pauseOnHover: true,
763+
draggable: true,
764+
closeButton: true,
765+
});
766+
},
767+
(progressEvent : any) => {
768+
if(progressEvent.total > 0){
769+
toast.update(toastID, {
770+
render:
771+
"Downloading file (" +Math.floor(
772+
(progressEvent.loaded / progressEvent.total) * 100) +"%)",
773+
isLoading: true,
774+
});
775+
}
776+
}
777+
)
778+
} else {
779+
toast.update(toastID, {
780+
render: "Invoice download failed",
781+
type: "error",
782+
className: "file-upload-toast",
783+
isLoading: false,
784+
autoClose: 3000,
785+
hideProgressBar: true,
786+
closeOnClick: true,
787+
pauseOnHover: true,
788+
draggable: true,
789+
closeButton: true,
790+
});
791+
return;
792+
}
793+
})
794+
} else {
795+
toast.update(toastID, {
796+
render: "Invoice upload failed",
797+
type: "error",
798+
className: "file-upload-toast",
799+
isLoading: false,
800+
autoClose: 3000,
801+
hideProgressBar: true,
802+
closeOnClick: true,
803+
pauseOnHover: true,
804+
draggable: true,
805+
closeButton: true,
806+
});
807+
return;
808+
}
809+
}
703810

704811
return (
705812
<div className="foi-review-container">
@@ -816,10 +923,19 @@ export const Fees = ({
816923
isMinistry={isMinistry}
817924
setCreateModalOpen={setCreateModalOpen}
818925
disableNewCfrFormBtn={disableNewCfrFormBtn}
926+
handleGenerateInvoice={handleGenerateInvoice}
927+
cfrStatus={initialCFRState.status}
928+
isProcessingFeeSubTab={selectedSubtab == FeesSubtabValues.PROCESSINGFEE}
819929
/>
820930
</div>
821931
</div>
822932
</Box>
933+
<GenerateInvoiceModal
934+
modalOpen={invoiceModalOpen}
935+
handleClose={handleInvoiceModalClose}
936+
handleSave={handleInvoiceSave}
937+
cfrFees={initialCFRState.feedata}
938+
/>
823939
<StateChangeDialog
824940
modalOpen={modalOpen}
825941
handleClose={handleClose}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""New Table: FOIRequestInvoices
2+
3+
Revision ID: 8253475faad7
4+
Revises: 2d8cf3fb689f
5+
Create Date: 2025-12-19 14:28:29.852354
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
from sqlalchemy.dialects import postgresql
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '8253475faad7'
14+
down_revision = '2d8cf3fb689f'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# Create foirequestinvoice table
21+
op.create_table(
22+
'FOIRequestInvoices',
23+
sa.Column('invoiceid', sa.Integer(), autoincrement=True, nullable=False),
24+
sa.Column('foirequestcfrfee_id', sa.Integer(), nullable=False),
25+
sa.Column('foirequestcfrfeeversion_id', sa.Integer(), nullable=False),
26+
sa.Column('documentpath', sa.Text(), nullable=False),
27+
sa.Column('filename', sa.String(length=500),nullable=False),
28+
sa.Column('applicant_name', sa.String(length=120),nullable=False),
29+
sa.Column('applicant_address', sa.String(length=200),nullable=False),
30+
sa.Column('created_at', sa.DateTime(), nullable=False),
31+
sa.Column('created_by', sa.String(length=120), nullable=False),
32+
sa.ForeignKeyConstraint(['foirequestcfrfee_id','foirequestcfrfeeversion_id'], ['FOIRequestCFRFees.cfrfeeid', 'FOIRequestCFRFees.version']),
33+
sa.PrimaryKeyConstraint('invoiceid')
34+
)
35+
36+
#Create foirequestinvoice template
37+
op.execute('''INSERT INTO public."DocumentTypes" (document_type_name, description) VALUES ('cfr_fee_invoice', 'CFR Fee Invoice'); ''')
38+
op.execute('''INSERT INTO public."DocumentTemplates" (extension, document_type_id) SELECT 'docx', document_type_id FROM public."DocumentTypes" WHERE document_type_name = 'cfr_fee_invoice';''')
39+
40+
41+
def downgrade():
42+
op.execute('''DELETE FROM public."DocumentTemplates" WHERE document_type_id IN (SELECT document_type_id FROM public."DocumentTypes" WHERE document_type_name = 'cfr_fee_invoice');''')
43+
op.execute('''DELETE FROM public."DocumentTypes" WHERE document_type_name = 'cfr_fee_invoice';''')
44+
op.drop_table("FOIRequestInvoices")

request-management-api/request_api/email_templates/acknowledgement_letter.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
<p style="text-align: center;"><span style="font-size: 13px; ">&nbsp;</span></p>
3838
<p><span style='font-size:13px;font-family:"BC Sans";'></span><span style='font-size:13px;font-family:"BC Sans";'>{{assignedToFirstName}} {{assignedToLastName}}</span><span style="font-size:11px;">&nbsp;</span></p>
3939
<p><span style='font-size:13px;font-family:"BC Sans";'>Consolidated Intake</span></p>
40-
<p><span style='font-size:13px;font-family:"BC Sans";'>Information Access Operations</span></p>
40+
<p><span style='font-size:13px;font-family:"BC Sans";'>FOI Operations</span></p>
4141
<p><span style='font-size:13px;font-family:"BC Sans";'>&nbsp;</span></p>
4242
<p><span style='font-size:13px;font-family:"BC Sans";'>Enclosure</span></p><strong><span style='font-size:13px;font-family:"BC Sans";'><br>&nbsp;</span></strong>
4343

request-management-api/request_api/email_templates/fee_payment_confirmation_full.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<p>Dear {{firstName}} {{lastName}},</p>
2-
<p>Information Access Operations has received your payment for FOI request #{{axisRequestId}} on {{ cfrfee.feedata.paymentdate| formatdate('%B %d, %Y')}}.</p>
2+
<p>FOI Operations has received your payment for FOI request #{{axisRequestId}} on {{ cfrfee.feedata.paymentdate| formatdate('%B %d, %Y')}}.</p>
33
<p>FOI Request # {{axisRequestId}} is now due on {{dueDate| formatdate('%B %d, %Y')}}.</p>
44
<p>You have paid the full estimate of <b>$ {{cfrfee.feedata.amountpaid}} </b> on your request. </p>
55
<p>Fees are subject to changes based off of the actual amount of time it takes the Ministry to gather records for your request. If there are any additional fees, you will NOT receive your records until the remaining balance is paid in full.</p>

0 commit comments

Comments
 (0)