Compare commits

...

3 Commits

Author SHA1 Message Date
Sosthene
56878c977c Add helper function to secure handling of processId in the code
Some checks failed
Build and Push to Registry / build-and-push (push) Failing after 4m22s
2025-09-12 13:00:51 +02:00
Sosthene
0a3be835a9 Fix Dashboard with folders 2025-09-12 13:00:51 +02:00
Sosthene
2450d674e2 Update CustomerService 2025-09-12 13:00:51 +02:00
14 changed files with 210 additions and 192 deletions

View File

@ -3,6 +3,7 @@ import { v4 as uuidv4 } from 'uuid';
import User from 'src/sdk/User'; import User from 'src/sdk/User';
import AbstractService from './AbstractService'; import AbstractService from './AbstractService';
import { DEFAULT_STORAGE_URLS } from '@Front/Config/AppConstants';
export default class CustomerService extends AbstractService { export default class CustomerService extends AbstractService {
@ -10,7 +11,7 @@ export default class CustomerService extends AbstractService {
super(); super();
} }
public static createCustomer(customerData: any, validatorId: string): Promise<any> { public static async createCustomer(customerData: any, validatorId: string): Promise<{ processId: string, processData: any }> {
const ownerId: string = User.getInstance().getPairingId()!; const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = { const processData: any = {
@ -23,114 +24,72 @@ export default class CustomerService extends AbstractService {
}; };
const privateFields: string[] = Object.keys(processData); const privateFields: string[] = Object.keys(processData);
privateFields.splice(privateFields.indexOf('uid'), 1); const allFields: string[] = [...privateFields, 'roles'];
privateFields.splice(privateFields.indexOf('utype'), 1);
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
const roles: any = { const roles: any = {
demiurge: { demiurge: {
members: [...[ownerId], validatorId], members: [ownerId, validatorId],
validation_rules: [], validation_rules: [],
storages: [] storages: []
}, },
owner: { owner: {
members: [ownerId], members: [ownerId, validatorId],
validation_rules: [ validation_rules: [
{ {
quorum: 0.5, quorum: 1,
fields: [...privateFields, 'roles', 'uid', 'utype'], fields: allFields,
min_sig_member: 1, min_sig_member: 1,
}, },
], ],
storages: [] storages: [...DEFAULT_STORAGE_URLS]
},
validator: {
members: [validatorId],
validation_rules: [
{
quorum: 0.5,
fields: ['idCertified', 'roles'],
min_sig_member: 1,
},
{
quorum: 0.0,
fields: [...privateFields],
min_sig_member: 0,
},
],
storages: []
}, },
apophis: { apophis: {
members: [ownerId], members: [ownerId, validatorId],
validation_rules: [], validation_rules: [],
storages: [] storages: []
} }
}; };
return new Promise<any>((resolve: (processCreated: any) => void, reject: (error: string) => void) => { try {
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => { const processCreated = await this.messageBus.createProcess(processData, privateFields, roles);
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => { await this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id);
this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => { await this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id);
this.getCustomerByUid(processCreated.processData.uid).then(resolve).catch(reject); const finalProcessData = await this.messageBus.getProcessData(processCreated.processId);
}).catch(reject);
}).catch(reject); return { processId: processCreated.processId, processData: finalProcessData[processCreated.processId] };
}).catch(reject); } catch (error) {
}); throw error;
}
} }
public static getCustomers(): Promise<any[]> { public static getCustomers(callback: (processes: Record<string, any>) => void): void {
// Check if we have valid cache // Check if we have valid cache
const items: any[] = this.getItems('_customers_'); const items: Record<string, any> = this.getItems('_customers_');
if (Object.keys(items).length > 0) {
return this.messageBus.getProcessesDecoded((publicValues: any) => setTimeout(() => callback(items), 0);
publicValues['uid'] &&
publicValues['utype'] &&
publicValues['utype'] === 'customer' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false' &&
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
).then((processes: any[]) => {
if (processes.length === 0) {
return items;
} else {
for (const process of processes) {
// Update cache
this.setItem('_customers_', process);
items.push(process);
}
return items;
}
});
}
public static getCustomerByUid(uid: string): Promise<any> {
// Check if we have valid cache
const item: any = this.getItem('_customers_', uid);
if (item) {
return Promise.resolve(item);
} }
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => { this.messageBus.getProcessesDecoded((_processId: string, values: any) => {
this.messageBus.getProcessesDecoded((publicValues: any) => return values['utype']
publicValues['uid'] && && values['utype'] === 'customer'
publicValues['uid'] === uid && && values['isDeleted']
publicValues['utype'] && && values['isDeleted'] === 'false';
publicValues['utype'] === 'customer' && }).then(async (processes: Record<string, any>) => {
publicValues['isDeleted'] && if (Object.keys(processes).length === 0) {
publicValues['isDeleted'] === 'false' callback(items);
).then((processes: any[]) => { return;
if (processes.length === 0) { }
resolve(null);
} else {
const process: any = processes[0];
// Update cache const updatedItems: Record<string, any> = { ...items };
this.setItem('_customers_', process);
resolve(process); for (const [processId, process] of Object.entries(processes)) {
} // Update cache
}).catch(reject); this.setItem('_customers_', processId, process);
updatedItems[processId] = process;
}
callback(updatedItems);
}); });
} }
@ -143,7 +102,7 @@ export default class CustomerService extends AbstractService {
const customerUid: string = process.processData.uid; const customerUid: string = process.processData.uid;
this.removeItem('_customers_', customerUid); this.removeItem('_customers_', customerUid);
this.getCustomerByUid(customerUid).then(resolve).catch(reject); resolve();
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);

