mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 19:10:22 +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:
parent
797f435a49
commit
d27dbaa843
30 changed files with 3882 additions and 1 deletions
196
frontend/packages/api-client/src/resources/articles.ts
Normal file
196
frontend/packages/api-client/src/resources/articles.ts
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
/**
|
||||
* Articles resource – Real HTTP implementation.
|
||||
* Endpoints: GET/POST /api/articles, GET/PUT /api/articles/{id},
|
||||
* POST /api/articles/{id}/activate|deactivate,
|
||||
* POST /api/articles/{id}/sales-units, DELETE /api/articles/{id}/sales-units/{suId},
|
||||
* PUT /api/articles/{id}/sales-units/{suId}/price,
|
||||
* POST /api/articles/{id}/suppliers, DELETE /api/articles/{id}/suppliers/{supplierId}
|
||||
*
|
||||
* NOTE: Backend returns domain objects with nested VOs:
|
||||
* { "id": {"value": "uuid"}, "name": {"value": "..."}, ...,
|
||||
* "supplierReferences": [{"value": "uuid"}],
|
||||
* "salesUnits": [{"id": {"value":"uuid"}, "unit":"KG", "priceModel":"WEIGHT_BASED",
|
||||
* "price": {"amount": 2.49, "currency": "EUR"}}] }
|
||||
* DELETE endpoints for sales-units and suppliers return 204 No Content → re-fetch.
|
||||
*/
|
||||
|
||||
import type { AxiosInstance } from 'axios';
|
||||
|
||||
export type Unit = 'PIECE_FIXED' | 'KG' | 'HUNDRED_GRAM' | 'PIECE_VARIABLE';
|
||||
export type PriceModel = 'FIXED' | 'WEIGHT_BASED';
|
||||
export type ArticleStatus = 'ACTIVE' | 'INACTIVE';
|
||||
|
||||
export const UNIT_LABELS: Record<Unit, string> = {
|
||||
PIECE_FIXED: 'Stück (fix)',
|
||||
KG: 'Kilogramm',
|
||||
HUNDRED_GRAM: '100g',
|
||||
PIECE_VARIABLE: 'Stück (variabel)',
|
||||
};
|
||||
|
||||
export const PRICE_MODEL_LABELS: Record<PriceModel, string> = {
|
||||
FIXED: 'Festpreis',
|
||||
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;
|
||||
}
|
||||
|
||||
// ── Backend response shapes (domain objects with nested VOs) ─────────────────
|
||||
|
||||
interface BackendSalesUnit {
|
||||
id: { value: string };
|
||||
unit: Unit;
|
||||
priceModel: PriceModel;
|
||||
price: { amount: number; currency: string };
|
||||
}
|
||||
|
||||
interface BackendArticle {
|
||||
id: { value: string };
|
||||
name: { value: string };
|
||||
articleNumber: { value: string };
|
||||
categoryId: { value: string };
|
||||
salesUnits: BackendSalesUnit[];
|
||||
status: ArticleStatus;
|
||||
supplierReferences: Array<{ value: string }>;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
function mapSalesUnit(bsu: BackendSalesUnit): SalesUnitDTO {
|
||||
return {
|
||||
id: bsu.id.value,
|
||||
unit: bsu.unit,
|
||||
priceModel: bsu.priceModel,
|
||||
price: bsu.price.amount,
|
||||
};
|
||||
}
|
||||
|
||||
function mapArticle(ba: BackendArticle): ArticleDTO {
|
||||
return {
|
||||
id: ba.id.value,
|
||||
name: ba.name.value,
|
||||
articleNumber: ba.articleNumber.value,
|
||||
categoryId: ba.categoryId.value,
|
||||
salesUnits: ba.salesUnits.map(mapSalesUnit),
|
||||
status: ba.status,
|
||||
supplierIds: ba.supplierReferences.map((sr) => sr.value),
|
||||
createdAt: ba.createdAt,
|
||||
updatedAt: ba.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
// ── Resource factory ─────────────────────────────────────────────────────────
|
||||
|
||||
export function createArticlesResource(client: AxiosInstance) {
|
||||
return {
|
||||
async list(): Promise<ArticleDTO[]> {
|
||||
const res = await client.get<BackendArticle[]>('/api/articles');
|
||||
return res.data.map(mapArticle);
|
||||
},
|
||||
|
||||
async getById(id: string): Promise<ArticleDTO> {
|
||||
const res = await client.get<BackendArticle>(`/api/articles/${id}`);
|
||||
return mapArticle(res.data);
|
||||
},
|
||||
|
||||
async create(request: CreateArticleRequest): Promise<ArticleDTO> {
|
||||
const res = await client.post<BackendArticle>('/api/articles', request);
|
||||
return mapArticle(res.data);
|
||||
},
|
||||
|
||||
async update(id: string, request: UpdateArticleRequest): Promise<ArticleDTO> {
|
||||
const res = await client.put<BackendArticle>(`/api/articles/${id}`, request);
|
||||
return mapArticle(res.data);
|
||||
},
|
||||
|
||||
async activate(id: string): Promise<ArticleDTO> {
|
||||
const res = await client.post<BackendArticle>(`/api/articles/${id}/activate`);
|
||||
return mapArticle(res.data);
|
||||
},
|
||||
|
||||
async deactivate(id: string): Promise<ArticleDTO> {
|
||||
const res = await client.post<BackendArticle>(`/api/articles/${id}/deactivate`);
|
||||
return mapArticle(res.data);
|
||||
},
|
||||
|
||||
async addSalesUnit(id: string, request: AddSalesUnitRequest): Promise<ArticleDTO> {
|
||||
const res = await client.post<BackendArticle>(`/api/articles/${id}/sales-units`, request);
|
||||
return mapArticle(res.data);
|
||||
},
|
||||
|
||||
// Returns 204 No Content → re-fetch article
|
||||
async removeSalesUnit(articleId: string, salesUnitId: string): Promise<ArticleDTO> {
|
||||
await client.delete(`/api/articles/${articleId}/sales-units/${salesUnitId}`);
|
||||
const res = await client.get<BackendArticle>(`/api/articles/${articleId}`);
|
||||
return mapArticle(res.data);
|
||||
},
|
||||
|
||||
async updateSalesUnitPrice(
|
||||
articleId: string,
|
||||
salesUnitId: string,
|
||||
request: UpdateSalesUnitPriceRequest,
|
||||
): Promise<ArticleDTO> {
|
||||
const res = await client.put<BackendArticle>(
|
||||
`/api/articles/${articleId}/sales-units/${salesUnitId}/price`,
|
||||
request,
|
||||
);
|
||||
return mapArticle(res.data);
|
||||
},
|
||||
|
||||
async assignSupplier(articleId: string, supplierId: string): Promise<ArticleDTO> {
|
||||
const res = await client.post<BackendArticle>(`/api/articles/${articleId}/suppliers`, {
|
||||
supplierId,
|
||||
});
|
||||
return mapArticle(res.data);
|
||||
},
|
||||
|
||||
// Returns 204 No Content → re-fetch article
|
||||
async removeSupplier(articleId: string, supplierId: string): Promise<ArticleDTO> {
|
||||
await client.delete(`/api/articles/${articleId}/suppliers/${supplierId}`);
|
||||
const res = await client.get<BackendArticle>(`/api/articles/${articleId}`);
|
||||
return mapArticle(res.data);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export type ArticlesResource = ReturnType<typeof createArticlesResource>;
|
||||
77
frontend/packages/api-client/src/resources/categories.ts
Normal file
77
frontend/packages/api-client/src/resources/categories.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* Categories resource – Real HTTP implementation.
|
||||
* Endpoints: GET/POST /api/categories, PUT/DELETE /api/categories/{id}
|
||||
*
|
||||
* NOTE: The backend returns domain objects serialized with Jackson field-visibility.
|
||||
* VOs like ProductCategoryId and CategoryName serialize as nested records:
|
||||
* { "id": {"value": "uuid"}, "name": {"value": "string"}, "description": "string|null" }
|
||||
*/
|
||||
|
||||
import type { AxiosInstance } from 'axios';
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// ── Backend response shapes (domain objects with nested VOs) ─────────────────
|
||||
|
||||
interface BackendProductCategory {
|
||||
id: { value: string };
|
||||
name: { value: string };
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
function mapCategory(bc: BackendProductCategory): ProductCategoryDTO {
|
||||
return {
|
||||
id: bc.id.value,
|
||||
name: bc.name.value,
|
||||
description: bc.description,
|
||||
};
|
||||
}
|
||||
|
||||
// ── Resource factory ─────────────────────────────────────────────────────────
|
||||
|
||||
export function createCategoriesResource(client: AxiosInstance) {
|
||||
return {
|
||||
async list(): Promise<ProductCategoryDTO[]> {
|
||||
const res = await client.get<BackendProductCategory[]>('/api/categories');
|
||||
return res.data.map(mapCategory);
|
||||
},
|
||||
|
||||
// No GET /api/categories/{id} endpoint – implemented as list + filter
|
||||
async getById(id: string): Promise<ProductCategoryDTO> {
|
||||
const res = await client.get<BackendProductCategory[]>('/api/categories');
|
||||
const cat = res.data.find((c) => c.id.value === id);
|
||||
if (!cat) throw new Error(`Kategorie nicht gefunden: ${id}`);
|
||||
return mapCategory(cat);
|
||||
},
|
||||
|
||||
async create(request: CreateCategoryRequest): Promise<ProductCategoryDTO> {
|
||||
const res = await client.post<BackendProductCategory>('/api/categories', request);
|
||||
return mapCategory(res.data);
|
||||
},
|
||||
|
||||
async update(id: string, request: UpdateCategoryRequest): Promise<ProductCategoryDTO> {
|
||||
const res = await client.put<BackendProductCategory>(`/api/categories/${id}`, request);
|
||||
return mapCategory(res.data);
|
||||
},
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
await client.delete(`/api/categories/${id}`);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export type CategoriesResource = ReturnType<typeof createCategoriesResource>;
|
||||
297
frontend/packages/api-client/src/resources/customers.ts
Normal file
297
frontend/packages/api-client/src/resources/customers.ts
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
/**
|
||||
* Customers resource – Real HTTP implementation.
|
||||
* Endpoints: GET/POST /api/customers, GET/PUT /api/customers/{id},
|
||||
* POST /api/customers/{id}/activate|deactivate,
|
||||
* POST /api/customers/{id}/delivery-addresses,
|
||||
* DELETE /api/customers/{id}/delivery-addresses/{label},
|
||||
* PUT /api/customers/{id}/frame-contract,
|
||||
* DELETE /api/customers/{id}/frame-contract,
|
||||
* PUT /api/customers/{id}/preferences
|
||||
*
|
||||
* NOTE: Backend returns domain objects with nested VOs:
|
||||
* { "id": {"value":"uuid"}, "name": {"value":"string"},
|
||||
* "billingAddress": {street, houseNumber, postalCode, city, country},
|
||||
* "contactInfo": {phone, email, contactPerson},
|
||||
* "paymentTerms": {paymentDueDays, description},
|
||||
* "deliveryAddresses": [{label, address: {...}, contactPerson, deliveryNotes}],
|
||||
* "frameContract": {"id": {"value":"uuid"}, validFrom, validUntil, deliveryRhythm, lineItems},
|
||||
* "preferences": ["BIO", ...], "status": "ACTIVE", ... }
|
||||
* DELETE delivery-addresses/{label} and DELETE frame-contract return 204 → re-fetch.
|
||||
*/
|
||||
|
||||
import type { AxiosInstance } from 'axios';
|
||||
import type { AddressDTO, ContactInfoDTO, PaymentTermsDTO } from './suppliers.js';
|
||||
|
||||
export type CustomerType = 'B2B' | 'B2C';
|
||||
export type CustomerStatus = 'ACTIVE' | 'INACTIVE';
|
||||
export type CustomerPreference =
|
||||
| 'BIO'
|
||||
| 'REGIONAL'
|
||||
| 'TIERWOHL'
|
||||
| 'HALAL'
|
||||
| 'KOSHER'
|
||||
| 'GLUTENFREI'
|
||||
| 'LAKTOSEFREI';
|
||||
export type DeliveryRhythm = 'DAILY' | 'WEEKLY' | 'BIWEEKLY' | 'MONTHLY' | 'ON_DEMAND';
|
||||
|
||||
export const CUSTOMER_PREFERENCE_LABELS: Record<CustomerPreference, string> = {
|
||||
BIO: 'Bio',
|
||||
REGIONAL: 'Regional',
|
||||
TIERWOHL: 'Tierwohl',
|
||||
HALAL: 'Halal',
|
||||
KOSHER: 'Koscher',
|
||||
GLUTENFREI: 'Glutenfrei',
|
||||
LAKTOSEFREI: 'Laktosefrei',
|
||||
};
|
||||
|
||||
export const DELIVERY_RHYTHM_LABELS: Record<DeliveryRhythm, string> = {
|
||||
DAILY: 'Täglich',
|
||||
WEEKLY: 'Wöchentlich',
|
||||
BIWEEKLY: 'Zweiwöchentlich',
|
||||
MONTHLY: 'Monatlich',
|
||||
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[];
|
||||
}
|
||||
|
||||
// ── Backend response shapes (domain objects with nested VOs) ─────────────────
|
||||
|
||||
interface BackendPaymentTerms {
|
||||
paymentDueDays: number;
|
||||
description: string | null; // Note: backend field is "description", not "paymentDescription"
|
||||
}
|
||||
|
||||
interface BackendContractLineItem {
|
||||
articleId: { value: string };
|
||||
agreedPrice: { amount: number; currency: string };
|
||||
agreedQuantity: number | null;
|
||||
unit: string | null;
|
||||
}
|
||||
|
||||
interface BackendFrameContract {
|
||||
id: { value: string };
|
||||
validFrom: string | null;
|
||||
validUntil: string | null;
|
||||
deliveryRhythm: DeliveryRhythm;
|
||||
lineItems: BackendContractLineItem[];
|
||||
}
|
||||
|
||||
interface BackendCustomer {
|
||||
id: { value: string };
|
||||
name: { value: string };
|
||||
type: CustomerType;
|
||||
status: CustomerStatus;
|
||||
billingAddress: AddressDTO;
|
||||
contactInfo: ContactInfoDTO;
|
||||
paymentTerms: BackendPaymentTerms | null;
|
||||
deliveryAddresses: DeliveryAddressDTO[]; // DeliveryAddress is a record → matches DTO shape
|
||||
frameContract: BackendFrameContract | null;
|
||||
preferences: CustomerPreference[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
function mapLineItem(bli: BackendContractLineItem): ContractLineItemDTO {
|
||||
return {
|
||||
articleId: bli.articleId.value,
|
||||
agreedPrice: bli.agreedPrice.amount,
|
||||
agreedQuantity: bli.agreedQuantity,
|
||||
unit: bli.unit,
|
||||
};
|
||||
}
|
||||
|
||||
function mapFrameContract(bfc: BackendFrameContract): FrameContractDTO {
|
||||
return {
|
||||
id: bfc.id.value,
|
||||
validFrom: bfc.validFrom,
|
||||
validUntil: bfc.validUntil,
|
||||
deliveryRhythm: bfc.deliveryRhythm,
|
||||
lineItems: bfc.lineItems.map(mapLineItem),
|
||||
};
|
||||
}
|
||||
|
||||
function mapCustomer(bc: BackendCustomer): CustomerDTO {
|
||||
return {
|
||||
id: bc.id.value,
|
||||
name: bc.name.value,
|
||||
type: bc.type,
|
||||
status: bc.status,
|
||||
billingAddress: bc.billingAddress,
|
||||
contactInfo: bc.contactInfo,
|
||||
paymentTerms: bc.paymentTerms
|
||||
? {
|
||||
paymentDueDays: bc.paymentTerms.paymentDueDays,
|
||||
paymentDescription: bc.paymentTerms.description,
|
||||
}
|
||||
: null,
|
||||
deliveryAddresses: bc.deliveryAddresses,
|
||||
frameContract: bc.frameContract ? mapFrameContract(bc.frameContract) : null,
|
||||
preferences: bc.preferences,
|
||||
createdAt: bc.createdAt,
|
||||
updatedAt: bc.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
// ── Resource factory ─────────────────────────────────────────────────────────
|
||||
|
||||
export function createCustomersResource(client: AxiosInstance) {
|
||||
return {
|
||||
async list(): Promise<CustomerDTO[]> {
|
||||
const res = await client.get<BackendCustomer[]>('/api/customers');
|
||||
return res.data.map(mapCustomer);
|
||||
},
|
||||
|
||||
async getById(id: string): Promise<CustomerDTO> {
|
||||
const res = await client.get<BackendCustomer>(`/api/customers/${id}`);
|
||||
return mapCustomer(res.data);
|
||||
},
|
||||
|
||||
async create(request: CreateCustomerRequest): Promise<CustomerDTO> {
|
||||
const res = await client.post<BackendCustomer>('/api/customers', request);
|
||||
return mapCustomer(res.data);
|
||||
},
|
||||
|
||||
async update(id: string, request: UpdateCustomerRequest): Promise<CustomerDTO> {
|
||||
const res = await client.put<BackendCustomer>(`/api/customers/${id}`, request);
|
||||
return mapCustomer(res.data);
|
||||
},
|
||||
|
||||
async activate(id: string): Promise<CustomerDTO> {
|
||||
const res = await client.post<BackendCustomer>(`/api/customers/${id}/activate`);
|
||||
return mapCustomer(res.data);
|
||||
},
|
||||
|
||||
async deactivate(id: string): Promise<CustomerDTO> {
|
||||
const res = await client.post<BackendCustomer>(`/api/customers/${id}/deactivate`);
|
||||
return mapCustomer(res.data);
|
||||
},
|
||||
|
||||
async addDeliveryAddress(id: string, request: AddDeliveryAddressRequest): Promise<CustomerDTO> {
|
||||
const res = await client.post<BackendCustomer>(
|
||||
`/api/customers/${id}/delivery-addresses`,
|
||||
request,
|
||||
);
|
||||
return mapCustomer(res.data);
|
||||
},
|
||||
|
||||
// Returns 204 No Content → re-fetch customer
|
||||
async removeDeliveryAddress(id: string, label: string): Promise<CustomerDTO> {
|
||||
await client.delete(`/api/customers/${id}/delivery-addresses/${encodeURIComponent(label)}`);
|
||||
const res = await client.get<BackendCustomer>(`/api/customers/${id}`);
|
||||
return mapCustomer(res.data);
|
||||
},
|
||||
|
||||
async setFrameContract(id: string, request: SetFrameContractRequest): Promise<CustomerDTO> {
|
||||
const res = await client.put<BackendCustomer>(
|
||||
`/api/customers/${id}/frame-contract`,
|
||||
request,
|
||||
);
|
||||
return mapCustomer(res.data);
|
||||
},
|
||||
|
||||
// Returns 204 No Content → re-fetch customer
|
||||
async removeFrameContract(id: string): Promise<CustomerDTO> {
|
||||
await client.delete(`/api/customers/${id}/frame-contract`);
|
||||
const res = await client.get<BackendCustomer>(`/api/customers/${id}`);
|
||||
return mapCustomer(res.data);
|
||||
},
|
||||
|
||||
async setPreferences(id: string, preferences: CustomerPreference[]): Promise<CustomerDTO> {
|
||||
const res = await client.put<BackendCustomer>(`/api/customers/${id}/preferences`, {
|
||||
preferences,
|
||||
});
|
||||
return mapCustomer(res.data);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export type CustomersResource = ReturnType<typeof createCustomersResource>;
|
||||
236
frontend/packages/api-client/src/resources/suppliers.ts
Normal file
236
frontend/packages/api-client/src/resources/suppliers.ts
Normal 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>;
|
||||
Loading…
Add table
Add a link
Reference in a new issue