refactor : merge Inventory_frontend submodule into frontend/ directory

Merges the full git history of Inventory_frontend into the monorepo
under frontend/. Removes the submodule in favor of a unified repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-04-01 14:17:57 +02:00
226 changed files with 56920 additions and 4 deletions

View File

@@ -0,0 +1,261 @@
import { useRequestFetch } from '#imports';
import type {
ComponentModelStructure,
PieceModelStructure,
ProductModelStructure,
} from '~/shared/types/inventory';
export type ModelCategory = 'COMPONENT' | 'PIECE' | 'PRODUCT';
export type ModelTypeStructure =
| ComponentModelStructure
| PieceModelStructure
| ProductModelStructure
| null;
export interface BaseModelTypePayload {
name: string;
code: string;
notes?: string | null;
description?: string | null;
}
export interface ComponentModelTypePayload extends BaseModelTypePayload {
category: 'COMPONENT';
structure?: ComponentModelStructure | null;
referenceFormula?: string | null;
requiredFieldsForReference?: string[] | null;
}
export interface PieceModelTypePayload extends BaseModelTypePayload {
category: 'PIECE';
structure?: PieceModelStructure | null;
referenceFormula?: string | null;
requiredFieldsForReference?: string[] | null;
}
export interface ProductModelTypePayload extends BaseModelTypePayload {
category: 'PRODUCT';
structure?: ProductModelStructure | null;
}
export type ModelTypePayload =
| ComponentModelTypePayload
| PieceModelTypePayload
| ProductModelTypePayload;
export interface ModelType extends BaseModelTypePayload {
id: string;
createdAt: string;
updatedAt: string;
category: ModelCategory;
structure: ModelTypeStructure;
referenceFormula?: string | null;
requiredFieldsForReference?: string[] | null;
}
export interface ModelTypeListParams {
q?: string;
category?: ModelCategory;
sort?: 'name' | 'createdAt';
dir?: 'asc' | 'desc';
limit?: number;
offset?: number;
}
export interface ModelTypeListResponse {
items: ModelType[];
total: number;
offset: number;
limit: number;
}
const ENDPOINT = '/model_types';
function resolveBaseUrl() {
const runtimeConfig = useRuntimeConfig();
return runtimeConfig.public.apiBaseUrl || '';
}
function createOptions(options: Record<string, unknown> = {}): Record<string, unknown> {
return {
baseURL: resolveBaseUrl(),
credentials: 'include',
...options,
};
}
const normalizeModelType = (item: any): ModelType => {
if (!item || typeof item !== 'object') {
return item as ModelType;
}
return item as ModelType;
};
export async function listModelTypes(params: ModelTypeListParams = {}, opts: { signal?: AbortSignal } = {}) {
const requestFetch = useRequestFetch();
const query: Record<string, string | number> = {};
if (params.q) {
query.name = params.q;
}
if (params.category) {
query.category = params.category;
}
// Sort: API Platform OrderFilter uses order[field]=direction
const sortField = params.sort || 'name';
const sortDir = params.dir || 'asc';
query[`order[${sortField}]`] = sortDir;
// Pagination: API Platform uses page + itemsPerPage
const effectiveLimit = typeof params.limit === 'number' ? params.limit : 20;
const effectiveOffset = typeof params.offset === 'number' ? params.offset : 0;
const page = Math.floor(effectiveOffset / effectiveLimit) + 1;
query.itemsPerPage = effectiveLimit;
query.page = page;
const payload = await requestFetch<Record<string, any>>(ENDPOINT, createOptions({
method: 'GET',
query,
signal: opts.signal,
}));
const rawItems = Array.isArray(payload?.member)
? payload.member
: Array.isArray(payload?.['hydra:member'])
? payload['hydra:member']
: Array.isArray(payload?.items)
? payload.items
: [];
const total = typeof payload?.totalItems === 'number'
? payload.totalItems
: typeof payload?.['hydra:totalItems'] === 'number'
? payload['hydra:totalItems']
: rawItems.length;
const items = rawItems.map(normalizeModelType);
return {
items,
total,
offset: effectiveOffset,
limit: effectiveLimit,
} satisfies ModelTypeListResponse;
}
export function createModelType(payload: ModelTypePayload, opts: { signal?: AbortSignal } = {}) {
const requestFetch = useRequestFetch();
return requestFetch<ModelType>(ENDPOINT, createOptions({
method: 'POST',
headers: {
'Content-Type': 'application/ld+json',
Accept: 'application/ld+json',
},
body: payload,
signal: opts.signal,
})).then(normalizeModelType);
}
export function updateModelType(id: string, payload: Partial<ModelTypePayload>, opts: { signal?: AbortSignal } = {}) {
const requestFetch = useRequestFetch();
return requestFetch<ModelType>(`${ENDPOINT}/${id}`, createOptions({
method: 'PATCH',
headers: {
'Content-Type': 'application/merge-patch+json',
Accept: 'application/ld+json',
},
body: payload,
signal: opts.signal,
})).then(normalizeModelType);
}
export function deleteModelType(id: string, opts: { signal?: AbortSignal } = {}) {
const requestFetch = useRequestFetch();
return requestFetch<void>(`${ENDPOINT}/${id}`, createOptions({
method: 'DELETE',
signal: opts.signal,
}));
}
export function getModelType(id: string, opts: { signal?: AbortSignal } = {}) {
const requestFetch = useRequestFetch();
return requestFetch<ModelType>(`${ENDPOINT}/${id}`, createOptions({
method: 'GET',
signal: opts.signal,
})).then(normalizeModelType);
}
export interface ConversionCheck {
canConvert: boolean;
direction: 'piece_to_component' | 'component_to_piece' | null;
itemCount: number;
names: string[];
blockers: string[];
}
export interface ConversionResult {
success: boolean;
convertedCount: number;
error?: string | null;
}
export function checkConversion(id: string, opts: { signal?: AbortSignal } = {}) {
const requestFetch = useRequestFetch();
return requestFetch<ConversionCheck>(`${ENDPOINT}/${id}/conversion-check`, createOptions({
method: 'GET',
signal: opts.signal,
}));
}
export function convertCategory(id: string, opts: { signal?: AbortSignal } = {}) {
const requestFetch = useRequestFetch();
return requestFetch<ConversionResult>(`${ENDPOINT}/${id}/convert`, createOptions({
method: 'POST',
signal: opts.signal,
}));
}
export interface SyncPreviewResult {
modelTypeId: string;
category: string;
itemCount: number;
additions: Record<string, number>;
deletions: Record<string, number>;
modifications: Record<string, number>;
}
export interface SyncExecuteResult {
itemsUpdated: number;
additions: Record<string, number>;
deletions: Record<string, number>;
modifications: Record<string, number>;
}
export function syncPreview(id: string, structure: unknown, opts: { signal?: AbortSignal } = {}) {
const requestFetch = useRequestFetch();
return requestFetch<SyncPreviewResult>(`${ENDPOINT}/${id}/sync-preview`, createOptions({
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: { structure },
signal: opts.signal,
}));
}
export function syncExecute(id: string, confirmation: { confirmDeletions: boolean; confirmTypeChanges: boolean }, opts: { signal?: AbortSignal } = {}) {
const requestFetch = useRequestFetch();
return requestFetch<SyncExecuteResult>(`${ENDPOINT}/${id}/sync`, createOptions({
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: confirmation,
signal: opts.signal,
}));
}