feat(products): defer SKU generation to draft-pending promotion status transition

This commit is contained in:
Mahir-Mohamed
2026-08-13 14:53:05 +05:30
parent 90a6c3238c
commit 6e49ded9c4
@@ -291,6 +291,32 @@ export class ProductService {
return formatProductResponse(json);
}
async generateSku(family, finalCode, transaction) {
let familyPrefix = 'PRD';
if (family) {
familyPrefix = (family.name || family.code || 'PRD')
.toUpperCase()
.replace(/[^A-Z0-9]/g, '')
.substring(0, 4);
if (!familyPrefix || familyPrefix.length < 2) familyPrefix = 'PRD';
}
let runningSeq = '00001';
if (family) {
const familyProductCount = await models.Product.count({
where: { family_id: family.id },
transaction
});
runningSeq = String(familyProductCount + 1).padStart(5, '0');
} else {
const totalProductCount = await models.Product.count({
transaction
});
runningSeq = String(totalProductCount + 1).padStart(5, '0');
}
return `${familyPrefix}-${finalCode}-${runningSeq}`;
}
async create(data, context = {}) {
const transaction = await sequelize.transaction();
try {
@@ -384,31 +410,6 @@ export class ProductService {
}
data.code = finalCode;
// 4b. Autogenerate Master SKU (FAMILY_PREFIX-PRODUCT_CODE-SEQUENCE)
let familyPrefix = 'PRD';
if (family) {
familyPrefix = (family.name || family.code || 'PRD')
.toUpperCase()
.replace(/[^A-Z0-9]/g, '')
.substring(0, 4);
if (!familyPrefix || familyPrefix.length < 2) familyPrefix = 'PRD';
}
let runningSeq = '00001';
if (family) {
const familyProductCount = await models.Product.count({
where: { family_id: family.id },
transaction
});
runningSeq = String(familyProductCount + 1).padStart(5, '0');
} else {
const totalProductCount = await models.Product.count({
transaction
});
runningSeq = String(totalProductCount + 1).padStart(5, '0');
}
const generatedSku = `${familyPrefix}-${finalCode}-${runningSeq}`;
// Setup default inherited channels and workflows
const metadata = data.metadata || {};
if (!metadata.channels) {
@@ -438,7 +439,12 @@ export class ProductService {
metadata.currentStage = currentStage;
// Pack general form fields into metadata since they aren't core columns
metadata.sku = generatedSku;
const initialStatus = data.status || 'draft';
if (initialStatus === 'pending') {
metadata.sku = await this.generateSku(family, finalCode, transaction);
} else {
metadata.sku = null;
}
metadata.price = data.price || '';
metadata.stock = data.stock !== undefined ? data.stock : 0;
metadata.barcode = data.barcode || '';
@@ -478,7 +484,7 @@ export class ProductService {
}
}
}
if (Array.isArray(family.attributes)) {
if (family && Array.isArray(family.attributes)) {
for (const a of family.attributes) {
if (a && a.id) familyAttributesMap.set(a.id, a);
}
@@ -486,12 +492,14 @@ export class ProductService {
const attrKeys = typeof bodyAttributes === 'object' && bodyAttributes !== null ? Object.keys(bodyAttributes) : [];
if (familyAttributesMap.size === 0 && attrKeys.length > 0) {
const uuidKeys = attrKeys.filter(k => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(k));
const orConditions = [{ code: attrKeys }];
if (uuidKeys.length > 0) {
orConditions.push({ id: uuidKeys });
}
const dbAttrs = await models.Attribute.findAll({
where: {
[Op.or]: [
{ code: attrKeys },
{ id: attrKeys }
]
[Op.or]: orConditions
},
transaction
});
@@ -644,6 +652,13 @@ export class ProductService {
metadata.currentStage = record.metadata?.currentStage || 'draft';
}
const oldStatus = record.status || record.metadata?.currentStage || 'draft';
const newStatus = data.status || metadata.currentStage || oldStatus;
if (!metadata.sku && oldStatus === 'draft' && newStatus === 'pending') {
metadata.sku = await this.generateSku(family, record.code, transaction);
}
// Keep general fields inside metadata up to date
if (data.hasOwnProperty('sku')) metadata.sku = data.sku;
if (data.hasOwnProperty('price')) metadata.price = data.price;
@@ -697,12 +712,14 @@ export class ProductService {
const attrKeys = typeof bodyAttributes === 'object' && bodyAttributes !== null ? Object.keys(bodyAttributes) : [];
if (familyAttributesMap.size === 0 && attrKeys.length > 0) {
const uuidKeys = attrKeys.filter(k => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(k));
const orConditions = [{ code: attrKeys }];
if (uuidKeys.length > 0) {
orConditions.push({ id: uuidKeys });
}
const dbAttrs = await models.Attribute.findAll({
where: {
[Op.or]: [
{ code: attrKeys },
{ id: attrKeys }
]
[Op.or]: orConditions
},
transaction
});