145 lines
3.8 KiB
TypeScript
145 lines
3.8 KiB
TypeScript
import type { CreateOrderDto, CustomerBalanceDto } from '@gold-bank/shared-types';
|
|||
|
|
|
||
|
|
const BASE = '/api';
|
||
|
|
|
||
|
|
async function request<T>(path: string, options?: RequestInit): Promise<T> {
|
||
|
|
const res = await fetch(`${BASE}${path}`, {
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
...options,
|
||
|
|
});
|
||
|
|
if (!res.ok) {
|
||
|
|
const body = await res.text();
|
||
|
|
throw new Error(`API ${path} failed (${res.status}): ${body}`);
|
||
|
|
}
|
||
|
|
return res.json();
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface ProductVariantSummary {
|
||
|
|
id: string;
|
||
|
|
title: string;
|
||
|
|
rashWeightGrams: string;
|
||
|
|
defaultWagePercent: string;
|
||
|
|
/// On-hand stock and the "orderable at zero stock" flag. Both exist on
|
||
|
|
/// ProductVariant and are already returned by GET /catalog/products (the
|
||
|
|
/// catalog service includes every variant field) — declared here so the UI
|
||
|
|
/// can read them.
|
||
|
|
qty?: number;
|
||
|
|
isPreOrder?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface CategorySummary {
|
||
|
|
id: string;
|
||
|
|
title: string;
|
||
|
|
parentId: string | null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface BrandSummary {
|
||
|
|
id: string;
|
||
|
|
title: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface CollectionSummary {
|
||
|
|
id: string;
|
||
|
|
title: string;
|
||
|
|
description: string | null;
|
||
|
|
isActive: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface ProductSummary {
|
||
|
|
id: string;
|
||
|
|
title: string;
|
||
|
|
description: string | null;
|
||
|
|
/// Legacy Product.product_code — the human-facing code. Already returned by
|
||
|
|
/// GET /catalog/products.
|
||
|
|
productCode?: string | null;
|
||
|
|
variants: ProductVariantSummary[];
|
||
|
|
images: Array<{ url: string }>;
|
||
|
|
category: CategorySummary | null;
|
||
|
|
brand: BrandSummary | null;
|
||
|
|
collections: Array<{ collectionId: string; collection: CollectionSummary }>;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface LedgerEntrySummary {
|
||
|
|
id: string;
|
||
|
|
currency: string;
|
||
|
|
direction: string;
|
||
|
|
sourceType: string;
|
||
|
|
amount: string;
|
||
|
|
confirmed: boolean;
|
||
|
|
createdAt: string;
|
||
|
|
note: string | null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface CustomerSummary {
|
||
|
|
id: string;
|
||
|
|
fullName: string;
|
||
|
|
mobile: string;
|
||
|
|
goldDebtGrams: string;
|
||
|
|
fiatDebtRial: string;
|
||
|
|
goldCreditLimitGrams: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface OrderSummary {
|
||
|
|
id: string;
|
||
|
|
orderNumber: string;
|
||
|
|
status: string;
|
||
|
|
totalGoldWeightGrams: string;
|
||
|
|
isSuspendedOverCreditLimit: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface OrderItemDetail {
|
||
|
|
id: string;
|
||
|
|
productVariantId: string;
|
||
|
|
quantity: number;
|
||
|
|
rashWeightGramsSnapshot: string;
|
||
|
|
wagePercentSnapshot: string;
|
||
|
|
discountPercentSnapshot: string;
|
||
|
|
computedWeightGrams: string;
|
||
|
|
flatRialCharge: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface OrderDetail {
|
||
|
|
id: string;
|
||
|
|
orderNumber: string;
|
||
|
|
status: string;
|
||
|
|
totalGoldWeightGrams: string;
|
||
|
|
wageWeightGrams: string;
|
||
|
|
flatRialCharge: string;
|
||
|
|
isSuspendedOverCreditLimit: boolean;
|
||
|
|
deliveredAt: string | null;
|
||
|
|
createdAt: string;
|
||
|
|
items: OrderItemDetail[];
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface CustomerDetail {
|
||
|
|
id: string;
|
||
|
|
fullName: string;
|
||
|
|
mobile: string;
|
||
|
|
nationalCode: string;
|
||
|
|
cityId: string | null;
|
||
|
|
goldCreditLimitGrams: string;
|
||
|
|
goldDebtGrams: string;
|
||
|
|
fiatDebtRial: string;
|
||
|
|
verificationStep: string;
|
||
|
|
createdAt: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface GoldPriceQuote {
|
||
|
|
id: string;
|
||
|
|
karatPerMille: number;
|
||
|
|
pricePerGramRial: string;
|
||
|
|
source: string;
|
||
|
|
quotedAt: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const api = {
|
||
|
|
listProducts: () => request<ProductSummary[]>('/catalog/products'),
|
||
|
|
listCustomers: () => request<CustomerSummary[]>('/customers'),
|
||
|
|
getCustomer: (id: string) => request<CustomerDetail>(`/customers/${id}`),
|
||
|
|
getBalance: (customerId: string) => request<CustomerBalanceDto>(`/accounting/customers/${customerId}/balance`),
|
||
|
|
getLedgerEntries: (customerId: string) => request<LedgerEntrySummary[]>(`/customers/${customerId}/ledger-entries`),
|
||
|
|
createOrder: (dto: CreateOrderDto) => request<OrderSummary>('/orders', { method: 'POST', body: JSON.stringify(dto) }),
|
||
|
|
getCustomerOrders: (customerId: string) => request<OrderDetail[]>(`/customers/${customerId}/orders`),
|
||
|
|
getLatestGoldPrice: (karatPerMille = 750) => request<GoldPriceQuote>(`/pricing/latest?karatPerMille=${karatPerMille}`),
|
||
|
|
};
|