feat: implement business pricing service and money utilities
- Added `businessPricing.service.js` to handle business customer pricing logic. - Introduced `money.js` for money parsing, formatting, and calculations. - Created `pricing.service.js` to manage variant quoting and promotion application. - Updated validation schemas in `catalogue.schemas.js` and `inventoryMerchandising.schemas.js` for new pricing and inventory features. - Implemented cron job for inventory reservation expiry in `inventoryReservationExpiry.cron.js`. - Created database migrations for catalogue and inventory structures. - Added unit tests for catalogue localization, validation, and inventory merchandising functionalities.
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
const {z}=require("zod");const req=(body)=>z.object({body:z.object(body).strict(),params:z.object({}).passthrough(),query:z.object({}).passthrough()});const id=z.string().min(1).max(100);const slug=z.string().trim().toLowerCase().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).max(200);const money=z.string().regex(/^\d{1,13}(?:\.\d{1,2})?$/);const locale=z.enum(["en","si","ta"]);const translation=z.object({locale,name:z.string().trim().min(1).max(220),shortDescription:z.string().trim().max(500).optional(),description:z.string().trim().max(20000).optional(),careInstructions:z.string().trim().max(5000).optional(),materials:z.string().trim().max(2000).optional(),origin:z.string().trim().max(120).optional(),metaTitle:z.string().trim().max(180).optional(),metaDescription:z.string().trim().max(320).optional()}).strict();
|
||||
const variant=z.object({sku:z.string().trim().regex(/^[A-Za-z0-9._-]+$/).max(100),barcode:z.string().trim().max(100).optional(),basePrice:money,compareAtPrice:money.optional().nullable(),currency:z.string().length(3).transform(v=>v.toUpperCase()).default("LKR"),weight:z.string().regex(/^\d+(?:\.\d{1,3})?$/).optional(),sortOrder:z.number().int().min(0).default(0)}).strict();
|
||||
module.exports={slug,money,locale,productCreate:req({brandId:id,defaultCategoryId:id.optional(),slug,productCode:z.string().trim().min(1).max(100),productType:z.string().trim().max(80).default("STANDARD"),featured:z.boolean().default(false),newArrivalUntil:z.coerce.date().optional().nullable(),translations:z.array(translation).min(1),categoryIds:z.array(id).min(1),variants:z.array(variant).min(1),mediaUploadIds:z.array(z.number().int().positive()).default([])}),productUpdate:req({brandId:id.optional(),defaultCategoryId:id.optional().nullable(),slug:slug.optional(),productType:z.string().trim().max(80).optional(),featured:z.boolean().optional(),newArrivalUntil:z.coerce.date().optional().nullable(),status:z.enum(["DRAFT","ACTIVE","INACTIVE","ARCHIVED"]).optional(),visibility:z.enum(["PUBLIC","HIDDEN"]).optional()}),variantCreate:req(variant.shape),variantUpdate:req({status:z.enum(["ACTIVE","INACTIVE"]).optional(),basePrice:money.optional(),compareAtPrice:money.optional().nullable(),currency:z.string().length(3).transform(v=>v.toUpperCase()).optional(),weight:z.string().regex(/^\d+(?:\.\d{1,3})?$/).optional(),sortOrder:z.number().int().min(0).optional(),optionValueIds:z.array(id).optional()}),brand:req({name:z.string().trim().min(1).max(160),slug,description:z.string().trim().max(5000).optional(),logoUploadId:z.number().int().positive().optional().nullable(),website:z.string().url().optional().nullable(),status:z.enum(["ACTIVE","INACTIVE"]).default("INACTIVE"),sortOrder:z.number().int().min(0).default(0)}),category:req({parentId:id.optional().nullable(),code:z.string().trim().regex(/^[A-Za-z0-9_-]+$/).max(80),slug,status:z.enum(["ACTIVE","INACTIVE"]).default("INACTIVE"),sortOrder:z.number().int().min(0).default(0),imageUploadId:z.number().int().positive().optional().nullable(),translations:z.array(translation.pick({locale:true,name:true,description:true,metaTitle:true,metaDescription:true})).min(1)}),collection:req({slug,type:z.string().trim().min(1).max(80),status:z.enum(["DRAFT","ACTIVE","INACTIVE"]).default("DRAFT"),startsAt:z.coerce.date().optional().nullable(),endsAt:z.coerce.date().optional().nullable(),heroUploadId:z.number().int().positive().optional().nullable(),sortOrder:z.number().int().min(0).default(0),translations:z.array(z.object({locale,name:z.string().trim().min(1).max(180),description:z.string().max(10000).optional(),headline:z.string().max(250).optional(),subheadline:z.string().max(300).optional()}).strict()).min(1),productIds:z.array(id).default([])}),review:req({rating:z.number().int().min(1).max(5),title:z.string().trim().min(1).max(160),body:z.string().trim().min(3).max(5000)}),reviewStatus:req({status:z.enum(["APPROVED","REJECTED"])}),sizeGuide:req({code:z.string().trim().regex(/^[A-Za-z0-9_-]+$/).max(80),name:z.string().trim().min(1).max(160),locale,data:z.object({columns:z.array(z.string().max(80)).min(1).max(20),rows:z.array(z.array(z.string().max(100)).max(20)).max(100)}).strict(),categoryId:id.optional().nullable(),status:z.enum(["ACTIVE","INACTIVE"]).default("ACTIVE")})};
|
||||
module.exports.option=req({name:z.string().trim().min(1).max(100),values:z.array(z.string().trim().min(1).max(100)).min(1),sortOrder:z.number().int().min(0).default(0)});
|
||||
module.exports.media=req({uploadId:z.number().int().positive(),variantId:id.optional().nullable(),altText:z.string().trim().max(250).optional(),isPrimary:z.boolean().default(false),sortOrder:z.number().int().min(0).default(0)});
|
||||
module.exports.relation=req({targetProductId:id,relationType:z.enum(["RELATED","SIMILAR","COMPLETE_THE_LOOK"]),sortOrder:z.number().int().min(0).default(0)});
|
||||
@@ -0,0 +1,8 @@
|
||||
const {z}=require("zod");const wrap=body=>z.object({body:body.strict(),params:z.object({}).passthrough(),query:z.object({}).passthrough()});const id=z.string().min(1).max(160),money=z.string().regex(/^\d+(\.\d{1,2})?$/),date=z.coerce.date();
|
||||
exports.adjustment=wrap(z.object({warehouseId:id,variantId:id,quantityDelta:z.number().int().refine(v=>v!==0),reason:z.string().min(1).max(500)}));
|
||||
exports.transfer=wrap(z.object({sourceWarehouseId:id,destinationWarehouseId:id,variantId:id,quantity:z.number().int().positive()}));
|
||||
exports.warehouse=wrap(z.object({code:z.string().trim().min(1).max(50),name:z.string().min(1).max(150),status:z.enum(["ACTIVE","INACTIVE"]).default("ACTIVE"),isDefault:z.boolean().default(false),timezone:z.string().max(80).optional(),city:z.string().max(100).optional(),countryCode:z.string().length(2).optional()}));
|
||||
exports.businessPrice=wrap(z.object({variantId:id,businessTierId:id.optional(),businessCustomerId:id.optional(),currency:z.string().length(3).transform(v=>v.toUpperCase()),price:money,minimumQuantity:z.number().int().positive().default(1),status:z.enum(["ACTIVE","INACTIVE"]).default("ACTIVE"),startsAt:date.optional(),endsAt:date.optional(),tiers:z.array(z.object({minQuantity:z.number().int().positive(),maxQuantity:z.number().int().positive().nullable().optional(),unitPrice:money}).strict()).default([])}).refine(x=>Boolean(x.businessTierId)!==Boolean(x.businessCustomerId),"Exactly one audience required"));
|
||||
exports.promotion=wrap(z.object({name:z.string().min(1).max(180),type:z.enum(["PERCENTAGE","FIXED_AMOUNT","FIXED_PRICE"]),status:z.enum(["DRAFT","ACTIVE","INACTIVE","ARCHIVED"]).default("DRAFT"),startsAt:date.optional(),endsAt:date.optional(),priority:z.number().int().default(0),stackable:z.boolean().default(false),discountPercent:money.optional(),discountAmount:money.optional(),fixedPrice:money.optional(),minimumQuantity:z.number().int().positive().optional(),targets:z.array(z.object({type:z.enum(["ALL","PRODUCT","VARIANT","CATEGORY","BRAND","COLLECTION"]),id:id.nullable().optional()}).strict()).default([{type:"ALL"}])}));
|
||||
exports.coupon=wrap(z.object({code:z.string().trim().min(1).max(50).regex(/^[A-Za-z0-9_-]+$/).transform(v=>v.toUpperCase()),promotionId:id,status:z.enum(["ACTIVE","INACTIVE","ARCHIVED"]).default("ACTIVE"),startsAt:date.optional(),expiresAt:date.optional()}));
|
||||
exports.banner=wrap(z.object({placement:z.string().trim().min(1).max(80),status:z.enum(["DRAFT","ACTIVE","INACTIVE","ARCHIVED"]).default("DRAFT"),startsAt:date.optional(),endsAt:date.optional(),imageUploadId:z.number().int().positive(),mobileImageUploadId:z.number().int().positive().optional(),targetUrl:z.string().url().optional(),audienceType:z.enum(["ALL","CUSTOMER","BUSINESS_CUSTOMER"]).default("ALL"),businessTierId:id.optional(),sortOrder:z.number().int().default(0),translations:z.array(z.object({locale:z.enum(["en","si","ta"]),headline:z.string().max(180).optional(),subheadline:z.string().max(500).optional(),ctaText:z.string().max(80).optional(),altText:z.string().max(255).optional()}).strict()).min(1)}));
|
||||
Reference in New Issue
Block a user