This commit is contained in:
omaroughriss 2025-09-12 09:49:21 +02:00
commit b6899cbf3f
11 changed files with 533 additions and 898 deletions

View File

@ -4,9 +4,6 @@ import User from 'src/sdk/User';
import AbstractService from './AbstractService'; import AbstractService from './AbstractService';
import OfficeService from './OfficeService';
import RoleService from './RoleService';
import OfficeRoleService from './OfficeRoleService';
import { DEFAULT_STORAGE_URLS } from '@Front/Config/AppConstants'; import { DEFAULT_STORAGE_URLS } from '@Front/Config/AppConstants';
export default class CollaboratorService extends AbstractService { export default class CollaboratorService extends AbstractService {
@ -15,7 +12,7 @@ export default class CollaboratorService extends AbstractService {
super(); super();
} }
public static createCollaborator(collaboratorData: any, validatorId: string): Promise<any> { public static async createCollaborator(collaboratorData: 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 = {
@ -28,134 +25,85 @@ export default class CollaboratorService 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], members: [ownerId, validatorId],
validation_rules: [], validation_rules: [],
storages: [] storages: []
}, },
owner: { owner: {
members: [ownerId], members: [ownerId, validatorId],
validation_rules: [ validation_rules: [
{ {
quorum: 1, quorum: 0.01,
fields: [...privateFields, 'roles', 'uid', 'utype', 'isDeleted'], fields: allFields,
min_sig_member: 1, min_sig_member: 0.01,
},
],
storages: [...DEFAULT_STORAGE_URLS]
},
validator: {
members: [validatorId],
validation_rules: [
{
quorum: 1,
fields: [...privateFields, 'roles', 'uid', 'utype', 'isDeleted'],
min_sig_member: 1,
}, },
], ],
storages: [...DEFAULT_STORAGE_URLS] storages: [...DEFAULT_STORAGE_URLS]
}, },
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.getCollaboratorByUid(processCreated.processData.uid).then(resolve).catch(reject); const finalProcessData = await this.messageBus.getProcessData(processCreated.processId);
}).catch(reject); return { processId: processCreated.processId, processData: finalProcessData };
}).catch(reject); } catch (error) {
}).catch(reject); throw error;
}); }
} }
public static getCollaborators(callback: (processes: any[]) => void, waitForAll: boolean = false): void { public static getCollaborators(callback: (processes: Record<string, any>) => void): void {
// Check if we have valid cache const items: Record<string, any> = this.getItems('_collaborators_');
const items: any[] = this.getItems('_collaborators_'); if (Object.keys(items).length > 0) {
if (items.length > 0 && !waitForAll) { setTimeout(() => callback(items), 0);
setTimeout(() => callback([...items]), 0);
} }
this.messageBus.getProcessesDecoded((publicValues: any) => this.messageBus.getProcessesDecoded((processId: string, data: any) =>
publicValues['uid'] && data['utype'] &&
publicValues['utype'] && data['utype'] === 'collaborator' &&
publicValues['utype'] === 'collaborator' && data['isDeleted'] &&
publicValues['isDeleted'] && data['isDeleted'] === 'false' &&
publicValues['isDeleted'] === 'false' && !Object.keys(items).includes(processId)
!items.map((item: any) => item.processData.uid).includes(publicValues['uid']) ).then(async (processesData: Record<string, any>) => {
).then(async (processes: any[]) => { if (Object.keys(processesData).length === 0) {
if (processes.length === 0) { // If no new processes and no cached items, return empty array
if (waitForAll) { if (Object.keys(items).length === 0) {
callback([...items]); callback([]);
} }
return; return;
} }
const updatedItems: any[] = [...items]; // Single callback with all completed items
callback(items);
for (let processIndex = 0; processIndex < processes.length; processIndex++) { }).catch(error => {
let process = processes[processIndex]; console.error('Failed to fetch collaborators:', error);
// Return cached data if available, otherwise empty array
if (!waitForAll) { callback(items);
process = await this.completeCollaborator(process, (processInProgress: any) => {
const currentItems: any[] = [...updatedItems];
const existingIndex: number = currentItems.findIndex(item => item.processData?.uid === processInProgress.processData?.uid);
if (existingIndex >= 0) {
currentItems[existingIndex] = processInProgress;
} else {
currentItems.push(processInProgress);
}
callback(currentItems);
});
} else {
process = await this.completeCollaborator(process);
}
// Update cache
this.setItem('_collaborators_', process);
const existingIndex: number = updatedItems.findIndex(item => item.processData?.uid === process.processData?.uid);
if (existingIndex >= 0) {
updatedItems[existingIndex] = process;
} else {
updatedItems.push(process);
}
if (!waitForAll) {
callback([...updatedItems]);
}
}
if (waitForAll) {
callback([...updatedItems]);
}
}); });
} }
public static getCollaboratorsBy(whereClause: { [path: string]: any }): Promise<any[]> { public static getCollaboratorsBy(whereClause: { [path: string]: any }): Promise<Record<string, any>> {
return new Promise<any[]>((resolve: (collaborators: any[]) => void) => { return new Promise<Record<string, any>>((resolve: (collaborators: Record<string, any>) => void) => {
this.getCollaborators((processes: any[]) => { this.getCollaborators((processes: Record<string, any>) => {
if (processes.length === 0) { if (Object.keys(processes).length === 0) {
resolve([]); resolve({});
} else { } else {
resolve(processes.filter((process: any) => { const filteredEntries = Object.entries(processes).filter(([processId, process]) => {
const collaborator: any = process.processData;
for (const path in whereClause) { for (const path in whereClause) {
const paths: string[] = path.split('.'); const paths: string[] = path.split('.');
let value: any = collaborator; let value: any = process;
value['processId'] = processId;
for (let i = 0; i < paths.length; i++) { for (let i = 0; i < paths.length; i++) {
const currentPath = paths[i]; const currentPath = paths[i];
if (!currentPath || value === undefined || value === null) { if (!currentPath || value === undefined || value === null) {
@ -170,19 +118,28 @@ export default class CollaboratorService extends AbstractService {
} }
return true; return true;
})); });
// Convert filtered entries back to a Record
const filteredProcesses: Record<string, any> = {};
filteredEntries.forEach(([processId, process]) => {
filteredProcesses[processId] = process;
});
resolve(filteredProcesses);
} }
}, true); });
}); });
} }
public static getCollaboratorBy(whereClause: { [path: string]: any }): Promise<any | null> { public static getCollaboratorBy(whereClause: { [path: string]: any }): Promise<any | null> {
return new Promise<any | null>((resolve: (collaborator: any | null) => void) => { return new Promise<any | null>((resolve: (collaborator: any | null) => void) => {
this.getCollaborators((processes: any[]) => { this.getCollaborators((processes: Record<string, any>) => {
if (processes.length === 0) { const processArray = Object.values(processes);
if (processArray.length === 0) {
resolve(null); resolve(null);
} else { } else {
resolve(processes.find((process: any) => { resolve(processArray.find((process: any) => {
const collaborator: any = process.processData; const collaborator: any = process.processData;
for (const path in whereClause) { for (const path in whereClause) {
@ -205,120 +162,30 @@ export default class CollaboratorService extends AbstractService {
return true; return true;
})); }));
} }
}, true); });
}); });
} }
public static getCollaboratorByUid(uid: string, forceRefresh: boolean = false): Promise<any> { public static async updateCollaborator(processId: string, newData: any): Promise<void> {
// Check if we have valid cache try {
const item: any = this.getItem('_collaborators_', uid); const processUpdated = await this.messageBus.updateProcess(
if (item && !forceRefresh) { processId,
return Promise.resolve(item); { updated_at: new Date().toISOString(), ...newData },
[],
null
);
const newStateId: string = processUpdated.diffs[0]?.state_id;
await this.messageBus.notifyUpdate(processId, newStateId);
await this.messageBus.validateState(processId, newStateId);
const processData = await this.messageBus.getProcessData(processId);
// Update cache
this.setItem('_collaborators_', processId, processData);
} catch (error) {
console.error('Failed to update collaborator:', error);
throw error; // Re-throw to allow caller to handle
} }
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['uid'] === uid &&
publicValues['utype'] &&
publicValues['utype'] === 'collaborator' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false'
).then(async (processes: any[]) => {
if (processes.length === 0) {
resolve(null);
} else {
let process: any = processes[0];
process = await this.completeCollaborator(process);
// Update cache
this.setItem('_collaborators_', process);
resolve(process);
}
}).catch(reject);
});
}
public static updateCollaborator(process: any, newData: any): Promise<void> {
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
const newStateId: string = processUpdated.diffs[0]?.state_id;
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
const collaboratorUid: string = process.processData.uid;
this.removeItem('_collaborators_', collaboratorUid);
this.getCollaboratorByUid(collaboratorUid, true).then(resolve).catch(reject);
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
}
private static async completeCollaborator(process: any, progressCallback?: (processInProgress: any) => void): Promise<any> {
const progressiveProcess: any = JSON.parse(JSON.stringify(process));
if (process.processData.office) {
const office: any = (await OfficeService.getOfficeByUid(process.processData.office.uid)).processData;
if (office) {
process.processData.office = {
uid: office.uid,
idNot: office.idNot,
crpcen: office.crpcen,
name: office.name,
office_status: office.office_status
};
if (progressCallback) {
progressiveProcess.processData.office = process.processData.office;
progressCallback(JSON.parse(JSON.stringify(progressiveProcess)));
}
} else {
console.error('Office not found');
}
}
if (process.processData.role) {
const role: any = (await RoleService.getRoleByUid(process.processData.role.uid)).processData;
if (!role) {
console.error('Role not found');
} else {
process.processData.role = {
uid: role.uid,
name: role.name
};
}
if (progressCallback) {
progressiveProcess.processData.role = process.processData.role;
progressCallback(JSON.parse(JSON.stringify(progressiveProcess)));
}
}
if (process.processData.office_role) {
const officeRole: any = (await OfficeRoleService.getOfficeRoleByUid(process.processData.office_role.uid)).processData;
if (!officeRole) {
console.error('Office role not found');
} else {
process.processData.office_role = {
uid: officeRole.uid,
name: officeRole.name,
rules: officeRole.rules?.map((rule: any) => {
return {
uid: rule.uid,
name: rule.name
};
})
};
}
if (progressCallback) {
progressiveProcess.processData.office_role = process.processData.office_role;
progressCallback(JSON.parse(JSON.stringify(progressiveProcess)));
}
}
return process;
} }
} }

View File

@ -21,6 +21,7 @@ export default class DeedTypeService extends AbstractService {
isDeleted: 'false', isDeleted: 'false',
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
document_types: [],
...deedTypeData ...deedTypeData
}; };
@ -63,39 +64,36 @@ export default class DeedTypeService extends AbstractService {
} }
} }
public static getDeedTypes(callback: (processes: any[]) => void): void { public static getDeedTypes(callback: (processes: Record<string, any>) => void): void {
// Check if we have valid cache // Check if we have valid cache
const items: any[] = this.getItems('_deed_types_'); const items: Record<string, any> = this.getItems('_deed_types_');
if (items.length > 0) { if (Object.keys(items).length > 0) {
setTimeout(() => callback([...items]), 0); setTimeout(() => callback(items), 0);
} }
this.messageBus.getProcessesDecoded((values: any) => { this.messageBus.getProcessesDecoded((_processId: string, values: any) => {
return values['utype'] === 'deedType' return values['utype']
&& values['utype'] === 'deedType'
&& values['isDeleted']
&& values['isDeleted'] === 'false'; && values['isDeleted'] === 'false';
}).then(async (processes: any) => { }).then(async (processes: Record<string, any>) => {
if (processes.length === 0) { if (Object.keys(processes).length === 0) {
callback([...items]); callback(items);
return; return;
} }
const updatedItems: any[] = [...items]; console.log('[DeedTypeService/getDeedTypes] processes', processes);
for (let processIndex = 0; processIndex < processes.length; processIndex++) { const updatedItems: Record<string, any> = { ...items };
const process = processes[processIndex];
for (const [processId, process] of Object.entries(processes)) {
// Update cache // Update cache
this.setItem('_deed_types_', process); this.setItem('_deed_types_', processId, process);
const existingIndex: number = updatedItems.findIndex(item => item.processData?.uid === process.processData?.uid); updatedItems[processId] = process;
if (existingIndex >= 0) {
updatedItems[existingIndex] = process;
} else {
updatedItems.push(process);
}
} }
callback([...updatedItems]); callback(updatedItems);
}); });
} }
@ -115,7 +113,7 @@ export default class DeedTypeService extends AbstractService {
const processData = await this.messageBus.getProcessData(processId); const processData = await this.messageBus.getProcessData(processId);
// Update cache // Update cache
this.setItem('_deed_types_', processData); this.setItem('_deed_types_', processId, processData);
} catch (error) { } catch (error) {
console.error('Failed to update deed type:', error); console.error('Failed to update deed type:', error);
throw error; // Re-throw to allow caller to handle throw error; // Re-throw to allow caller to handle

View File

@ -63,106 +63,55 @@ export default class DocumentTypeService extends AbstractService {
}); });
} }
public static getDocumentTypes(callback: (processes: any[]) => void): void { public static getDocumentTypes(callback: (processes: Record<string, any>) => void): void {
// Check if we have valid cache // Check if we have valid cache
const items: any[] = this.getItems('_document_types_'); const items: Record<string, any> = this.getItems('_document_types_');
if (items.length > 0) { if (Object.keys(items).length > 0) {
setTimeout(() => callback([...items]), 0); setTimeout(() => callback(items), 0);
} }
this.messageBus.getProcessesDecoded((values: any) => { this.messageBus.getProcessesDecoded((_processId: string, values: any) => {
return values['utype'] === 'documentType' return values['utype'] === 'documentType'
&& values['isDeleted'] === 'false'; && values['isDeleted'] === 'false';
}).then(async (processes: any) => { }).then(async (processes: Record<string, any>) => {
if (processes.length === 0) { if (Object.keys(processes).length === 0) {
callback([...items]); callback(items);
return; return;
} }
const updatedItems: any[] = [...items]; const updatedItems: Record<string, any> = { ...items };
for (let processIndex = 0; processIndex < processes.length; processIndex++) { for (const [processId, process] of Object.entries(processes)) {
const process = processes[processIndex];
// Update cache // Update cache
this.setItem('_document_types_', process); this.setItem('_document_types_', processId, process);
const existingIndex: number = updatedItems.findIndex(item => item.processId === process.processId); updatedItems[processId] = process;
if (existingIndex >= 0) {
updatedItems[existingIndex] = process;
} else {
updatedItems.push(process);
}
} }
callback([...updatedItems]); callback(updatedItems);
}); });
} }
public static getDocumentTypeByProcessId(processId: string): Promise<any> { public static async updateDocumentType(processId: string, newData: any): Promise<void> {
// Check if we have valid cache try {
const item: any = this.getItem('_document_types_', processId); const processUpdated = await this.messageBus.updateProcess(
if (item) { processId,
return Promise.resolve(item); { updated_at: new Date().toISOString(), ...newData },
[],
null
);
const newStateId: string = processUpdated.diffs[0]?.state_id;
await this.messageBus.notifyUpdate(processId, newStateId);
await this.messageBus.validateState(processId, newStateId);
const processData = await this.messageBus.getProcessData(processId);
// Update cache
this.setItem('_document_types_', processId, processData);
} catch (error) {
console.error('Failed to update deed type:', error);
throw error; // Re-throw to allow caller to handle
} }
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) => {
return publicValues['utype'] === 'documentType' && publicValues['isDeleted'] === 'false';
}).then((processes: any[]) => {
// Find the process with matching processId
const process = processes.find((p: any) => p.processId === processId);
if (!process) {
resolve(null);
return;
}
// Update cache
this.setItem('_document_types_', process);
resolve(process);
}).catch(reject);
});
}
// Keep the old method for backward compatibility but mark as deprecated
/** @deprecated Use getDocumentTypeByProcessId instead */
public static getDocumentTypeByUid(uid: string): Promise<any> {
// Check if we have valid cache
const item: any = this.getItem('_document_types_', uid);
if (item) {
return Promise.resolve(item);
}
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['uid'] === uid && publicValues['utype'] && publicValues['utype'] === 'documentType' && publicValues['isDeleted'] && publicValues['isDeleted'] === 'false').then((processes: any[]) => {
if (processes.length === 0) {
resolve(null);
} else {
const process: any = processes[0];
// Update cache
this.setItem('_document_types_', process);
resolve(process);
}
}).catch(reject);
});
}
public static updateDocumentType(process: any, newData: any): Promise<void> {
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
const newStateId: string = processUpdated.diffs[0]?.state_id;
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
this.removeItem('_document_types_', process.processId);
this.getDocumentTypeByProcessId(process.processId).then(resolve).catch(reject);
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
} }
} }

View File

@ -3,13 +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 CustomerService from './CustomerService'; import { DEFAULT_STORAGE_URLS, DEFAULT_VALIDATOR_ID } from '@Front/Config/AppConstants';
import CollaboratorService from './CollaboratorService';
import DeedTypeService from './DeedTypeService';
import DocumentTypeService from './DocumentTypeService';
import DocumentService from './DocumentService';
import FileService from './FileService';
import NoteService from './NoteService';
export default class FolderService extends AbstractService { export default class FolderService extends AbstractService {
@ -17,7 +11,7 @@ export default class FolderService extends AbstractService {
super(); super();
} }
public static createFolder(folderData: any, stakeholdersId: string[], customersId: string[]): Promise<any> { public static async createFolder(folderData: any, customersId: string[]): Promise<{ processId: string, processData: any }> {
const ownerId: string = User.getInstance().getPairingId()!; const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = { const processData: any = {
@ -30,9 +24,7 @@ export default class FolderService 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: {
@ -40,135 +32,101 @@ export default class FolderService extends AbstractService {
validation_rules: [], validation_rules: [],
storages: [] storages: []
}, },
owner: { owners: {
members: [ownerId], members: [ownerId, DEFAULT_VALIDATOR_ID],
validation_rules: [ validation_rules: [
{ {
quorum: 0.5, quorum: 0.01,
fields: [...privateFields, 'roles', 'uid', 'utype'], fields: allFields,
min_sig_member: 1, min_sig_member: 0.01,
}, },
], ],
storages: [] storages: [...DEFAULT_STORAGE_URLS]
}, },
stakeholders: { guests: {
members: stakeholdersId, members: [],
validation_rules: [ validation_rules: [{
{ quorum: 0.01,
quorum: 0.5, fields: allFields,
fields: ['documents', 'motes'], min_sig_member: 0.01,
min_sig_member: 1, }],
}, storages: [...DEFAULT_STORAGE_URLS]
],
storages: []
}, },
customers: { customers: {
members: customersId, members: customersId,
validation_rules: [ validation_rules: [
{ {
quorum: 0.0, quorum: 0.0,
fields: privateFields, fields: allFields,
min_sig_member: 0.0, min_sig_member: 0.0,
}, },
], ],
storages: [] storages: [...DEFAULT_STORAGE_URLS]
}, },
apophis: { apophis: {
members: [ownerId], members: [ownerId, DEFAULT_VALIDATOR_ID],
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.getFolderByUid(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 };
}).catch(reject); } catch (error) {
}); throw error;
}
} }
public static getFolders(callback: (processes: any[]) => void, waitForAll: boolean = false): void { public static getFolders(callback: (processes: Record<string, any>) => void): void {
// Check if we have valid cache // Check if we have valid cache
const items: any[] = this.getItems('_folders_'); const items: Record<string, any> = this.getItems('_folders_');
if (items.length > 0 && !waitForAll) { if (Object.keys(items).length > 0) {
setTimeout(() => callback([...items]), 0); setTimeout(() => callback(items), 0);
} }
this.messageBus.getProcessesDecoded((publicValues: any) => this.messageBus.getProcessesDecoded((_processId: string, values: any) => {
publicValues['uid'] && return values['utype']
publicValues['utype'] && && values['utype'] === 'folder'
publicValues['utype'] === 'folder' && && values['isDeleted']
publicValues['isDeleted'] && && values['isDeleted'] === 'false';
publicValues['isDeleted'] === 'false' && }).then(async (processes: Record<string, any>) => {
!items.map((item: any) => item.processData.uid).includes(publicValues['uid']) if (Object.keys(processes).length === 0) {
).then(async (processes: any[]) => { callback(items);
if (processes.length === 0) {
if (waitForAll) {
callback([...items]);
}
return; return;
} }
const updatedItems: any[] = [...items]; console.log('[FolderService/getFolders] processes', processes);
for (let processIndex = 0; processIndex < processes.length; processIndex++) { const updatedItems: Record<string, any> = { ...items };
let process = processes[processIndex];
if (!waitForAll) {
process = await this.completeFolder(process, (processInProgress: any) => {
const currentItems: any[] = [...updatedItems];
const existingIndex: number = currentItems.findIndex(item => item.processData?.uid === processInProgress.processData?.uid);
if (existingIndex >= 0) {
currentItems[existingIndex] = processInProgress;
} else {
currentItems.push(processInProgress);
}
callback(currentItems);
});
} else {
process = await this.completeFolder(process);
}
for (const [processId, process] of Object.entries(processes)) {
// Update cache // Update cache
this.setItem('_folders_', process); this.setItem('_folders_', processId, process);
const existingIndex: number = updatedItems.findIndex(item => item.processData?.uid === process.processData?.uid); updatedItems[processId] = process;
if (existingIndex >= 0) {
updatedItems[existingIndex] = process;
} else {
updatedItems.push(process);
}
if (!waitForAll) {
callback([...updatedItems]);
}
} }
if (waitForAll) { callback(updatedItems);
callback([...updatedItems]);
}
}); });
} }
public static getFoldersBy(whereClause: { [path: string]: any }): Promise<any[]> { public static getFoldersBy(whereClause: { [path: string]: any }): Promise<Record<string, any>> {
return new Promise<any[]>((resolve: (folders: any[]) => void) => { return new Promise<Record<string, any>>((resolve: (folders: Record<string, any>) => void) => {
this.getFolders((processes: any[]) => { this.getFolders((processes: Record<string, any>) => {
if (processes.length === 0) { if (Object.keys(processes).length === 0) {
resolve([]); resolve({});
} else { } else {
resolve(processes.filter((process: any) => { const filteredEntries = Object.entries(processes).filter(([processId, process]) => {
const folder: any = process.processData;
for (const path in whereClause) { for (const path in whereClause) {
const paths: string[] = path.split('.'); const paths: string[] = path.split('.');
let value: any = folder; let value: any = process;
value['processId'] = processId;
for (let i = 0; i < paths.length; i++) { for (let i = 0; i < paths.length; i++) {
const currentPath = paths[i]; const currentPath = paths[i];
if (!currentPath || value === undefined || value === null) { if (!currentPath || value === undefined || value === null) {
@ -183,146 +141,40 @@ export default class FolderService extends AbstractService {
} }
return true; return true;
})); });
// Convert filtered entries back to a Record
const filteredProcesses: Record<string, any> = {};
filteredEntries.forEach(([processId, process]) => {
filteredProcesses[processId] = process;
});
resolve(filteredProcesses);
} }
}, true);
});
}
public static getFolderByUid(uid: string): Promise<any> {
// Check if we have valid cache
const item: any = this.getItem('_folders_', uid);
if (item) {
return Promise.resolve(item);
}
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['uid'] === uid &&
publicValues['utype'] &&
publicValues['utype'] === 'folder' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false'
).then(async (processes: any[]) => {
if (processes.length === 0) {
resolve(null);
} else {
let process: any = processes[0];
process = await this.completeFolder(process);
// Update cache
this.setItem('_folders_', process);
resolve(process);
}
}).catch(reject);
});
}
public static updateFolder(process: any, newData: any): Promise<void> {
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
const newStateId: string = processUpdated.diffs[0]?.state_id;
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
const folderUid: string = process.processData.uid;
this.removeItem('_folders_', folderUid);
this.getFolderByUid(folderUid).then(resolve).catch(reject);
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
}
public static refreshFolderByUid(uid: string): Promise<void> {
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
this.getFolderByUid(uid).then((process: any) => {
this.updateFolder(process, {}).then(resolve).catch(reject);
}).catch(reject);
});
}
private static async completeFolder(process: any, progressCallback?: (processInProgress: any) => void): Promise<any> {
const progressiveProcess: any = JSON.parse(JSON.stringify(process));
if (process.processData.customers && process.processData.customers.length > 0) {
process.processData.customers = await new Promise<any[]>(async (resolve: (customers: any[]) => void) => {
const customers: any[] = [];
for (const customer of process.processData.customers) {
customers.push((await CustomerService.getCustomerByUid(customer.uid)).processData);
}
resolve(customers);
}); });
});
}
if (process.processData.customers && process.processData.customers.length > 0) { public static async updateFolder(processId: string, newData: any): Promise<void> {
const documents: any[] = (await DocumentService.getDocuments()).map((process: any) => process.processData); try {
for (const customer of process.processData.customers) { const processUpdated = await this.messageBus.updateProcess(
customer.documents = documents.filter((document: any) => (document.depositor && document.depositor.uid === customer.uid) || (document.customer && document.customer.uid === customer.uid)); processId,
{ updated_at: new Date().toISOString(), ...newData },
[],
null
);
const newStateId: string = processUpdated.diffs[0]?.state_id;
await this.messageBus.notifyUpdate(processId, newStateId);
await this.messageBus.validateState(processId, newStateId);
for (const document of customer.documents) { const processData = await this.messageBus.getProcessData(processId);
if (document.document_type) {
document.document_type = (await DocumentTypeService.getDocumentTypeByUid(document.document_type.uid)).processData; // Update cache
} this.setItem('_folders_', processId, processData);
} catch (error) {
if (document.files && document.files.length > 0) { console.error('Failed to update folder:', error);
const files: any[] = []; throw error; // Re-throw to allow caller to handle
for (const file of document.files) {
files.push({ uid: file.uid, file_name: (await FileService.getFileByUid(file.uid)).processData.file_name });
}
document.files = files;
}
}
}
}
if (progressCallback) {
progressiveProcess.processData.customers = process.processData.customers;
progressCallback(JSON.parse(JSON.stringify(progressiveProcess)));
}
} }
if (process.processData.stakeholders && process.processData.stakeholders.length > 0) {
process.processData.stakeholders = await new Promise<any[]>(async (resolve: (stakeholders: any[]) => void) => {
const stakeholders: any[] = [];
for (const stakeholder of process.processData.stakeholders) {
stakeholders.push((await CollaboratorService.getCollaboratorByUid(stakeholder.uid)).processData);
}
resolve(stakeholders);
});
if (progressCallback) {
progressiveProcess.processData.stakeholders = process.processData.stakeholders;
progressCallback(JSON.parse(JSON.stringify(progressiveProcess)));
}
}
if (process.processData.deed && process.processData.deed.deed_type) {
const deed_type: any = (await DeedTypeService.getDeedTypeByUid(process.processData.deed.deed_type.uid)).processData;
process.processData.deed.deed_type = deed_type;
if (deed_type.document_types && deed_type.document_types.length > 0) {
// Remove duplicates
process.processData.deed.document_types = deed_type.document_types.filter((item: any, index: number) => deed_type.document_types.findIndex((t: any) => t.uid === item.uid) === index);
}
if (progressCallback) {
progressiveProcess.processData.deed = process.processData.deed;
progressCallback(JSON.parse(JSON.stringify(progressiveProcess)));
}
}
const notes: any[] = (await NoteService.getNotes()).map((process: any) => process.processData);
if (notes.length > 0) {
process.processData.notes = notes.filter((note: any) => note.folder.uid === process.processData.uid);
if (progressCallback) {
progressiveProcess.processData.notes = process.processData.notes;
progressCallback(JSON.parse(JSON.stringify(progressiveProcess)));
}
}
return process;
} }
} }

