feat: centralize constructeur link synchronization
This commit is contained in:
248
src/common/utils/constructeur-link.util.ts
Normal file
248
src/common/utils/constructeur-link.util.ts
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
import { Prisma } from '@prisma/client';
|
||||||
|
import type { PrismaService } from '../../prisma/prisma.service';
|
||||||
|
|
||||||
|
type PrismaExecutor = Prisma.TransactionClient | PrismaService;
|
||||||
|
|
||||||
|
type LinkOrientation = {
|
||||||
|
parentColumn: 'A' | 'B';
|
||||||
|
constructeurColumn: 'A' | 'B';
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_ORIENTATIONS: Record<string, LinkOrientation> = {
|
||||||
|
_MachineConstructeurs: { parentColumn: 'A', constructeurColumn: 'B' },
|
||||||
|
_ComposantConstructeurs: { parentColumn: 'A', constructeurColumn: 'B' },
|
||||||
|
_PieceConstructeurs: { parentColumn: 'A', constructeurColumn: 'B' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const sanitizeTableName = (tableName: string): string => {
|
||||||
|
if (!/^_[A-Za-z0-9]+$/.test(tableName)) {
|
||||||
|
throw new Error(`Invalid constructeur link table name: ${tableName}`);
|
||||||
|
}
|
||||||
|
return `"${tableName}"`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ORIENTATION_CACHE = new Map<string, LinkOrientation>();
|
||||||
|
const KNOWN_PARENT_TABLES = new Set(['machines', 'composants', 'pieces']);
|
||||||
|
const oppositeColumn = (column: 'A' | 'B'): 'A' | 'B' =>
|
||||||
|
column === 'A' ? 'B' : 'A';
|
||||||
|
|
||||||
|
async function resolveOrientation(
|
||||||
|
prisma: PrismaExecutor & {
|
||||||
|
__getConstructeurLinkOrientation?: (
|
||||||
|
table: string,
|
||||||
|
) => LinkOrientation | Promise<LinkOrientation>;
|
||||||
|
},
|
||||||
|
tableName: string,
|
||||||
|
): Promise<LinkOrientation> {
|
||||||
|
const cached = ORIENTATION_CACHE.get(tableName);
|
||||||
|
if (cached) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof prisma.__getConstructeurLinkOrientation === 'function') {
|
||||||
|
const orientation = await prisma.__getConstructeurLinkOrientation(tableName);
|
||||||
|
ORIENTATION_CACHE.set(tableName, orientation);
|
||||||
|
return orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = await prisma.$queryRaw<
|
||||||
|
Array<{ column_name: string; foreign_table_name: string }>
|
||||||
|
>(Prisma.sql`
|
||||||
|
SELECT
|
||||||
|
kcu.column_name,
|
||||||
|
ccu.table_name AS foreign_table_name
|
||||||
|
FROM information_schema.table_constraints tc
|
||||||
|
JOIN information_schema.key_column_usage kcu
|
||||||
|
ON tc.constraint_name = kcu.constraint_name
|
||||||
|
AND tc.table_schema = kcu.table_schema
|
||||||
|
JOIN information_schema.constraint_column_usage ccu
|
||||||
|
ON tc.constraint_name = ccu.constraint_name
|
||||||
|
AND tc.table_schema = ccu.table_schema
|
||||||
|
WHERE tc.constraint_type = 'FOREIGN KEY'
|
||||||
|
AND tc.table_schema = 'public'
|
||||||
|
AND tc.table_name = ${tableName}
|
||||||
|
`);
|
||||||
|
|
||||||
|
let parentColumn: 'A' | 'B' | null = null;
|
||||||
|
let constructeurColumn: 'A' | 'B' | null = null;
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
const column = row.column_name?.toUpperCase?.() as 'A' | 'B' | undefined;
|
||||||
|
if (column !== 'A' && column !== 'B') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.foreign_table_name === 'constructeurs') {
|
||||||
|
constructeurColumn = column;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (KNOWN_PARENT_TABLES.has(row.foreign_table_name)) {
|
||||||
|
parentColumn = column;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parentColumn) {
|
||||||
|
parentColumn = column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentColumn && !constructeurColumn) {
|
||||||
|
constructeurColumn = oppositeColumn(parentColumn);
|
||||||
|
} else if (!parentColumn && constructeurColumn) {
|
||||||
|
parentColumn = oppositeColumn(constructeurColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parentColumn || !constructeurColumn) {
|
||||||
|
const fallback = DEFAULT_ORIENTATIONS[tableName];
|
||||||
|
if (fallback) {
|
||||||
|
parentColumn ??= fallback.parentColumn;
|
||||||
|
constructeurColumn ??= fallback.constructeurColumn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parentColumn || !constructeurColumn) {
|
||||||
|
const columns = rows
|
||||||
|
.map(
|
||||||
|
(row) =>
|
||||||
|
row.column_name?.toUpperCase?.() as 'A' | 'B' | undefined,
|
||||||
|
)
|
||||||
|
.filter((column): column is 'A' | 'B' => column === 'A' || column === 'B');
|
||||||
|
|
||||||
|
if (columns.length === 2) {
|
||||||
|
if (!parentColumn) {
|
||||||
|
parentColumn = columns[0];
|
||||||
|
}
|
||||||
|
if (!constructeurColumn) {
|
||||||
|
const alternative = columns.find((column) => column !== parentColumn);
|
||||||
|
if (alternative) {
|
||||||
|
constructeurColumn = alternative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parentColumn || !constructeurColumn) {
|
||||||
|
throw new Error(
|
||||||
|
`Impossible de déterminer l'orientation de la table ${tableName}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const orientation: LinkOrientation = {
|
||||||
|
parentColumn,
|
||||||
|
constructeurColumn,
|
||||||
|
};
|
||||||
|
ORIENTATION_CACHE.set(tableName, orientation);
|
||||||
|
return orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function syncConstructeurLinks(
|
||||||
|
prisma: PrismaExecutor,
|
||||||
|
tableName: string,
|
||||||
|
parentId: string,
|
||||||
|
constructeurIds: string[],
|
||||||
|
): Promise<string[]> {
|
||||||
|
const executor = prisma as PrismaExecutor & {
|
||||||
|
__syncConstructeurLinks?: (
|
||||||
|
table: string,
|
||||||
|
parent: string,
|
||||||
|
ids: string[],
|
||||||
|
) => Promise<void> | void;
|
||||||
|
__getConstructeurLinkOrientation?: (
|
||||||
|
table: string,
|
||||||
|
) => LinkOrientation | Promise<LinkOrientation>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const table = Prisma.raw(sanitizeTableName(tableName));
|
||||||
|
const uniqueConstructeurIds = Array.from(
|
||||||
|
new Set(
|
||||||
|
constructeurIds
|
||||||
|
.map((value) => (typeof value === 'string' ? value.trim() : ''))
|
||||||
|
.filter((value) => value.length > 0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let targetConstructeurIds = uniqueConstructeurIds;
|
||||||
|
|
||||||
|
const constructeurDelegate = (executor as any)?.constructeur as
|
||||||
|
| {
|
||||||
|
findMany?: (args: {
|
||||||
|
where: { id: { in: string[] } };
|
||||||
|
select: { id: boolean };
|
||||||
|
}) => Promise<Array<{ id: string }>>;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
if (targetConstructeurIds.length > 0 && constructeurDelegate?.findMany) {
|
||||||
|
const existing = await constructeurDelegate.findMany({
|
||||||
|
where: { id: { in: targetConstructeurIds } },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
const existingIds = new Set(existing.map(({ id }) => id));
|
||||||
|
targetConstructeurIds = targetConstructeurIds.filter((id) =>
|
||||||
|
existingIds.has(id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const orientation = await resolveOrientation(executor, tableName);
|
||||||
|
|
||||||
|
if (typeof executor.__syncConstructeurLinks === 'function') {
|
||||||
|
await executor.__syncConstructeurLinks(
|
||||||
|
tableName,
|
||||||
|
parentId,
|
||||||
|
targetConstructeurIds,
|
||||||
|
);
|
||||||
|
return targetConstructeurIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.$executeRaw(
|
||||||
|
Prisma.sql`DELETE FROM ${table} WHERE ${Prisma.raw(
|
||||||
|
`"${orientation.parentColumn}"`,
|
||||||
|
)} = ${parentId}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (targetConstructeurIds.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const valueTuples = targetConstructeurIds.map((constructeurId) =>
|
||||||
|
Prisma.sql`(${parentId}, ${constructeurId})`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await prisma.$executeRaw(
|
||||||
|
Prisma.sql`
|
||||||
|
INSERT INTO ${table} (
|
||||||
|
${Prisma.raw(`"${orientation.parentColumn}"`)},
|
||||||
|
${Prisma.raw(`"${orientation.constructeurColumn}"`)}
|
||||||
|
)
|
||||||
|
VALUES ${Prisma.join(valueTuples)}
|
||||||
|
ON CONFLICT DO NOTHING
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
return fetchConstructeurIds(executor, tableName, parentId, orientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchConstructeurIds(
|
||||||
|
prisma: PrismaExecutor,
|
||||||
|
tableName: string,
|
||||||
|
parentId: string,
|
||||||
|
orientationOverride?: LinkOrientation,
|
||||||
|
): Promise<string[]> {
|
||||||
|
const orientation =
|
||||||
|
orientationOverride ?? (await resolveOrientation(prisma as any, tableName));
|
||||||
|
const table = Prisma.raw(sanitizeTableName(tableName));
|
||||||
|
const rows = await prisma.$queryRaw<Array<{ constructeurId: string | null }>>(
|
||||||
|
Prisma.sql`
|
||||||
|
SELECT ${Prisma.raw(
|
||||||
|
`"${orientation.constructeurColumn}"`,
|
||||||
|
)} AS "constructeurId"
|
||||||
|
FROM ${table}
|
||||||
|
WHERE ${Prisma.raw(`"${orientation.parentColumn}"`)} = ${parentId}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return rows
|
||||||
|
.map((row) =>
|
||||||
|
typeof row.constructeurId === 'string' ? row.constructeurId : null,
|
||||||
|
)
|
||||||
|
.filter((id): id is string => Boolean(id));
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
COMPONENT_WITH_RELATIONS_INCLUDE,
|
COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||||
ComposantWithRelations,
|
ComposantWithRelations,
|
||||||
} from '../common/constants/component-includes';
|
} from '../common/constants/component-includes';
|
||||||
|
import { syncConstructeurLinks } from '../common/utils/constructeur-link.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ComposantsService {
|
export class ComposantsService {
|
||||||
@@ -16,7 +17,10 @@ export class ComposantsService {
|
|||||||
|
|
||||||
private async buildCreateInput(
|
private async buildCreateInput(
|
||||||
createComposantDto: CreateComposantDto,
|
createComposantDto: CreateComposantDto,
|
||||||
): Promise<Prisma.ComposantCreateInput> {
|
): Promise<{
|
||||||
|
data: Prisma.ComposantCreateInput;
|
||||||
|
constructeurIds: string[];
|
||||||
|
}> {
|
||||||
const data: Prisma.ComposantCreateInput = {
|
const data: Prisma.ComposantCreateInput = {
|
||||||
name: createComposantDto.name,
|
name: createComposantDto.name,
|
||||||
reference: createComposantDto.reference ?? null,
|
reference: createComposantDto.reference ?? null,
|
||||||
@@ -29,11 +33,6 @@ export class ComposantsService {
|
|||||||
);
|
);
|
||||||
const resolvedConstructeurIds =
|
const resolvedConstructeurIds =
|
||||||
await this.resolveExistingConstructeurIds(constructeurIds);
|
await this.resolveExistingConstructeurIds(constructeurIds);
|
||||||
if (resolvedConstructeurIds.length) {
|
|
||||||
data.constructeurs = {
|
|
||||||
connect: resolvedConstructeurIds.map((id) => ({ id })),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (createComposantDto.typeComposantId) {
|
if (createComposantDto.typeComposantId) {
|
||||||
data.typeComposant = {
|
data.typeComposant = {
|
||||||
@@ -45,17 +44,41 @@ export class ComposantsService {
|
|||||||
data.structure = createComposantDto.structure as Prisma.InputJsonValue;
|
data.structure = createComposantDto.structure as Prisma.InputJsonValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return { data, constructeurIds: resolvedConstructeurIds };
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(createComposantDto: CreateComposantDto) {
|
async create(createComposantDto: CreateComposantDto) {
|
||||||
try {
|
try {
|
||||||
|
const { data, constructeurIds } = await this.buildCreateInput(
|
||||||
|
createComposantDto,
|
||||||
|
);
|
||||||
const created = await this.prisma.composant.create({
|
const created = await this.prisma.composant.create({
|
||||||
data: await this.buildCreateInput(createComposantDto),
|
data,
|
||||||
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||||
});
|
});
|
||||||
|
|
||||||
return created as ComposantWithRelations;
|
let syncedConstructeurIds: string[] = [];
|
||||||
|
if (constructeurIds.length > 0) {
|
||||||
|
syncedConstructeurIds = await syncConstructeurLinks(
|
||||||
|
this.prisma,
|
||||||
|
'_ComposantConstructeurs',
|
||||||
|
created.id,
|
||||||
|
constructeurIds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshed = (await this.prisma.composant.findUnique({
|
||||||
|
where: { id: created.id },
|
||||||
|
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||||
|
})) as ComposantWithRelations | null;
|
||||||
|
|
||||||
|
if (refreshed && syncedConstructeurIds.length > 0) {
|
||||||
|
(refreshed as ComposantWithRelations & {
|
||||||
|
constructeurIds?: string[];
|
||||||
|
}).constructeurIds = [...syncedConstructeurIds];
|
||||||
|
}
|
||||||
|
|
||||||
|
return refreshed;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handlePrismaError(error);
|
this.handlePrismaError(error);
|
||||||
}
|
}
|
||||||
@@ -90,15 +113,14 @@ export class ComposantsService {
|
|||||||
data.prix = updateComposantDto.prix;
|
data.prix = updateComposantDto.prix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let resolvedConstructeurIds: string[] | undefined;
|
||||||
if (updateComposantDto.constructeurIds !== undefined) {
|
if (updateComposantDto.constructeurIds !== undefined) {
|
||||||
const constructeurIds = this.normalizeConstructeurIds(
|
const constructeurIds = this.normalizeConstructeurIds(
|
||||||
updateComposantDto.constructeurIds,
|
updateComposantDto.constructeurIds,
|
||||||
);
|
);
|
||||||
const resolvedConstructeurIds =
|
resolvedConstructeurIds = await this.resolveExistingConstructeurIds(
|
||||||
await this.resolveExistingConstructeurIds(constructeurIds);
|
constructeurIds,
|
||||||
data.constructeurs = {
|
);
|
||||||
set: resolvedConstructeurIds.map((id) => ({ id })),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateComposantDto.typeComposantId !== undefined) {
|
if (updateComposantDto.typeComposantId !== undefined) {
|
||||||
@@ -111,12 +133,36 @@ export class ComposantsService {
|
|||||||
data.structure = updateComposantDto.structure as Prisma.InputJsonValue;
|
data.structure = updateComposantDto.structure as Prisma.InputJsonValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let syncedConstructeurIds: string[] | undefined;
|
||||||
try {
|
try {
|
||||||
return (await this.prisma.composant.update({
|
await this.prisma.$transaction(async (tx) => {
|
||||||
|
await tx.composant.update({
|
||||||
|
where: { id },
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resolvedConstructeurIds !== undefined) {
|
||||||
|
syncedConstructeurIds = await syncConstructeurLinks(
|
||||||
|
tx,
|
||||||
|
'_ComposantConstructeurs',
|
||||||
|
id,
|
||||||
|
resolvedConstructeurIds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const refreshed = (await this.prisma.composant.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
data,
|
|
||||||
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||||
})) as ComposantWithRelations;
|
})) as ComposantWithRelations | null;
|
||||||
|
|
||||||
|
if (refreshed && syncedConstructeurIds) {
|
||||||
|
(refreshed as ComposantWithRelations & {
|
||||||
|
constructeurIds?: string[];
|
||||||
|
}).constructeurIds = [...syncedConstructeurIds];
|
||||||
|
}
|
||||||
|
|
||||||
|
return refreshed;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handlePrismaError(error);
|
this.handlePrismaError(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ import {
|
|||||||
MachinePieceLinkInput,
|
MachinePieceLinkInput,
|
||||||
} from '../shared/dto/machine.dto';
|
} from '../shared/dto/machine.dto';
|
||||||
import { buildComponentHierarchy } from '../common/utils/component-tree.util';
|
import { buildComponentHierarchy } from '../common/utils/component-tree.util';
|
||||||
|
import {
|
||||||
|
fetchConstructeurIds,
|
||||||
|
syncConstructeurLinks,
|
||||||
|
} from '../common/utils/constructeur-link.util';
|
||||||
|
|
||||||
const CUSTOM_FIELD_SELECT = {
|
const CUSTOM_FIELD_SELECT = {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -402,12 +406,18 @@ export class MachinesService {
|
|||||||
| (MachineWithRelations & {
|
| (MachineWithRelations & {
|
||||||
componentLinks: HydratedComponentLink[];
|
componentLinks: HydratedComponentLink[];
|
||||||
pieceLinks: HydratedPieceLink[];
|
pieceLinks: HydratedPieceLink[];
|
||||||
|
constructeurIds: string[];
|
||||||
|
constructeurs: MachineWithRelations['constructeurs'];
|
||||||
})
|
})
|
||||||
| null {
|
| null {
|
||||||
if (!machine) {
|
if (!machine) {
|
||||||
return machine;
|
return machine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolvedConstructeurs = Array.isArray(machine.constructeurs)
|
||||||
|
? machine.constructeurs
|
||||||
|
: [];
|
||||||
|
|
||||||
const componentLinks = this.hydrateComponentLinks(
|
const componentLinks = this.hydrateComponentLinks(
|
||||||
machine.componentLinks ?? [],
|
machine.componentLinks ?? [],
|
||||||
);
|
);
|
||||||
@@ -419,10 +429,18 @@ export class MachinesService {
|
|||||||
const hydratedMachine = machine as MachineWithRelations & {
|
const hydratedMachine = machine as MachineWithRelations & {
|
||||||
componentLinks: HydratedComponentLink[];
|
componentLinks: HydratedComponentLink[];
|
||||||
pieceLinks: HydratedPieceLink[];
|
pieceLinks: HydratedPieceLink[];
|
||||||
|
constructeurIds: string[];
|
||||||
|
constructeurs: MachineWithRelations['constructeurs'];
|
||||||
};
|
};
|
||||||
|
|
||||||
hydratedMachine.componentLinks = componentLinks;
|
hydratedMachine.componentLinks = componentLinks;
|
||||||
hydratedMachine.pieceLinks = rootPieceLinks;
|
hydratedMachine.pieceLinks = rootPieceLinks;
|
||||||
|
hydratedMachine.constructeurIds = resolvedConstructeurs
|
||||||
|
.map((constructeur) =>
|
||||||
|
typeof constructeur?.id === 'string' ? constructeur.id : null,
|
||||||
|
)
|
||||||
|
.filter((id): id is string => Boolean(id));
|
||||||
|
hydratedMachine.constructeurs = resolvedConstructeurs;
|
||||||
|
|
||||||
return hydratedMachine;
|
return hydratedMachine;
|
||||||
}
|
}
|
||||||
@@ -432,10 +450,85 @@ export class MachinesService {
|
|||||||
): (MachineWithRelations & {
|
): (MachineWithRelations & {
|
||||||
componentLinks: HydratedComponentLink[];
|
componentLinks: HydratedComponentLink[];
|
||||||
pieceLinks: HydratedPieceLink[];
|
pieceLinks: HydratedPieceLink[];
|
||||||
|
constructeurIds: string[];
|
||||||
|
constructeurs: MachineWithRelations['constructeurs'];
|
||||||
})[] {
|
})[] {
|
||||||
return machines.map((machine) => this.hydrateMachine(machine)!);
|
return machines.map((machine) => this.hydrateMachine(machine)!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async ensureMachineConstructeurs<
|
||||||
|
T extends
|
||||||
|
| (MachineWithRelations & {
|
||||||
|
componentLinks: HydratedComponentLink[];
|
||||||
|
pieceLinks: HydratedPieceLink[];
|
||||||
|
constructeurIds: string[];
|
||||||
|
constructeurs: MachineWithRelations['constructeurs'];
|
||||||
|
})
|
||||||
|
| null,
|
||||||
|
>(machine: T): Promise<T> {
|
||||||
|
if (!machine) {
|
||||||
|
return machine;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingConstructeurs = Array.isArray(machine.constructeurs)
|
||||||
|
? machine.constructeurs
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const idsFromConstructeurs = existingConstructeurs
|
||||||
|
.map((constructeur) =>
|
||||||
|
typeof constructeur?.id === 'string' ? constructeur.id : null,
|
||||||
|
)
|
||||||
|
.filter((id): id is string => Boolean(id));
|
||||||
|
|
||||||
|
const initialIds =
|
||||||
|
Array.isArray(machine.constructeurIds) && machine.constructeurIds.length > 0
|
||||||
|
? machine.constructeurIds
|
||||||
|
: idsFromConstructeurs;
|
||||||
|
|
||||||
|
let resolvedIds = initialIds;
|
||||||
|
if (resolvedIds.length === 0) {
|
||||||
|
resolvedIds = await fetchConstructeurIds(
|
||||||
|
this.prisma,
|
||||||
|
'_MachineConstructeurs',
|
||||||
|
machine.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
machine.constructeurIds = [...resolvedIds];
|
||||||
|
|
||||||
|
if (
|
||||||
|
existingConstructeurs.length > 0 &&
|
||||||
|
resolvedIds.length === existingConstructeurs.length
|
||||||
|
) {
|
||||||
|
return machine;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolvedIds.length === 0) {
|
||||||
|
machine.constructeurs = [];
|
||||||
|
return machine;
|
||||||
|
}
|
||||||
|
|
||||||
|
const constructeurs = await this.prisma.constructeur.findMany({
|
||||||
|
where: { id: { in: resolvedIds } },
|
||||||
|
});
|
||||||
|
const byId = new Map(constructeurs.map((item) => [item.id, item]));
|
||||||
|
|
||||||
|
const orderedConstructeurs = resolvedIds
|
||||||
|
.map((id) => byId.get(id))
|
||||||
|
.filter(
|
||||||
|
(
|
||||||
|
record,
|
||||||
|
): record is (typeof constructeurs)[number] =>
|
||||||
|
Boolean(record),
|
||||||
|
);
|
||||||
|
|
||||||
|
machine.constructeurs =
|
||||||
|
orderedConstructeurs as MachineWithRelations['constructeurs'];
|
||||||
|
|
||||||
|
return machine;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private slugifyName(name: string): string {
|
private slugifyName(name: string): string {
|
||||||
return name
|
return name
|
||||||
.normalize('NFD')
|
.normalize('NFD')
|
||||||
@@ -1744,36 +1837,27 @@ export class MachinesService {
|
|||||||
const { componentRequirementMap, pieceRequirementMap } =
|
const { componentRequirementMap, pieceRequirementMap } =
|
||||||
this.buildConfigurationContext(typeMachine, componentLinks, pieceLinks);
|
this.buildConfigurationContext(typeMachine, componentLinks, pieceLinks);
|
||||||
|
|
||||||
let machine: Awaited<ReturnType<typeof this.prisma.machine.create>>;
|
const baseMachine = await this.prisma.machine.create({
|
||||||
try {
|
data: machineData,
|
||||||
const createData: any = {
|
include: {
|
||||||
...machineData,
|
site: true,
|
||||||
};
|
typeMachine: true,
|
||||||
|
constructeurs: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (resolvedConstructeurIds.length) {
|
const syncedConstructeurIds = await syncConstructeurLinks(
|
||||||
createData.constructeurs = {
|
this.prisma,
|
||||||
connect: resolvedConstructeurIds.map((id) => ({ id })),
|
'_MachineConstructeurs',
|
||||||
};
|
baseMachine.id,
|
||||||
}
|
resolvedConstructeurIds,
|
||||||
|
);
|
||||||
machine = await this.prisma.machine.create({
|
|
||||||
data: createData,
|
|
||||||
include: {
|
|
||||||
site: true,
|
|
||||||
typeMachine: true,
|
|
||||||
constructeurs: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
this.handlePrismaError(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeMachine.customFields && typeMachine.customFields.length > 0) {
|
if (typeMachine.customFields && typeMachine.customFields.length > 0) {
|
||||||
await this.createMachineCustomFieldsFromType(
|
await this.createMachineCustomFieldsFromType(
|
||||||
this.prisma,
|
this.prisma,
|
||||||
machine.id,
|
baseMachine.id,
|
||||||
typeMachine.customFields,
|
typeMachine.customFields,
|
||||||
typeMachine.id,
|
typeMachine.id,
|
||||||
);
|
);
|
||||||
@@ -1781,39 +1865,48 @@ export class MachinesService {
|
|||||||
|
|
||||||
const componentIndex = await this.createComponentLinksForMachine(
|
const componentIndex = await this.createComponentLinksForMachine(
|
||||||
this.prisma,
|
this.prisma,
|
||||||
machine.id,
|
baseMachine.id,
|
||||||
componentRequirementMap,
|
componentRequirementMap,
|
||||||
componentLinks,
|
componentLinks,
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.createPieceLinksForMachine(
|
await this.createPieceLinksForMachine(
|
||||||
this.prisma,
|
this.prisma,
|
||||||
machine.id,
|
baseMachine.id,
|
||||||
pieceRequirementMap,
|
pieceRequirementMap,
|
||||||
pieceLinks,
|
pieceLinks,
|
||||||
componentIndex,
|
componentIndex,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await this.prisma.machine
|
await this.prisma.machine
|
||||||
.delete({ where: { id: machine.id } })
|
.delete({ where: { id: baseMachine.id } })
|
||||||
.catch(() => undefined);
|
.catch(() => undefined);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createdMachine = await this.prisma.machine.findUnique({
|
const createdMachine = await this.prisma.machine.findUnique({
|
||||||
where: { id: machine.id },
|
where: { id: baseMachine.id },
|
||||||
include: MACHINE_DEFAULT_INCLUDE,
|
include: MACHINE_DEFAULT_INCLUDE,
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.hydrateMachine(createdMachine);
|
const hydrated = this.hydrateMachine(createdMachine);
|
||||||
|
if (hydrated && syncedConstructeurIds.length > 0) {
|
||||||
|
hydrated.constructeurIds = [...syncedConstructeurIds];
|
||||||
|
}
|
||||||
|
return this.ensureMachineConstructeurs(hydrated);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll() {
|
async findAll() {
|
||||||
const machines = await this.prisma.machine.findMany({
|
const machines = await this.prisma.machine.findMany({
|
||||||
include: MACHINE_DEFAULT_INCLUDE,
|
include: MACHINE_DEFAULT_INCLUDE,
|
||||||
});
|
});
|
||||||
|
const hydrated = this.hydrateMachines(machines);
|
||||||
return this.hydrateMachines(machines);
|
const enriched = await Promise.all(
|
||||||
|
hydrated.map((machine) => this.ensureMachineConstructeurs(machine)),
|
||||||
|
);
|
||||||
|
return enriched.filter(
|
||||||
|
(machine): machine is NonNullable<typeof machine> => Boolean(machine),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne(id: string) {
|
async findOne(id: string) {
|
||||||
@@ -1821,8 +1914,8 @@ export class MachinesService {
|
|||||||
where: { id },
|
where: { id },
|
||||||
include: MACHINE_DEFAULT_INCLUDE,
|
include: MACHINE_DEFAULT_INCLUDE,
|
||||||
});
|
});
|
||||||
|
const hydrated = this.hydrateMachine(machine);
|
||||||
return this.hydrateMachine(machine);
|
return this.ensureMachineConstructeurs(hydrated);
|
||||||
}
|
}
|
||||||
|
|
||||||
async reconfigure(id: string, reconfigureMachineDto: ReconfigureMachineDto) {
|
async reconfigure(id: string, reconfigureMachineDto: ReconfigureMachineDto) {
|
||||||
@@ -1877,7 +1970,8 @@ export class MachinesService {
|
|||||||
include: MACHINE_DEFAULT_INCLUDE,
|
include: MACHINE_DEFAULT_INCLUDE,
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.hydrateMachine(updatedMachine);
|
const hydrated = this.hydrateMachine(updatedMachine);
|
||||||
|
return this.ensureMachineConstructeurs(hydrated);
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, updateMachineDto: UpdateMachineDto) {
|
async update(id: string, updateMachineDto: UpdateMachineDto) {
|
||||||
@@ -1894,15 +1988,13 @@ export class MachinesService {
|
|||||||
data.reference = reference;
|
data.reference = reference;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let resolvedConstructeurIds: string[] | undefined;
|
||||||
if (constructeurIds !== undefined) {
|
if (constructeurIds !== undefined) {
|
||||||
const normalizedConstructeurIds =
|
const normalizedConstructeurIds =
|
||||||
this.normalizeConstructeurIds(constructeurIds);
|
this.normalizeConstructeurIds(constructeurIds);
|
||||||
const resolvedConstructeurIds = await this.resolveExistingConstructeurIds(
|
resolvedConstructeurIds = await this.resolveExistingConstructeurIds(
|
||||||
normalizedConstructeurIds,
|
normalizedConstructeurIds,
|
||||||
);
|
);
|
||||||
data.constructeurs = {
|
|
||||||
set: resolvedConstructeurIds.map((id) => ({ id })),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prix !== undefined) {
|
if (prix !== undefined) {
|
||||||
@@ -1921,14 +2013,34 @@ export class MachinesService {
|
|||||||
: { disconnect: true };
|
: { disconnect: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let syncedConstructeurIds: string[] | undefined;
|
||||||
try {
|
try {
|
||||||
const machine = await this.prisma.machine.update({
|
await this.prisma.$transaction(async (tx) => {
|
||||||
|
await tx.machine.update({
|
||||||
|
where: { id },
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resolvedConstructeurIds !== undefined) {
|
||||||
|
syncedConstructeurIds = await syncConstructeurLinks(
|
||||||
|
tx,
|
||||||
|
'_MachineConstructeurs',
|
||||||
|
id,
|
||||||
|
resolvedConstructeurIds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const refreshedMachine = await this.prisma.machine.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
data,
|
|
||||||
include: MACHINE_DEFAULT_INCLUDE,
|
include: MACHINE_DEFAULT_INCLUDE,
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.hydrateMachine(machine);
|
const hydrated = this.hydrateMachine(refreshedMachine);
|
||||||
|
if (hydrated && syncedConstructeurIds) {
|
||||||
|
hydrated.constructeurIds = [...syncedConstructeurIds];
|
||||||
|
}
|
||||||
|
return this.ensureMachineConstructeurs(hydrated);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handlePrismaError(error);
|
this.handlePrismaError(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ConflictException, Injectable } from '@nestjs/common';
|
import { ConflictException, Injectable } from '@nestjs/common';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
import { syncConstructeurLinks } from '../common/utils/constructeur-link.util';
|
||||||
import { CreatePieceDto, UpdatePieceDto } from '../shared/dto/piece.dto';
|
import { CreatePieceDto, UpdatePieceDto } from '../shared/dto/piece.dto';
|
||||||
import { PieceModelStructureSchema } from '../shared/schemas/inventory';
|
import { PieceModelStructureSchema } from '../shared/schemas/inventory';
|
||||||
import type { PieceModelStructure } from '../shared/types/inventory';
|
import type { PieceModelStructure } from '../shared/types/inventory';
|
||||||
@@ -35,7 +36,7 @@ export class PiecesService {
|
|||||||
|
|
||||||
private async buildCreateInput(
|
private async buildCreateInput(
|
||||||
createPieceDto: CreatePieceDto,
|
createPieceDto: CreatePieceDto,
|
||||||
): Promise<Prisma.PieceCreateInput> {
|
): Promise<{ data: Prisma.PieceCreateInput; constructeurIds: string[] }> {
|
||||||
const data: Prisma.PieceCreateInput = {
|
const data: Prisma.PieceCreateInput = {
|
||||||
name: createPieceDto.name,
|
name: createPieceDto.name,
|
||||||
reference: createPieceDto.reference ?? null,
|
reference: createPieceDto.reference ?? null,
|
||||||
@@ -47,11 +48,6 @@ export class PiecesService {
|
|||||||
);
|
);
|
||||||
const resolvedConstructeurIds =
|
const resolvedConstructeurIds =
|
||||||
await this.resolveExistingConstructeurIds(constructeurIds);
|
await this.resolveExistingConstructeurIds(constructeurIds);
|
||||||
if (resolvedConstructeurIds.length) {
|
|
||||||
data.constructeurs = {
|
|
||||||
connect: resolvedConstructeurIds.map((id) => ({ id })),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (createPieceDto.typePieceId) {
|
if (createPieceDto.typePieceId) {
|
||||||
data.typePiece = {
|
data.typePiece = {
|
||||||
@@ -59,25 +55,46 @@ export class PiecesService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return { data, constructeurIds: resolvedConstructeurIds };
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(createPieceDto: CreatePieceDto) {
|
async create(createPieceDto: CreatePieceDto) {
|
||||||
try {
|
try {
|
||||||
|
const { data, constructeurIds } = await this.buildCreateInput(
|
||||||
|
createPieceDto,
|
||||||
|
);
|
||||||
const created = await this.prisma.piece.create({
|
const created = await this.prisma.piece.create({
|
||||||
data: await this.buildCreateInput(createPieceDto),
|
data,
|
||||||
include: PIECE_WITH_RELATIONS_INCLUDE,
|
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let syncedConstructeurIds: string[] = [];
|
||||||
|
if (constructeurIds.length > 0) {
|
||||||
|
syncedConstructeurIds = await syncConstructeurLinks(
|
||||||
|
this.prisma,
|
||||||
|
'_PieceConstructeurs',
|
||||||
|
created.id,
|
||||||
|
constructeurIds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await this.applyPieceSkeleton({
|
await this.applyPieceSkeleton({
|
||||||
pieceId: created.id,
|
pieceId: created.id,
|
||||||
typePiece: created.typePiece as PieceTypeWithSkeleton | null,
|
typePiece: created.typePiece as PieceTypeWithSkeleton | null,
|
||||||
|
prisma: this.prisma,
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.prisma.piece.findUnique({
|
const refreshed = await this.prisma.piece.findUnique({
|
||||||
where: { id: created.id },
|
where: { id: created.id },
|
||||||
include: PIECE_WITH_RELATIONS_INCLUDE,
|
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (refreshed && syncedConstructeurIds.length > 0) {
|
||||||
|
(refreshed as typeof refreshed & { constructeurIds?: string[] }).constructeurIds =
|
||||||
|
[...syncedConstructeurIds];
|
||||||
|
}
|
||||||
|
|
||||||
|
return refreshed;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handlePrismaError(error);
|
this.handlePrismaError(error);
|
||||||
}
|
}
|
||||||
@@ -112,15 +129,14 @@ export class PiecesService {
|
|||||||
data.prix = updatePieceDto.prix;
|
data.prix = updatePieceDto.prix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let resolvedConstructeurIds: string[] | undefined;
|
||||||
if (updatePieceDto.constructeurIds !== undefined) {
|
if (updatePieceDto.constructeurIds !== undefined) {
|
||||||
const constructeurIds = this.normalizeConstructeurIds(
|
const constructeurIds = this.normalizeConstructeurIds(
|
||||||
updatePieceDto.constructeurIds,
|
updatePieceDto.constructeurIds,
|
||||||
);
|
);
|
||||||
const resolvedConstructeurIds =
|
resolvedConstructeurIds = await this.resolveExistingConstructeurIds(
|
||||||
await this.resolveExistingConstructeurIds(constructeurIds);
|
constructeurIds,
|
||||||
data.constructeurs = {
|
);
|
||||||
set: resolvedConstructeurIds.map((id) => ({ id })),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updatePieceDto.typePieceId !== undefined) {
|
if (updatePieceDto.typePieceId !== undefined) {
|
||||||
@@ -129,22 +145,42 @@ export class PiecesService {
|
|||||||
: { disconnect: true };
|
: { disconnect: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let syncedConstructeurIds: string[] | undefined;
|
||||||
try {
|
try {
|
||||||
const updated = await this.prisma.piece.update({
|
await this.prisma.$transaction(async (tx) => {
|
||||||
|
const updated = await tx.piece.update({
|
||||||
|
where: { id },
|
||||||
|
data,
|
||||||
|
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resolvedConstructeurIds !== undefined) {
|
||||||
|
syncedConstructeurIds = await syncConstructeurLinks(
|
||||||
|
tx,
|
||||||
|
'_PieceConstructeurs',
|
||||||
|
id,
|
||||||
|
resolvedConstructeurIds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.applyPieceSkeleton({
|
||||||
|
pieceId: updated.id,
|
||||||
|
typePiece: updated.typePiece as PieceTypeWithSkeleton | null,
|
||||||
|
prisma: tx,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const refreshed = await this.prisma.piece.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
data,
|
|
||||||
include: PIECE_WITH_RELATIONS_INCLUDE,
|
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.applyPieceSkeleton({
|
if (refreshed && syncedConstructeurIds) {
|
||||||
pieceId: updated.id,
|
(refreshed as typeof refreshed & { constructeurIds?: string[] }).constructeurIds =
|
||||||
typePiece: updated.typePiece as PieceTypeWithSkeleton | null,
|
[...syncedConstructeurIds];
|
||||||
});
|
}
|
||||||
|
|
||||||
return this.prisma.piece.findUnique({
|
return refreshed;
|
||||||
where: { id: updated.id },
|
|
||||||
include: PIECE_WITH_RELATIONS_INCLUDE,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handlePrismaError(error);
|
this.handlePrismaError(error);
|
||||||
}
|
}
|
||||||
@@ -211,9 +247,11 @@ export class PiecesService {
|
|||||||
private async applyPieceSkeleton({
|
private async applyPieceSkeleton({
|
||||||
pieceId,
|
pieceId,
|
||||||
typePiece,
|
typePiece,
|
||||||
|
prisma,
|
||||||
}: {
|
}: {
|
||||||
pieceId: string;
|
pieceId: string;
|
||||||
typePiece: PieceTypeWithSkeleton | null;
|
typePiece: PieceTypeWithSkeleton | null;
|
||||||
|
prisma: Prisma.TransactionClient | PrismaService;
|
||||||
}) {
|
}) {
|
||||||
if (!typePiece?.id) {
|
if (!typePiece?.id) {
|
||||||
return;
|
return;
|
||||||
@@ -230,8 +268,13 @@ export class PiecesService {
|
|||||||
|
|
||||||
const customFields = skeleton.customFields ?? [];
|
const customFields = skeleton.customFields ?? [];
|
||||||
|
|
||||||
await this.ensurePieceCustomFieldDefinitions(typePiece.id, customFields);
|
await this.ensurePieceCustomFieldDefinitions(
|
||||||
|
prisma,
|
||||||
|
typePiece.id,
|
||||||
|
customFields,
|
||||||
|
);
|
||||||
await this.createPieceCustomFieldValues(
|
await this.createPieceCustomFieldValues(
|
||||||
|
prisma,
|
||||||
pieceId,
|
pieceId,
|
||||||
typePiece.id,
|
typePiece.id,
|
||||||
customFields,
|
customFields,
|
||||||
@@ -275,6 +318,7 @@ export class PiecesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async ensurePieceCustomFieldDefinitions(
|
private async ensurePieceCustomFieldDefinitions(
|
||||||
|
prisma: Prisma.TransactionClient | PrismaService,
|
||||||
typePieceId: string,
|
typePieceId: string,
|
||||||
customFields: PieceModelStructure['customFields'],
|
customFields: PieceModelStructure['customFields'],
|
||||||
) {
|
) {
|
||||||
@@ -286,7 +330,7 @@ export class PiecesService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const existing = await this.prisma.customField.findMany({
|
const existing = await prisma.customField.findMany({
|
||||||
where: { typePieceId },
|
where: { typePieceId },
|
||||||
select: { id: true, name: true, orderIndex: true },
|
select: { id: true, name: true, orderIndex: true },
|
||||||
});
|
});
|
||||||
@@ -312,7 +356,7 @@ export class PiecesService {
|
|||||||
const existingField = existingByName.get(name);
|
const existingField = existingByName.get(name);
|
||||||
if (existingField) {
|
if (existingField) {
|
||||||
if (existingField.orderIndex !== index) {
|
if (existingField.orderIndex !== index) {
|
||||||
await this.prisma.customField.update({
|
await prisma.customField.update({
|
||||||
where: { id: existingField.id },
|
where: { id: existingField.id },
|
||||||
data: { orderIndex: index },
|
data: { orderIndex: index },
|
||||||
});
|
});
|
||||||
@@ -324,7 +368,7 @@ export class PiecesService {
|
|||||||
const required = Boolean(field.required);
|
const required = Boolean(field.required);
|
||||||
const options = this.normalizeOptions(field);
|
const options = this.normalizeOptions(field);
|
||||||
|
|
||||||
const created = await this.prisma.customField.create({
|
const created = await prisma.customField.create({
|
||||||
data: {
|
data: {
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
@@ -341,6 +385,7 @@ export class PiecesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async createPieceCustomFieldValues(
|
private async createPieceCustomFieldValues(
|
||||||
|
prisma: Prisma.TransactionClient | PrismaService,
|
||||||
pieceId: string,
|
pieceId: string,
|
||||||
typePieceId: string,
|
typePieceId: string,
|
||||||
customFields: PieceModelStructure['customFields'],
|
customFields: PieceModelStructure['customFields'],
|
||||||
@@ -353,7 +398,7 @@ export class PiecesService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const definitions = await this.prisma.customField.findMany({
|
const definitions = await prisma.customField.findMany({
|
||||||
where: { typePieceId },
|
where: { typePieceId },
|
||||||
select: { id: true, name: true },
|
select: { id: true, name: true },
|
||||||
});
|
});
|
||||||
@@ -369,7 +414,7 @@ export class PiecesService {
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
const existingValues = await this.prisma.customFieldValue.findMany({
|
const existingValues = await prisma.customFieldValue.findMany({
|
||||||
where: { pieceId },
|
where: { pieceId },
|
||||||
select: { customFieldId: true },
|
select: { customFieldId: true },
|
||||||
});
|
});
|
||||||
@@ -393,7 +438,7 @@ export class PiecesService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.prisma.customFieldValue.create({
|
await prisma.customFieldValue.create({
|
||||||
data: {
|
data: {
|
||||||
customFieldId: definitionId,
|
customFieldId: definitionId,
|
||||||
pieceId,
|
pieceId,
|
||||||
|
|||||||
6
src/shared/types/constructeur-summary.type.ts
Normal file
6
src/shared/types/constructeur-summary.type.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export type ConstructeurSummary = {
|
||||||
|
id: string;
|
||||||
|
name: string | null;
|
||||||
|
email: string | null;
|
||||||
|
phone: string | null;
|
||||||
|
};
|
||||||
@@ -109,6 +109,15 @@ type PieceRecord = {
|
|||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ConstructeurRecord = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
email: Nullable<string>;
|
||||||
|
phone: Nullable<string>;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
};
|
||||||
|
|
||||||
type MachineComponentLinkRecord = {
|
type MachineComponentLinkRecord = {
|
||||||
id: string;
|
id: string;
|
||||||
machineId: string;
|
machineId: string;
|
||||||
@@ -202,6 +211,15 @@ class InMemoryPrismaService {
|
|||||||
private customFields: CustomFieldRecord[] = [];
|
private customFields: CustomFieldRecord[] = [];
|
||||||
private customFieldValues: CustomFieldValueRecord[] = [];
|
private customFieldValues: CustomFieldValueRecord[] = [];
|
||||||
private profiles: ProfileRecord[] = [];
|
private profiles: ProfileRecord[] = [];
|
||||||
|
private constructeurs: ConstructeurRecord[] = [];
|
||||||
|
private readonly constructeurLinkOrientation: Record<
|
||||||
|
string,
|
||||||
|
{ parentColumn: 'A' | 'B'; constructeurColumn: 'A' | 'B' }
|
||||||
|
> = {
|
||||||
|
_MachineConstructeurs: { parentColumn: 'A', constructeurColumn: 'B' },
|
||||||
|
_ComposantConstructeurs: { parentColumn: 'A', constructeurColumn: 'B' },
|
||||||
|
_PieceConstructeurs: { parentColumn: 'A', constructeurColumn: 'B' },
|
||||||
|
};
|
||||||
|
|
||||||
async onModuleInit() {}
|
async onModuleInit() {}
|
||||||
async onModuleDestroy() {}
|
async onModuleDestroy() {}
|
||||||
@@ -229,6 +247,7 @@ class InMemoryPrismaService {
|
|||||||
this.customFields = [];
|
this.customFields = [];
|
||||||
this.customFieldValues = [];
|
this.customFieldValues = [];
|
||||||
this.profiles = [];
|
this.profiles = [];
|
||||||
|
this.constructeurs = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly modelTypeDelegate = {
|
private readonly modelTypeDelegate = {
|
||||||
@@ -984,6 +1003,120 @@ class InMemoryPrismaService {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constructeur = {
|
||||||
|
findMany: async ({ where, select, orderBy }: any = {}) => {
|
||||||
|
let records = [...this.constructeurs];
|
||||||
|
|
||||||
|
if (where?.id?.in) {
|
||||||
|
const ids = Array.isArray(where.id.in) ? where.id.in : [];
|
||||||
|
records = records.filter((item) => ids.includes(item.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (where?.OR && Array.isArray(where.OR)) {
|
||||||
|
const termCandidate = where.OR.find(
|
||||||
|
(clause: any) => clause?.name?.contains,
|
||||||
|
);
|
||||||
|
const term =
|
||||||
|
termCandidate?.name?.contains ??
|
||||||
|
termCandidate?.email?.contains ??
|
||||||
|
termCandidate?.phone?.contains ??
|
||||||
|
'';
|
||||||
|
const normalized = term.toLowerCase().trim();
|
||||||
|
if (normalized) {
|
||||||
|
records = records.filter((record) => {
|
||||||
|
const nameMatch = record.name.toLowerCase().includes(normalized);
|
||||||
|
const emailMatch =
|
||||||
|
record.email?.toLowerCase().includes(normalized) ?? false;
|
||||||
|
const phoneMatch =
|
||||||
|
record.phone?.toLowerCase().includes(normalized) ?? false;
|
||||||
|
return nameMatch || emailMatch || phoneMatch;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orderBy?.name) {
|
||||||
|
records = [...records].sort((a, b) =>
|
||||||
|
orderBy.name === 'desc'
|
||||||
|
? b.name.localeCompare(a.name)
|
||||||
|
: a.name.localeCompare(b.name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return records.map((record) => this.applySelect(record, select));
|
||||||
|
},
|
||||||
|
|
||||||
|
findUnique: async ({ where, select }: any) => {
|
||||||
|
const record = this.constructeurs.find((item) => item.id === where?.id);
|
||||||
|
return record ? this.applySelect(record, select) : null;
|
||||||
|
},
|
||||||
|
|
||||||
|
findFirst: async ({ where, select }: any = {}) => {
|
||||||
|
if (where?.name?.equals) {
|
||||||
|
const target = where.name.equals.toLowerCase();
|
||||||
|
const record = this.constructeurs.find(
|
||||||
|
(item) => item.name.toLowerCase() === target,
|
||||||
|
);
|
||||||
|
return record ? this.applySelect(record, select) : null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
create: async ({ data, select }: any) => {
|
||||||
|
const now = new Date();
|
||||||
|
const record: ConstructeurRecord = {
|
||||||
|
id: data.id ?? generateId('constructeur'),
|
||||||
|
name: data.name?.trim?.() ?? '',
|
||||||
|
email: data.email?.trim?.() ?? null,
|
||||||
|
phone: data.phone?.trim?.() ?? null,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
};
|
||||||
|
this.constructeurs.push(record);
|
||||||
|
return this.applySelect(record, select);
|
||||||
|
},
|
||||||
|
|
||||||
|
update: async ({ where, data, select }: any) => {
|
||||||
|
const record = this.constructeurs.find((item) => item.id === where?.id);
|
||||||
|
if (!record) {
|
||||||
|
throw new Error('Constructeur not found');
|
||||||
|
}
|
||||||
|
if (data.name !== undefined) {
|
||||||
|
record.name =
|
||||||
|
this.applyUpdateValue<string>(data.name)?.trim?.() ?? record.name;
|
||||||
|
}
|
||||||
|
if (data.email !== undefined) {
|
||||||
|
record.email =
|
||||||
|
this.applyUpdateValue<string | null>(data.email)?.trim?.() ?? null;
|
||||||
|
}
|
||||||
|
if (data.phone !== undefined) {
|
||||||
|
record.phone =
|
||||||
|
this.applyUpdateValue<string | null>(data.phone)?.trim?.() ?? null;
|
||||||
|
}
|
||||||
|
record.updatedAt = new Date();
|
||||||
|
return this.applySelect(record, select);
|
||||||
|
},
|
||||||
|
|
||||||
|
delete: async ({ where, select }: any) => {
|
||||||
|
const index = this.constructeurs.findIndex(
|
||||||
|
(item) => item.id === where?.id,
|
||||||
|
);
|
||||||
|
if (index === -1) {
|
||||||
|
throw new Error('Constructeur not found');
|
||||||
|
}
|
||||||
|
const [deleted] = this.constructeurs.splice(index, 1);
|
||||||
|
return this.applySelect(deleted, select);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async __getConstructeurLinkOrientation(table: string) {
|
||||||
|
return (
|
||||||
|
this.constructeurLinkOrientation[table] ?? {
|
||||||
|
parentColumn: 'A',
|
||||||
|
constructeurColumn: 'B',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
profile = {
|
profile = {
|
||||||
count: async ({ where }: any) => {
|
count: async ({ where }: any) => {
|
||||||
return this.profiles.filter((profile) => {
|
return this.profiles.filter((profile) => {
|
||||||
@@ -1315,6 +1448,40 @@ class InMemoryPrismaService {
|
|||||||
.map((item) => ({ ...item }));
|
.map((item) => ({ ...item }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async __syncConstructeurLinks(
|
||||||
|
table: string,
|
||||||
|
parentId: string,
|
||||||
|
ids: string[],
|
||||||
|
): Promise<void> {
|
||||||
|
const filtered = ids.filter((id) =>
|
||||||
|
this.constructeurs.some((item) => item.id === id),
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (table) {
|
||||||
|
case '_MachineConstructeurs':
|
||||||
|
this.assignConstructeurs(this.machines, parentId, filtered);
|
||||||
|
break;
|
||||||
|
case '_ComposantConstructeurs':
|
||||||
|
this.assignConstructeurs(this.composants, parentId, filtered);
|
||||||
|
break;
|
||||||
|
case '_PieceConstructeurs':
|
||||||
|
this.assignConstructeurs(this.pieces, parentId, filtered);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported constructeur link table: ${table}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private assignConstructeurs<
|
||||||
|
T extends { id: string; constructeurIds: string[] },
|
||||||
|
>(collection: T[], parentId: string, ids: string[]) {
|
||||||
|
const record = collection.find((item) => item.id === parentId);
|
||||||
|
if (!record) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
record.constructeurIds = [...ids];
|
||||||
|
}
|
||||||
|
|
||||||
private buildMachine(machine: MachineRecord, include: any) {
|
private buildMachine(machine: MachineRecord, include: any) {
|
||||||
const base: any = { ...machine };
|
const base: any = { ...machine };
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user