View File

@ -7,6 +7,7 @@ import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDas
import { DeedType } from "le-coffre-resources/dist/Notary"; import { DeedType } from "le-coffre-resources/dist/Notary";
import DeedTypeService from "src/common/Api/LeCoffreApi/sdk/DeedTypeService"; import DeedTypeService from "src/common/Api/LeCoffreApi/sdk/DeedTypeService";
import { idAsUrl } from "@Front/Utils/ProcessIdUtils";
type IProps = IPropsDashboardWithList; type IProps = IPropsDashboardWithList;
@ -17,13 +18,13 @@ export default function DefaultDeedTypeDashboard(props: IProps) {
const [deedTypes, setDeedTypes] = React.useState<DeedType[] | null>(null); const [deedTypes, setDeedTypes] = React.useState<DeedType[] | null>(null);
useEffect(() => { useEffect(() => {
DeedTypeService.getDeedTypes((processes: any[]) => { DeedTypeService.getDeedTypes((processes: Record<string, any>) => {
if (processes.length > 0) { const deedTypes = Object.entries(processes).map(([processId, processData]) => ({
let deedTypes = processes.map((process: any) => process.processData); ...processData,
processId: processId
// FilterBy archived_at = null or not defined }));
deedTypes = deedTypes.filter((deedType: any) => !deedType.archived_at); if (deedTypes.length > 0) {
deedTypes.filter((deedType: any) => !deedType.archived_at);
// OrderBy name asc // OrderBy name asc
deedTypes.sort((a: any, b: any) => a.name.localeCompare(b.name)); deedTypes.sort((a: any, b: any) => a.name.localeCompare(b.name));
@ -35,7 +36,9 @@ export default function DefaultDeedTypeDashboard(props: IProps) {
}, []); }, []);
const onSelectedBlock = (block: IBlock) => { const onSelectedBlock = (block: IBlock) => {
router.push(Module.getInstance().get().modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", block.id)); // Remove ':0' suffix from processId for URL navigation
const urlId = idAsUrl(block.id as string);
router.push(Module.getInstance().get().modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", urlId));
}; };
return ( return (
@ -44,11 +47,14 @@ export default function DefaultDeedTypeDashboard(props: IProps) {
onSelectedBlock={onSelectedBlock} onSelectedBlock={onSelectedBlock}
blocks={ blocks={
deedTypes deedTypes
? deedTypes.map((deedTypes) => ({ ? deedTypes.map((deedType: any) => {
id: deedTypes.uid!, const urlId = idAsUrl(deedType.processId);
primaryText: deedTypes.name, return {
isActive: deedTypes.uid === deedTypeUid, id: deedType.processId, // Keep full processId for internal use
})) primaryText: deedType.name,
isActive: urlId === deedTypeUid, // Compare without ':0' suffix
};
})
: [] : []
} }
bottomButton={{ bottomButton={{

View File

@ -9,6 +9,7 @@ import { useRouter } from "next/router";
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList"; import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService"; import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
import { idAsUrl } from "@Front/Utils/ProcessIdUtils";
type IProps = IPropsDashboardWithList & { type IProps = IPropsDashboardWithList & {
isArchived?: boolean; isArchived?: boolean;
@ -25,11 +26,11 @@ export default function DefaultNotaryDashboard(props: IProps) {
: Module.getInstance().get().modules.pages.Folder.pages.FolderInformation.props.path; : Module.getInstance().get().modules.pages.Folder.pages.FolderInformation.props.path;
const getBlocks = useCallback( const getBlocks = useCallback(
(folders: OfficeFolder[]): IBlock[] => { (folders: any[]): IBlock[] => {
const pendingFolders = folders const pendingFolders = folders
.filter((folder) => { .filter((folder) => {
const pendingDocuments = (folder.documents ?? []).filter( const pendingDocuments = (folder.documents ?? []).filter(
(document) => document.document_status === EDocumentStatus.DEPOSITED, (document: any) => document.document_status === EDocumentStatus.DEPOSITED,
); );
return pendingDocuments.length >= 1; return pendingDocuments.length >= 1;
}) })
@ -40,7 +41,7 @@ export default function DefaultNotaryDashboard(props: IProps) {
const otherFolders = folders const otherFolders = folders
.filter((folder) => { .filter((folder) => {
const pendingDocuments = (folder.documents ?? []).filter( const pendingDocuments = (folder.documents ?? []).filter(
(document) => document.document_status === EDocumentStatus.DEPOSITED, (document: any) => document.document_status === EDocumentStatus.DEPOSITED,
); );
return pendingDocuments.length === 0; return pendingDocuments.length === 0;
}) })
@ -48,15 +49,18 @@ export default function DefaultNotaryDashboard(props: IProps) {
return folder1.created_at! > folder2.created_at! ? -1 : 1; return folder1.created_at! > folder2.created_at! ? -1 : 1;
}); });
return [...pendingFolders, ...otherFolders].map((folder) => { const blocks = [...pendingFolders, ...otherFolders].map((folder) => {
return { const res = {
id: folder.uid!, id: idAsUrl(folder.processId),
primaryText: folder.name, primaryText: folder.name,
secondaryText: folder.folder_number, secondaryText: folder.folder_number,
isActive: folderUid === folder.uid, isActive: folderUid === idAsUrl(folder.processId),
showAlert: folder.documents?.some((document) => document.document_status === EDocumentStatus.DEPOSITED), showAlert: folder.documents?.some((document: any) => document.document_status === EDocumentStatus.DEPOSITED),
}; };
return res;
}); });
return blocks;
}, },
[folderUid], [folderUid],
); );
@ -115,14 +119,26 @@ export default function DefaultNotaryDashboard(props: IProps) {
.then((folders) => setFolders(folders)); .then((folders) => setFolders(folders));
*/ */
FolderService.getFolders((processes: any[]) => { FolderService.getFolders((processes: Record<string, any>) => {
if (processes.length > 0) { if (Object.keys(processes).length > 0) {
let folders: any[] = processes.map((process: any) => process.processData); let folders: any[] = Object.entries(processes).map(([processId, process]) => {
const res = {
...process,
processId: processId
};
return res;
});
// FilterBy status // FilterBy status
folders = folders.filter((folder: any) => folder.status === targetedStatus); folders = folders.filter((folder: any) => {
const matches = folder.status === targetedStatus;
return matches;
});
setFolders(folders); setFolders(folders);
} else {
console.debug('[DefaultNotaryDashboard] No processes found');
} }
}); });
}, [isArchived]); }, [isArchived]);

View File

@ -19,6 +19,7 @@ import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import DeedTypeService from "src/common/Api/LeCoffreApi/sdk/DeedTypeService"; import DeedTypeService from "src/common/Api/LeCoffreApi/sdk/DeedTypeService";
import { DEFAULT_VALIDATOR_ID } from "@Front/Config/AppConstants"; import { DEFAULT_VALIDATOR_ID } from "@Front/Config/AppConstants";
import Auth from "@Front/Api/Auth/IdNot"; import Auth from "@Front/Api/Auth/IdNot";
import { idAsUrl } from "@Front/Utils/ProcessIdUtils";
type IProps = {}; type IProps = {};
export default function DeedTypesCreate(props: IProps) { export default function DeedTypesCreate(props: IProps) {
@ -80,7 +81,7 @@ export default function DeedTypesCreate(props: IProps) {
}); });
const deedTypeUid = processCreated.processId; const deedTypeUid = processCreated.processId;
// Remove ':0' suffix from processId for URL navigation // Remove ':0' suffix from processId for URL navigation
const urlId = deedTypeUid.replace(/:0$/, ''); const urlId = idAsUrl(deedTypeUid);
router.push( router.push(
Module.getInstance() Module.getInstance()
.get() .get()

View File

@ -17,6 +17,7 @@ import classes from "./classes.module.scss";
import DeedTypeService from "src/common/Api/LeCoffreApi/sdk/DeedTypeService"; import DeedTypeService from "src/common/Api/LeCoffreApi/sdk/DeedTypeService";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService"; import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import MessageBus from "src/sdk/MessageBus"; import MessageBus from "src/sdk/MessageBus";
import { idAsProcessId } from "@Front/Utils/ProcessIdUtils";
export default function DeedTypesEdit() { export default function DeedTypesEdit() {
const router = useRouter(); const router = useRouter();
@ -33,7 +34,7 @@ export default function DeedTypesEdit() {
if (!deedTypeUid) return; if (!deedTypeUid) return;
LoaderService.getInstance().show(); LoaderService.getInstance().show();
// deedTypeUid comes from URL without ':0' suffix, add it back for API calls // deedTypeUid comes from URL without ':0' suffix, add it back for API calls
const processId = (deedTypeUid as string) + ':0'; const processId = idAsProcessId(deedTypeUid as string);
MessageBus.getInstance().getProcessData(processId).then((processData: any) => { MessageBus.getInstance().getProcessData(processId).then((processData: any) => {
if (processData) { if (processData) {
setDeedTypeSelected(processData); setDeedTypeSelected(processData);
@ -66,7 +67,7 @@ export default function DeedTypesEdit() {
try { try {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
// deedTypeUid comes from URL without ':0' suffix, add it back for API calls // deedTypeUid comes from URL without ':0' suffix, add it back for API calls
const processId = (deedTypeUid as string) + ':0'; const processId = idAsProcessId(deedTypeUid as string);
const process = await MessageBus.getInstance().getProcessData(processId); const process = await MessageBus.getInstance().getProcessData(processId);
if (process) { if (process) {

View File

@ -22,6 +22,7 @@ import DeedTypeService from "src/common/Api/LeCoffreApi/sdk/DeedTypeService";
import DocumentTypeService from "src/common/Api/LeCoffreApi/sdk/DocumentTypeService"; import DocumentTypeService from "src/common/Api/LeCoffreApi/sdk/DocumentTypeService";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService"; import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import MessageBus from "src/sdk/MessageBus"; import MessageBus from "src/sdk/MessageBus";
import { idAsProcessId } from "@Front/Utils/ProcessIdUtils";
type IProps = {}; type IProps = {};
export default function DeedTypesInformations(props: IProps) { export default function DeedTypesInformations(props: IProps) {
@ -51,12 +52,14 @@ export default function DeedTypesInformations(props: IProps) {
setIsSaveModalOpened(false); setIsSaveModalOpened(false);
}, []); }, []);
const deleteDeedType = useCallback(async () => { const deleteDeedType = async () => {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
// deedTypeUid comes from URL without ':0' suffix, add it back for API calls try {
const processId = (deedTypeUid as string) + ':0'; // deedTypeUid comes from URL without ':0' suffix, add it back for API calls
MessageBus.getInstance().getProcessData(processId).then(async (process: any) => { const processId = idAsProcessId(deedTypeUid as string);
if (process) { const process = await MessageBus.getInstance().getProcessData(processId);
if (process && process[processId]) {
// New data // New data
const newData: any = { const newData: any = {
isDeleted: 'true', isDeleted: 'true',
@ -64,19 +67,22 @@ export default function DeedTypesInformations(props: IProps) {
}; };
// Merge process data with new data & update process // Merge process data with new data & update process
process.processData.isDeleted = newData.isDeleted; process[processId].isDeleted = newData.isDeleted;
process.processData.archived_at = newData.archived_at; process[processId].archived_at = newData.archived_at;
await DeedTypeService.updateDeedType(process, newData); await DeedTypeService.updateDeedType(processId, newData);
router.push( router.push(
Module.getInstance() Module.getInstance()
.get() .get()
.modules.pages.DeedTypes.props.path .modules.pages.DeedTypes.props.path
); );
LoaderService.getInstance().hide();
} }
}); } catch (error) {
}, [deedTypeUid, router]); console.error('Error deleting deed type:', error);
} finally {
LoaderService.getInstance().hide();
}
};
useEffect(() => { useEffect(() => {
async function getDeedType() { async function getDeedType() {
@ -84,7 +90,7 @@ export default function DeedTypesInformations(props: IProps) {
setSelectedDocuments([]); setSelectedDocuments([]);
// deedTypeUid comes from URL without ':0' suffix, add it back for API calls // deedTypeUid comes from URL without ':0' suffix, add it back for API calls
const processId = (deedTypeUid as string) + ':0'; const processId = idAsProcessId(deedTypeUid as string);
MessageBus.getInstance().getProcessData(processId).then((process: any) => { MessageBus.getInstance().getProcessData(processId).then((process: any) => {
if (process) { if (process) {
console.log('[DeedTypesInformations] process', process); console.log('[DeedTypesInformations] process', process);
@ -131,7 +137,7 @@ export default function DeedTypesInformations(props: IProps) {
const saveDocumentTypes = useCallback(async () => { const saveDocumentTypes = useCallback(async () => {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
// deedTypeUid comes from URL without ':0' suffix, add it back for API calls // deedTypeUid comes from URL without ':0' suffix, add it back for API calls
const processId = (deedTypeUid as string) + ':0'; const processId = idAsProcessId(deedTypeUid as string);
console.log('[DeedTypesInformations] processId', processId); console.log('[DeedTypesInformations] processId', processId);
const deedType = (await MessageBus.getInstance().getProcessData(processId))[processId]; const deedType = (await MessageBus.getInstance().getProcessData(processId))[processId];
if (deedType) { if (deedType) {

View File

@ -16,6 +16,7 @@ import classes from "./classes.module.scss";
import DocumentTypeService from "src/common/Api/LeCoffreApi/sdk/DocumentTypeService"; import DocumentTypeService from "src/common/Api/LeCoffreApi/sdk/DocumentTypeService";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService"; import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import { idAsUrl } from "@Front/Utils/ProcessIdUtils";
import UserStore from "@Front/Stores/UserStore"; import UserStore from "@Front/Stores/UserStore";
import { DEFAULT_VALIDATOR_ID } from "@Front/Config/AppConstants"; import { DEFAULT_VALIDATOR_ID } from "@Front/Config/AppConstants";
@ -68,7 +69,7 @@ export default function DocumentTypesCreate(props: IProps) {
title: "Succès !", title: "Succès !",
description: "Type de document créé avec succès" description: "Type de document créé avec succès"
}); });
const documentTypeUid = processCreated.processId.split(':')[0]; const documentTypeUid = idAsUrl(processCreated.processId);
if (!documentTypeUid) { if (!documentTypeUid) {
console.error("DocumentTypesCreate: documentTypeUid is undefined - processCreated.processId is missing"); console.error("DocumentTypesCreate: documentTypeUid is undefined - processCreated.processId is missing");
return; return;

View File

@ -33,9 +33,10 @@ export default function ClientBox(props: IProps) {
const handleDelete = useCallback( const handleDelete = useCallback(
(customerUid: string) => { (customerUid: string) => {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
DocumentService.getDocuments().then((processes: any[]) => { DocumentService.getDocuments().then((processes: Record<string, any>) => {
if (processes.length > 0) { const processArray = Object.values(processes);
let documents: any[] = processes.map((process: any) => process.processData); if (processArray.length > 0) {
let documents: any[] = processArray.map((process: any) => process.processData);
// FilterBy folder.uid & depositor.uid // FilterBy folder.uid & depositor.uid
documents = documents.filter((document: any) => document.folder.uid === folderUid && document.depositor && document.depositor.uid === customerUid); documents = documents.filter((document: any) => document.folder.uid === folderUid && document.depositor && document.depositor.uid === customerUid);

View File

@ -1,11 +1,9 @@
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Tabs from "@Front/Components/Elements/Tabs"; import Tabs from "@Front/Components/Elements/Tabs";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { DocumentIcon, UserPlusIcon } from "@heroicons/react/24/outline"; import { DocumentIcon, UserPlusIcon } from "@heroicons/react/24/outline";
import Customer from "le-coffre-resources/dist/Customer"; import Customer from "le-coffre-resources/dist/Customer";
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document"; import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import Link from "next/link"; import Link from "next/link";
import { useCallback, useMemo, useState } from "react"; import { useCallback, useMemo, useState } from "react";
@ -18,9 +16,10 @@ import EmailReminder from "./EmailReminder";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService"; import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService"; import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import MessageBus from "src/sdk/MessageBus"; import MessageBus from "src/sdk/MessageBus";
import { idAsUrl } from "@Front/Utils/ProcessIdUtils";
type IProps = { type IProps = {
folder: { processId: string, FolderData: OfficeFolder}; folder: any;
anchorStatus: AnchorStatus; anchorStatus: AnchorStatus;
}; };
@ -92,7 +91,7 @@ export default function ClientView(props: IProps) {
<Link <Link
href={Module.getInstance() href={Module.getInstance()
.get() .get()
.modules.pages.Folder.pages.AddClient.props.path.replace("[folderUid]", folder.processId ?? "")}> .modules.pages.Folder.pages.AddClient.props.path.replace("[folderUid]", idAsUrl(folder.processId) ?? "")}>
<Button <Button
size={EButtonSize.MD} size={EButtonSize.MD}
rightIcon={<UserPlusIcon />} rightIcon={<UserPlusIcon />}

View File

@ -1,6 +1,7 @@
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import EmptyAlert from "@Front/Components/DesignSystem/EmptyAlert"; import EmptyAlert from "@Front/Components/DesignSystem/EmptyAlert";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { idAsUrl } from "@Front/Utils/ProcessIdUtils";
import { UserPlusIcon } from "@heroicons/react/24/outline"; import { UserPlusIcon } from "@heroicons/react/24/outline";
import Link from "next/link"; import Link from "next/link";
import React, { useMemo } from "react"; import React, { useMemo } from "react";
@ -14,7 +15,7 @@ export default function AddClientSection(props: IProps) {
const addClientPath = useMemo(() => { const addClientPath = useMemo(() => {
if (!folderUid) return ""; if (!folderUid) return "";
return Module.getInstance().get().modules.pages.Folder.pages.AddClient.props.path.replace("[folderUid]", folderUid); return Module.getInstance().get().modules.pages.Folder.pages.AddClient.props.path.replace("[folderUid]", idAsUrl(folderUid));
}, [folderUid]); }, [folderUid]);
return ( return (

View File

@ -20,8 +20,9 @@ import InformationSection from "./InformationSection";
import NoClientView from "./NoClientView"; import NoClientView from "./NoClientView";
import AnchoringProcessingInfo from "./elements/AnchoringProcessingInfo"; import AnchoringProcessingInfo from "./elements/AnchoringProcessingInfo";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus"; import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus";
import MessageBus from "src/sdk/MessageBus";
import { idAsProcessId } from "@Front/Utils/ProcessIdUtils";
export enum AnchorStatus { export enum AnchorStatus {
"VERIFIED_ON_CHAIN" = "VERIFIED_ON_CHAIN", "VERIFIED_ON_CHAIN" = "VERIFIED_ON_CHAIN",
@ -62,63 +63,34 @@ export default function FolderInformation(props: IProps) {
const doesFolderHaveClient = useMemo(() => folder?.customers?.length !== 0, [folder]); const doesFolderHaveClient = useMemo(() => folder?.customers?.length !== 0, [folder]);
const fetchFolder = useCallback(async () => { const fetchFolder = useCallback(async () => {
if (!folderUid) return; if (!folderUid) {
console.log('[FolderInformation] No folderUid, skipping fetch');
return;
}
/* const processId = idAsProcessId(folderUid as string);
const query = { return MessageBus.getInstance().getProcessData(processId).then(async (process: { [key: string]: any }) => {
q: {
deed: { include: { deed_type: true, document_types: true } },
office: true,
customers: {
include: {
contact: true,
documents: {
where: {
folder_uid: folderUid,
},
include: {
folder: true,
document_type: true,
files: true,
},
},
},
},
documents: {
include: {
depositor: {
include: {
contact: true,
},
},
},
},
folder_anchor: true,
notes: {
include: {
customer: true,
},
},
},
};
return Folders.getInstance()
.getByUid(folderUid, query)
.then((folder) => setFolder(folder));
*/
// TODO: review
return FolderService.getFolderByUid(folderUid).then(async (process: any) => {
if (process) { if (process) {
const folder: any = process.processData; const processEntries = Object.entries(process);
setFolder(folder); if (processEntries.length === 1) {
const [processId, processData] = processEntries[0]!;
const folder: any = {
...processData,
processId: processId
};
setFolder(folder);
} else if (processEntries.length > 1) {
console.error('[FolderInformation] Multiple processes found for folderUid ', folderUid, ':', processEntries);
}
} }
}).catch((e) => {
console.error('[FolderInformation] No process found for folderUid ', folderUid, ':', e);
}); });
}, [folderUid]); }, [folderUid]);
const fetchAnchorStatus = useCallback(() => { const fetchAnchorStatus = useCallback(() => {
return OfficeFolderAnchors.getInstance() return OfficeFolderAnchors.getInstance()
.getByUid(folderUid) .getByUid(folderUid as string)
.then((anchorStatus) => .then((anchorStatus) =>
setAnchorStatus(anchorStatus.status === "VERIFIED_ON_CHAIN" ? AnchorStatus.VERIFIED_ON_CHAIN : AnchorStatus.ANCHORING), setAnchorStatus(anchorStatus.status === "VERIFIED_ON_CHAIN" ? AnchorStatus.VERIFIED_ON_CHAIN : AnchorStatus.ANCHORING),
) )
@ -168,7 +140,7 @@ export default function FolderInformation(props: IProps) {
)} )}
{!isArchived && anchorStatus === AnchorStatus.ANCHORING && <AnchoringProcessingInfo />} {!isArchived && anchorStatus === AnchorStatus.ANCHORING && <AnchoringProcessingInfo />}
{isArchived && folderUid && ( {isArchived && folderUid && (
<ArchiveAlertWarning folderUid={folderUid} onDownloadAnchoringProof={downloadAnchoringProofModal.open} /> <ArchiveAlertWarning folderUid={folderUid as string} onDownloadAnchoringProof={downloadAnchoringProofModal.open} />
)} )}
{folder && !doesFolderHaveClient && <NoClientView folder={folder} anchorStatus={anchorStatus} />} {folder && !doesFolderHaveClient && <NoClientView folder={folder} anchorStatus={anchorStatus} />}
{folder && doesFolderHaveClient && <ClientView folder={folder} anchorStatus={anchorStatus} />} {folder && doesFolderHaveClient && <ClientView folder={folder} anchorStatus={anchorStatus} />}
@ -176,7 +148,7 @@ export default function FolderInformation(props: IProps) {
<AnchoringModal <AnchoringModal
isOpen={anchoringModal.isOpen} isOpen={anchoringModal.isOpen}
onClose={anchoringModal.close} onClose={anchoringModal.close}
folderUid={folderUid} folderUid={folderUid as string}
onAnchorSuccess={fetchData} onAnchorSuccess={fetchData}
/> />
)} )}
@ -192,7 +164,7 @@ export default function FolderInformation(props: IProps) {
onClose={requireAnchoringModal.close} onClose={requireAnchoringModal.close}
onAnchor={anchoringModal.open} onAnchor={anchoringModal.open}
/> />
{folderUid && <ArchiveModal isOpen={archiveModal.isOpen} onClose={archiveModal.close} folderUid={folderUid} />} {folderUid && <ArchiveModal isOpen={archiveModal.isOpen} onClose={archiveModal.close} folderUid={folderUid as string} />}
</div> </div>
)} )}
{isLoading && ( {isLoading && (

View File

@ -24,6 +24,8 @@ import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
import FileService from "src/common/Api/LeCoffreApi/sdk/FileService"; import FileService from "src/common/Api/LeCoffreApi/sdk/FileService";
import CustomerService from "src/common/Api/LeCoffreApi/sdk/CustomerService"; import CustomerService from "src/common/Api/LeCoffreApi/sdk/CustomerService";
import { DEFAULT_VALIDATOR_ID } from "@Front/Config/AppConstants"; import { DEFAULT_VALIDATOR_ID } from "@Front/Config/AppConstants";
import { idAsProcessId } from "@Front/Utils/ProcessIdUtils";
import MessageBus from "src/sdk/MessageBus";
enum EClientSelection { enum EClientSelection {
ALL_CLIENTS = "all_clients", ALL_CLIENTS = "all_clients",
@ -70,7 +72,7 @@ export default function SendDocuments() {
const customer: any = await new Promise<void>((resolve: (customer: any) => void) => { const customer: any = await new Promise<void>((resolve: (customer: any) => void) => {
CustomerService.getCustomerByUid(selectedClient as string).then((process: any) => { CustomerService.getCustomerByUid(selectedClient as string).then((process: any) => {
if (process) { if (process) {
const customer: any = process.processData; const customer: any = process;
resolve(customer); resolve(customer);
} }
}); });
@ -100,7 +102,7 @@ export default function SendDocuments() {
}; };
FileService.createFile(fileData, DEFAULT_VALIDATOR_ID).then((processCreated: any) => { FileService.createFile(fileData, DEFAULT_VALIDATOR_ID).then((processCreated: any) => {
const fileUid: string = processCreated.processData.uid; const fileUid: string = processCreated.uid;
const documentData: any = { const documentData: any = {
folder: { folder: {
@ -173,9 +175,9 @@ export default function SendDocuments() {
const fetchFolder = useCallback(async () => { const fetchFolder = useCallback(async () => {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
FolderService.getFolderByUid(folderUid as string).then((process: any) => { MessageBus.getInstance().getProcessData(idAsProcessId(folderUid as string)).then((process: any) => {
if (process) { if (process) {
const folder: any = process.processData; const folder: any = process;
setFolder(folder); setFolder(folder);
LoaderService.getInstance().hide(); LoaderService.getInstance().hide();
} }

View File

@ -16,6 +16,7 @@ import { useEffect, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService"; import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
import { idAsUrl } from "@Front/Utils/ProcessIdUtils";
export default function Folder() { export default function Folder() {
const [_isArchivedModalOpen, _setIsArchivedModalOpen] = useState(true); const [_isArchivedModalOpen, _setIsArchivedModalOpen] = useState(true);
@ -39,7 +40,7 @@ export default function Folder() {
folders = folders.sort((a: any, b: any) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); folders = folders.sort((a: any, b: any) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
if (folders.length > 0) { if (folders.length > 0) {
const folderUid = folders[0]?.processId; const folderUid = idAsUrl(folders[0]?.processId);
if (!folderUid) { if (!folderUid) {
return; return;

View File

@ -0,0 +1,52 @@
/**
* Utility functions to safely handle conversion between URL-friendly IDs and process IDs.
*
* Process IDs in the system use a ":0" suffix for API calls, but URLs use the clean ID without the suffix.
* These functions provide safe conversion between the two formats to avoid error-prone string manipulation.
*/
/**
* Converts a process ID to URL-friendly format by removing the ":0" suffix if present.
* Safe to call multiple times - won't remove ":0" from IDs that don't end with it.
*
* @param processId - The process ID that may or may not have ":0" suffix
* @returns The ID without ":0" suffix, suitable for use in URLs. Returns empty string for null/undefined.
*
* @example
* idAsUrl("abc123:0") // returns "abc123"
* idAsUrl("abc123") // returns "abc123" (no change)
* idAsUrl("abc:123:0") // returns "abc:123" (only removes trailing ":0")
* idAsUrl(null) // returns ""
* idAsUrl(undefined) // returns ""
*/
export function idAsUrl(processId: string | null | undefined): string {
if (!processId || typeof processId !== 'string') {
return '';
}
// Only remove ":0" if it's at the very end of the string
return processId.replace(/:0$/, '');
}
/**
* Converts a URL-friendly ID to process ID format by adding ":0" suffix if not already present.
* Safe to call multiple times - won't add ":0" if it's already there.
*
* @param urlId - The URL-friendly ID that may or may not have ":0" suffix
* @returns The ID with ":0" suffix, suitable for use in API calls. Returns ":0" for null/undefined.
*
* @example
* idAsProcessId("abc123") // returns "abc123:0"
* idAsProcessId("abc123:0") // returns "abc123:0" (no change)
* idAsProcessId("abc:123") // returns "abc:123:0"
* idAsProcessId(null) // returns ""
* idAsProcessId(undefined) // returns ""
*/
export function idAsProcessId(urlId: string | null | undefined): string {
if (!urlId || typeof urlId !== 'string') {
return '';
}
// Only add ":0" if it's not already at the end
return urlId.endsWith(':0') ? urlId : `${urlId}:0`;
}