1
0
Fork 0
mirror of https://github.com/s-frick/effigenix.git synced 2026-03-28 17:19:56 +01:00

feat(cli): Stammdaten-TUI mit Master Data API-Anbindung

- Neue Screens: Kategorien, Lieferanten, Artikel, Kunden (jeweils
  Liste, Detail, Anlegen + Detailaktionen wie Bewertung, Zertifikate,
  Verkaufseinheiten, Lieferadressen, Präferenzen)
- API-Client: Resources für alle 4 Stammdaten-Aggregate implementiert
  (categories, suppliers, articles, customers) mit Mapping von
  verschachtelten Domain-VOs auf flache DTOs
- Lieferant, Artikel, Kategorie: echte HTTP-Calls gegen Backend
  (/api/suppliers, /api/articles, /api/categories, /api/customers)
- 204-No-Content-Endpoints (removeSalesUnit, removeSupplier,
  removeCertificate, removeDeliveryAddress, removeFrameContract)
  lösen Re-Fetch des Aggregats aus
- MasterdataMenu, Navigation-Erweiterung, App.tsx-Routing
This commit is contained in:
Sebastian Frick 2026-02-18 13:35:20 +01:00
parent 797f435a49
commit d27dbaa843
30 changed files with 3882 additions and 1 deletions

View file

@ -0,0 +1,236 @@
/**
* Suppliers resource Real HTTP implementation.
* Endpoints: GET/POST /api/suppliers, GET/PUT /api/suppliers/{id},
* POST /api/suppliers/{id}/activate|deactivate,
* POST /api/suppliers/{id}/rating,
* POST /api/suppliers/{id}/certificates,
* DELETE /api/suppliers/{id}/certificates (with body)
*
* NOTE: Backend returns domain objects with nested VOs:
* { "id": {"value":"uuid"}, "name": {"value":"string"},
* "address": {"street":"...","houseNumber":"...","postalCode":"...","city":"...","country":"DE"},
* "contactInfo": {"phone":"...","email":"...","contactPerson":"..."},
* "paymentTerms": {"paymentDueDays":30,"description":"..."},
* "certificates": [{"certificateType":"...","issuer":"...","validFrom":"2024-01-01","validUntil":"2026-12-31"}],
* "rating": {"qualityScore":4,"deliveryScore":4,"priceScore":5},
* "status": "ACTIVE", "createdAt":"...", "updatedAt":"..." }
* DELETE /api/suppliers/{id}/certificates returns 204 No Content re-fetch.
*/
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;
}
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;
}
// ── Backend response shapes (domain objects with nested VOs) ─────────────────
interface BackendAddress {
street: string;
houseNumber: string | null;
postalCode: string;
city: string;
country: string;
}
interface BackendContactInfo {
phone: string;
email: string | null;
contactPerson: string | null;
}
interface BackendPaymentTerms {
paymentDueDays: number;
description: string | null; // Note: backend field is "description", not "paymentDescription"
}
interface BackendQualityCertificate {
certificateType: string;
issuer: string;
validFrom: string; // LocalDate → "2024-01-01"
validUntil: string;
}
interface BackendSupplierRating {
qualityScore: number;
deliveryScore: number;
priceScore: number;
}
interface BackendSupplier {
id: { value: string };
name: { value: string };
address: BackendAddress | null;
contactInfo: BackendContactInfo;
paymentTerms: BackendPaymentTerms | null;
certificates: BackendQualityCertificate[];
rating: BackendSupplierRating | null;
status: SupplierStatus;
createdAt: string;
updatedAt: string;
}
function mapSupplier(bs: BackendSupplier): SupplierDTO {
return {
id: bs.id.value,
name: bs.name.value,
status: bs.status,
address: bs.address,
contactInfo: bs.contactInfo,
paymentTerms: bs.paymentTerms
? {
paymentDueDays: bs.paymentTerms.paymentDueDays,
paymentDescription: bs.paymentTerms.description,
}
: null,
certificates: bs.certificates,
rating: bs.rating,
createdAt: bs.createdAt,
updatedAt: bs.updatedAt,
};
}
// ── Resource factory ─────────────────────────────────────────────────────────
export function createSuppliersResource(client: AxiosInstance) {
return {
async list(): Promise<SupplierDTO[]> {
const res = await client.get<BackendSupplier[]>('/api/suppliers');
return res.data.map(mapSupplier);
},
async getById(id: string): Promise<SupplierDTO> {
const res = await client.get<BackendSupplier>(`/api/suppliers/${id}`);
return mapSupplier(res.data);
},
async create(request: CreateSupplierRequest): Promise<SupplierDTO> {
const res = await client.post<BackendSupplier>('/api/suppliers', request);
return mapSupplier(res.data);
},
async update(id: string, request: UpdateSupplierRequest): Promise<SupplierDTO> {
const res = await client.put<BackendSupplier>(`/api/suppliers/${id}`, request);
return mapSupplier(res.data);
},
async activate(id: string): Promise<SupplierDTO> {
const res = await client.post<BackendSupplier>(`/api/suppliers/${id}/activate`);
return mapSupplier(res.data);
},
async deactivate(id: string): Promise<SupplierDTO> {
const res = await client.post<BackendSupplier>(`/api/suppliers/${id}/deactivate`);
return mapSupplier(res.data);
},
async rate(id: string, request: RateSupplierRequest): Promise<SupplierDTO> {
const res = await client.post<BackendSupplier>(`/api/suppliers/${id}/rating`, request);
return mapSupplier(res.data);
},
async addCertificate(id: string, request: AddCertificateRequest): Promise<SupplierDTO> {
const res = await client.post<BackendSupplier>(`/api/suppliers/${id}/certificates`, request);
return mapSupplier(res.data);
},
// Returns 204 No Content → re-fetch supplier
async removeCertificate(id: string, request: RemoveCertificateRequest): Promise<SupplierDTO> {
await client.delete(`/api/suppliers/${id}/certificates`, { data: request });
const res = await client.get<BackendSupplier>(`/api/suppliers/${id}`);
return mapSupplier(res.data);
},
};
}
export type SuppliersResource = ReturnType<typeof createSuppliersResource>;