feat(products): port missing variant fields from henza and redesign variant modal

This commit is contained in:
mahmood
2026-10-25 16:06:17 +03:30
parent 143d9aefbb
commit 55d27151c9
236 changed files with 1009 additions and 352 deletions
@@ -73,6 +73,12 @@ export class AdminProductsController {
return this.products.createVariant(body);
}
@Get('colors')
@RequirePermission('product.list')
colors() {
return this.products.colors();
}
@Get('show-type-options')
@RequirePermission('product.list')
showTypeOptions() {
@@ -20,6 +20,13 @@ const SHOW_TYPE_LABELS: Record<number, string> = {
3: 'سایت و گوشی',
};
export const VARIANT_COLORS: Record<number, string> = {
0: 'سفید',
1: 'زرد',
2: 'شکلاتی',
3: 'رزگلد',
};
@Injectable()
export class AdminProductsService {
constructor(
@@ -201,6 +208,7 @@ export class AdminProductsService {
}
private variantRow(v: Prisma.ProductVariantGetPayload<object> & { image?: { id: string; url: string } | null }) {
const colorNum = v.color != null ? Number(v.color) : null;
return {
id: v.id,
product_id_id: v.productId,
@@ -216,13 +224,22 @@ export class AdminProductsService {
// without a second lookup. Only populated where the query included it.
image_id: v.imageId,
image: v.image ? { id: v.image.id, url: v.image.url } : null,
// Legacy had a separate rial making-charge column; the schema models wage
// as a percentage only. Returned as null rather than faked.
rial_pay: null,
color_str: null,
color: colorNum,
color_str: colorNum != null ? (VARIANT_COLORS[colorNum] ?? null) : null,
rial_pay: v.rialWage != null ? Number(v.rialWage) : null,
details: v.details ?? null,
description: v.description ?? null,
};
}
/** Legacy `GET product/color` — options for variant color dropdown. */
colors() {
return Object.entries(VARIANT_COLORS).map(([id, title]) => ({
id: Number(id),
title,
}));
}
/** Legacy `POST get/variants` — a POST that reads, per legacy's own routing. */
async listVariants(query: TableQueryDto & { product_id?: string }) {
this.table.assertNotExport(query);
@@ -339,6 +356,13 @@ export class AdminProductsService {
qty: Number(v.qty ?? 0),
isPreOrder: Boolean(v.is_pre_order),
isActive: v.is_active !== undefined ? Boolean(v.is_active) : true,
color: v.color !== undefined && v.color !== null && v.color !== '' ? Number(v.color) : null,
rialWage:
v.rial_pay !== undefined && v.rial_pay !== null && v.rial_pay !== ''
? new Prisma.Decimal(String(v.rial_pay))
: null,
details: v.details ? String(v.details) : null,
description: v.description ? String(v.description) : null,
imageId,
};
}
@@ -225,6 +225,8 @@ export class CatalogService {
const stockBlocked = await this.accounting.stockBlocker();
const out = [];
const colorNames: Record<number, string> = { 0: 'سفید', 1: 'زرد', 2: 'شکلاتی', 3: 'رزگلد' };
for (const p of products) {
const { accountingStockWeight, accountingStockCount, accountingStockAt, ...product } = p;
// D27 — out of stock in the accounting software: every variant reads as
@@ -232,7 +234,22 @@ export class CatalogService {
// the cart unless it is a pre-order variant. The stock figures themselves
// stay server-side; this endpoint is public.
const blocked = stockBlocked({ accountingStockWeight, accountingStockCount, accountingStockAt });
out.push(blocked ? { ...product, variants: product.variants.map((v) => (v.isPreOrder ? v : { ...v, qty: 0 })) } : product);
const mappedVariants = product.variants.map((v) => {
const cNum = v.color != null ? Number(v.color) : null;
const color_str = cNum != null ? (colorNames[cNum] ?? null) : null;
const base = {
...v,
color: cNum,
color_str,
rial_pay: v.rialWage != null ? Number(v.rialWage) : null,
};
return blocked && !v.isPreOrder ? { ...base, qty: 0 } : base;
});
out.push({
...product,
variants: mappedVariants,
});
}
return out;
}