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:
261
frontend/app/services/modelTypes.ts
Normal file
261
frontend/app/services/modelTypes.ts
Normal 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,
|
||||
}));
|
||||
}
|
||||
Reference in New Issue
Block a user