feat(F-102): completed feature
This commit is contained in:
@@ -94,6 +94,8 @@ const newProductSchema = z.object({
|
||||
categoryIds: z.array(z.uuid()).max(50).optional(),
|
||||
brandId: z.uuid().optional().nullable(),
|
||||
expirationDate: z.iso.date().optional().nullable(),
|
||||
unitWeightKg: z.number().positive().max(1000).optional(),
|
||||
minPurchaseQty: z.number().int().min(1).max(999).optional(),
|
||||
});
|
||||
|
||||
const productPatchSchema = newProductSchema
|
||||
@@ -727,6 +729,8 @@ function serializeProduct(product: Product, images: ProductImage[] = []) {
|
||||
brandId: product.brandId,
|
||||
brand: product.brand,
|
||||
expirationDate: product.expirationDate,
|
||||
unitWeightKg: product.unitWeightKg,
|
||||
minPurchaseQty: product.minPurchaseQty,
|
||||
createdAt: product.createdAt.toISOString(),
|
||||
updatedAt: product.updatedAt.toISOString(),
|
||||
};
|
||||
|
||||
@@ -51,6 +51,10 @@ export interface Product {
|
||||
brandId: string | null;
|
||||
brand?: ProductBrandSummary;
|
||||
expirationDate: string | null;
|
||||
/** Peso unitario en kg (el peso del pedido es cantidad × este valor). */
|
||||
unitWeightKg: number;
|
||||
/** Cantidad mínima de compra; el frontend bloquea pedidos por debajo. */
|
||||
minPurchaseQty: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
@@ -68,6 +72,8 @@ export interface NewProduct {
|
||||
categoryIds?: string[];
|
||||
brandId?: string | null;
|
||||
expirationDate?: string | null;
|
||||
unitWeightKg?: number;
|
||||
minPurchaseQty?: number;
|
||||
}
|
||||
|
||||
/** Fields a product update may set. Undefined = leave unchanged. */
|
||||
|
||||
@@ -26,6 +26,8 @@ export interface ProductRow {
|
||||
brand_name: string | null;
|
||||
brand_slug: string | null;
|
||||
expiration_date: string | null;
|
||||
unit_weight_kg: string | number;
|
||||
min_purchase_qty: number;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
@@ -55,6 +57,8 @@ const UPDATABLE: ReadonlyArray<[keyof ProductPatch, string]> = [
|
||||
['seoDescription', 'seo_description'],
|
||||
['brandId', 'brand_id'],
|
||||
['expirationDate', 'expiration_date'],
|
||||
['unitWeightKg', 'unit_weight_kg'],
|
||||
['minPurchaseQty', 'min_purchase_qty'],
|
||||
];
|
||||
|
||||
export class PgProductRepository implements ProductRepository {
|
||||
@@ -93,8 +97,8 @@ export class PgProductRepository implements ProductRepository {
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
const result = await client.query<ProductRow>(
|
||||
`INSERT INTO catalog_products (name, slug, description, state, seo_title, seo_description, brand_id, expiration_date)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
`INSERT INTO catalog_products (name, slug, description, state, seo_title, seo_description, brand_id, expiration_date, unit_weight_kg, min_purchase_qty)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
RETURNING *, ARRAY[]::uuid[] AS category_ids`,
|
||||
[
|
||||
input.name,
|
||||
@@ -105,6 +109,8 @@ export class PgProductRepository implements ProductRepository {
|
||||
input.seoDescription ?? null,
|
||||
input.brandId ?? null,
|
||||
input.expirationDate ?? null,
|
||||
input.unitWeightKg ?? 1,
|
||||
input.minPurchaseQty ?? 1,
|
||||
],
|
||||
);
|
||||
const row = result.rows[0];
|
||||
@@ -306,6 +312,8 @@ export function toProduct(row: ProductRow): Product {
|
||||
: undefined,
|
||||
categoryIds: row.category_ids ?? [],
|
||||
expirationDate: row.expiration_date ?? null,
|
||||
unitWeightKg: Number(row.unit_weight_kg ?? 1),
|
||||
minPurchaseQty: row.min_purchase_qty ?? 1,
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
};
|
||||
|
||||
@@ -17,6 +17,8 @@ function product(input: Partial<Product> & Pick<Product, 'id' | 'name' | 'slug'>
|
||||
categoryIds: [],
|
||||
brandId: null,
|
||||
expirationDate: null,
|
||||
unitWeightKg: 1,
|
||||
minPurchaseQty: 1,
|
||||
createdAt: new Date('2026-01-01T00:00:00Z'),
|
||||
updatedAt: new Date('2026-01-01T00:00:00Z'),
|
||||
...input,
|
||||
|
||||
@@ -21,6 +21,8 @@ function product(input: Partial<Product> & Pick<Product, 'id' | 'name' | 'slug'>
|
||||
categoryIds: [],
|
||||
brandId: null,
|
||||
expirationDate: null,
|
||||
unitWeightKg: 1,
|
||||
minPurchaseQty: 1,
|
||||
createdAt: new Date('2026-01-01T00:00:00Z'),
|
||||
updatedAt: new Date('2026-01-01T00:00:00Z'),
|
||||
...input,
|
||||
|
||||
Reference in New Issue
Block a user