View File

@ -108,7 +108,7 @@ export default class Auth extends BaseApiService {
} }
} }
public async getOfficeProcessByIdNot(): Promise<any> { public async getOfficeProcessByIdNot(): Promise<{ success: boolean; data: { processId: string, processData: { [key: string]: any } } }> {
const baseBackUrl = 'http://localhost:8080';//variables.BACK_API_PROTOCOL + variables.BACK_API_HOST; const baseBackUrl = 'http://localhost:8080';//variables.BACK_API_PROTOCOL + variables.BACK_API_HOST;
const url = new URL(`${baseBackUrl}/api/v1/process/office`); const url = new URL(`${baseBackUrl}/api/v1/process/office`);

View File

@ -7,7 +7,6 @@ import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
import DefaultDeedTypesDashboard from "@Front/Components/LayoutTemplates/DefaultDeedTypeDashboard"; import DefaultDeedTypesDashboard from "@Front/Components/LayoutTemplates/DefaultDeedTypeDashboard";
import { ToasterService } from "@Front/Components/DesignSystem/Toaster"; import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { DeedType, Office } from "le-coffre-resources/dist/Admin";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
@ -19,6 +18,7 @@ import UserStore from "@Front/Stores/UserStore";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService"; 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";
type IProps = {}; type IProps = {};
export default function DeedTypesCreate(props: IProps) { export default function DeedTypesCreate(props: IProps) {
@ -31,27 +31,44 @@ export default function DeedTypesCreate(props: IProps) {
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => { async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
try { try {
const user: any = UserStore.instance.getUser(); const user: any = UserStore.instance.getUser();
const officeId: string = user.office.uid; if (!user) {
console.error("DeedTypesCreate: User not found - user is null or undefined");
const deedType = DeedType.hydrate<DeedType>({
name: values["name"],
description: values["description"],
office: Office.hydrate<Office>({
uid: officeId,
}),
});
try {
await validateOrReject(deedType, { groups: ["createDeedType"], forbidUnknownValues: true });
} catch (validationErrors: Array<ValidationError> | any) {
setValidationError(validationErrors as ValidationError[]);
return; return;
} }
const res = await Auth.getInstance().getOfficeProcessByIdNot();
if (!res.success) {
console.error("DeedTypesCreate: Office not found - office is null or undefined");
return;
}
const officeId: string | undefined = res.data.processId;
const officeIdNot: string | undefined = res.data.processData['idNot'];
if (!officeId || !officeIdNot) {
console.error("DeedTypesCreate: officeId or officeIdNot is undefined - office.processData.idNot is missing");
return;
}
// TODO: We should update the type definition to be able to use validation again
// const deedType = DeedType.hydrate<DeedType>({
// name: values["name"],
// description: values["description"],
// office: Office.hydrate<Office>({
// idNot: officeId,
// }),
// });
// try {
// await validateOrReject(deedType, { groups: ["createDeedType"], forbidUnknownValues: true });
// } catch (validationErrors: Array<ValidationError> | any) {
// console.log("validationErrors", validationErrors);
// setValidationError(Array.isArray(validationErrors) ? validationErrors : []);
// return;
// }
const deedTypeData: any = { const deedTypeData: any = {
name: values["name"], name: values["name"],
description: values["description"], description: values["description"],
office: { office: {
uid: officeId, uid: officeId,
idNot: officeIdNot,
} }
}; };
@ -61,15 +78,18 @@ export default function DeedTypesCreate(props: IProps) {
title: "Succès !", title: "Succès !",
description: "Type d'acte créé avec succès" description: "Type d'acte créé avec succès"
}); });
const deedTypeUid = processCreated.processId;
// Remove ':0' suffix from processId for URL navigation
const urlId = deedTypeUid.replace(/:0$/, '');
router.push( router.push(
Module.getInstance() Module.getInstance()
.get() .get()
.modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", processCreated.processData.uid), .modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", urlId),
); );
LoaderService.getInstance().hide(); LoaderService.getInstance().hide();
}); });
} catch (validationErrors: Array<ValidationError> | any) { } catch (validationErrors: Array<ValidationError> | any) {
setValidationError(validationErrors as ValidationError[]); setValidationError(Array.isArray(validationErrors) ? validationErrors : []);
return; return;
} }
}, },
@ -106,12 +126,12 @@ export default function DeedTypesCreate(props: IProps) {
<TextField <TextField
name="name" name="name"
placeholder="Nom de l'acte" placeholder="Nom de l'acte"
validationError={validationError.find((error) => error.property === "name")} validationError={Array.isArray(validationError) ? validationError.find((error) => error.property === "name") : undefined}
/> />
<TextAreaField <TextAreaField
name="description" name="description"
placeholder="Description" placeholder="Description"
validationError={validationError.find((error) => error.property === "description")} validationError={Array.isArray(validationError) ? validationError.find((error) => error.property === "description") : undefined}
/> />
<div className={classes["buttons-container"]}> <div className={classes["buttons-container"]}>
<Button variant={EButtonVariant.PRIMARY} styletype={EButtonstyletype.OUTLINED} onClick={onCancel}> <Button variant={EButtonVariant.PRIMARY} styletype={EButtonstyletype.OUTLINED} onClick={onCancel}>

View File

@ -16,6 +16,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";
export default function DeedTypesEdit() { export default function DeedTypesEdit() {
const router = useRouter(); const router = useRouter();
@ -31,10 +32,11 @@ export default function DeedTypesEdit() {
async function getDeedType() { async function getDeedType() {
if (!deedTypeUid) return; if (!deedTypeUid) return;
LoaderService.getInstance().show(); LoaderService.getInstance().show();
DeedTypeService.getDeedTypeByUid(deedTypeUid as string).then((process: any) => { // deedTypeUid comes from URL without ':0' suffix, add it back for API calls
if (process) { const processId = (deedTypeUid as string) + ':0';
const deedType: any = process.processData; MessageBus.getInstance().getProcessData(processId).then((processData: any) => {
setDeedTypeSelected(deedType); if (processData) {
setDeedTypeSelected(processData);
} }
LoaderService.getInstance().hide(); LoaderService.getInstance().hide();
}); });
@ -47,51 +49,52 @@ export default function DeedTypesEdit() {
setIsConfirmModalVisible(false); setIsConfirmModalVisible(false);
}, []); }, []);
const onSubmitHandler = useCallback( const onSubmitHandler = async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string | undefined }) => {
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string | undefined }) => { // const deedType = DeedType.hydrate<DeedType>({
const deedType = DeedType.hydrate<DeedType>({ // name: values["name"],
uid: deedTypeUid as string, // description: values["description"],
name: values["name"], // });
description: values["description"],
}); // try {
try { // await deedType.validateOrReject?.({ groups: ["updateDeedType"], forbidUnknownValues: true });
await deedType.validateOrReject?.({ groups: ["updateDeedType"], forbidUnknownValues: true }); // } catch (validationErrors: Array<ValidationError> | any) {
} catch (validationErrors: Array<ValidationError> | any) { // if (!Array.isArray(validationErrors)) return;
if (!Array.isArray(validationErrors)) return; // setValidationError(validationErrors as ValidationError[]);
setValidationError(validationErrors as ValidationError[]); // return;
return; // }
}
try { try {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
DeedTypeService.getDeedTypeByUid(deedTypeUid as string).then(async (process: any) => { // deedTypeUid comes from URL without ':0' suffix, add it back for API calls
if (process) { const processId = (deedTypeUid as string) + ':0';
// New data
const newData: any = { const process = await MessageBus.getInstance().getProcessData(processId);
name: values["name"], if (process) {
description: values["description"] console.log('process', process);
}; // New data
const newData: any = {
name: values["name"],
description: values["description"]
};
// Merge process data with new data & update process // Merge process data with new data & update process
process.processData.name = newData.name; process[processId].name = newData.name;
process.processData.description = newData.description; process[processId].description = newData.description;
await DeedTypeService.updateDeedType(process, newData); await DeedTypeService.updateDeedType(processId, newData);
router.push( router.push(
Module.getInstance() Module.getInstance()
.get() .get()
.modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", deedTypeUid as string), .modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", deedTypeUid as string),
); );
LoaderService.getInstance().hide();
}
});
} catch (validationErrors) {
if (!Array.isArray(validationErrors)) return;
setValidationError(validationErrors as ValidationError[]);
return;
} }
}, } catch (error) {
[deedTypeUid, router], console.error('Error updating deed type:', error);
); // Handle error appropriately
} finally {
LoaderService.getInstance().hide();
}
};
const onFieldChange = useCallback((name: string, field: any) => { const onFieldChange = useCallback((name: string, field: any) => {
setHasChanged(true); setHasChanged(true);

View File

@ -21,6 +21,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 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";
type IProps = {}; type IProps = {};
export default function DeedTypesInformations(props: IProps) { export default function DeedTypesInformations(props: IProps) {
@ -52,7 +53,9 @@ export default function DeedTypesInformations(props: IProps) {
const deleteDeedType = useCallback(async () => { const deleteDeedType = useCallback(async () => {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
DeedTypeService.getDeedTypeByUid(deedTypeUid as string).then(async (process: any) => { // deedTypeUid comes from URL without ':0' suffix, add it back for API calls
const processId = (deedTypeUid as string) + ':0';
MessageBus.getInstance().getProcessData(processId).then(async (process: any) => {
if (process) { if (process) {
// New data // New data
const newData: any = { const newData: any = {
@ -80,9 +83,12 @@ export default function DeedTypesInformations(props: IProps) {
if (!deedTypeUid) return; if (!deedTypeUid) return;
setSelectedDocuments([]); setSelectedDocuments([]);
DeedTypeService.getDeedTypeByUid(deedTypeUid as string).then((process: any) => { // deedTypeUid comes from URL without ':0' suffix, add it back for API calls
const processId = (deedTypeUid as string) + ':0';
MessageBus.getInstance().getProcessData(processId).then((process: any) => {
if (process) { if (process) {
const deedType: any = process.processData; console.log('[DeedTypesInformations] process', process);
const deedType: any = process;
setDeedTypeSelected(deedType); setDeedTypeSelected(deedType);
if (!deedType.document_types) return; if (!deedType.document_types) return;
@ -95,15 +101,17 @@ export default function DeedTypesInformations(props: IProps) {
}) })
.sort((a: any, b: any) => a.label.localeCompare(b.label)); .sort((a: any, b: any) => a.label.localeCompare(b.label));
setSelectedDocuments(documentsOptions); setSelectedDocuments(documentsOptions);
} else {
console.warn('[DeedTypesInformations] process not found:', processId);
} }
}); });
} }
async function getDocuments() { function getDocuments() {
setAvailableDocuments([]); setAvailableDocuments([]);
DocumentTypeService.getDocumentTypes().then((processes: any[]) => { DocumentTypeService.getDocumentTypes((processes: Record<string, any>) => {
if (processes.length) { const documents = Object.values(processes);
const documents: any[] = processes.map((process: any) => process.processData); if (documents.length) {
setAvailableDocuments(documents); setAvailableDocuments(documents);
} }
}); });
@ -120,32 +128,36 @@ export default function DeedTypesInformations(props: IProps) {
[openSaveModal], [openSaveModal],
); );
const saveDocumentTypes = useCallback(() => { const saveDocumentTypes = useCallback(async () => {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
DeedTypeService.getDeedTypeByUid(deedTypeUid as string).then(async (process: any) => { // deedTypeUid comes from URL without ':0' suffix, add it back for API calls
if (process) { const processId = (deedTypeUid as string) + ':0';
const deedType: any = process.processData; console.log('[DeedTypesInformations] processId', processId);
const deedType = (await MessageBus.getInstance().getProcessData(processId))[processId];
if (deedType) {
console.log('[DeedTypesInformations] deedType', deedType);
let document_types: any[] = deedType.document_types; let document_types: any[] = deedType['document_types'];
if (!document_types) { if (!document_types) {
document_types = []; document_types = [];
}
selectedDocuments.map((selectedDocument: any) => selectedDocument.id as string)
.forEach((uid: any) => document_types.push(availableDocuments.find((document: any) => document.uid === uid)));
// New data
const newData: any = {
document_types: document_types
};
// Merge process data with new data & update process
process.processData.document_types = newData.document_types;
await DeedTypeService.updateDeedType(process, newData);
LoaderService.getInstance().hide();
closeSaveModal();
} }
}); selectedDocuments.map((selectedDocument: any) => selectedDocument.id as string)
.forEach((uid: any) => document_types.push(availableDocuments.find((document: any) => document.uid === uid)));
// New data
const newData: any = {
document_types: document_types
};
console.log('[DeedTypesInformations] newData', newData);
await DeedTypeService.updateDeedType(processId, newData);
LoaderService.getInstance().hide();
closeSaveModal();
} else {
console.warn('[DeedTypesInformations] process not found:', processId);
}
}, [closeSaveModal, deedTypeUid, selectedDocuments]); }, [closeSaveModal, deedTypeUid, selectedDocuments]);
const onDocumentChangeHandler = useCallback((options: IOption[] | null) => { const onDocumentChangeHandler = useCallback((options: IOption[] | null) => {

View File

@ -17,9 +17,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";
type IProps = { type IProps = {
folder: OfficeFolder; folder: { processId: string, FolderData: OfficeFolder};
anchorStatus: AnchorStatus; anchorStatus: AnchorStatus;
}; };
@ -30,7 +31,7 @@ export default function ClientView(props: IProps) {
const customers: ICustomer[] = useMemo( const customers: ICustomer[] = useMemo(
() => { () => {
return folder?.customers return folder?.FolderData?.customers
?.map((customer: any) => ({ ?.map((customer: any) => ({
id: customer.uid ?? '', id: customer.uid ?? '',
...customer, ...customer,
@ -62,16 +63,17 @@ export default function ClientView(props: IProps) {
const handleClientDelete = useCallback( const handleClientDelete = useCallback(
(customerUid: string) => { (customerUid: string) => {
if (!folder.uid) return; if (!folder.processId) return;
LoaderService.getInstance().show(); LoaderService.getInstance().show();
FolderService.getFolderByUid(folder.uid).then((process: any) => { MessageBus.getInstance().getProcessData(folder.processId).then((process: { [key: string]: any }) => {
if (process) { if (process) {
const folder: any = process.processData; const processId = folder.processId;
const folderData = process[processId];
// FilterBy customerUid // FilterBy customerUid
const customers = folder.customers.filter((uid: string) => uid !== customerUid); const customers = folderData.customers.filter((uid: string) => uid !== customerUid);
FolderService.updateFolder(process, { customers: customers }).then(() => { FolderService.updateFolder(processId, { customers: customers }).then(() => {
LoaderService.getInstance().hide(); LoaderService.getInstance().hide();
window.location.reload(); window.location.reload();
}); });
@ -90,7 +92,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.uid ?? "")}> .modules.pages.Folder.pages.AddClient.props.path.replace("[folderUid]", folder.processId ?? "")}>
<Button <Button
size={EButtonSize.MD} size={EButtonSize.MD}
rightIcon={<UserPlusIcon />} rightIcon={<UserPlusIcon />}
@ -106,17 +108,17 @@ export default function ClientView(props: IProps) {
<ClientBox <ClientBox
customer={customer} customer={customer}
anchorStatus={anchorStatus} anchorStatus={anchorStatus}
folderUid={folder.uid} folderUid={folder.processId}
onDelete={handleClientDelete} onDelete={handleClientDelete}
customerNote={folder.notes!.find((value: any) => value.customer?.uid === customer.uid) ?? null} customerNote={folder.FolderData?.notes?.find((value: any) => value.customer?.uid === customer.uid) ?? null}
/> />
<div className={classes["button-container"]}> <div className={classes["button-container"]}>
{anchorStatus === AnchorStatus.NOT_ANCHORED && ( {anchorStatus === AnchorStatus.NOT_ANCHORED && (
<Link <Link
href={Module.getInstance() href={Module.getInstance()
.get() .get()
.modules.pages.Folder.pages.AskDocument.props.path.replace("[folderUid]", folder.uid ?? "") .modules.pages.Folder.pages.AskDocument.props.path.replace("[folderUid]", folder.processId ?? "")
.replace("[customerUid]", customer.uid ?? "")}> .replace("[customerUid]", customer?.uid ?? "")}>
<Button rightIcon={<DocumentIcon />} variant={EButtonVariant.PRIMARY} fullwidth> <Button rightIcon={<DocumentIcon />} variant={EButtonVariant.PRIMARY} fullwidth>
Demander un document Demander un document
</Button> </Button>
@ -126,7 +128,7 @@ export default function ClientView(props: IProps) {
</div> </div>
</div> </div>
{customer.uid && folder.uid && <DocumentTables customerUid={customer.uid} folderUid={folder.uid} />} {customer?.uid && folder.processId && <DocumentTables customerUid={customer.uid} folderUid={folder.processId} />}
</div> </div>
</section> </section>
); );

View File

@ -25,40 +25,36 @@ export default function Folder() {
useEffect(() => { useEffect(() => {
// TODO: review // TODO: review
FolderService.getFoldersBy({ status: EFolderStatus.LIVE }).then((processes: any[]) => { FolderService.getFoldersBy({ status: EFolderStatus.LIVE }).then((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;
});
// OrderBy created_at desc // OrderBy created_at desc
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;
if (!folderUid) {
return;
}
router.push( router.push(
Module.getInstance() Module.getInstance()
.get() .get()
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folders[0].uid) .modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid as string)
); );
} }
} else {
console.debug('[Folder] No folders found');
} }
}); });
/*
Folders.getInstance()
.get({
q: {
where: { status: EFolderStatus.LIVE },
orderBy: { created_at: "desc" },
},
})
.then((folders) => {
if (folders.length > 0)
router.push(
Module.getInstance()
.get()
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folders[0]?.uid ?? ""),
);
});
*/
}, [router]); }, [router]);
return ( return (
<DefaultNotaryDashboard title={"Dossier"} mobileBackText={"Liste des dossiers"}> <DefaultNotaryDashboard title={"Dossier"} mobileBackText={"Liste des dossiers"}>

View File

@ -7,6 +7,7 @@ import HelpBox from "@Front/Components/Elements/HelpBox";
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage"; import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import UserStore from "@Front/Stores/UserStore"; import UserStore from "@Front/Stores/UserStore";
import CookieService from "@Front/Services/CookieService/CookieService";
import Image from "next/image"; import Image from "next/image";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
@ -21,16 +22,10 @@ import MessageBus from "src/sdk/MessageBus";
import Iframe from "src/sdk/Iframe"; import Iframe from "src/sdk/Iframe";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService"; import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import ImportData, { ProgressInfo } from "src/common/Api/LeCoffreApi/sdk/ImportData"; import { ProgressInfo } from "src/common/Api/LeCoffreApi/sdk/ImportData";
import RoleService from "src/common/Api/LeCoffreApi/sdk/RoleService";
import OfficeService from "src/common/Api/LeCoffreApi/sdk/OfficeService";
import OfficeRoleService from "src/common/Api/LeCoffreApi/sdk/OfficeRoleService";
import CollaboratorService from "src/common/Api/LeCoffreApi/sdk/CollaboratorService";
import { DEFAULT_STORAGE_URLS, DEFAULT_VALIDATOR_ID } from "@Front/Config/AppConstants";
export default function LoginCallBack() { export default function LoginCallBack() {
const router = useRouter(); const router = useRouter();
const [idNotUser, setIdNotUser] = useState<any>(null);
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false); const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
const [isConnected, setIsConnected] = useState(false); const [isConnected, setIsConnected] = useState(false);
@ -43,169 +38,26 @@ export default function LoginCallBack() {
description: '' description: ''
}); });
const getOffice = async (idNotUser: any) => { const waitForUserInfo = (): Promise<any> => {
return await new Promise<any>((resolve: (office: any) => void) => { return new Promise((resolve, reject) => {
OfficeService.getOffices().then((processes: any[]) => { if (UserStore.instance.getUser()) {
const officeFound: any = processes.length > 0 ? processes.find((office: any) => office.processData.idNot === idNotUser.office.idNot) : null; resolve(UserStore.instance.getUser());
if (officeFound) {
resolve(officeFound);
} else {
// Some info must be here or have some value, just to be sure
if (!idNotUser.office.office_status || idNotUser.office.office_status !== 'ACTIVATED') {
console.error(`[LoginCallback] office_status is not ACTIVATED for idNot ${idNotUser.office.idNot}`);
return;
}
// I guess if we don't have crpcen that's also a big problem
if (!idNotUser.office.crpcen) {
console.error(`[LoginCallback] crpcen is not set for idNot ${idNotUser.office.idNot}`);
return;
}
// We create office
const officeData: any = {
idNot: idNotUser.office.idNot,
name: idNotUser.office.name,
crpcen: idNotUser.office.crpcen,
address: {
create: {
address: idNotUser.office.address.address,
zip_code: idNotUser.office.address.zip_code,
city: idNotUser.office.address.city
}
},
office_status: idNotUser.office.office_status // must be ACTIVATED though
};
Auth.getInstance().getIdNotUserForOffice(idNotUser.office.idNot).then((users: any) => {
console.log('users : ', users);
const activeUsers = users.result.filter((user: any) => user.activite === 'En exercice');
let officeCollaborators: any[] = [];
for (const user of activeUsers) {
CollaboratorService.getCollaboratorByUid(user.uid).then((collaborator: any) => {
console.log('collaborator : ', collaborator);
officeCollaborators.push(collaborator);
});
}
OfficeService.createOffice(officeData, officeCollaborators, DEFAULT_VALIDATOR_ID, [...DEFAULT_STORAGE_URLS]).then((process: any) => {
if (process) {
const office: any = process.processData;
resolve(office);
}
});
});
}
return; return;
});
});
};
const getCollaborator = async (idNotUser: any) => {
return await new Promise<any>(async (resolve: (role: any) => void) => {
const processFound: any | null = await CollaboratorService.getCollaboratorBy({ idNot: idNotUser.idNot });
if (processFound) {
console.log('Found a collaborator for idNot', idNotUser.idNot);
// TODO: check if the collaborator is in the office process
const office: any = await getOffice(idNotUser);
// Take the role of the collaborator
MessageBus.getInstance().getRolesForProcess(processFound.processId).then((roles: any) => {
console.log('roles : ', roles);
// We should find one pairing id in the role 'owner'
const owners = roles['owner'].members;
if (owners.length !== 1) {
console.error('[LoginCallback] owner should have 1 member');
return;
}
const ownerPairingId = owners[0];
// Now we can check if the owner pairing id is in the office roles
MessageBus.getInstance().getRolesForProcess(office.processId).then((officeRoles: any) => {
const officeOwners = officeRoles['owner'].members;
if (!officeOwners.includes(ownerPairingId)) {
// We add the newly created collaborator to the office roles
OfficeService.addCollaborators(office, officeRoles, [ownerPairingId]).then((process: any) => {
resolve(processFound);
});
} else {
// Nothing to do
resolve(processFound);
}
});
});
} else {
console.log('No collaborator found for idNot', idNotUser.idNot);
const office: any = await getOffice(idNotUser);
const role: any = (await RoleService.getRoles())
.map((process: any) => process.processData)
.find((role: any) => role.name === idNotUser.role.name);
const officeRole: any = (await OfficeRoleService.getOfficeRoles())
.map((process: any) => process.processData)
.filter((officeRole: any) => officeRole.office.uid === office.processData.uid)
.find((officeRole: any) => officeRole.name === idNotUser.office_role.name);
if (!office || !role || !officeRole) {
LoaderService.getInstance().hide();
setShowProgress(true);
await ImportData.import(office, DEFAULT_VALIDATOR_ID, (info: ProgressInfo) => {
setProgressInfo(info);
});
setShowProgress(false);
LoaderService.getInstance().show();
}
const collaboratorData: any = {
idNot: idNotUser.idNot,
contact: idNotUser.contact,
office: {
uid: office.uid
},
role: {
uid: role.uid
},
office_role: {
uid: officeRole.uid
}
};
CollaboratorService.createCollaborator(collaboratorData, DEFAULT_VALIDATOR_ID).then((newCollaborator: any) => {
if (newCollaborator) {
// Now that we created the collaborator, we must check that it's in the office roles (probably not)
MessageBus.getInstance().getRolesForProcess(newCollaborator.processId).then((roles: any) => {
console.log('roles : ', roles);
// We should have our own pairing id in roles['owner']
const owner = roles['owner'].members;
if (owner.length !== 1) {
console.error('[LoginCallback] owner should have 1 member');
return;
}
const ownerPairingId = owner[0];
if (ownerPairingId !== newCollaborator.processData.uid) {
console.error('[LoginCallback] owner pairing id is not the same as the collaborator uid');
return;
}
// is ownerPairingId in roles for the office process?
MessageBus.getInstance().getRolesForProcess(office.processId).then((officeRoles: any) => {
const officeOwners = officeRoles['owner'].members;
if (!officeOwners.includes(ownerPairingId)) {
// We add the newly created collaborator to the office roles
OfficeService.addCollaborators(office, officeRoles, [ownerPairingId]).then((process: any) => {
resolve(newCollaborator);
});
} else {
// Nothing to do
resolve(newCollaborator);
}
});
});
}
});
} }
// Poll for userInfo every 100ms
const checkInterval = setInterval(() => {
if (UserStore.instance.getUser()) {
clearInterval(checkInterval);
resolve(UserStore.instance.getUser());
}
}, 100);
// Timeout after 60 seconds
setTimeout(() => {
clearInterval(checkInterval);
reject(new Error('Timeout waiting for user info'));
}, 60000);
}); });
}; };
@ -216,8 +68,8 @@ export default function LoginCallBack() {
// TODO: review // TODO: review
// HACK: If start with http://local.lecoffreio.4nkweb:3000/authorized-client // HACK: If start with http://local.lecoffreio.4nkweb:3000/authorized-client
// Replace with http://localhost:3000/authorized-client // Replace with http://localhost:3000/authorized-client
if (window.location.href.startsWith('http://local.4nkweb.com')) { if (window.location.href.startsWith('http://local.lecoffreio.4nkweb')) {
window.location.href = window.location.href.replace('http://local.4nkweb.com:3000/authorized-client', 'http://localhost:3000/authorized-client'); window.location.href = window.location.href.replace('http://local.lecoffreio.4nkweb:3000/authorized-client', 'http://localhost:3000/authorized-client');
return; return;
} }
@ -230,22 +82,34 @@ export default function LoginCallBack() {
window.history.replaceState({}, document.title, rootUrl); window.history.replaceState({}, document.title, rootUrl);
} }
const user: any = await Auth.getInstance().getIdNotUser(code as string); const user: any = await Auth.getInstance().idNotAuth(code as string);
setIdNotUser(user.idNotUser);
setIsAuthModalOpen(true); // Extract both user data and auth token from the response
console.log('[LoginCallback] idNotUser', idNotUser); const { idNotUser, authToken } = user;
/*
const token: any = null; if (!authToken) {
if (!token) return router.push(Module.getInstance().get().modules.pages.Login.props.path); console.error('[LoginCallback] No authToken received from backend');
await UserStore.instance.connect(token.accessToken, token.refreshToken); return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
const jwt = JwtService.getInstance().decodeJwt();
if (!jwt) return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
if (jwt.rules && !jwt.rules.includes("GET folders")) {
return router.push(Module.getInstance().get().modules.pages.Subscription.pages.New.props.path);
} }
// Store the auth token for API authentication
// TODO The authToken is just a uuid for now, it's very broken
CookieService.getInstance().setCookie("leCoffreAccessToken", authToken);
// Test that we can get user info and the authToken works
// TODO test that what's returned is identical to what we got before
const userInfoResponse = await Auth.getInstance().getIdNotUser();
console.log('[LoginCallback] userInfoResponse:', userInfoResponse);
if (!userInfoResponse || !userInfoResponse.success || !userInfoResponse.data) {
console.error('[LoginCallback] No userInfo received from backend');
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
}
// Store user info as a cookie
CookieService.getInstance().setCookie("leCoffreUserInfo", JSON.stringify(userInfoResponse.data));
setIsAuthModalOpen(true); setIsAuthModalOpen(true);
//return router.push(Module.getInstance().get().modules.pages.Folder.props.path); console.log('[LoginCallback] authToken stored successfully');
*/
return; return;
} catch (e: any) { } catch (e: any) {
if (e.http_status === 401 && e.message === "Email not found") { if (e.http_status === 401 && e.message === "Email not found") {
@ -257,21 +121,6 @@ export default function LoginCallBack() {
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
} }
} }
/*
const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken");
if (!refreshToken) return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
const isTokenRefreshed = await JwtService.getInstance().refreshToken(refreshToken);
const jwt = JwtService.getInstance().decodeJwt();
if (!jwt) return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
if (!jwt.rules.includes("GET folders")) {
return router.push(Module.getInstance().get().modules.pages.Subscription.pages.New.props.path);
}
if (isTokenRefreshed) {
//setIsAuthModalOpen(true);
//return router.push(Module.getInstance().get().modules.pages.Folder.props.path);
return;
}
*/
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=2"); return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=2");
} }
getUser(); getUser();
@ -354,23 +203,110 @@ export default function LoginCallBack() {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
MessageBus.getInstance().initMessageListener(); MessageBus.getInstance().initMessageListener();
MessageBus.getInstance().isReady().then(async () => { MessageBus.getInstance().isReady().then(async () => {
const collaborator: any = await getCollaborator(idNotUser); try {
if (!UserStore.instance.connect(collaborator)) { const userInfo = await waitForUserInfo();
console.error('[LoginCallback] collaborator not connected'); if (!userInfo) {
console.error('[LoginCallback] No userInfo received from backend');
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
}
// console.log('userInfo : ', userInfo);
// Here we are now authenticated with idNot, we have our idnot user info
// We also have a device and it should be paired
// What we may not have yet is a collaborator process
// Office may not have a process too
let collaboratorProcess: { processId: string, processData: { [key: string]: any } } | null = null;
let officeProcess: { processId: string, processData: { [key: string]: any } } | null = null;
// Initialize collaborator process
try {
// Wait for pairing ID to be available before proceeding
const pairingId = await MessageBus.getInstance().getPairingId();
console.log('[LoginCallback] Pairing ID obtained:', pairingId);
// Check if we are part of the right collaborator process
const myCollaboratorProcessesData = await MessageBus.getInstance().getProcessesDecoded((processId: string, values: { [key: string]: any }) => {
return values['utype'] === 'collaborator'
&& values['idNot'] === userInfo.idNot
&& values['isDeleted'] === 'false';
});
if (myCollaboratorProcessesData && Object.keys(myCollaboratorProcessesData).length !== 0) {
collaboratorProcess = { processId: Object.keys(myCollaboratorProcessesData)[0]!, processData: Object.values(myCollaboratorProcessesData)[0]! };
} else {
const res = await Auth.getInstance().getUserProcessByIdNot(pairingId);
if (res.success) {
collaboratorProcess = res.data;
} else {
console.error('[LoginCallback] Error getting collaborator process');
router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
return;
}
// await new Promise(resolve => setTimeout(resolve, 100));
}
// If we're on a new device, signer should notice and add us to the process
// TODO check that we're part of the collaborator process
} catch (error: any) {
console.error('[LoginCallback] Error getting collaborator process:', error);
if (error.message === 'Timeout waiting for pairing ID') {
console.error('[LoginCallback] Pairing ID not available after timeout');
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
}
// If we can't get collaborator process, we can't proceed
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
}
// Initialize office process
try {
// Wait for pairing ID to be available before proceeding
const pairingId = await MessageBus.getInstance().getPairingId();
console.log('[LoginCallback] Pairing ID obtained:', pairingId);
// Now we need to check for office process
const myOfficeProcessesData = await MessageBus.getInstance().getProcessesDecoded((processId: string, values: { [key: string]: any }) => {
return values['utype'] === 'office'
&& values['idNot'] === userInfo.office.idNot
&& values['isDeleted'] === 'false';
});
if (myOfficeProcessesData && Object.keys(myOfficeProcessesData).length !== 0) {
officeProcess = { processId: Object.keys(myOfficeProcessesData)[0]!, processData: Object.values(myOfficeProcessesData)[0]! };
} else {
const res = await Auth.getInstance().getUserProcessByIdNot(pairingId);
if (res.success) {
officeProcess = res.data;
} else {
console.error('[LoginCallback] Error getting collaborator process');
router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
return;
}
}
// TODO Check that we're part of the office process
// For now we rely on the signer to get office data, for the sake of simplicity
} catch (error: any) {
console.error('[LoginCallback] Error getting office process:', error);
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
}
// Verify both processes are initialized before proceeding
if (!collaboratorProcess || !officeProcess) {
console.error('[LoginCallback] Failed to initialize required processes');
router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
return;
}
if (!UserStore.instance.connect(collaboratorProcess.processData, officeProcess.processData)) {
console.error('[LoginCallback] collaborator not connected');
router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
return;
}
window.location.href = Module.getInstance().get().modules.pages.Folder.props.path;
} catch (error) {
console.error('[LoginCallback] Error waiting for user info or processing collaborator:', error);
router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
return; } finally {
MessageBus.getInstance().destroyMessageListener();
LoaderService.getInstance().hide();
} }
return;
MessageBus.getInstance().destroyMessageListener();
LoaderService.getInstance().hide();
/*
if (jwt.rules && !jwt.rules.includes("GET folders")) {
router.push(Module.getInstance().get().modules.pages.Subscription.pages.New.props.path);
}
*/
window.location.href = Module.getInstance().get().modules.pages.Folder.props.path;
}); });
}, 100); }, 100);
}} }}