diff --git a/api/reviewer_api/models/DocumentMaster.py b/api/reviewer_api/models/DocumentMaster.py index cca2050ec..cc1e0a045 100644 --- a/api/reviewer_api/models/DocumentMaster.py +++ b/api/reviewer_api/models/DocumentMaster.py @@ -48,7 +48,7 @@ def getdocumentmaster(cls, ministryrequestid): and dm.documentmasterid not in (select distinct d.documentmasterid from "DocumentMaster" d , "DocumentDeleted" dd where d.filepath like dd.filepath||'%' and d.ministryrequestid = dd.ministryrequestid and d.ministryrequestid =:ministryrequestid) - order by da.attributes->>'lastmodified' DESC""" + order by da.attributes->>'lastmodified' DESC, da.attributeid ASC""" rs = db.session.execute(text(sql), {'ministryrequestid': ministryrequestid}) for row in rs: # if row["documentmasterid"] not in deleted: diff --git a/api/reviewer_api/resources/document.py b/api/reviewer_api/resources/document.py index 6373554af..2cd9b7d8c 100644 --- a/api/reviewer_api/resources/document.py +++ b/api/reviewer_api/resources/document.py @@ -22,7 +22,7 @@ from reviewer_api.tracer import Tracer from reviewer_api.utils.util import cors_preflight, allowedorigins, getrequiredmemberships from reviewer_api.exceptions import BusinessException -from reviewer_api.schemas.document import FOIRequestDeleteRecordsSchema, FOIRequestUpdateRecordsSchema, DocumentDeletedPage +from reviewer_api.schemas.document import FOIRequestDeleteRecordsSchema, FOIRequestUpdateRecordsSchema, DocumentDeletedPage, FOIRequestUpdateRecordPersonalAttributesSchema import json import requests import logging @@ -70,6 +70,7 @@ class UpdateDocumentAttributes(Resource): def post(): try: payload = request.get_json() + # print("payload: ", payload) payload = FOIRequestUpdateRecordsSchema().load(payload) result = documentservice().updatedocumentattributes(payload, AuthHelper.getuserid()) return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 @@ -78,6 +79,28 @@ def post(): except BusinessException as exception: return {'status': exception.status_code, 'message':exception.message}, 500 +@cors_preflight('POST,OPTIONS') +@API.route('/document/update/personal') +class UpdateDocumentPersonalAttributes(Resource): + """Add document to deleted list. + """ + @staticmethod + @TRACER.trace() + @cross_origin(origins=allowedorigins()) + @auth.require + @auth.ismemberofgroups(getrequiredmemberships()) + def post(): + try: + payload = request.get_json() + # print("payload personal: ", payload) + payload = FOIRequestUpdateRecordPersonalAttributesSchema().load(payload) + result = documentservice().updatedocumentpersonalattributes(payload, AuthHelper.getuserid()) + return {'status': result.success, 'message':result.message,'id':result.identifier} , 200 + except KeyError as error: + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except BusinessException as exception: + return {'status': exception.status_code, 'message':exception.message}, 500 + @cors_preflight('GET,OPTIONS') @API.route('/document/') class GetDocuments(Resource): diff --git a/api/reviewer_api/resources/foiflowmasterdata.py b/api/reviewer_api/resources/foiflowmasterdata.py index 924910634..bcf1892a4 100644 --- a/api/reviewer_api/resources/foiflowmasterdata.py +++ b/api/reviewer_api/resources/foiflowmasterdata.py @@ -38,10 +38,14 @@ from reviewer_api.services.documentservice import documentservice from reviewer_api.utils.constants import FILE_CONVERSION_FILE_TYPES +import requests +import logging + API = Namespace( "FOI Flow Master Data", description="Endpoints for FOI Flow master data" ) TRACER = Tracer.get_instance() +CUSTOM_KEYERROR_MESSAGE = "Key error has occured: " s3host = os.getenv("OSS_S3_HOST") s3region = os.getenv("OSS_S3_REGION") @@ -49,6 +53,8 @@ imageextensions = [".png", ".jpg", ".jpeg", ".gif"] +requestapiurl = os.getenv("FOI_REQ_MANAGEMENT_API_URL") +requestapitimeout = os.getenv("FOI_REQ_MANAGEMENT_API_TIMEOUT") @cors_preflight("GET,OPTIONS") @API.route("/foiflow/oss/presigned/") @@ -166,6 +172,8 @@ class FOIFlowS3PresignedRedline(Resource): def post(ministryrequestid, redactionlayer="redline", layertype="redline"): try: data = request.get_json() + # print("data!:",data) + requesttype = data["requestType"] documentmapper = redactionservice().getdocumentmapper( data["divdocumentList"][0]["documentlist"][0]["filepath"].split("/")[3] ) @@ -192,11 +200,16 @@ def post(ministryrequestid, redactionlayer="redline", layertype="redline"): packagetype = "redline" if redactionlayer == "oipc": packagetype = "oipcreview" if layertype == "oipcreview" else "oipcredline" - + + #check if is single redline package + is_single_redline = is_single_redline_package(_bcgovcode, packagetype, requesttype) + # print("is_single_redline:",is_single_redline) + #print("divdocumentList:",data["divdocumentList"]) for div in data["divdocumentList"]: if len(div["documentlist"]) > 0: + # print("filepathlist:" , div["documentlist"][0]["filepath"]) filepathlist = div["documentlist"][0]["filepath"].split("/")[4:] - if is_single_redline_package(_bcgovcode, packagetype) == False: + if is_single_redline == False: division_name = div["divisionname"] # generate save url for stitched file filepath_put = "{0}/{2}/{1}/{0} - {2} - {1}.pdf".format( @@ -254,14 +267,17 @@ def post(ministryrequestid, redactionlayer="redline", layertype="redline"): ) elif len(div["incompatableList"]) > 0: filepathlist = div["incompatableList"][0]["filepath"].split("/")[4:] - if is_single_redline_package(_bcgovcode, packagetype) and singlepkgpath is None : + if is_single_redline and singlepkgpath is None : if len(div["documentlist"]) > 0 or len(div["incompatableList"]) > 0: div = data["divdocumentList"][0] filepathlist = div["documentlist"][0]["filepath"].split("/")[4:] + # print("filepathlist:",filepathlist) filename = filepathlist[0] + # print("filename1:",filename) filepath_put = "{0}/{2}/{1}-Redline.pdf".format( filepathlist[0],filename, packagetype ) + # print("filepath_put:",filepath_put) s3path_save = s3client.generate_presigned_url( ClientMethod="get_object", Params={ @@ -272,10 +288,11 @@ def post(ministryrequestid, redactionlayer="redline", layertype="redline"): ExpiresIn=3600, HttpMethod="PUT", ) + # print("s3path_save:",s3path_save) singlepkgpath = s3path_save data["s3path_save"] = s3path_save - if is_single_redline_package(_bcgovcode, packagetype): + if is_single_redline: for div in data["divdocumentList"]: if len(div["documentlist"]) > 0: documentlist_copy = div["documentlist"][:] @@ -294,7 +311,7 @@ def post(ministryrequestid, redactionlayer="redline", layertype="redline"): data["requestnumber"] = filepathlist[0] data["bcgovcode"] = _bcgovcode - data["issingleredlinepackage"] = "Y" if is_single_redline_package(_bcgovcode, packagetype) else "N" + data["issingleredlinepackage"] = "Y" if is_single_redline else "N" return json.dumps(data), 200 except BusinessException as exception: return {"status": exception.status_code, "message": exception.message}, 500 @@ -373,3 +390,40 @@ def get(): return json.dumps({"license": webviewerlicense}), 200 except BusinessException as exception: return {"status": exception.status_code, "message": exception.message}, 500 + + +@cors_preflight('GET,OPTIONS') +@API.route('/foiflow/personalattributes/') +class GetPersonalTags(Resource): + """Get document list. + """ + @staticmethod + @TRACER.trace() + @cross_origin(origins=allowedorigins()) + @auth.require + @auth.ismemberofgroups(getrequiredmemberships()) + def get(bcgovcode): + try: + attributes = ["people", "filetypes", "volumes", "personaltag"] + personalattributes = {} + + for attribute in attributes: + response = requests.request( + method='GET', + url= requestapiurl + "/api/foiflow/divisions/" + bcgovcode + "/true/" + attribute, + headers={'Authorization': AuthHelper.getauthtoken(), 'Content-Type': 'application/json'}, + timeout=float(requestapitimeout) + ) + response.raise_for_status() + # get request status + jsonobj = response.json() + personalattributes.update(jsonobj) + + return json.dumps(personalattributes), 200 + except KeyError as error: + return {'status': False, 'message': CUSTOM_KEYERROR_MESSAGE + str(error)}, 400 + except BusinessException as exception: + return {'status': exception.status_code, 'message':exception.message}, 500 + except requests.exceptions.HTTPError as err: + logging.error("Request Management API returned the following message: {0} - {1}".format(err.response.status_code, err.response.text)) + return {'status': False, 'message': err.response.text}, err.response.status_code \ No newline at end of file diff --git a/api/reviewer_api/resources/redaction.py b/api/reviewer_api/resources/redaction.py index 9e67940f8..4b633b1f3 100644 --- a/api/reviewer_api/resources/redaction.py +++ b/api/reviewer_api/resources/redaction.py @@ -39,7 +39,7 @@ SectionSchema, ) from reviewer_api.schemas.redline import RedlineSchema -from reviewer_api.schemas.finalpackage import FinalPackageSchema +from reviewer_api.schemas.finalpackage import FinalPackageSchema, MCFFinalPackageSchema API = Namespace( "Document and annotations", @@ -341,7 +341,12 @@ class SaveFinalPackage(Resource): def post(): try: requestjson = request.get_json() - finalpackageschema = FinalPackageSchema().load(requestjson) + print("\nrequestjson:",requestjson) + if(requestjson['bcgovcode'] == "mcf"): + finalpackageschema = MCFFinalPackageSchema().load(requestjson) + else: + finalpackageschema = FinalPackageSchema().load(requestjson) + print("\nfinalpackageschema:",finalpackageschema) result = redactionservice().triggerdownloadredlinefinalpackage( finalpackageschema, AuthHelper.getuserinfo() ) diff --git a/api/reviewer_api/schemas/document.py b/api/reviewer_api/schemas/document.py index 67973e80b..a3acf80c8 100644 --- a/api/reviewer_api/schemas/document.py +++ b/api/reviewer_api/schemas/document.py @@ -24,6 +24,17 @@ class Meta: # pylint: disable=too-few-public-methods unknown = EXCLUDE divisionid = fields.Int(data_key="divisionid",allow_none=False) +class PersonalAttributesSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + person = fields.Str(data_key="person",allow_none=True) + filetype = fields.Str(data_key="filetype",allow_none=True) + volume = fields.Str(data_key="volume",allow_none=True) + trackingid = fields.Str(data_key="trackingid",allow_none=True) + personaltag = fields.Str(data_key="personaltag",allow_none=True) + class FOIRequestUpdateRecordsSchema(Schema): class Meta: # pylint: disable=too-few-public-methods """Exclude unknown fields in the deserialized output.""" @@ -34,6 +45,14 @@ class Meta: # pylint: disable=too-few-public-methods divisions = fields.Nested(DivisionSchema,many=True,validate=validate.Length(min=1),allow_none=True,required=False) rotatedpages = fields.Dict(keys=fields.Str,values=fields.Int,allow_none=True,required=False) # {page: rotation in degrees} +class FOIRequestUpdateRecordPersonalAttributesSchema(Schema): + class Meta: # pylint: disable=too-few-public-methods + """Exclude unknown fields in the deserialized output.""" + + unknown = EXCLUDE + documentmasterids = fields.List(fields.Integer(),data_key="documentmasterids",allow_none=False) + ministryrequestid = fields.Int(data_key="ministryrequestid",allow_none=True) + personalattributes = fields.Nested(PersonalAttributesSchema,required=False,allow_none=False) class DocumentPage(Schema): class Meta: # pylint: disable=too-few-public-methods diff --git a/api/reviewer_api/schemas/finalpackage.py b/api/reviewer_api/schemas/finalpackage.py index 702aeb858..94f5f0b4b 100644 --- a/api/reviewer_api/schemas/finalpackage.py +++ b/api/reviewer_api/schemas/finalpackage.py @@ -4,7 +4,8 @@ class FileSchema(Schema): s3uripath = fields.Str(data_key="s3uripath", allow_none=False) filename = fields.Str(data_key="filename", allow_none=False) - + recordname = fields.Str(data_key="recordname", allow_none=False) + documentid = fields.Int(data_key="documentid", allow_none=True) class AttributeSchema(Schema): files = fields.Nested(FileSchema, many=True, required=True, allow_none=False) @@ -12,7 +13,8 @@ class AttributeSchema(Schema): class SummaryPkgSchema(Schema): divisionid = fields.Int(data_key="divisionid", allow_none=True) - documentids = fields.List(fields.Int()) + recordname = fields.Str(data_key="recordname", allow_none=True) + documentids = fields.List(fields.Int(), allow_none=True) class SummarySchema(Schema): pkgdocuments = fields.List(fields.Nested(SummaryPkgSchema, allow_none=True)) @@ -27,4 +29,28 @@ class FinalPackageSchema(Schema): AttributeSchema, many=True, required=True, allow_none=False ) summarydocuments = fields.Nested(SummarySchema, allow_none=True) + redactionlayerid = fields.Int(data_key="redactionlayerid", allow_none=False) + +class SummaryRecordSchema(Schema): + recordname = fields.Str(data_key="recordname", allow_none=True) + documentids = fields.List(fields.Int(), allow_none=True) + +class MCFSummaryPkgSchema(Schema): + divisionid = fields.Int(data_key="divisionid", allow_none=True) + documentids = fields.List(fields.Int(), allow_none=True) + records = fields.List(fields.Nested(SummaryRecordSchema), allow_none=True) + +class MCFSummarySchema(Schema): + pkgdocuments = fields.List(fields.Nested(MCFSummaryPkgSchema, allow_none=True)) + sorteddocuments = fields.List(fields.Int()) + +class MCFFinalPackageSchema(Schema): + ministryrequestid = fields.Str(data_key="ministryrequestid", allow_none=False) + category = fields.Str(data_key="category", allow_none=False) + requestnumber = fields.Str(data_key="requestnumber", allow_none=False) + bcgovcode = fields.Str(data_key="bcgovcode", allow_none=False) + attributes = fields.Nested( + AttributeSchema, many=True, required=True, allow_none=False + ) + summarydocuments = fields.Nested(MCFSummarySchema, allow_none=True) redactionlayerid = fields.Int(data_key="redactionlayerid", allow_none=False) \ No newline at end of file diff --git a/api/reviewer_api/services/documentservice.py b/api/reviewer_api/services/documentservice.py index 301c09c1c..c11dd6db9 100644 --- a/api/reviewer_api/services/documentservice.py +++ b/api/reviewer_api/services/documentservice.py @@ -430,6 +430,52 @@ def updatedocumentattributes(self, payload, userid): ) return DocumentAttributes.update(newRows, oldRows) + + def updatedocumentpersonalattributes(self, payload, userid): + """update document attributes""" + + docattributeslist = DocumentAttributes.getdocumentattributesbyid( + payload["documentmasterids"] + ) + oldRows = [] + newRows = [] + for docattributes in docattributeslist: + oldRows.append( + { + "attributeid": docattributes["attributeid"], + "version": docattributes["version"], + "documentmasterid": docattributes["documentmasterid"], + "attributes": docattributes["attributes"], + "createdby": docattributes["createdby"], + "created_at": docattributes["created_at"], + "updatedby": userid, + "updated_at": datetime2.now(), + "isactive": False, + } + ) + newdocattributes = json.loads(json.dumps(docattributes["attributes"])) + if payload["personalattributes"] is not None: + #apply change to all + if(len(payload["documentmasterids"]) > 1): + if(payload["personalattributes"]["person"] is not None): + newdocattributes["personalattributes"]["person"]=payload["personalattributes"]["person"] + if(payload["personalattributes"]["filetype"] is not None): + newdocattributes["personalattributes"]["filetype"]=payload["personalattributes"]["filetype"] + #apply change to individual + else: + newdocattributes["personalattributes"] = payload["personalattributes"] + newRows.append( + DocumentAttributes( + version=docattributes["version"] + 1, + documentmasterid=docattributes["documentmasterid"], + attributes=newdocattributes, + createdby=docattributes["createdby"], + created_at=docattributes["created_at"], + isactive=True, + ) + ) + + return DocumentAttributes.update(newRows, oldRows) def getdocuments(self, requestid,bcgovcode): diff --git a/api/reviewer_api/utils/constants.py b/api/reviewer_api/utils/constants.py index b198bfaac..ad03fe46b 100644 --- a/api/reviewer_api/utils/constants.py +++ b/api/reviewer_api/utils/constants.py @@ -22,6 +22,7 @@ BLANK_EXCEPTION_MESSAGE = 'Field cannot be blank' MAX_EXCEPTION_MESSAGE = 'Field exceeds the size limit' REDLINE_SINGLE_PKG_MINISTRIES = getenv('REDLINE_SINGLE_PKG_MINISTRIES','NA') +REDLINE_SINGLE_PKG_MINISTRIES_PERSONAL = getenv('REDLINE_SINGLE_PKG_MINISTRIES_PERSONAL','NA') try: response = requests.request( method='GET', diff --git a/api/reviewer_api/utils/util.py b/api/reviewer_api/utils/util.py index 1758ec7ac..0a5f52954 100644 --- a/api/reviewer_api/utils/util.py +++ b/api/reviewer_api/utils/util.py @@ -34,7 +34,7 @@ ProcessingTeamWithKeycloackGroup, ) import maya -from reviewer_api.utils.constants import REDLINE_SINGLE_PKG_MINISTRIES +from reviewer_api.utils.constants import REDLINE_SINGLE_PKG_MINISTRIES, REDLINE_SINGLE_PKG_MINISTRIES_PERSONAL def cors_preflight(methods): # Render an option method on the class. @@ -136,11 +136,15 @@ def getbatchconfig(): _limit = _batchconfig["limit"] if "limit" in _batchconfig else 250 return _begin, _size, _limit -def is_single_redline_package(bcgovcode, packagetype): +def is_single_redline_package(bcgovcode, packagetype, requesttype): if (packagetype == "oipcreview"): return True if REDLINE_SINGLE_PKG_MINISTRIES not in (None, ""): _pkg_ministries = REDLINE_SINGLE_PKG_MINISTRIES.replace(" ", "").split(',') if bcgovcode.upper() in _pkg_ministries: return True + if REDLINE_SINGLE_PKG_MINISTRIES_PERSONAL not in (None, ""): + _pkg_ministries_personal = REDLINE_SINGLE_PKG_MINISTRIES_PERSONAL.replace(" ", "").split(',') + if bcgovcode.upper() in _pkg_ministries_personal and requesttype.upper() == "PERSONAL": + return True return False \ No newline at end of file diff --git a/computingservices/DocumentServices/services/dts/redactionsummary.py b/computingservices/DocumentServices/services/dts/redactionsummary.py index 4459a5c23..a70208a3f 100644 --- a/computingservices/DocumentServices/services/dts/redactionsummary.py +++ b/computingservices/DocumentServices/services/dts/redactionsummary.py @@ -1,11 +1,16 @@ from services.dal.documentpageflag import documentpageflag from rstreamio.message.schemas.redactionsummary import get_in_summary_object,get_in_summarypackage_object +import json +from collections import defaultdict class redactionsummary(): def prepareredactionsummary(self, message, documentids, pageflags, programareas): - redactionsummary = self.__packaggesummary(message, documentids, pageflags, programareas) - if message.category == "responsepackage": + if message.bcgovcode == 'mcf' and message.category == "responsepackage": + redactionsummary = self.__packagesummaryforcfdrequests(message, documentids) + else: + redactionsummary = self.__packaggesummary(message, documentids, pageflags, programareas) + if message.category == "responsepackage" and message.bcgovcode != 'mcf': consolidated_redactions = [] for entry in redactionsummary['data']: consolidated_redactions += entry['sections'] @@ -20,16 +25,19 @@ def __getrangenumber(self, rangeval): def __packaggesummary(self, message, documentids, pageflags, programareas): try: + # print("\nInside __packaggesummary") redactionlayerid = self.__getredactionlayerid(message) summarymsg = message.summarydocuments summaryobject = get_in_summary_object(summarymsg) ordereddocids = summaryobject.sorteddocuments stitchedpagedata = documentpageflag().getpagecount_by_documentid(message.ministryrequestid, ordereddocids) totalpagecount = self.__calculate_totalpages(stitchedpagedata) + # print("\ntotalpagecount",totalpagecount) + if totalpagecount <=0: return _pageflags = self.__transformpageflags(pageflags) - + # print("\n_pageflags",_pageflags) summarydata = [] docpageflags = documentpageflag().get_documentpageflag(message.ministryrequestid, redactionlayerid, ordereddocids) deletedpages = self.__getdeletedpages(message.ministryrequestid, ordereddocids) @@ -48,7 +56,7 @@ def __packaggesummary(self, message, documentids, pageflags, programareas): pageflag['docpageflags'] = pageflag['docpageflags'] + self.__get_pagesection_mapping(filteredpages, docpagesections, docpageconsults) skippages = self.__get_skippagenos(docpageflag['pageflag'], message.category) pagecount = (pagecount+stitchedpagedata[docid]["pagecount"])-len(skippages) - + # print("\n_pageflags1",_pageflags) for pageflag in _pageflags: _data = {} if len(pageflag['docpageflags']) > 0: @@ -61,6 +69,265 @@ def __packaggesummary(self, message, documentids, pageflags, programareas): except (Exception) as error: print('error occured in redaction dts service: ', error) + + + def __packagesummaryforcfdrequests(self, message, documentids): + try: + redactionlayerid = self.__getredactionlayerid(message) + summarymsg = message.summarydocuments + summaryobject = get_in_summary_object(summarymsg) + ordereddocids = summaryobject.sorteddocuments + stitchedpagedata = documentpageflag().getpagecount_by_documentid(message.ministryrequestid, ordereddocids) + totalpagecount = self.__calculate_totalpages(stitchedpagedata) + + if totalpagecount <= 0: + return + + pkgdocuments = summaryobject.pkgdocuments + records = pkgdocuments[0].get('records', []) + summarydata = [] + + docpageflags = documentpageflag().get_documentpageflag(message.ministryrequestid, redactionlayerid, ordereddocids) + sorted_docpageflags = {k: docpageflags[k] for k in ordereddocids} + # print("============>sorted_docpageflags:", sorted_docpageflags) + deletedpages = self.__getdeletedpages(message.ministryrequestid, ordereddocids) + #print("============>deletedpages:", deletedpages) + mapped_flags = self.process_page_flags(sorted_docpageflags,deletedpages) + #print("###mapped_flags1:",mapped_flags) + pagecounts= self.count_pages_per_doc(mapped_flags) + # print("pagecounts:",pagecounts) + #document_pages = self.__get_document_pages(docpageflags) + #original_pages = self.__adjust_original_pages(document_pages) + end_page = 0 + for record in records: + if record["documentids"][0] in pagecounts: + # print("-----------------------Record : ---------------------------", record["documentids"]) + record_range, totalpagecount1,end_page = self.__createrecordpagerange(record, pagecounts,end_page ) + # print(f"Range for each record- record_range:{record_range} &&& totalpagecount1:{totalpagecount1} \ + # &&& end_page-{end_page}") + self.assignfullpagesections(redactionlayerid, mapped_flags) + # print("\nMapped_flags::",mapped_flags) + range_result = self.__calculate_range(mapped_flags, record["documentids"]) + # print("range_result:",range_result) + recordwise_pagecount = next((record["pagecount"] for record in record_range if record["recordname"] == record['recordname'].upper()), 0) + # print(f"{record['recordname']} :{recordwise_pagecount}") + summarydata.append(self.__create_summary_data(record, range_result, mapped_flags, recordwise_pagecount)) + + # print("\n summarydata:",summarydata) + return {"requestnumber": message.requestnumber, "data": summarydata} + + except Exception as error: + print('Error occurred in redaction dts service: ', error) + + + def __calculate_range(self, mapped_flags, docids): + if not mapped_flags: + return {} + #min_stitched_page = min(flag['stitchedpageno'] for flag in mapped_flags) + min_stitched_page = min(flag['stitchedpageno'] for flag in mapped_flags if flag['docid'] in docids) + max_stitched_page = max(flag['stitchedpageno'] for flag in mapped_flags if flag['docid'] in docids) + + filtered_mapper = [flag for flag in mapped_flags if flag['docid'] in docids and flag.get('flagid') == 3] + # Sort the filtered flags by stitchedpageno + filtered_mapper.sort(key=lambda x: x['stitchedpageno']) + + grouped_flags= self.__groupbysections(filtered_mapper) + ranges = self.__create_ranges(grouped_flags) + # print("\n ranges:",ranges) + return {"range": f"{min_stitched_page} - {max_stitched_page}" if min_stitched_page != max_stitched_page else f"{min_stitched_page}", "flagged_range":ranges} + + + def assignfullpagesections(self, redactionlayerid, mapped_flags): + document_pages= self.get_sorted_original_pages_by_docid(mapped_flags) + # print("document_pages:",document_pages) + for item in document_pages: + for doc_id, pages in item.items(): + docpagesections = documentpageflag().getsections_by_documentid_pageno(redactionlayerid, doc_id, pages) + # print(f"\n doc_id-{doc_id}, docpagesections-{docpagesections}") + for flag in mapped_flags: + if flag['docid'] == doc_id and flag['flagid'] == 3: + flag['sections']= self.__get_sections_mcf1(docpagesections, flag['dbpageno']) + #self.__get_pagesection_mapping_cfd1(mapped_flags, docpagesections) + + def __get_sections_mcf1(self, docpagesections, pageno): + sections = [] + filtered = [x for x in docpagesections if x['pageno'] == pageno] + # print(f"\n pageno-{pageno}, filtered-{filtered}") + if filtered: + for dta in filtered: + sections += [x.strip() for x in dta['section'].split(",")] + #print("\nSections::",sections) + return list(filter(None, sections)) + + + def get_sorted_original_pages_by_docid(self,mapped_flags): + pages_by_docid = {} + for entry in mapped_flags: + docid = entry['docid'] + original_page = entry['dbpageno'] + + if docid not in pages_by_docid: + pages_by_docid[docid] = [] + + pages_by_docid[docid].append(original_page) + + # Sort the original pages for each docid + for docid in pages_by_docid: + pages_by_docid[docid].sort() + + # Convert to the desired format + result = [{docid: pages} for docid, pages in pages_by_docid.items()] + return result + + def __createrecordpagerange(self, record, pagecounts, previous_end_page=0): + totalpagecount1 = 0 + for doc_id in record["documentids"]: + if doc_id in pagecounts: + totalpagecount1 += pagecounts[doc_id] + + if totalpagecount1 == 0: + return [], totalpagecount1, previous_end_page + + start_page = previous_end_page + 1 + end_page = previous_end_page + totalpagecount1 + + range_string = f"{start_page} - {end_page}" if totalpagecount1 > 1 else f"{start_page}" + result = { + "recordname": record['recordname'].upper(), + "range": range_string, + "pagecount": totalpagecount1 + } + + return [result], totalpagecount1, end_page + + def count_pages_per_doc(self, mapped_flags): + page_counts = {} + for entry in mapped_flags: + doc_id = entry['docid'] + if doc_id in page_counts: + page_counts[doc_id] += 1 + else: + page_counts[doc_id] = 1 + return page_counts + + + def check_docid_in_stitched_pages(self, stitched_pages, doc_id): + for key in stitched_pages.keys(): + if key[0] == doc_id: + return True + return False + + def get_pagecount_for_doc(self,stitched_pages, doc_id): + # Initialize page count for the specified doc_id + pagecount = 0 + # Iterate over the keys in stitched_pages + for d_id, page in stitched_pages.keys(): + if d_id == doc_id: + pagecount += 1 + return pagecount + + + def process_page_flags(self,docpageflags, deletedpages): + result = [] + stitched_pages = {} + current_stitched_page = 1 + + for doc_id, details in docpageflags.items(): + docdeletedpages = deletedpages.get(doc_id, []) + for flag in details['pageflag']: + original_page = flag['page'] + + # Skip pages with flagid 5 or 6 + if flag['flagid'] in (5, 6): + continue + # Skip deleted pages + if original_page in docdeletedpages: + continue + + dbpageno = original_page - 1 + stitchedpageno = current_stitched_page + stitched_pages[(doc_id, original_page)] = stitchedpageno + result.append({ + 'docid': doc_id, + 'originalpageno': original_page, + 'dbpageno': dbpageno, + 'stitchedpageno': stitchedpageno, + 'flagid': flag['flagid'] + }) + current_stitched_page += 1 + return result + + + def __groupbysections(self, filtered_mapper): + # print("\n __groupbysections: ", filtered_mapper) + # Group by sections + grouped_flags = defaultdict(list) + for flag in filtered_mapper: + # Convert the list of sections to a tuple, or use an empty tuple if sections is empty + sections_key = tuple(flag['sections']) if 'sections' in flag and flag['sections'] else ('No Section',) + grouped_flags[sections_key].append(flag) + grouped_flags = dict(grouped_flags) + # print("\n grouped_flags:", grouped_flags) + return grouped_flags + + + def __create_ranges(self, grouped_flags): + ranges = {} + for sections_key, flags in grouped_flags.items(): + # Extract and sort stitched page numbers + stitched_pagenos = sorted(flag['stitchedpageno'] for flag in flags) + range_list = [] + # Create ranges + start = stitched_pagenos[0] + prev = start + for page in stitched_pagenos[1:]: + if page == prev + 1: + prev = page + else: + if start == prev: + range_list.append(f"{start}") + else: + range_list.append(f"{start} - {prev}") + start = page + prev = page + # Add the last range + if start == prev: + range_list.append(f"{start}") + else: + range_list.append(f"{start} - {prev}") + # Save the range list for the current sections_key + ranges[sections_key] = range_list + return ranges + + + def __create_summary_data(self, record, range_result, mapped_flags, recordwise_pagecount): + #print(f"record --> {record}, range_result --> {range_result}, mapped_flags --> {mapped_flags}, recordwise_pagecount --> {recordwise_pagecount}") + return { + "recordname": record['recordname'].upper(), + "pagecount": recordwise_pagecount, + "sections": [{ + "range": range_result.get("range", ""), + "section": self.generate_text(range_result) + }] + } + + def generate_text(self, range_result): + pageflag = "Withheld in Full" + section_list = [] + # Iterate over the items in the flagged_range dictionary + for sections_key, range_list in range_result.get("flagged_range", {}).items(): + # Convert the sections_key to a string for better readability + sections_str = ", ".join(sections_key) if isinstance(sections_key, tuple) else sections_key + + # Iterate over each range in the range_list + for range_item in range_list: + # Format the section information + formatted_sections = f"{pageflag} under {sections_str}" if sections_str else "" + # Append the formatted text to the section list + section_list.append({"formatted" :f"pg(s). {range_item} {formatted_sections}" if formatted_sections else range_item}) + return section_list + + def __getredactionlayerid(self, message): if message.category == "responsepackage": return documentpageflag().getrecentredactionlayerid(message.ministryrequestid) @@ -124,7 +391,7 @@ def __format_redaction_summary(self, pageflag, pageredactions, category): range_sections = currentpg["sections"] if range_start == 0 else range_sections range_start = currentpg["stitchedpageno"] if range_start == 0 else range_start range_consults = currentpg["consults"] - skipconsult = True if category in ('oipcreviewredline','responsepackage') else False + skipconsult = True if category in ('oipcreviewredline','responsepackage', 'CFD_responsepackage') else False if currentpg["stitchedpageno"]+1 == nextpg["stitchedpageno"] and (skipconsult == True or (skipconsult == False and currentpg["consults"] == nextpg["consults"])): range_sections.extend(nextpg["sections"]) range_end = nextpg["stitchedpageno"] @@ -150,10 +417,13 @@ def __get_pagesection_mapping(self, docpages, docpagesections, docpageconsults): entry["sections"] = self.__get_sections(docpagesections, entry['originalpageno']) entry["consults"] = docpageconsults[entry['originalpageno']] if entry['originalpageno'] in docpageconsults else None return docpages - + + def __get_sections(self, docpagesections, pageno): + # print(f"\n pageno-{pageno}, docpagesections-{docpagesections}") sections = [] filtered = [x for x in docpagesections if x['pageno'] == pageno] + # print("\n filtered:",filtered) for dta in filtered: sections += [x.strip() for x in dta['section'].split(",")] return list(filter(None, sections)) @@ -162,13 +432,15 @@ def __get_pages_by_flagid(self, _docpageflags, deletedpages, totalpages, flagid, pagenos = [] skippages = self.__get_skippagenos(_docpageflags,category) for x in _docpageflags: - if x["flagid"] == flagid and x["page"] not in deletedpages: + if x["flagid"] == flagid and x["page"] not in deletedpages: + #print("\nInsideLoop") pagenos.append({'originalpageno':x["page"]-1, 'stitchedpageno':self.__calcstitchedpageno(x["page"], totalpages, category, skippages, deletedpages)}) return pagenos + def __get_skippagenos(self, _docpageflags, category): skippages = [] - if category == 'responsepackage': + if category in ['responsepackage', 'CFD_responsepackage']: for x in _docpageflags: if x['flagid'] in (5,6) and x['page'] not in skippages: skippages.append(x['page']) @@ -176,11 +448,12 @@ def __get_skippagenos(self, _docpageflags, category): def __calcstitchedpageno(self, pageno, totalpages, category, skippages, deletedpages): skipcount = 0 - if category == "responsepackage": + if category in ["responsepackage", 'CFD_responsepackage']: skipcount = self.__calculateskipcount(pageno, skippages) skipcount = self.__calculateskipcount(pageno, deletedpages, skipcount) return (pageno+totalpages)-skipcount + def __calculateskipcount(self, pageno, ignorepages, skipcount=0): for dno in ignorepages: if dno < pageno: diff --git a/computingservices/DocumentServices/services/redactionsummaryservice.py b/computingservices/DocumentServices/services/redactionsummaryservice.py index fb21e91b4..9facce3e0 100644 --- a/computingservices/DocumentServices/services/redactionsummaryservice.py +++ b/computingservices/DocumentServices/services/redactionsummaryservice.py @@ -17,22 +17,30 @@ def processmessage(self,incomingmessage): pdfstitchjobactivity().recordjobstatus(message,3,"redactionsummarystarted") summarymsg = message.summarydocuments #Condition for handling oipcredline category - category = message.category - documenttypename= category+"_redaction_summary" if category == 'responsepackage' else "redline_redaction_summary" + bcgovcode= message.bcgovcode + category = message.category + if bcgovcode == 'mcf' and category == 'responsepackage': + documenttypename= 'CFD_responsepackage_redaction_summary' + else: + documenttypename= category+"_redaction_summary" if category == 'responsepackage' else "redline_redaction_summary" #print('documenttypename', documenttypename) upload_responses=[] pageflags = self.__get_pageflags(category) programareas = documentpageflag().get_all_programareas() + messageattributes= json.loads(message.attributes) + #print("\nmessageattributes:",messageattributes) divisiondocuments = get_in_summary_object(summarymsg).pkgdocuments + #print("\n divisiondocuments:",divisiondocuments) for entry in divisiondocuments: - if 'documentids' in entry and len(entry['documentids']) > 0: + #print("\n entry:",entry) + if 'documentids' in entry and len(entry['documentids']) > 0 : + # print("\n entry['divisionid']:",entry['divisionid']) divisionid = entry['divisionid'] documentids = entry['documentids'] formattedsummary = redactionsummary().prepareredactionsummary(message, documentids, pageflags, programareas) #print("formattedsummary", formattedsummary) template_path='templates/'+documenttypename+'.docx' redaction_summary= documentgenerationservice().generate_pdf(formattedsummary, documenttypename,template_path) - messageattributes= json.loads(message.attributes) divisioname = None if len(messageattributes)>1: filesobj=(next(item for item in messageattributes if item['divisionid'] == divisionid))['files'][0] @@ -43,10 +51,14 @@ def processmessage(self,incomingmessage): divisioname = messageattributes[0]['divisionname'] if category not in ('responsepackage','oipcreviewredline') else None stitcheddocs3uri = filesobj['s3uripath'] - stitcheddocfilename = filesobj['filename'] - s3uricategoryfolder= "oipcreview" if category == 'oipcreviewredline' else category + stitcheddocfilename = filesobj['filename'] + if category == 'oipcreviewredline': + s3uricategoryfolder = "oipcreview" + else: + s3uricategoryfolder = category s3uri = stitcheddocs3uri.split(s3uricategoryfolder+"/")[0] + s3uricategoryfolder+"/" filename =self.__get_summaryfilename(message.requestnumber, category, divisioname, stitcheddocfilename) + # print("\n filename:",filename) uploadobj= uploadbytes(filename,redaction_summary.content,s3uri) upload_responses.append(uploadobj) if uploadobj["uploadresponse"].status_code == 200: @@ -56,6 +68,7 @@ def processmessage(self,incomingmessage): summaryuploaderror= True summaryuploaderrormsg = uploadobj.uploadresponse.text pdfstitchjobactivity().recordjobstatus(message,4,"redactionsummaryuploaded",summaryuploaderror,summaryuploaderrormsg) + # print("\ns3uripath:",uploadobj["documentpath"]) summaryfilestozip.append({"filename": uploadobj["filename"], "s3uripath":uploadobj["documentpath"]}) return summaryfilestozip except (Exception) as error: @@ -72,7 +85,8 @@ def __get_summaryfilename(self, requestnumber, category, divisionname, stitchedd else: _filename = requestnumber+" - "+category if divisionname not in (None, ''): - _filename = _filename+" - "+divisionname + _filename = _filename+" - "+divisionname + # print("---->",stitchedfilepath+_filename+" - summary.pdf") return stitchedfilepath+_filename+" - summary.pdf" def __get_pageflags(self, category): diff --git a/computingservices/DocumentServices/templates/CFD_responsepackage_redaction_summary.docx b/computingservices/DocumentServices/templates/CFD_responsepackage_redaction_summary.docx new file mode 100644 index 000000000..a4f656097 Binary files /dev/null and b/computingservices/DocumentServices/templates/CFD_responsepackage_redaction_summary.docx differ diff --git a/computingservices/DocumentServices/templates/redline_redaction_summary.docx b/computingservices/DocumentServices/templates/redline_redaction_summary.docx index e08da9f00..35b681462 100644 Binary files a/computingservices/DocumentServices/templates/redline_redaction_summary.docx and b/computingservices/DocumentServices/templates/redline_redaction_summary.docx differ diff --git a/computingservices/DocumentServices/templates/responsepackage_redaction_summary.docx b/computingservices/DocumentServices/templates/responsepackage_redaction_summary.docx index 86a3730ab..e81ba1b3f 100644 Binary files a/computingservices/DocumentServices/templates/responsepackage_redaction_summary.docx and b/computingservices/DocumentServices/templates/responsepackage_redaction_summary.docx differ diff --git a/computingservices/PageCountCalculator/services/dal/pagecount/ministryservice.py b/computingservices/PageCountCalculator/services/dal/pagecount/ministryservice.py index a358236b4..8d415ce29 100644 --- a/computingservices/PageCountCalculator/services/dal/pagecount/ministryservice.py +++ b/computingservices/PageCountCalculator/services/dal/pagecount/ministryservice.py @@ -11,7 +11,7 @@ def getlatestrecordspagecount(cls, ministryrequestid): cursor = conn.cursor() query = ''' SELECT recordspagecount - FROM public."FOIMinistryRequests" + FROM "FOIMinistryRequests" WHERE foiministryrequestid = %s::integer AND isactive = true ORDER BY version DESC LIMIT 1; ''' @@ -33,7 +33,7 @@ def updaterecordspagecount(cls, ministryrequestid, pagecount, userid): try: cursor = conn.cursor() query = ''' - UPDATE public."FOIMinistryRequests" SET recordspagecount = %s::integer, updated_at = %s, updatedby = %s + UPDATE "FOIMinistryRequests" SET recordspagecount = %s::integer, updated_at = %s, updatedby = %s WHERE foiministryrequestid = %s::integer AND isactive = true; ''' parameters = (pagecount, datetime.now().isoformat(), userid, ministryrequestid,) diff --git a/docker-compose.yml b/docker-compose.yml index 70f0807fc..4cb79291a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -81,6 +81,7 @@ services: - DOCUMENTSERVICE_STREAM_KEY=${DOCUMENTSERVICE_STREAM_KEY} - BATCH_CONFIG=${BATCH_CONFIG} - REDLINE_SINGLE_PKG_MINISTRIES=${REDLINE_SINGLE_PKG_MINISTRIES} + - REDLINE_SINGLE_PKG_MINISTRIES_PERSONAL=${REDLINE_SINGLE_PKG_MINISTRIES_PERSONAL} - PAGECALCULATOR_STREAM_KEY=${PAGECALCULATOR_STREAM_KEY} foi-docreviewer-db: diff --git a/sample.env b/sample.env index e3105bd8d..7ed1e4b6b 100644 --- a/sample.env +++ b/sample.env @@ -116,6 +116,7 @@ FILE_CONVERSION_WAITTIME=2000 #Provide the values in upper case with comma seperation REDLINE_SINGLE_PKG_MINISTRIES=VALUE1,VALUE2 +REDLINE_SINGLE_PKG_MINISTRIES_PERSONAL=VALUE3 PAGECALCULATOR_STREAM_KEY="CALCULATE-PAGE-COUNT" DOCUMENTSERVICE_DB_HOST= diff --git a/web/package.json b/web/package.json index aabc9d752..38e32d7f6 100644 --- a/web/package.json +++ b/web/package.json @@ -13,6 +13,7 @@ "@mui/lab": "^5.0.0-alpha.103", "@mui/material": "^5.15.14", "@mui/styles": "^5.2.3", + "@mui/x-data-grid": "^5.2.2", "@mui/x-tree-view": "^7.1.0", "@pdftron/webviewer": "10.9.0", "@reduxjs/toolkit": "^1.8.5", diff --git a/web/src/actions/actionConstants.ts b/web/src/actions/actionConstants.ts index d32432043..cbae36c3d 100644 --- a/web/src/actions/actionConstants.ts +++ b/web/src/actions/actionConstants.ts @@ -19,7 +19,11 @@ const ACTION_CONSTANTS = { SET_CURRENT_LAYER: "SET_CURRENT_LAYER", INC_REDACTION_LAYER: "INC_REDACTION_LAYER", SET_REQUEST_NUMBER:"SET_REQUEST_NUMBER", - SET_DELETED_PAGES: "SET_DELETED_PAGES" + SET_DELETED_PAGES: "SET_DELETED_PAGES", + FOI_PERSONAL_SECTIONS: "FOI_PERSONAL_SECTIONS", + FOI_PERSONAL_PEOPLE: "FOI_PERSONAL_PEOPLE", + FOI_PERSONAL_FILETYPES: "FOI_PERSONAL_FILETYPES", + FOI_PERSONAL_VOLUMES: "FOI_PERSONAL_VOLUMES", }; export default ACTION_CONSTANTS; diff --git a/web/src/actions/documentActions.ts b/web/src/actions/documentActions.ts index 15290158c..da519f073 100644 --- a/web/src/actions/documentActions.ts +++ b/web/src/actions/documentActions.ts @@ -91,4 +91,29 @@ export const setDeletedPages = (data: any) => (dispatch:any) =>{ type:ACTION_CONSTANTS.SET_DELETED_PAGES, payload:data }) -} \ No newline at end of file +} + +export const setFOIPersonalSections = (data: any) => (dispatch:any) =>{ + dispatch({ + type:ACTION_CONSTANTS.FOI_PERSONAL_SECTIONS, + payload:data + }) + } + export const setFOIPersonalPeople = (data: any) => (dispatch:any) =>{ + dispatch({ + type:ACTION_CONSTANTS.FOI_PERSONAL_PEOPLE, + payload:data + }) + } + export const setFOIPersonalFiletypes = (data: any) => (dispatch:any) =>{ + dispatch({ + type:ACTION_CONSTANTS.FOI_PERSONAL_FILETYPES, + payload:data + }) + } + export const setFOIPersonalVolumes = (data: any) => (dispatch:any) =>{ + dispatch({ + type:ACTION_CONSTANTS.FOI_PERSONAL_VOLUMES, + payload:data + }) + } \ No newline at end of file diff --git a/web/src/apiManager/endpoints/index.tsx b/web/src/apiManager/endpoints/index.tsx index fb3407ef2..c1babec77 100644 --- a/web/src/apiManager/endpoints/index.tsx +++ b/web/src/apiManager/endpoints/index.tsx @@ -18,5 +18,7 @@ const API = { DOCREVIEWER_FINALPACKAGE:`${DOCREVIEWER_BASE_API_URL}/api/triggerdownloadfinalpackage`, DOCREVIEWER_LICENSE:`${DOCREVIEWER_BASE_API_URL}/api/foiflow/webviewerlicense`, DOCREVIEWER_DOCUMENT_PAGE_DELETE:`${DOCREVIEWER_BASE_API_URL}/api/document/ministryrequest//deletedpages`, + FOI_GET_PERSONALTAG:`${DOCREVIEWER_BASE_API_URL}/api/foiflow/personalattributes`, + DOCREVIEWER_EDIT_PERSONAL_ATTRIBUTES:`${DOCREVIEWER_BASE_API_URL}/api/document/update/personal`, }; export default API; diff --git a/web/src/apiManager/services/docReviewerService.tsx b/web/src/apiManager/services/docReviewerService.tsx index 595db2449..72a167fae 100644 --- a/web/src/apiManager/services/docReviewerService.tsx +++ b/web/src/apiManager/services/docReviewerService.tsx @@ -3,7 +3,8 @@ import { httpGETRequest, httpGETBigRequest, httpPOSTRequest } from "../httpReque import API from "../endpoints"; import UserService from "../../services/UserService"; import { setRedactionInfo, setIsPageLeftOff, setSections, - setDocumentList, setRequestStatus, setRedactionLayers, incrementLayerCount, setRequestNumber, setRequestInfo, setDeletedPages + setDocumentList, setRequestStatus, setRedactionLayers, incrementLayerCount, setRequestNumber, setRequestInfo, setDeletedPages, + setFOIPersonalSections, setFOIPersonalPeople, setFOIPersonalFiletypes, setFOIPersonalVolumes } from "../../actions/documentActions"; import { store } from "../../services/StoreService"; import { number } from "yargs"; @@ -35,7 +36,8 @@ export const fetchDocuments = ( store.dispatch(setRequestNumber(res.data.requestnumber) as any); store.dispatch(setRequestStatus(res.data.requeststatuslabel) as any); store.dispatch(setRequestInfo(res.data.requestinfo) as any); - callback(res.data.documents, res.data.documentdivisions); + // callback(__files, res.data.documentdivisions, res.data.requestinfo); + callback(res.data.documents, res.data.documentdivisions, res.data.requestinfo); } else { throw new Error(); } @@ -510,4 +512,52 @@ export const fetchDeletedDocumentPages = ( .catch((error:any) => { errorCallback("Error in fetching deleted pages:",error); }); +}; + +export const fetchPersonalAttributes = ( + bcgovcode: string, + errorCallback: any +) => { + let apiUrlGet: string = `${API.FOI_GET_PERSONALTAG}/${bcgovcode}` + + httpGETRequest(apiUrlGet, {}, UserService.getToken()) + .then((res:any) => { + if (res.data) { + store.dispatch(setFOIPersonalPeople(res.data) as any); + store.dispatch(setFOIPersonalFiletypes(res.data) as any); + store.dispatch(setFOIPersonalVolumes(res.data) as any); + store.dispatch(setFOIPersonalSections(res.data) as any); + // callback(res.data); + } else { + throw new Error(); + } + }) + .catch((error:any) => { + console.log(error); + errorCallback("Error in fetching personal attributes"); + }); +}; + +export const editPersonalAttributes = ( + foiministryrquestid: string, + callback: any, + errorCallback: any, + data?: any +) => { + let apiUrlPost: string = replaceUrl( + API.DOCREVIEWER_EDIT_PERSONAL_ATTRIBUTES, + "", + foiministryrquestid + ) + httpPOSTRequest({url: apiUrlPost, data: data, token: UserService.getToken() ?? '', isBearer: true}) + .then((res:any) => { + if (res.data) { + callback(res.data); + } else { + throw new Error(`Error while editing personal attributes for requestid ${foiministryrquestid}`); + } + }) + .catch((error:any) => { + errorCallback("Error in editing personal attributes"); + }); }; \ No newline at end of file diff --git a/web/src/apiManager/services/foiOSSService.tsx b/web/src/apiManager/services/foiOSSService.tsx index 5d79746eb..bb170b92a 100644 --- a/web/src/apiManager/services/foiOSSService.tsx +++ b/web/src/apiManager/services/foiOSSService.tsx @@ -46,6 +46,7 @@ export const getFOIS3DocumentPreSignedUrls = ( export const getFOIS3DocumentRedlinePreSignedUrl = ( ministryrequestID: any, + requestType: any, documentList: any[], callback: any, errorCallback: any, @@ -64,7 +65,7 @@ export const getFOIS3DocumentRedlinePreSignedUrl = ( - httpPOSTRequest({url: apiurl, data: {"divdocumentList":documentList}, token: UserService.getToken() || ''}) + httpPOSTRequest({url: apiurl, data: {"divdocumentList":documentList,"requestType":requestType}, token: UserService.getToken() || ''}) .then((res:any) => { if (res.data) { callback(res.data); diff --git a/web/src/components/FOI/Home/ContextMenu.tsx b/web/src/components/FOI/Home/ContextMenu.tsx index 6b4b9608b..62a357f6f 100644 --- a/web/src/components/FOI/Home/ContextMenu.tsx +++ b/web/src/components/FOI/Home/ContextMenu.tsx @@ -1,11 +1,11 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import Popover from "@mui/material/Popover"; import MenuList from "@mui/material/MenuList"; import MenuItem from "@mui/material/MenuItem"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCirclePlus, faAngleRight } from "@fortawesome/free-solid-svg-icons"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; -import { savePageFlag } from "../../../apiManager/services/docReviewerService"; +import { savePageFlag, editPersonalAttributes } from "../../../apiManager/services/docReviewerService"; import ConsultModal from "./ConsultModal"; import { getStitchedPageNoFromOriginal, @@ -14,6 +14,7 @@ import { updatePageFlagOnPage, } from "./utils"; import { useAppSelector } from "../../../hooks/hook"; +import MCFPersonal from "./MCFPersonal"; import { pageFlagTypes } from "../../../constants/enum"; const ContextMenu = ({ @@ -29,8 +30,34 @@ const ContextMenu = ({ pageMappedDocs, pageFlags, syncPageFlagsOnAction, + filesForDisplay, + activeNode, + requestInfo, + currentEditRecord, + setCurrentEditRecord }: any) => { + const [editTagModalOpen, setEditTagModalOpen] = useState(false); + const [divisionModalTagValue, setDivisionModalTagValue] = useState(-1); + const [curPersonalAttributes, setCurPersonalAttributes] = useState({ + person: "", + filetype: "", + volume: "", + trackingid: "", + personaltag: "TBD" + }); + const [newPersonalAttributes, setNewPersonalAttributes] = useState(); + const [disablePageFlags, setDisablePageFlags] = useState(false); + + useEffect(() => { + if(currentEditRecord?.attributes?.personalattributes) + setCurPersonalAttributes(currentEditRecord.attributes.personalattributes); + },[currentEditRecord]) + + useEffect(() => { + setDisablePageFlags(Object.keys(activeNode).length == 1); + },[activeNode]) + const [openModal, setOpenModal] = useState(false); const [flagId, setFlagId] = React.useState(0); const currentLayer = useAppSelector( @@ -40,6 +67,10 @@ const ContextMenu = ({ (state: any) => state.documents?.requestinfo?.validoipcreviewlayer ); + const editTags = () => { + setEditTagModalOpen(true); + } + const openConsultModal = (flagId: number) => { setOpenModal(true); setFlagId(flagId); @@ -96,7 +127,7 @@ const ContextMenu = ({
- + {pageFlag?.name == "Consult" ? ( <>
openConsultModal(pageFlag.pageflagid)}> @@ -156,6 +187,62 @@ const ContextMenu = ({ }); }; + const comparePersonalAttributes = (a: any, b: any) => { + return a?.person === b?.person && a?.volume === b?.volume + && a?.filetype === b?.filetype + && a?.personaltag === b?.personaltag + && a?.trackingid === b?.trackingid; + }; + + const updatePersonalAttributes = (_all = false) => { + setEditTagModalOpen(false); + setOpenContextPopup(false); + var documentMasterIDs = []; + + if(newPersonalAttributes) { + if(_all) { + for (let record of filesForDisplay) { + if(record.attributes?.personalattributes?.person + && record.attributes?.personalattributes?.person === currentEditRecord.attributes?.personalattributes?.person + // && record.attributes?.personalattributes?.filetype + // && record.attributes?.personalattributes?.filetype === currentEditRecord.attributes?.personalattributes?.filetype + ) { + documentMasterIDs.push(record.documentmasterid); + } + } + } else { + documentMasterIDs.push(currentEditRecord.documentmasterid); + } + + if(currentEditRecord && !comparePersonalAttributes(newPersonalAttributes, curPersonalAttributes)) { + editPersonalAttributes( + requestId, + (data: any) => { + if(data.status == true){ + console.log("Personal attributes updated") + } + }, + (error: any) => console.log(error), + { + documentmasterids: documentMasterIDs, + personalattributes: newPersonalAttributes, + ministryrequestid: requestId + }, + ); + + setCurrentEditRecord(); + setCurPersonalAttributes({ + person: "", + filetype: "", + volume: "", + trackingid: "", + personaltag: "TBD" + }); + setNewPersonalAttributes({}); + } + } + }; + return ( <>
Export

+ {requestInfo?.bcgovcode === "MCF" && requestInfo?.requesttype === "personal" && (<> +
1 + ? "editPersonalTagsDisabled" + : "editPersonalTags" + } + onClick={() => { + if(selectedPages.length <= 1) { + editTags() + } + }} + > + Edit Tags +
+
+ )}
Page Flags
{showPageFlagList()} @@ -197,6 +301,19 @@ const ContextMenu = ({ programAreaList={getProgramAreas(pageFlagList)} /> )} + {(editTagModalOpen && requestInfo.bcgovcode === "MCF" && requestInfo.requesttype === "personal") && + + } ); }; diff --git a/web/src/components/FOI/Home/CreateResponsePDF/useSaveRedlineForSignOff.js b/web/src/components/FOI/Home/CreateResponsePDF/useSaveRedlineForSignOff.js index 63e5897be..8b81c5c90 100644 --- a/web/src/components/FOI/Home/CreateResponsePDF/useSaveRedlineForSignOff.js +++ b/web/src/components/FOI/Home/CreateResponsePDF/useSaveRedlineForSignOff.js @@ -68,6 +68,9 @@ const useSaveRedlineForSignoff = (initDocInstance, initDocViewer) => { const [filteredComments, setFilteredComments] = useState({}); const [alreadyStitchedList, setAlreadyStitchedList] = useState([]); + const requestInfo = useAppSelector((state) => state.documents?.requestinfo); + const requestType = requestInfo?.requesttype ? requestInfo.requesttype : "public"; + const isValidRedlineDivisionDownload = (divisionid, divisionDocuments) => { let isvalid = false; for (let divObj of divisionDocuments) { @@ -175,20 +178,18 @@ const useSaveRedlineForSignoff = (initDocInstance, initDocViewer) => { ) => { if (redlineSinglePkg === "Y") { let reqdocuments = []; - for (let divObj of divisionDocuments) { + for (let divObj of divisionDocuments) { for (let doc of divObj.documentlist) { reqdocuments.push(doc); } } // sort based on sortorder as the sortorder added based on the LastModified - prepareRedlinePageMappingByRequest( - sortBySortOrder(reqdocuments), - pageMappedDocs - ); + prepareRedlinePageMappingByRequest(sortBySortOrder(reqdocuments), pageMappedDocs); } else { prepareRedlinePageMappingByDivision(divisionDocuments); } - }; + } + const prepareRedlinePageMappingByRequest = ( divisionDocuments, pageMappedDocs @@ -294,122 +295,125 @@ const useSaveRedlineForSignoff = (initDocInstance, initDocViewer) => { 'NRwatermark': NRWatermarksPages }); }; + + const prepareRedlinePageMappingByDivision = (divisionDocuments) => { let removepages = {}; - let pageMappings = {}; - let divPageMappings = {}; - let pagesToRemove = []; - let totalPageCount = 0; - let totalPageCountIncludeRemoved = 0; - let divisionCount = 0; - let duplicateWatermarkPages = {}; - let duplicateWatermarkPagesEachDiv = []; - let NRWatermarksPages = {}; - let NRWatermarksPagesEachDiv = []; - for (let divObj of divisionDocuments) { - divisionCount++; - // sort based on sortorder as the sortorder added based on the LastModified - for (let doc of sortBySortOrder(divObj.documentlist)) { - if (doc.pagecount > 0) { - let pagesToRemoveEachDoc = []; - pageMappings[doc.documentid] = {}; - let pageIndex = 1; - //gather pages that need to be removed - doc.pageFlag.sort((a, b) => a.page - b.page); //sort pageflag by page # - let skipDocumentPages = false; - let skipOnlyDuplicateDocument = false; - let skipOnlyNRDocument = false; - if (!includeDuplicatePages && !includeNRPages) { - skipDocumentPages = skipDocument(doc.pageFlag, doc.pagecount, pageFlagTypes); - } - else if (!includeDuplicatePages) { - skipOnlyDuplicateDocument = skipDuplicateDocument(doc.pageFlag, doc.pagecount, pageFlagTypes); - } - else if (!includeNRPages) { - skipOnlyNRDocument = skipNRDocument(doc.pageFlag, doc.pagecount, pageFlagTypes); - } - //if(isIgnoredDocument(doc, doc['pagecount'], divisionDocuments) == false) { - for (const flagInfo of doc.pageFlag) { - if (flagInfo.flagid !== pageFlagTypes["Consult"]) { // ignore consult flag to fix bug FOIMOD-3062 - if (flagInfo.flagid == pageFlagTypes["Duplicate"]) { - if(includeDuplicatePages) { - duplicateWatermarkPagesEachDiv.push(pageIndex + totalPageCountIncludeRemoved - pagesToRemove.length); + let pageMappings = {}; + let divPageMappings = {}; + let pagesToRemove = []; + let totalPageCount = 0; + let totalPageCountIncludeRemoved = 0; + let divisionCount = 0; + let duplicateWatermarkPages = {}; + let duplicateWatermarkPagesEachDiv = []; + let NRWatermarksPages = {}; + let NRWatermarksPagesEachDiv = []; + for (let divObj of divisionDocuments) { + divisionCount++; + // sort based on sortorder as the sortorder added based on the LastModified + for (let doc of sortBySortOrder(divObj.documentlist)) { + if (doc.pagecount > 0) { + let pagesToRemoveEachDoc = []; + pageMappings[doc.documentid] = {}; + let pageIndex = 1; + //gather pages that need to be removed + doc.pageFlag.sort((a, b) => a.page - b.page); //sort pageflag by page # + let skipDocumentPages = false; + let skipOnlyDuplicateDocument = false; + let skipOnlyNRDocument = false; + if (!includeDuplicatePages && !includeNRPages) { + skipDocumentPages = skipDocument(doc.pageFlag, doc.pagecount, pageFlagTypes); + } + else if (!includeDuplicatePages) { + skipOnlyDuplicateDocument = skipDuplicateDocument(doc.pageFlag, doc.pagecount, pageFlagTypes); + } + else if (!includeNRPages) { + skipOnlyNRDocument = skipNRDocument(doc.pageFlag, doc.pagecount, pageFlagTypes); + } + //if(isIgnoredDocument(doc, doc['pagecount'], divisionDocuments) == false) { + for (const flagInfo of doc.pageFlag) { + if (flagInfo.flagid !== pageFlagTypes["Consult"]) { // ignore consult flag to fix bug FOIMOD-3062 + if (flagInfo.flagid == pageFlagTypes["Duplicate"]) { + if(includeDuplicatePages) { + duplicateWatermarkPagesEachDiv.push(pageIndex + totalPageCountIncludeRemoved - pagesToRemove.length); - pageMappings[doc.documentid][flagInfo.page] = - pageIndex + - totalPageCount - - pagesToRemoveEachDoc.length; - } else { - pagesToRemoveEachDoc.push(flagInfo.page); - if (!skipDocumentPages && !skipOnlyDuplicateDocument) { - pagesToRemove.push( - pageIndex + totalPageCountIncludeRemoved - ); - } + pageMappings[doc.documentid][flagInfo.page] = + pageIndex + + totalPageCount - + pagesToRemoveEachDoc.length; + } else { + pagesToRemoveEachDoc.push(flagInfo.page); + if (!skipDocumentPages && !skipOnlyDuplicateDocument) { + pagesToRemove.push( + pageIndex + totalPageCountIncludeRemoved + ); } + } - } else if (flagInfo.flagid == pageFlagTypes["Not Responsive"]) { - if(includeNRPages) { - NRWatermarksPagesEachDiv.push(pageIndex + totalPageCountIncludeRemoved - pagesToRemove.length); + } else if (flagInfo.flagid == pageFlagTypes["Not Responsive"]) { + if(includeNRPages) { + NRWatermarksPagesEachDiv.push(pageIndex + totalPageCountIncludeRemoved - pagesToRemove.length); - pageMappings[doc.documentid][flagInfo.page] = - pageIndex + - totalPageCount - - pagesToRemoveEachDoc.length; - } else { - pagesToRemoveEachDoc.push(flagInfo.page); - if (!skipDocumentPages && !skipOnlyNRDocument) { - pagesToRemove.push( - pageIndex + totalPageCountIncludeRemoved - ); - } - } + pageMappings[doc.documentid][flagInfo.page] = + pageIndex + + totalPageCount - + pagesToRemoveEachDoc.length; } else { - if (flagInfo.flagid !== pageFlagTypes["Consult"]) { - pageMappings[doc.documentid][flagInfo.page] = - pageIndex + - totalPageCount - - pagesToRemoveEachDoc.length; + pagesToRemoveEachDoc.push(flagInfo.page); + if (!skipDocumentPages && !skipOnlyNRDocument) { + pagesToRemove.push( + pageIndex + totalPageCountIncludeRemoved + ); } } + } else { if (flagInfo.flagid !== pageFlagTypes["Consult"]) { - pageIndex ++; + pageMappings[doc.documentid][flagInfo.page] = + pageIndex + + totalPageCount - + pagesToRemoveEachDoc.length; } } + if (flagInfo.flagid !== pageFlagTypes["Consult"]) { + pageIndex ++; + } } - //End of pageMappingsByDivisions - totalPageCount += Object.keys( - pageMappings[doc.documentid] - ).length; - if (!skipDocumentPages && !skipOnlyDuplicateDocument && !skipOnlyNRDocument) { - totalPageCountIncludeRemoved += doc.pagecount; - } - //} } - + //End of pageMappingsByDivisions + totalPageCount += Object.keys( + pageMappings[doc.documentid] + ).length; + if (!skipDocumentPages && !skipOnlyDuplicateDocument && !skipOnlyNRDocument) { + totalPageCountIncludeRemoved += doc.pagecount; + } + //} } - divPageMappings[divObj.divisionid] = pageMappings; - removepages[divObj.divisionid] = pagesToRemove; - duplicateWatermarkPages[divObj.divisionid] = duplicateWatermarkPagesEachDiv; - NRWatermarksPages[divObj.divisionid] = NRWatermarksPagesEachDiv; - pagesToRemove = []; - duplicateWatermarkPagesEachDiv = []; - NRWatermarksPagesEachDiv = []; - totalPageCount = 0; - totalPageCountIncludeRemoved = 0; - pageMappings = {} + } + divPageMappings[divObj.divisionid] = pageMappings; + removepages[divObj.divisionid] = pagesToRemove; + duplicateWatermarkPages[divObj.divisionid] = duplicateWatermarkPagesEachDiv; + NRWatermarksPages[divObj.divisionid] = NRWatermarksPagesEachDiv; + pagesToRemove = []; + duplicateWatermarkPagesEachDiv = []; + NRWatermarksPagesEachDiv = []; + totalPageCount = 0; + totalPageCountIncludeRemoved = 0; + pageMappings = {} + } + + setRedlinepageMappings({ + 'divpagemappings': divPageMappings, + 'pagemapping': pageMappings, + 'pagestoremove': removepages + }); + setRedlineWatermarkPageMapping({ + 'duplicatewatermark': duplicateWatermarkPages, + 'NRwatermark': NRWatermarksPages + }); + } - setRedlinepageMappings({ - 'divpagemappings': divPageMappings, - 'pagemapping': pageMappings, - 'pagestoremove': removepages - }); - setRedlineWatermarkPageMapping({ - 'duplicatewatermark': duplicateWatermarkPages, - 'NRwatermark': NRWatermarksPages - }); - }; const prepareRedlineIncompatibleMapping = (redlineAPIResponse) => { let divIncompatableMapping = {}; @@ -432,15 +436,15 @@ const useSaveRedlineForSignoff = (initDocInstance, initDocViewer) => { redlineAPIResponse.issingleredlinepackage === "N" ? divObj.divisionname + "/" + record.filename : record.filename; - return { - filename: fname, - s3uripath: record.filepath, - }; + return { + filename: fname, + s3uripath: record.filepath, + }; }); - incompatibleFiles = incompatibleFiles.concat(divIncompatableFiles); + incompatibleFiles = incompatibleFiles.concat(divIncompatableFiles); } - if (redlineAPIResponse.issingleredlinepackage === "Y") { - if (divCounter === redlineAPIResponse.divdocumentList.length) { + if (redlineAPIResponse.issingleredlinepackage == "Y") { + if (divCounter == redlineAPIResponse.divdocumentList.length) { incompatableObj["divisionid"] = "0"; incompatableObj["divisionname"] = "0"; incompatableObj["incompatibleFiles"] = incompatibleFiles; @@ -455,9 +459,9 @@ const useSaveRedlineForSignoff = (initDocInstance, initDocViewer) => { } } setRedlineIncompatabileMappings(divIncompatableMapping); - return divIncompatableMapping; + return divIncompatableMapping }; - + const fetchDocumentRedlineAnnotations = async ( requestid, documentids, @@ -481,15 +485,17 @@ const useSaveRedlineForSignoff = (initDocInstance, initDocViewer) => { } return "redline"; }; + + const prepareredlinesummarylist = (stitchDocuments) => { let summarylist = []; let alldocuments = []; for (const [key, value] of Object.entries(stitchDocuments)) { let summary_division = {}; - summary_division["divisionid"] = key; + summary_division["divisionid"] = key let documentlist = stitchDocuments[key]; - if (documentlist.length > 0) { - let summary_divdocuments = []; + if(documentlist.length > 0) { + let summary_divdocuments = [] for (let doc of documentlist) { summary_divdocuments.push(doc.documentid); alldocuments.push(doc); @@ -606,6 +612,13 @@ const useSaveRedlineForSignoff = (initDocInstance, initDocViewer) => { } } }; + + + + + + + const stitchSingleDivisionRedlineExport = async ( _instance, divisionDocuments, @@ -726,8 +739,10 @@ const useSaveRedlineForSignoff = (initDocInstance, initDocViewer) => { return sortedList; }; + const saveRedlineDocument = async ( - layertype, + _instance, + layertype, incompatibleFiles, documentList, pageMappedDocs, @@ -741,22 +756,18 @@ const useSaveRedlineForSignoff = (initDocInstance, initDocViewer) => { const divisionFilesList = [...documentList, ...incompatibleFiles]; const divisions = getDivisionsForSaveRedline(divisionFilesList); - const divisionDocuments = getDivisionDocumentMappingForRedline( - divisions, - documentList, - incompatibleFiles - ); + const divisionDocuments = getDivisionDocumentMappingForRedline(divisions, documentList, incompatibleFiles); const documentids = documentList.map((obj) => obj.documentid); getFOIS3DocumentRedlinePreSignedUrl( foiministryrequestid, //normalizeforPdfStitchingReq(divisionDocuments), + requestType, divisionDocuments, async (res) => { toast.update(toastId.current, { render: `Start saving redline...`, isLoading: true, }); - //setRedlineSinglePackage(res.issingleredlinepackage); setIsSingleRedlinePackage(res.issingleredlinepackage); let stitchDoc = {}; @@ -773,7 +784,7 @@ const useSaveRedlineForSignoff = (initDocInstance, initDocViewer) => { let documentsObjArr = []; let divisionstitchpages = []; let divCount = 0; - + console.log("RES:",res) for (let div of res.divdocumentList) { divCount++; let docCount = 0; @@ -880,10 +891,9 @@ const useSaveRedlineForSignoff = (initDocInstance, initDocViewer) => { summarydocuments: prepareredlinesummarylist(stitchDocuments), redactionlayerid: currentLayer.redactionlayerid }); - - if(res.issingleredlinepackage == 'Y'){ + if (res.issingleredlinepackage === "Y") { stitchSingleDivisionRedlineExport( - docInstance, + _instance, divisionDocuments, stitchDocuments, res.issingleredlinepackage @@ -891,7 +901,7 @@ const useSaveRedlineForSignoff = (initDocInstance, initDocViewer) => { } else { stitchForRedlineExport( - docInstance, + _instance, divisionDocuments, stitchDocuments, res.issingleredlinepackage, @@ -908,6 +918,7 @@ const useSaveRedlineForSignoff = (initDocInstance, initDocViewer) => { ); }; + const checkSavingRedline = (redlineReadyAndValid, instance) => { const validRedlineStatus = [ RequestStates["Records Review"], @@ -955,6 +966,8 @@ const useSaveRedlineForSignoff = (initDocInstance, initDocViewer) => { isSingleRedlinePackage, stitchedDocPath ); + setIncludeDuplicatePages(false); + setIncludeNRPages(false); }; const prepareMessageForRedlineZipping = ( divObj, @@ -971,6 +984,7 @@ const useSaveRedlineForSignoff = (initDocInstance, initDocViewer) => { includenrpages: includeNRPages, }; if (stitchedDocPath) { + console.log("stitchedDocPath:",stitchedDocPath) const stitchedDocPathArray = stitchedDocPath?.split("/"); let fileName = stitchedDocPathArray[stitchedDocPathArray.length - 1].split("?")[0]; @@ -1386,20 +1400,21 @@ const stampPageNumberRedline = async ( setDocViewer(initDocViewer); }, [initDocViewer]); - useEffect(() => { - const StitchAndUploadDocument = async () => { - const { PDFNet } = docInstance.Core; - const downloadType = "pdf"; - const divisionCountForToast = Object.keys(redlineStitchObject).length; - for (const [key, value] of Object.entries(redlineStitchObject)) { - toast.update(toastId.current, { - render: - isSingleRedlinePackage === "N" - ? `Saving redline PDF for ${divisionCountForToast} divisions to Object Storage...` - : `Saving redline PDF to Object Storage...`, - isLoading: true, - autoClose: 5000, - }); + const StitchAndUploadDocument = async () => { + const { PDFNet } = docInstance.Core; + const downloadType = "pdf"; + let currentDivisionCount = 0; + const divisionCountForToast = Object.keys(redlineStitchObject).length; + for (const [key, value] of Object.entries(redlineStitchObject)) { + currentDivisionCount++; + toast.update(toastId.current, { + render: + isSingleRedlinePackage == "N" + ? `Saving redline PDF for ${divisionCountForToast} divisions to Object Storage...` + : `Saving redline PDF to Object Storage...`, + isLoading: true, + autoClose: 5000, + }); let divisionid = key; let stitchObject = redlineStitchObject[key]; @@ -1454,108 +1469,107 @@ const stampPageNumberRedline = async ( let xfdfString = parser.toString(xmlObj); - //OIPC - Special Block (Redact S.14) : Begin - if(redlineCategory === "oipcreview") { - const rarr = []; - let annotationManager = docInstance?.Core.annotationManager; - let s14_sectionStamps = await annotationSectionsMapping(xfdfString, formattedAnnotationXML); - let rects = []; - for (const [key, value] of Object.entries(s14_sectionStamps)) { - let s14annoation = annotationManager.getAnnotationById(key); - if ( s14annoation.Subject === "Redact") { - rects = rects.concat( - s14annoation.getQuads().map((q) => { - return { - pageno: s14_sectionStamps[key], - recto: q.toRect(), - vpageno: s14annoation.getPageNumber() - }; - }) - ); - } - + //OIPC - Special Block (Redact S.14) : Begin + if(redlineCategory === "oipcreview") { + const rarr = []; + let annotationManager = docInstance?.Core.annotationManager; + let s14_sectionStamps = await annotationSectionsMapping(xfdfString, formattedAnnotationXML); + let rects = []; + for (const [key, value] of Object.entries(s14_sectionStamps)) { + let s14annoation = annotationManager.getAnnotationById(key); + if ( s14annoation.Subject === "Redact") { + rects = rects.concat( + s14annoation.getQuads().map((q) => { + return { + pageno: s14_sectionStamps[key], + recto: q.toRect(), + vpageno: s14annoation.getPageNumber() + }; + }) + ); + } - } - for (const rect of rects) { - let height = docViewer.getPageHeight(rect.vpageno); - rarr.push(await PDFNet.Redactor.redactionCreate(rect.pageno, (await PDFNet.Rect.init(rect.recto.x1,height-rect.recto.y1,rect.recto.x2,height-rect.recto.y2)), false, '')); - } - if (rarr.length > 0) { - const app = {}; - app.redaction_overlay = true; - app.border = false; - app.show_redacted_content_regions = false; - const doc = await stitchObject.getPDFDoc(); - await PDFNet.Redactor.redact(doc, rarr, app); - } - await stampPageNumberRedline( - stitchObject, - PDFNet, - redlineStitchInfo[divisionid]["stitchpages"], - isSingleRedlinePackage - ); + } - //OIPC - Special Block : End - - - - stitchObject - .getFileData({ - // saves the document with annotations in it - xfdfString: xfdfString, - downloadType: downloadType, - //flatten: true, //commented this as part of #4862 - }) - .then(async (_data) => { - const _arr = new Uint8Array(_data); - const _blob = new Blob([_arr], { - type: "application/pdf", - }); - - saveFilesinS3( - { filepath: redlineStitchInfo[divisionid]["s3path"] }, - _blob, - (_res) => { - // ######### call another process for zipping and generate download here ########## - toast.update(toastId.current, { - render: `Redline PDF saved to Object Storage`, - type: "success", - className: "file-upload-toast", - isLoading: false, - autoClose: 3000, - hideProgressBar: true, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - closeButton: true, - }); - triggerRedlineZipper( - redlineIncompatabileMappings[divisionid], - redlineStitchInfo[divisionid]["s3path"], - divisionCountForToast, - isSingleRedlinePackage - ); - }, - (_err) => { - console.log(_err); - toast.update(toastId.current, { - render: "Failed to save redline pdf to Object Storage", - type: "error", - className: "file-upload-toast", - isLoading: false, - autoClose: 3000, - hideProgressBar: true, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - closeButton: true, - }); - } - ); - }); + for (const rect of rects) { + let height = docViewer.getPageHeight(rect.vpageno); + rarr.push(await PDFNet.Redactor.redactionCreate(rect.pageno, (await PDFNet.Rect.init(rect.recto.x1,height-rect.recto.y1,rect.recto.x2,height-rect.recto.y2)), false, '')); + } + if (rarr.length > 0) { + const app = {}; + app.redaction_overlay = true; + app.border = false; + app.show_redacted_content_regions = false; + const doc = await stitchObject.getPDFDoc(); + await PDFNet.Redactor.redact(doc, rarr, app); + } + await stampPageNumberRedline( + stitchObject, + PDFNet, + redlineStitchInfo[divisionid]["stitchpages"], + isSingleRedlinePackage + ); } + //OIPC - Special Block : End + stitchObject + .getFileData({ + // saves the document with annotations in it + xfdfString: xfdfString, + downloadType: downloadType, + //flatten: true, //commented this as part of #4862 + }) + .then(async (_data) => { + const _arr = new Uint8Array(_data); + const _blob = new Blob([_arr], { + type: "application/pdf", + }); + + saveFilesinS3( + { filepath: redlineStitchInfo[divisionid]["s3path"] }, + _blob, + (_res) => { + // ######### call another process for zipping and generate download here ########## + toast.update(toastId.current, { + render: `Redline PDF saved to Object Storage`, + type: "success", + className: "file-upload-toast", + isLoading: false, + autoClose: 3000, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + closeButton: true, + }); + triggerRedlineZipper( + redlineIncompatabileMappings[divisionid], + redlineStitchInfo[divisionid]["s3path"], + divisionCountForToast, + isSingleRedlinePackage + ); + }, + (_err) => { + console.log(_err); + toast.update(toastId.current, { + render: "Failed to save redline pdf to Object Storage", + type: "error", + className: "file-upload-toast", + isLoading: false, + autoClose: 3000, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + closeButton: true, + }); + } + ); + }); } - }; + } + }; + + useEffect(() => { if ( redlineStitchObject && @@ -1566,7 +1580,8 @@ const stampPageNumberRedline = async ( StitchAndUploadDocument(); } }, [redlineDocumentAnnotations, redlineStitchObject, redlineStitchInfo]); - + + useEffect(() => { if ( pdftronDocObjectsForRedline?.length > 0 && diff --git a/web/src/components/FOI/Home/CreateResponsePDF/useSaveResponsePackage.js b/web/src/components/FOI/Home/CreateResponsePDF/useSaveResponsePackage.js index 4129dda4b..3a2a6a4c4 100644 --- a/web/src/components/FOI/Home/CreateResponsePDF/useSaveResponsePackage.js +++ b/web/src/components/FOI/Home/CreateResponsePDF/useSaveResponsePackage.js @@ -74,9 +74,17 @@ const useSaveResponsePackage = () => { }; const prepareMessageForResponseZipping = ( stitchedfilepath, - zipServiceMessage + zipServiceMessage, + personalAttributes, + documentid ) => { const stitchedDocPathArray = stitchedfilepath.split("/"); + let recordLabel = Object.keys(personalAttributes).length > 0 ? personalAttributes.person + ' - ' + + personalAttributes.filetype + ' - ' + + personalAttributes.trackingid : "" ; + if (personalAttributes.volume) { + recordLabel += (' - ' + personalAttributes.volume) + } let fileName = stitchedDocPathArray[stitchedDocPathArray.length - 1].split("?")[0]; @@ -84,7 +92,9 @@ const useSaveResponsePackage = () => { const file = { filename: fileName, + recordname:recordLabel, s3uripath: decodeURIComponent(stitchedfilepath.split("?")[0]), + documentid: documentid, }; const zipDocObj = { files: [], @@ -96,30 +106,106 @@ const useSaveResponsePackage = () => { console.log(error); }); }; - const prepareresponseredlinesummarylist = (documentlist) => { + // const prepareresponseredlinesummarylist = (documentlist) => { + // let summarylist = []; + // let summary_division = {}; + // let summary_divdocuments = []; + // let alldocuments = []; + // summary_division["divisionid"] = "0"; + // for (let doc of documentlist) { + // summary_divdocuments.push(doc.documentid); + // alldocuments.push(doc); + // } + // summary_division["documentids"] = summary_divdocuments; + // summarylist.push(summary_division); + + // let sorteddocids = []; + // // sort based on sortorder as the sortorder added based on the LastModified + // let sorteddocs = sortBySortOrder(alldocuments); + // for (const sorteddoc of sorteddocs) { + // sorteddocids.push(sorteddoc["documentid"]); + // } + // return { sorteddocuments: sorteddocids, pkgdocuments: summarylist }; + // }; + + const prepareresponseredlinesummarylist = (documentlist, bcgovcode) => { let summarylist = []; - let summary_division = {}; - let summary_divdocuments = []; let alldocuments = []; - summary_division["divisionid"] = "0"; - for (let doc of documentlist) { - summary_divdocuments.push(doc.documentid); - alldocuments.push(doc); - } - summary_division["documentids"] = summary_divdocuments; - summarylist.push(summary_division); - + console.log("\ndocumentlist:", documentlist); let sorteddocids = []; - // sort based on sortorder as the sortorder added based on the LastModified - let sorteddocs = sortBySortOrder(alldocuments); - for (const sorteddoc of sorteddocs) { - if (!sorteddocids.includes(sorteddoc['documentid'])) { - sorteddocids.push(sorteddoc['documentid']); + if (bcgovcode?.toLowerCase() === 'mcf') { + let labelGroups = {}; + let alldocids = []; + + for (let file of documentlist) { + var label = file.attributes.personalattributes.person == 'APPLICANT' ? + (file.attributes.personalattributes.person + ' - ' + + file.attributes.personalattributes.filetype + ' - ' + + file.attributes.personalattributes.trackingid) + : + (file.attributes.personalattributes.filetype + ' - ' + + file.attributes.personalattributes.trackingid) + if (file.attributes.personalattributes.volume) { + label += (' - ' + file.attributes.personalattributes.volume); + } + + if (!labelGroups[label]) { + labelGroups[label] = []; + } + labelGroups[label].push(file.documentid); + alldocids.push(file.documentid); + alldocuments.push(file); + } + + let divisionRecords = []; + for (let label in labelGroups) { + let record = { + "recordname": label, + "documentids": labelGroups[label] + }; + divisionRecords.push(record); + } + + let summary_division = { + "divisionid": 0, + "documentids":alldocids, + "records": divisionRecords + }; + + summarylist.push(summary_division); + + // Sort based on sortorder as the sortorder added based on the LastModified + let sorteddocs = sortBySortOrder(alldocuments); + for (const sorteddoc of sorteddocs) { + if (!sorteddocids.includes(sorteddoc['documentid'])) { + sorteddocids.push(sorteddoc['documentid']); + } + } + } else { + let summary_division = { + "divisionid": '0', + "documentids": [] + }; + + for (let doc of documentlist) { + summary_division.documentids.push(doc.documentid); + alldocuments.push(doc); + } + summarylist.push(summary_division); + + // Sort based on sortorder as the sortorder added based on the LastModified + let sorteddocs = sortBySortOrder(alldocuments); + for (const sorteddoc of sorteddocs) { + if (!sorteddocids.includes(sorteddoc['documentid'])) { + sorteddocids.push(sorteddoc['documentid']); + } } - } - return { sorteddocuments: sorteddocids, pkgdocuments: summarylist }; + + return {"sorteddocuments": sorteddocids, "pkgdocuments": summarylist}; }; + + const saveResponsePackage = async ( documentViewer, annotationManager, @@ -135,7 +221,7 @@ const useSaveResponsePackage = () => { attributes: [], requestnumber: "", bcgovcode: "", - summarydocuments: prepareresponseredlinesummarylist(documentList), + summarydocuments: {} , redactionlayerid: currentLayer.redactionlayerid, }; getResponsePackagePreSignedUrl( @@ -145,6 +231,7 @@ const useSaveResponsePackage = () => { const toastID = toast.loading("Start generating final package..."); zipServiceMessage.requestnumber = res.requestnumber; zipServiceMessage.bcgovcode = res.bcgovcode; + zipServiceMessage.summarydocuments= prepareresponseredlinesummarylist(documentList,zipServiceMessage.bcgovcode) let annotList = annotationManager.getAnnotationsList(); annotationManager.ungroupAnnotations(annotList); /** remove duplicate and not responsive pages */ @@ -240,7 +327,9 @@ const useSaveResponsePackage = () => { }); prepareMessageForResponseZipping( res.s3path_save, - zipServiceMessage + zipServiceMessage, + (Object.keys(res.attributes).length > 0 && 'personalattributes' in res.attributes && Object.keys(res.attributes?.personalattributes).length > 0) ? res.attributes.personalattributes: {}, + res.documentid ); setTimeout(() => { window.location.reload(true); @@ -271,7 +360,8 @@ const useSaveResponsePackage = () => { }; const checkSavingFinalPackage = (redlineReadyAndValid, instance) => { const validFinalPackageStatus = requestStatus === RequestStates["Response"]; - setEnableSavingFinal(redlineReadyAndValid && validFinalPackageStatus); + setEnableSavingFinal(true) + //setEnableSavingFinal(redlineReadyAndValid && validFinalPackageStatus); if (instance) { const document = instance.UI.iframeWindow.document; document.getElementById("final_package").disabled = diff --git a/web/src/components/FOI/Home/CustomTreeView.tsx b/web/src/components/FOI/Home/CustomTreeView.tsx index 1c187debc..b0b7f3dfd 100644 --- a/web/src/components/FOI/Home/CustomTreeView.tsx +++ b/web/src/components/FOI/Home/CustomTreeView.tsx @@ -37,6 +37,7 @@ const CustomTreeView = React.memo(React.forwardRef(({ assignIcon, pageFlags, syncPageFlagsOnAction, + requestInfo }: any, ref) => { const StyledTreeItem = styled(TreeItem)((props: any) => ({ [`& .${treeItemClasses.label}`]: { @@ -57,11 +58,11 @@ const CustomTreeView = React.memo(React.forwardRef(({ const [openContextPopup, setOpenContextPopup] = useState(false); const [disableHover, setDisableHover] = useState(false); const [anchorPosition, setAnchorPosition] = useState(undefined); - + const [activeNode, setActiveNode] = useState(); + const [currentEditRecord, setCurrentEditRecord] = useState(); useImperativeHandle(ref, () => ({ async scrollToPage(event: any, newExpandedItems: string[], pageId: string) { - setExpandedItems([...new Set(expandedItems.concat(newExpandedItems))]); await new Promise(resolve => setTimeout(resolve, 400)); // wait for expand animation to finish apiRef.current?.focusItem(event, pageId) @@ -71,10 +72,9 @@ const CustomTreeView = React.memo(React.forwardRef(({ scrollLeftPanelPosition(event: any) { let _lastselected = localStorage.getItem("lastselected") - if(_lastselected) { - let _docid = JSON.parse(_lastselected)?.docid + let _docid = JSON.parse(_lastselected)?.docid let docidstring = '' if(_lastselected.indexOf('division')>-1) { @@ -123,6 +123,7 @@ const CustomTreeView = React.memo(React.forwardRef(({ let selectedOthers = []; let selectedNodes = []; for (let nodeId of nodeIds) { + nodeId = nodeId.replace(/undefined/g, "null"); let node = JSON.parse(nodeId); selectedNodes.push(node); if (node.page) { @@ -173,12 +174,25 @@ const CustomTreeView = React.memo(React.forwardRef(({ } const CustomTreeItem = React.forwardRef((props: any, ref: any) => { - let itemid = JSON.parse(props.itemId); + + // props.itemId = props?.itemId?.replaceAll("undefined", "\"\""); + // let itemid = JSON.parse(props?.itemId); + //console.log("CustomTreeItem-",props) + const derivedItemId = props.itemId?.replaceAll("undefined", "\"\"") ?? ""; + // Parse the derived itemId + let itemid:any; + try { + itemid = JSON.parse(derivedItemId); + } catch (error) { + console.error("Invalid JSON in itemId:", error); + itemid = derivedItemId; // Fallback to the derived itemId if JSON parsing fails + } + return ( {return CloseSquare(props)}}} slots={{endIcon: (_props: any) => {return addIcons(itemid)}}} @@ -190,17 +204,32 @@ const CustomTreeView = React.memo(React.forwardRef(({ // }, // }} /> - ) + ) }); const openContextMenu = (e: any, props: any) => { - if (props.children) return + if (props.children && requestInfo.bcgovcode !== "MCF" && requestInfo.requesttype !== "personal") return e.preventDefault(); let nodeId: string = e.target.parentElement.parentElement.id; if (nodeId === "") { nodeId = e.currentTarget.id; } nodeId = nodeId.substring(nodeId.indexOf('{')); + nodeId = nodeId.replace(/undefined/g, "null"); + + //mcf personal + let nodeIdJson = JSON.parse(nodeId); + if(nodeIdJson.docid) { //popup menu only if docid exist (level 2 tree and children) + let currentFiles: any = filesForDisplay.filter((f: any) => { + return f.documentid === nodeIdJson.docid; + }); + setCurrentEditRecord(currentFiles[0]); + + setActiveNode(nodeIdJson); + } else { //mcf personal level 1 tree item + return; + } + let selectedNodes: any; if (!selected.includes(nodeId)) { selectedNodes = [nodeId] @@ -228,7 +257,7 @@ const CustomTreeView = React.memo(React.forwardRef(({ return ( <> - {openContextPopup === true && + {openContextPopup === true && } diff --git a/web/src/components/FOI/Home/DocumentSelector.scss b/web/src/components/FOI/Home/DocumentSelector.scss index 1be2a67ce..331b249aa 100644 --- a/web/src/components/FOI/Home/DocumentSelector.scss +++ b/web/src/components/FOI/Home/DocumentSelector.scss @@ -7,10 +7,12 @@ color: #808080; margin:16px 0px; font-size: 14px; + min-width: 90px; .heading { margin-left: 16px; margin-bottom:12px; + padding-right: 4px; } } @@ -281,3 +283,61 @@ gap: 2px !important; } } + +.pluscircle { + margin: 0px 8px 8px 0px; + position: relative; + display: inline-flex; + align-items: center; + height: 24px; +} +.taglist-cfdpersonal { + margin: 10px 9% 15px 9%; +} +.taglist { + margin: 15px 9% 25px 9%; +} +.tagtitle { + margin-left: 9%; + margin-top: 5px; +} +.tag-message-container { + margin: 5px 0px 0px 9%; + display: block; + p { + line-height: 1rem; + font-size: 12px; + } +} +.tag-message-container-scanning { + margin: 5px 0px 20px 9%; + display: block; + p { + line-height: 1rem; + font-size: 12px; + } + padding-right: 10px; +} +.search-grid-container { + display: inline-block !important; + margin: 20px 0 !important; +} +.search-grid { + // border-right: 2px solid #38598A; + background-color: rgba(56,89,138,0.1); +} +.search-icon { + color: #38598A; +} +.editPersonalTags { + padding: 4px 0px; + cursor: pointer; +} +.editPersonalTags:hover { + background-color: #f2f2f2; +} +.editPersonalTagsDisabled { + padding: 4px 0px; + cursor: not-allowed; + color: #cfcfcf; +} \ No newline at end of file diff --git a/web/src/components/FOI/Home/DocumentSelector.tsx b/web/src/components/FOI/Home/DocumentSelector.tsx index 3b6454575..c6c0d7b43 100644 --- a/web/src/components/FOI/Home/DocumentSelector.tsx +++ b/web/src/components/FOI/Home/DocumentSelector.tsx @@ -29,7 +29,6 @@ import { pageFlagTypes } from "../../../constants/enum"; import Popover from "@mui/material/Popover"; import CustomTreeView from "./CustomTreeView"; - const DocumentSelector = React.memo( React.forwardRef( ( @@ -57,7 +56,7 @@ const DocumentSelector = React.memo( const files = useMemo(() => documents, [documents]); const [organizeBy, setOrganizeBy] = useState("lastmodified"); const [pageFlagList, setPageFlagList] = useState([]); - const [filesForDisplay, setFilesForDisplay] = useState([]); + const [filesForDisplay, setFilesForDisplay] = useState([]); const [consultMinistries, setConsultMinistries] = useState([]); const [filterFlags, setFilterFlags] = useState([]); const [filteredFiles, setFilteredFiles] = useState(files); @@ -81,7 +80,24 @@ const DocumentSelector = React.memo( (f: any) => f.documentid === lookup.docid ); let pageId, newExpandedItems; - if (organizeBy === "lastmodified") { + if(requestInfo.bcgovcode === "MCF" && requestInfo.requesttype === "personal"){ + let label = file.attributes.personalattributes.person + ' - ' + file.attributes.personalattributes.filetype; + if (file.attributes.personalattributes.trackingid) { + label += ' - ' + file.attributes.personalattributes.trackingid; + } + if (file.attributes.personalattributes.volume) { + label += ' - ' + file.attributes.personalattributes.volume; + } + pageId = `{"filevolume": "${label}", "docid": ${file.documentid}, "page": ${ + lookup.page + }, "flagid": [${getPageFlagIds(file.pageFlag, lookup.page)}], "title": "${getFlagName(file, lookup.page)}"}`; + + newExpandedItems = [ + '{"filevolume": "' + label + '"}', + '{"filevolume": "' + label + '", "docid": ' + lookup.docid + '}' + ]; + } + else if (organizeBy === "lastmodified") { pageId = `{"docid": ${file.documentid}, "page": ${ lookup.page }, "flagid": [${getPageFlagIds( @@ -107,7 +123,6 @@ const DocumentSelector = React.memo( "}", ]; } - treeRef?.current?.scrollToPage(event, newExpandedItems, pageId); }, @@ -141,7 +156,7 @@ const DocumentSelector = React.memo( useEffect(() => { if ( requestInfo.requesttype == "personal" && - ["MSD", "MCF"].includes(requestInfo.bcgovcode) + ["MSD"].includes(requestInfo.bcgovcode) ) { setOrganizeBy("division"); } @@ -312,12 +327,24 @@ const DocumentSelector = React.memo( const onFilterChange = (filterValue: string) => { - setFilesForDisplay( - files.filter((file: any) => file.filename.includes(filterValue)) - ); - setFilteredFiles( - files.filter((file: any) => file.filename.includes(filterValue)) - ); + if(requestInfo.bcgovcode === "MCF" && requestInfo.requesttype === "personal"){ + let filtered = files.filter((file: any) => { + const personalAttributes = file.attributes.personalattributes; + return Object.values(personalAttributes).some((value: any) => + value.toLowerCase().includes(filterValue.toLowerCase()) + ); + }) + setFilesForDisplay(filtered); + setFilteredFiles(filtered); + } + else{ + setFilesForDisplay( + files.filter((file: any) => file.filename.includes(filterValue)) + ); + setFilteredFiles( + files.filter((file: any) => file.filename.includes(filterValue)) + ); + } }; const selectTreeItem = (docid: any, page: number) => { @@ -587,7 +614,20 @@ const DocumentSelector = React.memo( }); } }); - if (organizeBy === "lastmodified") { + if (requestInfo.bcgovcode === "MCF" && requestInfo.requesttype === "personal") { + return filteredpages.map((p: any) => { + return { + id: `{"filevolume": "${division}", "docid": ${ + file.documentid + }, "page": ${p}, "flagid": [${getPageFlagIds( + file.pageFlag, + p + )}], "title": "${getFlagName(file, p)}"}`, + label: getPageLabel(file, p), + }; + }); + } + else if (organizeBy === "lastmodified") { return filteredpages.map((p: any) => { return { id: `{"docid": ${ @@ -624,73 +664,106 @@ const DocumentSelector = React.memo( // } // } } else { - if (organizeBy === "lastmodified") { - return file.pages.map((p: any) => { - return { - id: `{"docid": ${ - file.documentid - }, "page": ${p}, "flagid": [${getPageFlagIds( - file.pageFlag, - p - )}], "title": "${getFlagName(file, p)}"}`, - label: getPageLabel(file, p), - }; - }); - } else { - return file.pages.map((p: any) => { - return { - id: `{"division": ${division?.divisionid}, "docid": ${ - file.documentid - }, "page": ${p}, "flagid": [${getPageFlagIds( - file.pageFlag, - p - )}], "title": "${getFlagName(file, p)}"}`, - label: getPageLabel(file, p), - }; - }); - } - } - }; - - const getTreeItems = () => { - if (pageFlags) { - if (organizeBy === "lastmodified") { - return filesForDisplay.map((file: any, index: number) => { - return { - id: `{"docid": ${file.documentid}}`, - label: file.filename, - children: getFilePages(file), - }; - }); - } else { - if (filesForDisplay.length > 0) { - return divisions.map((division: any) => { + if (requestInfo.bcgovcode === "MCF" && requestInfo.requesttype === "personal") { + return file.pages.map((p: any) => { + return { + id: `{"filevolume": "${division}", "docid": ${ + file.documentid + }, "page": ${p}, "flagid": [${getPageFlagIds( + file.pageFlag, + p + )}], "title": "${getFlagName(file, p)}"}`, + label: getPageLabel(file, p), + }; + }); + } + else if (organizeBy === "lastmodified") { + return file.pages.map((p: any) => { return { - id:`{"division": ${division.divisionid}}`, - label: division.name, - children: filesForDisplay - .filter((file: any) => - file.divisions - .map((d: any) => d.divisionid) - .includes(division.divisionid) - ) - .map((file: any, index: number) => { - return { - id: `{"division": ${division.divisionid}, "docid": ${file.documentid}}`, - label: file.filename, - children: getFilePages(file, division), - }; - }), + id: `{"docid": ${ + file.documentid + }, "page": ${p}, "flagid": [${getPageFlagIds( + file.pageFlag, + p + )}], "title": "${getFlagName(file, p)}"}`, + label: getPageLabel(file, p), }; }); } else { - return []; + return file.pages.map((p: any) => { + return { + id: `{"division": ${division?.divisionid}, "docid": ${ + file.documentid + }, "page": ${p}, "flagid": [${getPageFlagIds( + file.pageFlag, + p + )}], "title": "${getFlagName(file, p)}"}`, + label: getPageLabel(file, p), + }; + }); + } + } + } + + const getTreeItems = () => { + if (pageFlags) { + if (requestInfo.bcgovcode === "MCF" && requestInfo.requesttype === "personal") { + var index = 0; + let tree: any = [] + for (let file of filesForDisplay as any[]) { + var label = file.attributes.personalattributes.person + ' - ' + + file.attributes.personalattributes.filetype; + if (file.attributes.personalattributes.trackingid) { + label += (' - ' + file.attributes.personalattributes.trackingid) + } + if (file.attributes.personalattributes.volume) { + label += (' - ' + file.attributes.personalattributes.volume) + } + if (tree.length === 0 || tree[index].label !== label) { + tree.push({ + id: `{"filevolume": "${label}"}`, + label: label, + children: [] + }) + index = tree.length - 1 + } + tree[index].children.push({ + id: `{"filevolume": "${label}", "docid": ${file.documentid}}`, + label: (file.attributes.personalattributes.personaltag || 'TBD') + ' (' + file.pages.length + ')', + children: getFilePages(file, label) + }) + } + return tree; + } else if (organizeBy === "lastmodified" ) { + return filesForDisplay.map((file: any, index: number) => {return { + id: `{"docid": ${file.documentid}}`, + label: file.filename, + children: getFilePages(file) //file.pages.map( + // (p: any) => { + // return { + // id: `{"docid": ${file.documentid}, "page": ${p + 1}}`, + // label: getPageLabel(file, p) + // } + // } + // ) + }}) + } else if (organizeBy === "division" ) { + return divisions.map((division: any) => { + return { + id: `{"division": ${division.divisionid}}`, + label: division.name, + children: filesForDisplay.filter((file: any) => file.divisions.map((d: any) => d.divisionid).includes(division.divisionid)).map((file: any, index: number) => {return { + id: `{"division": ${division.divisionid}, "docid": ${file.documentid}}`, + label: file.filename, + children: getFilePages(file, division) + }}) + } + }) } - } } else { - return []; + return [] } - }; + } return (
@@ -773,40 +846,44 @@ const DocumentSelector = React.memo( />
-
-
-
- Organize by: -
-
- - { - setOrganizeBy("division"); - //setExpandedItems([]); - }} - clicked={organizeBy === "division"} - /> - { - setOrganizeBy("lastmodified"); - //setExpandedItems([]); - }} - clicked={organizeBy === "lastmodified"} - /> - + { (!['MCF'].includes(requestInfo.bcgovcode) || requestInfo.requesttype !== "personal") && + <> +
+
+
+ Organize by: +
+
+ + { + setOrganizeBy("division"); + //setExpandedItems([]); + }} + clicked={organizeBy === "division"} + /> + { + setOrganizeBy("lastmodified"); + //setExpandedItems([]); + }} + clicked={organizeBy === "lastmodified"} + /> + +
-
+ + }
Filter: @@ -933,6 +1010,7 @@ const DocumentSelector = React.memo( assignIcon={assignIcon} pageFlags={pageFlags} syncPageFlagsOnAction={syncPageFlagsOnAction} + requestInfo={requestInfo} /> ) } diff --git a/web/src/components/FOI/Home/Home.js b/web/src/components/FOI/Home/Home.js index 0ef80f555..e1b7ec441 100644 --- a/web/src/components/FOI/Home/Home.js +++ b/web/src/components/FOI/Home/Home.js @@ -9,6 +9,7 @@ import { fetchDocuments, fetchRedactionLayerMasterData, fetchDeletedDocumentPages, + fetchPersonalAttributes } from "../../../apiManager/services/docReviewerService"; import { getFOIS3DocumentPreSignedUrls } from "../../../apiManager/services/foiOSSService"; import { useParams } from "react-router-dom"; @@ -26,6 +27,7 @@ import IconButton from "@mui/material/IconButton"; function Home() { const user = useAppSelector((state) => state.user.userDetail); const validoipcreviewlayer = useAppSelector((state) => state.documents?.requestinfo?.validoipcreviewlayer); + const requestInfo = useAppSelector((state) => state.documents?.requestinfo); const redactionLayers = useAppSelector((state) => state.documents?.redactionLayers); const [files, setFiles] = useState([]); // added incompatibleFiles to capture incompatible files for download redline @@ -64,7 +66,7 @@ function Home() { fetchDocuments( parseInt(foiministryrequestid), - async (data, documentDivisions) => { + async (data, documentDivisions, _requestInfo) => { setDivisions(documentDivisions); const getFileExt = (filepath) => { const parts = filepath.split(".") @@ -90,8 +92,8 @@ function Home() { const isCompatible = !d.attributes.incompatible || isPdfFile return isCompatible }); - let sortedFiles = [] - sortDocList(_files, null, sortedFiles); + // let sortedFiles = [] + // sortDocList(_files, null, sortedFiles); // setFiles(sortedFiles); setCurrentPageInfo({ file: _files[0] || {}, page: 1 }); if (_files.length > 0) { @@ -102,11 +104,12 @@ function Home() { totalPageCountVal += filePageCount; }); - let doclist = []; + let doclist = []; + let requestInfo = _requestInfo; getFOIS3DocumentPreSignedUrls( documentObjs, (newDocumentObjs) => { - sortDocList(newDocumentObjs, null, doclist); + sortDocList(newDocumentObjs, null, doclist, requestInfo); //prepareMapperObj will add sortorder, stitchIndex and totalPageCount to doclist //and prepare the PageMappedDocs object prepareMapperObj(doclist, deletedDocPages); @@ -133,6 +136,8 @@ function Home() { console.log(error); } ); + + }, []); @@ -167,6 +172,17 @@ function Home() { } }, [validoipcreviewlayer, redactionLayers]) + useEffect(() => { + if(requestInfo?.bcgovcode && requestInfo.bcgovcode === "MCF" + && requestInfo?.requesttype && requestInfo.requesttype === "personal") { + fetchPersonalAttributes( + requestInfo.bcgovcode, + (error) => + console.log(error) + ); + } + }, [requestInfo]) + const prepareMapperObj = (doclistwithSortOrder, deletedDocPages) => { let mappedDocs = { stitchedPageLookup: {}, docIdLookup: {}, redlineDocIdLookup: {} }; let mappedDoc = { docId: 0, version: 0, division: "", pageMappings: [] }; diff --git a/web/src/components/FOI/Home/MCFPersonal.js b/web/src/components/FOI/Home/MCFPersonal.js new file mode 100644 index 000000000..7ba9912fc --- /dev/null +++ b/web/src/components/FOI/Home/MCFPersonal.js @@ -0,0 +1,656 @@ +import React, { useState, useEffect } from "react"; +import { useSelector } from "react-redux"; +// import "../FileUpload/FileUpload.scss"; +import Chip from "@mui/material/Chip"; +import Dialog from "@mui/material/Dialog"; +import DialogActions from "@mui/material/DialogActions"; +import DialogContent from "@mui/material/DialogContent"; +import DialogContentText from "@mui/material/DialogContentText"; +import DialogTitle from "@mui/material/DialogTitle"; +import CloseIcon from "@mui/icons-material/Close"; +import Grid from "@mui/material/Grid"; +import Paper from "@mui/material/Paper"; +import SearchIcon from "@mui/icons-material/Search"; +import InputAdornment from "@mui/material/InputAdornment"; +import InputBase from "@mui/material/InputBase"; +import IconButton from "@mui/material/IconButton"; +import TextField from '@mui/material/TextField'; +import AddCircleIcon from '@mui/icons-material/AddCircle'; +import _ from 'lodash'; +import { MCFPopularSections } from "../../../constants/enum"; +// import "./DocumentSelector.scss"; +// import "./FileUpload.scss"; + +const MCFPersonal = ({ + editTagModalOpen, + setEditTagModalOpen, + setOpenContextPopup, + setNewDivision, + // tagValue, + curPersonalAttributes, + setNewPersonalAttributes, + updatePersonalAttributes, + setCurPersonalAttributes, + setCurrentEditRecord +}) => { + const [personalAttributes, setPersonalAttributes] = useState({ + person: "", + filetype: "", + volume: "", + trackingid: "", + personaltag: "TBD" + }); + + const [searchValue, setSearchValue] = useState(""); + const [showAdditionalTags, setShowAdditionalTags] = useState(false); + const [additionalTagList, setAdditionalTagList] = useState([]); + + const MCFSections = useSelector((state) => state.documents.foiPersonalSections); + const [tagList, setTagList] = useState([]); + const [otherTagList, setOtherTagList] = useState([]); + + const MCFPeople = useSelector( + (state) => state.documents.foiPersonalPeople + ); + const MCFFiletypes = useSelector( + (state) => state.documents.foiPersonalFiletypes + ); + const MCFVolumes = useSelector( + (state) => state.documents.foiPersonalVolumes + ); + + const [people, setPeople] = useState([]); + const [volumes, setVolumes] = useState([]); + const [fileTypes, setFileTypes] = useState([]); + const [otherFileTypes, setOtherFileTypes] = useState([]); + + const [showAllPeople, setShowAllPeople] = useState(false); + const [showAllVolumes, setShowAllVolumes] = useState(false); + const [fileTypeSearchValue, setFileTypeSearchValue] = useState(""); + const [additionalFileTypes, setAdditionalFileTypes] = useState([]); + const [showAdditionalFileTypes, setShowAdditionalFileTypes] = useState(false); + + useEffect(() => { + setPersonalAttributes(curPersonalAttributes); + },[curPersonalAttributes]) + + useEffect(() => { + if(MCFSections?.sections) { + if(MCFSections.sections.length > MCFPopularSections-1) { + setTagList(MCFSections.sections.slice(0, MCFPopularSections-1)); + setOtherTagList(MCFSections.sections.slice(MCFPopularSections)); + } else { + setTagList(MCFSections.sections); + setOtherTagList([]); + } + } + },[MCFSections]) + + useEffect(() => { + if(MCFPeople?.people) { + if(MCFPeople.people.length > 5) { + setPeople(MCFPeople.people.slice(0, 5)); + } else { + setPeople(MCFPeople.people); + } + } + },[MCFPeople]) + + useEffect(() => { + if(!!MCFFiletypes && MCFVolumes?.volumes) { + if(MCFFiletypes.filetypes.length > 5) { + setVolumes(MCFVolumes.volumes.slice(0, 5)); + } else { + setVolumes(MCFVolumes.volumes); + } + } + },[MCFVolumes]) + + useEffect(() => { + if(MCFFiletypes?.filetypes) { + if(MCFFiletypes.filetypes.length > 6) { + setFileTypes(MCFFiletypes.filetypes.slice(0, 6)); + setOtherFileTypes(MCFFiletypes.filetypes.slice(6, MCFFiletypes.filetypes.length)) + } else { + setFileTypes(MCFFiletypes.filetypes); + setOtherFileTypes([]) + } + } + },[MCFFiletypes]) + + const searchFileTypes = (_fileTypeArray, _keyword, _selectedFileType) => { + let newFileTypeArray = []; + if(_keyword || _selectedFileType) { + _fileTypeArray.map((section) => { + if(_keyword && section.name.toLowerCase().includes(_keyword.toLowerCase())) { + newFileTypeArray.push(section); + } else if(section.name === _selectedFileType) { + newFileTypeArray.unshift(section); + } + }); + } + + if(newFileTypeArray.length > 0) { + setShowAdditionalFileTypes(true); + } else { + setShowAdditionalFileTypes(false); + } + + return newFileTypeArray; + } + + React.useEffect(() => { + if(showAllPeople) { + setPeople(MCFPeople.people) + } else { + setPeople(MCFPeople.people.slice(0, 5)) + } + if(showAllVolumes) { + setVolumes(MCFVolumes.volumes) + } else { + setVolumes(MCFVolumes.volumes.slice(0, 5)) + } + },[showAllPeople, showAllVolumes]) + + React.useEffect(() => { + if(MCFPeople.people.length > 0 && personalAttributes.person !== "") { + setShowAllPeople( MCFPeople.people.filter(p => p.name==personalAttributes.person)[0]?.sortorder >= 5 ); + } + + if(MCFVolumes.volumes.length > 0 && personalAttributes.volume !== "") { + setShowAllVolumes( MCFVolumes.volumes.filter(v => v.name==personalAttributes.volume)[0]?.sortorder >= 5 ); + } + },[personalAttributes]) + + React.useEffect(() => { + setAdditionalFileTypes(searchFileTypes(otherFileTypes, fileTypeSearchValue, personalAttributes?.filetype)); + },[fileTypeSearchValue, otherFileTypes, personalAttributes]) + + useEffect(() => { + setAdditionalTagList(searchSections(otherTagList, searchValue, personalAttributes?.personaltag)); + },[searchValue, otherTagList, personalAttributes]) + + const searchSections = (_sectionArray, _keyword, _selectedSectionValue) => { + let newSectionArray = []; + if(_keyword || _selectedSectionValue) { + _sectionArray.map((section) => { + if(_keyword && section.name.toLowerCase().includes(_keyword.toLowerCase())) { + newSectionArray.push(section); + } else if(section.name === _selectedSectionValue) { + newSectionArray.unshift(section); + } + }); + } + + if(newSectionArray.length > 0) { + setShowAdditionalTags(true); + } else { + setShowAdditionalTags(false); + } + return newSectionArray; + } + + const handlePersonalAttributesChange = (_attribute, _type) => { + let _newPersonalAttributes = { + person: personalAttributes.person, + filetype: personalAttributes.filetype, + volume: personalAttributes.volume, + trackingid: personalAttributes.trackingid, + personaltag: personalAttributes.personaltag + }; + + switch(_type) { + case "person": + _newPersonalAttributes.person = _attribute.name; + setPersonalAttributes(_newPersonalAttributes); + setNewPersonalAttributes(_newPersonalAttributes); + return; + case "filetype": + _newPersonalAttributes.filetype = _attribute.name; + setPersonalAttributes(_newPersonalAttributes); + setNewPersonalAttributes(_newPersonalAttributes); + return; + case "volume": + _newPersonalAttributes.volume = _attribute.name; + setPersonalAttributes(_newPersonalAttributes); + setNewPersonalAttributes(_newPersonalAttributes); + return; + case "trackingid": + _newPersonalAttributes.trackingid = _attribute; + setPersonalAttributes(_newPersonalAttributes); + setNewPersonalAttributes(_newPersonalAttributes); + return; + case "personaltag": + _newPersonalAttributes.personaltag = _attribute.name; + setPersonalAttributes(_newPersonalAttributes); + setNewPersonalAttributes(_newPersonalAttributes); + return; + default: + return; + } + }; + + const handleClose = () => { + setSearchValue(""); + setFileTypeSearchValue(""); + setCurrentEditRecord(); + setCurPersonalAttributes({ + person: "", + filetype: "", + volume: "", + trackingid: "", + personaltag: "TBD" + }); + setNewPersonalAttributes(); + setEditTagModalOpen(false); + setOpenContextPopup(false); + }; + + const reset = () => { + setSearchValue(""); + setFileTypeSearchValue(""); + }; + + const handleFileTypeSearchKeywordChange = (keyword) => { + setFileTypeSearchValue(keyword); + } + + const handleTagSearchKeywordChange = (keyword) => { + setSearchValue(keyword); + } + + return ( + +
+ handleClose()} + aria-labelledby="state-change-dialog-title" + aria-describedby="state-change-dialog-description" + maxWidth={"md"} + fullWidth={true} + disableEnforceFocus + // id="state-change-dialog" + > + +

Edit Tags

+ handleClose()} + > + Close + + +
+ + +
+ + Update the tag you want to associate with the selected record. + If this record is from the applicant's file, please select + applicant otherwise you can associate it to a different person. + If you choose to update all by ID, this edit will apply to all + records associated with the tracking ID and file type associated + to that person. + +
+ +
+ Select Person: * +
+
+ {people.map(p => + {handlePersonalAttributesChange(p, "person")}} + clicked={personalAttributes?.person === p.name} + /> + )} + {!showAllPeople && ({setShowAllPeople(true)}} + />)} +
+ +
+ Select Volume: +
+
+ {volumes.map(v => + {handlePersonalAttributesChange(v, "volume")}} + clicked={personalAttributes?.volume === v.name} + /> + )} + {!showAllVolumes && ({setShowAllVolumes(true)}} + />)} +
+ +
+ Select File Type: * +
+
+ {fileTypes.map(f => + {handlePersonalAttributesChange(f, "filetype")}} + clicked={personalAttributes?.filetype === f.name} + /> + )} +
+
+ + + + + {handleFileTypeSearchKeywordChange(e.target.value.trim())}} + sx={{ + color: "#38598A", + }} + startAdornment={ + + + Search any additional filetypes here + + + + } + fullWidth + /> + + + {showAdditionalFileTypes === true && ( + {additionalFileTypes.map(f => + {handlePersonalAttributesChange(f, "filetype")}} + clicked={personalAttributes?.filetype === f.name} + /> + )} + )} + +
+ +
+ {handlePersonalAttributesChange(e.target.value.trim(), "trackingid")}} + required={true} + /> +
+ +
+ Select Section Name: +
+
+ {tagList.map(tag => + {handlePersonalAttributesChange(tag, "personaltag");setNewDivision(tag.divisionid);}} + clicked={personalAttributes?.personaltag === tag.name} + /> + )} +
+
+ + + + + {handleTagSearchKeywordChange(e.target.value.trim())}} + sx={{ + color: "#38598A", + }} + startAdornment={ + + + Search any additional sections here + + + + } + fullWidth + /> + + + {showAdditionalTags === true && ( + {additionalTagList.map(tag => + {handlePersonalAttributesChange(tag, "personaltag");setNewDivision(tag.divisionid);}} + clicked={personalAttributes?.personaltag === tag.name} + /> + )} + )} + +
+
+
+ + + + + +
+
+ ); +}; + +const ClickableChip = ({ clicked, sx={}, color, ...rest }) => { + return ( + + ); +}; + +export default MCFPersonal; \ No newline at end of file diff --git a/web/src/components/FOI/Home/Redlining.js b/web/src/components/FOI/Home/Redlining.js index e2b3c9755..14a0699b1 100644 --- a/web/src/components/FOI/Home/Redlining.js +++ b/web/src/components/FOI/Home/Redlining.js @@ -1435,7 +1435,7 @@ const Redlining = React.forwardRef( let originalPageNo = customData.originalPageNo; let mappedDoc = pageMappedDocs?.docIdLookup[entry]; annot.attributes.page = ( - mappedDoc.pageMappings.find( + mappedDoc?.pageMappings.find( (p) => p.pageNo - 1 === Number(originalPageNo) )?.stitchedPageNo - 1 )?.toString(); @@ -2203,6 +2203,7 @@ const Redlining = React.forwardRef( case "oipcreview": case "redline": saveRedlineDocument( + docInstance, modalFor, incompatibleFiles, documentList, @@ -2222,8 +2223,8 @@ const Redlining = React.forwardRef( break; default: } - setIncludeDuplicatePages(false); - setIncludeNRPages(false); + // setIncludeDuplicatePages(false); + // setIncludeNRPages(false); }; const decodeAstr = (astr) => { @@ -2235,8 +2236,8 @@ const Redlining = React.forwardRef( return trnCustomData } - const NRID = sections?.find(s => s.section === "NR").id; - const blankID = sections?.find(s => s.section === "").id; + const NRID = sections?.find(s => s.section === "NR")?.id; + const blankID = sections?.find(s => s.section === "")?.id; const sectionIsDisabled = (sectionid) => { let isDisabled = false; diff --git a/web/src/components/FOI/Home/utils.js b/web/src/components/FOI/Home/utils.js index 7e8c94846..10c5217e7 100644 --- a/web/src/components/FOI/Home/utils.js +++ b/web/src/components/FOI/Home/utils.js @@ -65,8 +65,30 @@ export const docSorting = (a, b) => { return sort; }; +export const CFDSorting = (a, b) => { + if (a.file) { + a = a.file; + } + if (b.file) { + b = b.file; + } + if (a.attributes.personalattributes.person !== b.attributes.personalattributes.person) { + // return (a.attributes.personalattributes.person > b.attributes.personalattributes.person) ? 1 : -1 + return a.attributes.personalattributes.person.localeCompare(b.attributes.personalattributes.person, undefined, {numeric: true, sensitivity: 'base'}) + } else if (a.attributes.personalattributes.filetype !== b.attributes.personalattributes.filetype) { + return (a.attributes.personalattributes.filetype > b.attributes.personalattributes.filetype) ? 1 : -1 + } else if (a.attributes.personalattributes.trackingid !== b.attributes.personalattributes.trackingid) { + // return (a.attributes.personalattributes.trackingid > b.attributes.personalattributes.trackingid) ? 1 : -1 + return a.attributes.personalattributes.trackingid.localeCompare(b.attributes.personalattributes.trackingid, undefined, {numeric: true, sensitivity: 'base'}) + } else if (a.attributes.personalattributes.volume !== b.attributes.personalattributes.volume) { + // return (a.attributes.personalattributes.volume > b.attributes.personalattributes.volume) ? 1 : -1 + return a.attributes.personalattributes.volume?a.attributes.personalattributes.volume.localeCompare(b.attributes.personalattributes.volume, undefined, {numeric: true, sensitivity: 'base'}) : -1 + } + return Date.parse(a.created_at) - Date.parse(b.created_at); +}; + // sort by parent-attachment, then last modified date -export const sortDocList = (fullDocList, currentDoc, sortedDocList) => { +export const sortDocList = (fullDocList, currentDoc, sortedDocList, requestInfo) => { let parentid = null; if (currentDoc) { sortedDocList.push(currentDoc); @@ -87,7 +109,11 @@ export const sortDocList = (fullDocList, currentDoc, sortedDocList) => { if (childDocList.length == 1) { sortedChildDocList = childDocList; } else { - sortedChildDocList = childDocList.sort(docSorting); + if (requestInfo?.bcgovcode === "MCF" && requestInfo?.requesttype === "personal") { + sortedChildDocList = childDocList.sort(CFDSorting); + } else { + sortedChildDocList = childDocList.sort(docSorting); + } } sortedChildDocList.forEach((_doc, _index) => { diff --git a/web/src/constants/enum.ts b/web/src/constants/enum.ts index dea5c2241..2338be921 100644 --- a/web/src/constants/enum.ts +++ b/web/src/constants/enum.ts @@ -100,10 +100,13 @@ const RedactionTypes: RedactionType = { "blank": "blank" }; +const MCFPopularSections = 23 + export { KCProcessingTeams, MINISTRYGROUPS, pageFlagTypes, RequestStates, - RedactionTypes + RedactionTypes, + MCFPopularSections }; \ No newline at end of file diff --git a/web/src/modules/documentReducer.ts b/web/src/modules/documentReducer.ts index 24ed7eda7..302df096f 100644 --- a/web/src/modules/documentReducer.ts +++ b/web/src/modules/documentReducer.ts @@ -41,6 +41,14 @@ const documents = (state = initialState, action:any)=> { return {...state, redactionLayers: state.redactionLayers }; case ACTION_CONSTANTS.SET_DELETED_PAGES: return {...state, deletedDocPages: action.payload}; + case ACTION_CONSTANTS.FOI_PERSONAL_SECTIONS: + return { ...state, foiPersonalSections: action.payload }; + case ACTION_CONSTANTS.FOI_PERSONAL_PEOPLE: + return { ...state, foiPersonalPeople: action.payload }; + case ACTION_CONSTANTS.FOI_PERSONAL_FILETYPES: + return { ...state, foiPersonalFiletypes: action.payload }; + case ACTION_CONSTANTS.FOI_PERSONAL_VOLUMES: + return { ...state, foiPersonalVolumes: action.payload }; default: return state; }