mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 23:13:42 +01:00
feat: TUI-Screens für Inventar und Produktion + API-Client Typ-Migration
Neue TUI-Features:
- Inventar: Lageorte auflisten, anlegen, bearbeiten, (de-)aktivieren
- Produktion: Rezepte auflisten, anlegen, Detail-Ansicht
- Navigation erweitert (Hauptmenü, Routing)
API-Client auf generierte OpenAPI-Typen umgestellt:
- 6 neue Alias-Dateien in @effigenix/types (supplier, category, article,
customer, inventory, production)
- api-client Re-Exports direkt von @effigenix/types statt via Resources
- Backend: @Schema(requiredProperties) auf 16 Response-Records
- Backend: OpenApiCustomizer für application-layer DTOs (UserDTO, RoleDTO)
Hinweis: Backend-Endpoints für GET /api/recipes und
GET /api/inventory/storage-locations/{id} fehlen noch (separate Issues).
This commit is contained in:
parent
bee3f28b5f
commit
c26d72fbe7
48 changed files with 2090 additions and 474 deletions
|
|
@ -10,6 +10,14 @@
|
|||
*/
|
||||
|
||||
import type { AxiosInstance } from 'axios';
|
||||
import type {
|
||||
ArticleDTO,
|
||||
SalesUnitDTO,
|
||||
CreateArticleRequest,
|
||||
UpdateArticleRequest,
|
||||
AddSalesUnitRequest,
|
||||
UpdateSalesUnitPriceRequest,
|
||||
} from '@effigenix/types';
|
||||
|
||||
export type Unit = 'PIECE_FIXED' | 'KG' | 'HUNDRED_GRAM' | 'PIECE_VARIABLE';
|
||||
export type PriceModel = 'FIXED' | 'WEIGHT_BASED';
|
||||
|
|
@ -27,48 +35,14 @@ export const PRICE_MODEL_LABELS: Record<PriceModel, string> = {
|
|||
WEIGHT_BASED: 'Gewichtsbasiert',
|
||||
};
|
||||
|
||||
export interface SalesUnitDTO {
|
||||
id: string;
|
||||
unit: Unit;
|
||||
priceModel: PriceModel;
|
||||
price: number;
|
||||
}
|
||||
|
||||
export interface ArticleDTO {
|
||||
id: string;
|
||||
name: string;
|
||||
articleNumber: string;
|
||||
categoryId: string;
|
||||
salesUnits: SalesUnitDTO[];
|
||||
status: ArticleStatus;
|
||||
supplierIds: string[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface CreateArticleRequest {
|
||||
name: string;
|
||||
articleNumber: string;
|
||||
categoryId: string;
|
||||
unit: Unit;
|
||||
priceModel: PriceModel;
|
||||
price: number;
|
||||
}
|
||||
|
||||
export interface UpdateArticleRequest {
|
||||
name?: string;
|
||||
categoryId?: string;
|
||||
}
|
||||
|
||||
export interface AddSalesUnitRequest {
|
||||
unit: Unit;
|
||||
priceModel: PriceModel;
|
||||
price: number;
|
||||
}
|
||||
|
||||
export interface UpdateSalesUnitPriceRequest {
|
||||
price: number;
|
||||
}
|
||||
export type {
|
||||
ArticleDTO,
|
||||
SalesUnitDTO,
|
||||
CreateArticleRequest,
|
||||
UpdateArticleRequest,
|
||||
AddSalesUnitRequest,
|
||||
UpdateSalesUnitPriceRequest,
|
||||
};
|
||||
|
||||
// ── Resource factory ─────────────────────────────────────────────────────────
|
||||
|
||||
|
|
|
|||
|
|
@ -7,22 +7,17 @@
|
|||
*/
|
||||
|
||||
import type { AxiosInstance } from 'axios';
|
||||
import type {
|
||||
ProductCategoryDTO,
|
||||
CreateCategoryRequest,
|
||||
UpdateCategoryRequest,
|
||||
} from '@effigenix/types';
|
||||
|
||||
export interface ProductCategoryDTO {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
export interface CreateCategoryRequest {
|
||||
name: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface UpdateCategoryRequest {
|
||||
name?: string;
|
||||
description?: string | null;
|
||||
}
|
||||
export type {
|
||||
ProductCategoryDTO,
|
||||
CreateCategoryRequest,
|
||||
UpdateCategoryRequest,
|
||||
};
|
||||
|
||||
// ── Resource factory ─────────────────────────────────────────────────────────
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,16 @@
|
|||
*/
|
||||
|
||||
import type { AxiosInstance } from 'axios';
|
||||
import type { AddressDTO, ContactInfoDTO, PaymentTermsDTO } from './suppliers.js';
|
||||
import type {
|
||||
CustomerDTO,
|
||||
DeliveryAddressDTO,
|
||||
FrameContractDTO,
|
||||
ContractLineItemDTO,
|
||||
CreateCustomerRequest,
|
||||
UpdateCustomerRequest,
|
||||
AddDeliveryAddressRequest,
|
||||
SetFrameContractRequest,
|
||||
} from '@effigenix/types';
|
||||
|
||||
export type CustomerType = 'B2B' | 'B2C';
|
||||
export type CustomerStatus = 'ACTIVE' | 'INACTIVE';
|
||||
|
|
@ -44,96 +53,16 @@ export const DELIVERY_RHYTHM_LABELS: Record<DeliveryRhythm, string> = {
|
|||
ON_DEMAND: 'Nach Bedarf',
|
||||
};
|
||||
|
||||
export interface DeliveryAddressDTO {
|
||||
label: string;
|
||||
address: AddressDTO;
|
||||
contactPerson: string | null;
|
||||
deliveryNotes: string | null;
|
||||
}
|
||||
|
||||
export interface ContractLineItemDTO {
|
||||
articleId: string;
|
||||
agreedPrice: number;
|
||||
agreedQuantity: number | null;
|
||||
unit: string | null;
|
||||
}
|
||||
|
||||
export interface FrameContractDTO {
|
||||
id: string;
|
||||
validFrom: string | null;
|
||||
validUntil: string | null;
|
||||
deliveryRhythm: DeliveryRhythm;
|
||||
lineItems: ContractLineItemDTO[];
|
||||
}
|
||||
|
||||
export interface CustomerDTO {
|
||||
id: string;
|
||||
name: string;
|
||||
type: CustomerType;
|
||||
status: CustomerStatus;
|
||||
billingAddress: AddressDTO;
|
||||
contactInfo: ContactInfoDTO;
|
||||
paymentTerms: PaymentTermsDTO | null;
|
||||
deliveryAddresses: DeliveryAddressDTO[];
|
||||
frameContract: FrameContractDTO | null;
|
||||
preferences: CustomerPreference[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface CreateCustomerRequest {
|
||||
name: string;
|
||||
type: CustomerType;
|
||||
phone: string;
|
||||
street: string;
|
||||
houseNumber: string;
|
||||
postalCode: string;
|
||||
city: string;
|
||||
country: string;
|
||||
email?: string;
|
||||
contactPerson?: string;
|
||||
paymentDueDays?: number;
|
||||
paymentDescription?: string;
|
||||
}
|
||||
|
||||
export interface UpdateCustomerRequest {
|
||||
name?: string;
|
||||
phone?: string;
|
||||
email?: string | null;
|
||||
contactPerson?: string | null;
|
||||
street?: string;
|
||||
houseNumber?: string;
|
||||
postalCode?: string;
|
||||
city?: string;
|
||||
country?: string;
|
||||
paymentDueDays?: number | null;
|
||||
paymentDescription?: string | null;
|
||||
}
|
||||
|
||||
export interface AddDeliveryAddressRequest {
|
||||
label: string;
|
||||
street: string;
|
||||
houseNumber: string;
|
||||
postalCode: string;
|
||||
city: string;
|
||||
country: string;
|
||||
contactPerson?: string;
|
||||
deliveryNotes?: string;
|
||||
}
|
||||
|
||||
export interface SetFrameContractLineItem {
|
||||
articleId: string;
|
||||
agreedPrice: number;
|
||||
agreedQuantity?: number;
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
export interface SetFrameContractRequest {
|
||||
validFrom?: string;
|
||||
validUntil?: string;
|
||||
rhythm: DeliveryRhythm;
|
||||
lineItems: SetFrameContractLineItem[];
|
||||
}
|
||||
export type {
|
||||
CustomerDTO,
|
||||
DeliveryAddressDTO,
|
||||
FrameContractDTO,
|
||||
ContractLineItemDTO,
|
||||
CreateCustomerRequest,
|
||||
UpdateCustomerRequest,
|
||||
AddDeliveryAddressRequest,
|
||||
SetFrameContractRequest,
|
||||
};
|
||||
|
||||
// ── Resource factory ─────────────────────────────────────────────────────────
|
||||
|
||||
|
|
|
|||
66
frontend/packages/api-client/src/resources/recipes.ts
Normal file
66
frontend/packages/api-client/src/resources/recipes.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* Recipes resource – Production BC.
|
||||
* Endpoints: POST /api/recipes,
|
||||
* POST /api/recipes/{id}/ingredients,
|
||||
* DELETE /api/recipes/{id}/ingredients/{ingredientId}
|
||||
*/
|
||||
|
||||
import type { AxiosInstance } from 'axios';
|
||||
import type {
|
||||
RecipeDTO,
|
||||
IngredientDTO,
|
||||
CreateRecipeRequest,
|
||||
AddRecipeIngredientRequest,
|
||||
} from '@effigenix/types';
|
||||
|
||||
export type RecipeType = 'RAW_MATERIAL' | 'INTERMEDIATE' | 'FINISHED_PRODUCT';
|
||||
export type RecipeStatus = 'DRAFT' | 'ACTIVE' | 'ARCHIVED';
|
||||
|
||||
export const RECIPE_TYPE_LABELS: Record<RecipeType, string> = {
|
||||
RAW_MATERIAL: 'Rohstoff',
|
||||
INTERMEDIATE: 'Halbfabrikat',
|
||||
FINISHED_PRODUCT: 'Fertigprodukt',
|
||||
};
|
||||
|
||||
export type {
|
||||
RecipeDTO,
|
||||
IngredientDTO,
|
||||
CreateRecipeRequest,
|
||||
AddRecipeIngredientRequest,
|
||||
};
|
||||
|
||||
// ── Resource factory ─────────────────────────────────────────────────────────
|
||||
|
||||
const BASE = '/api/recipes';
|
||||
|
||||
export function createRecipesResource(client: AxiosInstance) {
|
||||
return {
|
||||
async list(): Promise<RecipeDTO[]> {
|
||||
const res = await client.get<RecipeDTO[]>(BASE);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
async getById(id: string): Promise<RecipeDTO> {
|
||||
const res = await client.get<RecipeDTO>(`${BASE}/${id}`);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
async create(request: CreateRecipeRequest): Promise<RecipeDTO> {
|
||||
const res = await client.post<RecipeDTO>(BASE, request);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
async addIngredient(id: string, request: AddRecipeIngredientRequest): Promise<RecipeDTO> {
|
||||
const res = await client.post<RecipeDTO>(`${BASE}/${id}/ingredients`, request);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
async removeIngredient(recipeId: string, ingredientId: string): Promise<RecipeDTO> {
|
||||
await client.delete(`${BASE}/${recipeId}/ingredients/${ingredientId}`);
|
||||
const res = await client.get<RecipeDTO>(`${BASE}/${recipeId}`);
|
||||
return res.data;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export type RecipesResource = ReturnType<typeof createRecipesResource>;
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* StorageLocations resource – Inventory BC.
|
||||
* Endpoints: GET/POST /api/inventory/storage-locations,
|
||||
* PUT /api/inventory/storage-locations/{id},
|
||||
* PATCH /api/inventory/storage-locations/{id}/activate|deactivate
|
||||
*/
|
||||
|
||||
import type { AxiosInstance } from 'axios';
|
||||
import type {
|
||||
StorageLocationDTO,
|
||||
TemperatureRangeDTO,
|
||||
CreateStorageLocationRequest,
|
||||
UpdateStorageLocationRequest,
|
||||
} from '@effigenix/types';
|
||||
|
||||
export type StorageType = 'COLD_ROOM' | 'FREEZER' | 'DRY_STORAGE' | 'DISPLAY_COUNTER' | 'PRODUCTION_AREA';
|
||||
|
||||
export const STORAGE_TYPE_LABELS: Record<StorageType, string> = {
|
||||
COLD_ROOM: 'Kühlraum',
|
||||
FREEZER: 'Tiefkühler',
|
||||
DRY_STORAGE: 'Trockenlager',
|
||||
DISPLAY_COUNTER: 'Vitrine',
|
||||
PRODUCTION_AREA: 'Produktionsbereich',
|
||||
};
|
||||
|
||||
export type {
|
||||
StorageLocationDTO,
|
||||
TemperatureRangeDTO,
|
||||
CreateStorageLocationRequest,
|
||||
UpdateStorageLocationRequest,
|
||||
};
|
||||
|
||||
export interface StorageLocationFilter {
|
||||
storageType?: string;
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
// ── Resource factory ─────────────────────────────────────────────────────────
|
||||
|
||||
const BASE = '/api/inventory/storage-locations';
|
||||
|
||||
export function createStorageLocationsResource(client: AxiosInstance) {
|
||||
return {
|
||||
async list(filter?: StorageLocationFilter): Promise<StorageLocationDTO[]> {
|
||||
const params: Record<string, string> = {};
|
||||
if (filter?.storageType) params['storageType'] = filter.storageType;
|
||||
if (filter?.active !== undefined) params['active'] = String(filter.active);
|
||||
const res = await client.get<StorageLocationDTO[]>(BASE, { params });
|
||||
return res.data;
|
||||
},
|
||||
|
||||
async getById(id: string): Promise<StorageLocationDTO> {
|
||||
const res = await client.get<StorageLocationDTO>(`${BASE}/${id}`);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
async create(request: CreateStorageLocationRequest): Promise<StorageLocationDTO> {
|
||||
const res = await client.post<StorageLocationDTO>(BASE, request);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
async update(id: string, request: UpdateStorageLocationRequest): Promise<StorageLocationDTO> {
|
||||
const res = await client.put<StorageLocationDTO>(`${BASE}/${id}`, request);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
async activate(id: string): Promise<StorageLocationDTO> {
|
||||
const res = await client.patch<StorageLocationDTO>(`${BASE}/${id}/activate`);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
async deactivate(id: string): Promise<StorageLocationDTO> {
|
||||
const res = await client.patch<StorageLocationDTO>(`${BASE}/${id}/deactivate`);
|
||||
return res.data;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export type StorageLocationsResource = ReturnType<typeof createStorageLocationsResource>;
|
||||
|
|
@ -10,100 +10,35 @@
|
|||
*/
|
||||
|
||||
import type { AxiosInstance } from 'axios';
|
||||
|
||||
export interface AddressDTO {
|
||||
street: string;
|
||||
houseNumber: string | null;
|
||||
postalCode: string;
|
||||
city: string;
|
||||
country: string;
|
||||
}
|
||||
|
||||
export interface ContactInfoDTO {
|
||||
phone: string;
|
||||
email: string | null;
|
||||
contactPerson: string | null;
|
||||
}
|
||||
|
||||
export interface PaymentTermsDTO {
|
||||
paymentDueDays: number;
|
||||
paymentDescription: string | null;
|
||||
}
|
||||
|
||||
export interface QualityCertificateDTO {
|
||||
certificateType: string;
|
||||
issuer: string;
|
||||
validFrom: string;
|
||||
validUntil: string;
|
||||
}
|
||||
|
||||
export interface SupplierRatingDTO {
|
||||
qualityScore: number;
|
||||
deliveryScore: number;
|
||||
priceScore: number;
|
||||
}
|
||||
import type {
|
||||
SupplierDTO,
|
||||
AddressDTO,
|
||||
ContactInfoDTO,
|
||||
PaymentTermsDTO,
|
||||
QualityCertificateDTO,
|
||||
SupplierRatingDTO,
|
||||
CreateSupplierRequest,
|
||||
UpdateSupplierRequest,
|
||||
RateSupplierRequest,
|
||||
AddCertificateRequest,
|
||||
RemoveCertificateRequest,
|
||||
} from '@effigenix/types';
|
||||
|
||||
export type SupplierStatus = 'ACTIVE' | 'INACTIVE';
|
||||
|
||||
export interface SupplierDTO {
|
||||
id: string;
|
||||
name: string;
|
||||
status: SupplierStatus;
|
||||
address: AddressDTO | null;
|
||||
contactInfo: ContactInfoDTO;
|
||||
paymentTerms: PaymentTermsDTO | null;
|
||||
certificates: QualityCertificateDTO[];
|
||||
rating: SupplierRatingDTO | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface CreateSupplierRequest {
|
||||
name: string;
|
||||
phone: string;
|
||||
email?: string;
|
||||
contactPerson?: string;
|
||||
street?: string;
|
||||
houseNumber?: string;
|
||||
postalCode?: string;
|
||||
city?: string;
|
||||
country?: string;
|
||||
paymentDueDays?: number;
|
||||
paymentDescription?: string;
|
||||
}
|
||||
|
||||
export interface UpdateSupplierRequest {
|
||||
name?: string;
|
||||
phone?: string;
|
||||
email?: string | null;
|
||||
contactPerson?: string | null;
|
||||
street?: string | null;
|
||||
houseNumber?: string | null;
|
||||
postalCode?: string | null;
|
||||
city?: string | null;
|
||||
country?: string | null;
|
||||
paymentDueDays?: number | null;
|
||||
paymentDescription?: string | null;
|
||||
}
|
||||
|
||||
export interface RateSupplierRequest {
|
||||
qualityScore: number;
|
||||
deliveryScore: number;
|
||||
priceScore: number;
|
||||
}
|
||||
|
||||
export interface AddCertificateRequest {
|
||||
certificateType: string;
|
||||
issuer: string;
|
||||
validFrom: string;
|
||||
validUntil: string;
|
||||
}
|
||||
|
||||
export interface RemoveCertificateRequest {
|
||||
certificateType: string;
|
||||
issuer: string;
|
||||
validFrom: string;
|
||||
}
|
||||
export type {
|
||||
SupplierDTO,
|
||||
AddressDTO,
|
||||
ContactInfoDTO,
|
||||
PaymentTermsDTO,
|
||||
QualityCertificateDTO,
|
||||
SupplierRatingDTO,
|
||||
CreateSupplierRequest,
|
||||
UpdateSupplierRequest,
|
||||
RateSupplierRequest,
|
||||
AddCertificateRequest,
|
||||
RemoveCertificateRequest,
|
||||
};
|
||||
|
||||
// ── Resource factory ─────────────────────────────────────────────────────────
|
||||
|
||||
|
|
|
|||
|
|
@ -4,45 +4,23 @@
|
|||
|
||||
import type { AxiosInstance } from 'axios';
|
||||
import { API_PATHS } from '@effigenix/config';
|
||||
import type {
|
||||
UserDTO,
|
||||
RoleDTO,
|
||||
CreateUserRequest,
|
||||
UpdateUserRequest,
|
||||
ChangePasswordRequest,
|
||||
AssignRoleRequest,
|
||||
} from '@effigenix/types';
|
||||
|
||||
export interface UserDTO {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
roles: RoleDTO[];
|
||||
branchId?: string;
|
||||
status: 'ACTIVE' | 'LOCKED';
|
||||
createdAt: string;
|
||||
lastLogin?: string;
|
||||
}
|
||||
|
||||
export interface RoleDTO {
|
||||
id: string;
|
||||
name: string;
|
||||
permissions: string[];
|
||||
}
|
||||
|
||||
export interface CreateUserRequest {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
roleNames: string[];
|
||||
branchId?: string;
|
||||
}
|
||||
|
||||
export interface UpdateUserRequest {
|
||||
email?: string;
|
||||
branchId?: string;
|
||||
}
|
||||
|
||||
export interface ChangePasswordRequest {
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
}
|
||||
|
||||
export interface AssignRoleRequest {
|
||||
roleName: string;
|
||||
}
|
||||
export type {
|
||||
UserDTO,
|
||||
RoleDTO,
|
||||
CreateUserRequest,
|
||||
UpdateUserRequest,
|
||||
ChangePasswordRequest,
|
||||
AssignRoleRequest,
|
||||
};
|
||||
|
||||
export function createUsersResource(client: AxiosInstance) {
|
||||
return {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue