merge: pull latest changes from origin/mahir_backend and resolve code generation conflicts
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
PORT=5002
|
||||
NODE_ENV=local
|
||||
CORS_ORIGIN=http://localhost:5173
|
||||
CORS_ORIGIN=http://localhost:5173,http://localhost:5174
|
||||
JWT_SECRET=supersecretjwtkeythatislongandsecure
|
||||
JWT_REFRESH_SECRET=supersecretrefreshjwtkeythatislongandsecure
|
||||
JWT_EXPIRES_IN=1d
|
||||
|
||||
@@ -17,7 +17,7 @@ app.use(helmet({
|
||||
crossOriginResourcePolicy: { policy: 'cross-origin' },
|
||||
}));
|
||||
app.use(cors({
|
||||
origin: process.env.CORS_ORIGIN || '*',
|
||||
origin: process.env.CORS_ORIGIN ? (process.env.CORS_ORIGIN.includes(',') ? process.env.CORS_ORIGIN.split(',') : process.env.CORS_ORIGIN) : '*',
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'Accept', 'X-Requested-With']
|
||||
}));
|
||||
|
||||
Generated
+3
@@ -270,6 +270,7 @@
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
|
||||
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
@@ -1219,6 +1220,7 @@
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
|
||||
"integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
@@ -2425,6 +2427,7 @@
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.22.0.tgz",
|
||||
"integrity": "sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"pg-connection-string": "^2.14.0",
|
||||
"pg-pool": "^3.14.0",
|
||||
|
||||
@@ -24,8 +24,8 @@ export class AttributeGroupService {
|
||||
async create(data, context = {}) {
|
||||
const transaction = await sequelize.transaction();
|
||||
try {
|
||||
data.code = await generateUniqueCode(models.AttributeGroup, data.code || data.name, 'code', transaction);
|
||||
|
||||
const baseCode = data.code || data.name || 'group';
|
||||
data.code = await generateUniqueCode(models.AttributeGroup, baseCode, 'code', transaction);
|
||||
|
||||
const record = await models.AttributeGroup.create(data, { transaction });
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ export class AttributeSetService {
|
||||
async create(data, context = {}) {
|
||||
const transaction = await sequelize.transaction();
|
||||
try {
|
||||
data.code = await generateUniqueCode(models.AttributeSet, data.code || data.name, 'code', transaction);
|
||||
|
||||
const baseCode = data.code || data.name || 'set';
|
||||
data.code = await generateUniqueCode(models.AttributeSet, baseCode, 'code', transaction);
|
||||
|
||||
const record = await models.AttributeSet.create(data, { transaction });
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { AuditService } from '../../../shared/services/audit.service.js';
|
||||
import { ApiError } from '../../../utils/helpers/ApiError.utils.js';
|
||||
import { generateUniqueCode } from '../../../utils/helpers/code.utils.js';
|
||||
import { Op } from 'sequelize';
|
||||
import { generateUniqueCode } from '../../../utils/helpers/code.utils.js';
|
||||
|
||||
|
||||
export class AttributeService {
|
||||
@@ -133,9 +134,8 @@ export class AttributeService {
|
||||
async create(data, context = {}) {
|
||||
const transaction = await sequelize.transaction();
|
||||
try {
|
||||
data.code = await generateUniqueCode(models.Attribute, data.code || data.name, 'code', transaction, { paranoid: false });
|
||||
|
||||
// Automatically assign display order if not provided
|
||||
const baseCode = data.code || data.name || 'attribute';
|
||||
data.code = await generateUniqueCode(models.Attribute, baseCode, 'code', transaction, { paranoid: false });
|
||||
if (data.display_order === undefined || data.display_order === null) {
|
||||
const maxOrder = await models.Attribute.max('display_order', { transaction }) || 0;
|
||||
data.display_order = maxOrder + 1;
|
||||
@@ -295,44 +295,44 @@ export class AttributeService {
|
||||
}
|
||||
|
||||
// Usage Check: Groups mapping (count only active groups)
|
||||
const groupCount = await models.AttributeGroup.count({
|
||||
const groupCount = await models.AttributeGroup.count({
|
||||
include: [{
|
||||
model: models.Attribute,
|
||||
as: 'attributes',
|
||||
where: { id: id },
|
||||
required: true
|
||||
}],
|
||||
transaction
|
||||
transaction
|
||||
});
|
||||
|
||||
|
||||
// Usage Check: Product Families mapping (count only active catalogs)
|
||||
const familyCount = await models.Catalog.count({
|
||||
const familyCount = await models.Catalog.count({
|
||||
include: [{
|
||||
model: models.Attribute,
|
||||
as: 'attributes',
|
||||
where: { id: id },
|
||||
required: true
|
||||
}],
|
||||
transaction
|
||||
transaction
|
||||
});
|
||||
|
||||
// Usage Check: Variant axes mapping (count only active catalogs)
|
||||
const axisCount = await models.Catalog.count({
|
||||
const axisCount = await models.Catalog.count({
|
||||
include: [{
|
||||
model: models.Attribute,
|
||||
as: 'variantAxes',
|
||||
where: { id: id },
|
||||
required: true
|
||||
}],
|
||||
transaction
|
||||
transaction
|
||||
});
|
||||
|
||||
// Usage Check: Variant Values
|
||||
let valCount = 0;
|
||||
if (models.VariantValue) {
|
||||
valCount = await models.VariantValue.count({
|
||||
valCount = await models.VariantValue.count({
|
||||
where: { axis_id: id },
|
||||
transaction
|
||||
transaction
|
||||
});
|
||||
}
|
||||
|
||||
@@ -354,7 +354,7 @@ export class AttributeService {
|
||||
const oldValues = record.toJSON();
|
||||
|
||||
// Change status to Archived, save updated_by and deleted_by context
|
||||
await record.update({
|
||||
await record.update({
|
||||
status: 'archived',
|
||||
updated_by: context.userId || null,
|
||||
deleted_by: context.userId || null
|
||||
@@ -396,19 +396,19 @@ export class AttributeService {
|
||||
async restore(id, context = {}) {
|
||||
const transaction = await sequelize.transaction();
|
||||
try {
|
||||
const record = await models.Attribute.findByPk(id, {
|
||||
const record = await models.Attribute.findByPk(id, {
|
||||
paranoid: false,
|
||||
transaction
|
||||
transaction
|
||||
});
|
||||
if (!record) {
|
||||
throw new Error('Attribute not found');
|
||||
}
|
||||
|
||||
await record.restore({ transaction });
|
||||
await record.update({
|
||||
status: 'active',
|
||||
await record.update({
|
||||
status: 'active',
|
||||
deleted_by: null,
|
||||
updated_by: context.userId || null
|
||||
updated_by: context.userId || null
|
||||
}, { transaction });
|
||||
|
||||
await transaction.commit();
|
||||
|
||||
@@ -24,8 +24,8 @@ export class BrandService {
|
||||
}
|
||||
|
||||
async create(data, context = {}) {
|
||||
data.code = await generateUniqueCode(models.Brand, data.code || data.name);
|
||||
|
||||
const baseCode = data.code || data.name || 'brand';
|
||||
data.code = await generateUniqueCode(models.Brand, baseCode, 'code');
|
||||
|
||||
const record = await repository.create(data, {}, context);
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ export class UnitService {
|
||||
}
|
||||
|
||||
async create(data, context = {}) {
|
||||
data.code = await generateUniqueCode(models.Unit, data.code || data.name);
|
||||
|
||||
const baseCode = data.code || data.name || 'unit';
|
||||
data.code = await generateUniqueCode(models.Unit, baseCode, 'code');
|
||||
|
||||
const record = await repository.create(data, {}, context);
|
||||
|
||||
|
||||
@@ -2,10 +2,9 @@ import { body, param } from 'express-validator';
|
||||
|
||||
export const createValidation = [
|
||||
body('code')
|
||||
.optional({ checkFalsy: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.notEmpty()
|
||||
.withMessage('Code is required')
|
||||
.matches(/^[a-z0-9_]+$/)
|
||||
.withMessage('Code must be lowercase alphanumeric and underscores only'),
|
||||
body('name')
|
||||
|
||||
@@ -4,7 +4,10 @@ import { SocketService } from '../../../shared/services/socket.service.js';
|
||||
import { AuditService } from '../../../shared/services/audit.service.js';
|
||||
import { ApiError } from '../../../utils/helpers/ApiError.utils.js';
|
||||
import { generateUniqueCode } from '../../../utils/helpers/code.utils.js';
|
||||
<<<<<<< HEAD
|
||||
|
||||
=======
|
||||
>>>>>>> origin/mahir_backend
|
||||
|
||||
export class CatalogService {
|
||||
async attachCounts(record, transaction) {
|
||||
@@ -125,10 +128,8 @@ export class CatalogService {
|
||||
async create(data, context = {}) {
|
||||
const transaction = await sequelize.transaction();
|
||||
try {
|
||||
// 1. Autogenerate unique code
|
||||
data.code = await generateUniqueCode(models.ProductFamily, data.code || data.name, 'code', transaction, { paranoid: false });
|
||||
|
||||
// Resolve attributes from attribute set if provided
|
||||
const baseCode = data.code || data.name || 'family';
|
||||
data.code = await generateUniqueCode(models.ProductFamily, baseCode, 'code', transaction, { paranoid: false });
|
||||
const attributeSetId = data.attributeSetId || data.attribute_set_id || null;
|
||||
if (attributeSetId) {
|
||||
const attributeSet = await models.AttributeSet.findByPk(attributeSetId, {
|
||||
|
||||
@@ -6,6 +6,7 @@ import NotificationService from '../../notifications/notifications/notification.
|
||||
import { ApiError } from '../../../utils/helpers/ApiError.utils.js';
|
||||
import { generateUniqueCode } from '../../../utils/helpers/code.utils.js';
|
||||
import { Op } from 'sequelize';
|
||||
import { generateUniqueCode } from '../../../utils/helpers/code.utils.js';
|
||||
|
||||
export class CategorieService {
|
||||
async getAll(query = {}, context = {}) {
|
||||
@@ -19,7 +20,7 @@ export class CategorieService {
|
||||
async getById(id, context = {}) {
|
||||
const record = await repository.findById(id, {}, context);
|
||||
if (!record) {
|
||||
throw new ApiError(404, 404, 'Category not found');
|
||||
throw new ApiError(404, 'Category not found');
|
||||
}
|
||||
return record;
|
||||
}
|
||||
@@ -27,7 +28,8 @@ export class CategorieService {
|
||||
async create(data, context = {}) {
|
||||
const transaction = await sequelize.transaction();
|
||||
try {
|
||||
data.code = await generateUniqueCode(models.Categorie, data.code || data.name, 'code', transaction);
|
||||
const baseCode = data.code || data.name || 'category';
|
||||
data.code = await generateUniqueCode(models.Categorie, baseCode, 'code', transaction);
|
||||
|
||||
let level = 0;
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@ import repository from './channelType.repository.js';
|
||||
import { models } from '../../../shared/database/models.js';
|
||||
import { SocketService } from '../../../shared/services/socket.service.js';
|
||||
import { AuditService } from '../../../shared/services/audit.service.js';
|
||||
<<<<<<< HEAD
|
||||
import { ApiError } from '../../../utils/helpers/ApiError.utils.js';
|
||||
=======
|
||||
>>>>>>> origin/mahir_backend
|
||||
import { generateUniqueCode } from '../../../utils/helpers/code.utils.js';
|
||||
|
||||
export class ChannelTypeService {
|
||||
@@ -23,7 +26,8 @@ export class ChannelTypeService {
|
||||
}
|
||||
|
||||
async create(data, userContext = {}) {
|
||||
data.code = await generateUniqueCode(models.ChannelType, data.code || data.name);
|
||||
const baseCode = data.code || data.name || 'channel_type';
|
||||
data.code = await generateUniqueCode(models.ChannelType, baseCode, 'code');
|
||||
|
||||
const record = await repository.create(data);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { models } from '../../../shared/database/models.js';
|
||||
import { SocketService } from '../../../shared/services/socket.service.js';
|
||||
import { AuditService } from '../../../shared/services/audit.service.js';
|
||||
import { ApiError } from '../../../utils/helpers/ApiError.utils.js';
|
||||
import { generateUniqueCode } from '../../../utils/helpers/code.utils.js';
|
||||
|
||||
export class ChannelService {
|
||||
async getAll(query = {}) {
|
||||
@@ -19,6 +20,9 @@ export class ChannelService {
|
||||
}
|
||||
|
||||
async create(data, userContext = {}) {
|
||||
const baseCode = data.code || data.name || 'channel';
|
||||
data.code = await generateUniqueCode(models.Channel, baseCode, 'code');
|
||||
|
||||
const record = await repository.create(data);
|
||||
|
||||
// Broadcast event
|
||||
|
||||
@@ -3,6 +3,7 @@ import { models } from '../../../shared/database/models.js';
|
||||
import { SocketService } from '../../../shared/services/socket.service.js';
|
||||
import { AuditService } from '../../../shared/services/audit.service.js';
|
||||
import { ApiError } from '../../../utils/helpers/ApiError.utils.js';
|
||||
import { generateUniqueCode } from '../../../utils/helpers/code.utils.js';
|
||||
|
||||
export class AssetFamilyService {
|
||||
encodeDescription(text = '', assetTypeIds = []) {
|
||||
@@ -100,11 +101,8 @@ export class AssetFamilyService {
|
||||
async create(data, userContext = {}) {
|
||||
const { assetTypes, assetTypeIds, description, ...familyData } = data;
|
||||
|
||||
if (!familyData.code || !familyData.code.trim()) {
|
||||
familyData.code = familyData.name
|
||||
? familyData.name.toLowerCase().replace(/[^a-z0-9]/g, '_').replace(/_+/g, '_').replace(/^_|_$/g, '')
|
||||
: `family_${Date.now()}`;
|
||||
}
|
||||
const baseCode = familyData.code || familyData.name || 'asset_family';
|
||||
familyData.code = await generateUniqueCode(models.AssetFamily, baseCode, 'code');
|
||||
|
||||
const inputTypeIds = (assetTypes || assetTypeIds || []).map(item => typeof item === 'object' ? (item.assetTypeId || item.id) : item).filter(Boolean);
|
||||
familyData.description = this.encodeDescription(description, inputTypeIds);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { models } from '../../../shared/database/models.js';
|
||||
import { SocketService } from '../../../shared/services/socket.service.js';
|
||||
import { AuditService } from '../../../shared/services/audit.service.js';
|
||||
import { ApiError } from '../../../utils/helpers/ApiError.utils.js';
|
||||
import { generateUniqueCode } from '../../../utils/helpers/code.utils.js';
|
||||
|
||||
export class AssetTypeService {
|
||||
async getAll(query = {}) {
|
||||
@@ -22,27 +23,8 @@ export class AssetTypeService {
|
||||
}
|
||||
|
||||
async create(data, userContext = {}) {
|
||||
let rawCode = data.code ? data.code.trim() : '';
|
||||
let isAutoGenerated = !rawCode;
|
||||
|
||||
if (!rawCode) {
|
||||
rawCode = data.name ? data.name.toLowerCase().replace(/[^a-z0-9]/g, '_').replace(/_+/g, '_').replace(/^_|_$/g, '') : `ast_type_${Date.now()}`;
|
||||
isAutoGenerated = true;
|
||||
}
|
||||
|
||||
let finalCode = rawCode;
|
||||
let existing = await models.AssetType.findOne({ where: { code: finalCode } });
|
||||
if (existing) {
|
||||
if (isAutoGenerated) {
|
||||
finalCode = `${rawCode}_${Date.now().toString().slice(-4)}`;
|
||||
} else {
|
||||
const err = new Error(`Asset Type code "${rawCode}" is already in use. Please enter a different code.`);
|
||||
err.statusCode = 400;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
data.code = finalCode;
|
||||
const baseCode = data.code || data.name || 'asset_type';
|
||||
data.code = await generateUniqueCode(models.AssetType, baseCode, 'code');
|
||||
|
||||
const payload = {
|
||||
name: data.name,
|
||||
|
||||
@@ -7,6 +7,7 @@ import NotificationService from '../../notifications/notifications/notification.
|
||||
import { ApiError } from '../../../utils/helpers/ApiError.utils.js';
|
||||
import CompletenessService from './completeness.service.js';
|
||||
|
||||
|
||||
export function formatProductResponse(json) {
|
||||
if (!json) return json;
|
||||
|
||||
@@ -15,7 +16,7 @@ export function formatProductResponse(json) {
|
||||
|
||||
if (json.metadata) {
|
||||
const {
|
||||
sku, price, stock, barcode, gtin, upc, ean, country, hsn, type, shortDesc, description, categories, attributes,
|
||||
sku, price, stock, barcode, gtin, upc, ean, country, hsn, type, description, categories, attributes,
|
||||
...restMetadata
|
||||
} = json.metadata;
|
||||
|
||||
@@ -33,7 +34,6 @@ export function formatProductResponse(json) {
|
||||
country: country !== undefined ? country : '',
|
||||
hsn: hsn !== undefined ? hsn : '',
|
||||
type: type !== undefined ? type : 'simple',
|
||||
shortDesc: shortDesc !== undefined ? shortDesc : '',
|
||||
description: description !== undefined ? description : '',
|
||||
categories: Array.isArray(categories) ? categories : (json.category_id ? [json.category_id] : []),
|
||||
attributes: attributes || {},
|
||||
@@ -53,7 +53,6 @@ export function formatProductResponse(json) {
|
||||
country: '',
|
||||
hsn: '',
|
||||
type: 'simple',
|
||||
shortDesc: '',
|
||||
description: '',
|
||||
categories: json.category_id ? [json.category_id] : []
|
||||
};
|
||||
@@ -295,51 +294,43 @@ export class ProductService {
|
||||
async create(data, context = {}) {
|
||||
const transaction = await sequelize.transaction();
|
||||
try {
|
||||
// 1. SKU / Code uniqueness validation (if explicitly provided)
|
||||
if (data.code || data.sku) {
|
||||
const existing = await models.Product.findOne({
|
||||
where: { code: data.code || data.sku },
|
||||
paranoid: false,
|
||||
transaction
|
||||
// 2. Validate Family exists if provided
|
||||
const familyId = data.family_id || data.familyId;
|
||||
let family = null;
|
||||
if (familyId) {
|
||||
family = await models.Catalog.findByPk(familyId, {
|
||||
transaction,
|
||||
include: [
|
||||
{
|
||||
model: models.FamilyChannel,
|
||||
as: 'channels'
|
||||
},
|
||||
{
|
||||
model: models.AttributeSet,
|
||||
as: 'attributeSet',
|
||||
include: [
|
||||
{
|
||||
model: models.AttributeGroup,
|
||||
as: 'groups',
|
||||
include: [
|
||||
{
|
||||
model: models.Attribute,
|
||||
as: 'attributes'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
if (existing) {
|
||||
throw new Error(`Product SKU / Code "${data.code || data.sku}" already exists`);
|
||||
if (!family) {
|
||||
throw new Error('Product Family (Catalog) must exist if specified');
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Validate Family exists
|
||||
const family = await models.Catalog.findByPk(data.family_id || data.familyId, {
|
||||
transaction,
|
||||
include: [
|
||||
{
|
||||
model: models.FamilyChannel,
|
||||
as: 'channels'
|
||||
},
|
||||
{
|
||||
model: models.AttributeSet,
|
||||
as: 'attributeSet',
|
||||
include: [
|
||||
{
|
||||
model: models.AttributeGroup,
|
||||
as: 'groups',
|
||||
include: [
|
||||
{
|
||||
model: models.Attribute,
|
||||
as: 'attributes'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
if (!family) {
|
||||
throw new Error('Product Family (Catalog) is required and must exist');
|
||||
}
|
||||
|
||||
// Validate Brand is allowed by Family
|
||||
const brandId = data.brand_id || data.brandId || data.brand;
|
||||
if (brandId) {
|
||||
if (brandId && family) {
|
||||
const allowedBrands = family.completeness_rules?.allowedBrands || [];
|
||||
if (allowedBrands.length > 0 && !allowedBrands.includes(brandId)) {
|
||||
throw new Error('Selected brand is not allowed for this product family');
|
||||
@@ -348,7 +339,7 @@ export class ProductService {
|
||||
|
||||
// Validate Unit is allowed by Family
|
||||
const unitId = data.unit_id || data.unitId || data.unit;
|
||||
if (unitId) {
|
||||
if (unitId && family) {
|
||||
const allowedUnits = family.completeness_rules?.allowedUnits || [];
|
||||
if (allowedUnits.length > 0 && !allowedUnits.includes(unitId)) {
|
||||
throw new Error('Selected unit is not allowed for this product family');
|
||||
@@ -394,27 +385,38 @@ export class ProductService {
|
||||
data.code = finalCode;
|
||||
|
||||
// 4b. Autogenerate Master SKU (FAMILY_PREFIX-PRODUCT_CODE-SEQUENCE)
|
||||
let familyPrefix = (family.name || family.code || 'PRD')
|
||||
.toUpperCase()
|
||||
.replace(/[^A-Z0-9]/g, '')
|
||||
.substring(0, 4);
|
||||
if (!familyPrefix || familyPrefix.length < 2) familyPrefix = 'PRD';
|
||||
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';
|
||||
}
|
||||
|
||||
const familyProductCount = await models.Product.count({
|
||||
where: { family_id: family.id },
|
||||
transaction
|
||||
});
|
||||
const runningSeq = String(familyProductCount + 1).padStart(5, '0');
|
||||
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) {
|
||||
metadata.channels = (family.channels || []).map(c => c.channel_code);
|
||||
metadata.channels = family ? (family.channels || []).map(c => c.channel_code) : [];
|
||||
}
|
||||
|
||||
// Fetch workflow details dynamically
|
||||
const workflowCode = family.workflow_code || 'standard';
|
||||
const workflowCode = family ? (family.workflow_code || 'standard') : 'standard';
|
||||
let workflowName = 'Standard Approval';
|
||||
let currentStage = 'draft';
|
||||
|
||||
@@ -446,7 +448,6 @@ export class ProductService {
|
||||
metadata.country = data.country || '';
|
||||
metadata.hsn = data.hsn || '';
|
||||
metadata.type = data.type || 'simple';
|
||||
metadata.shortDesc = data.shortDesc || '';
|
||||
metadata.description = data.description || '';
|
||||
metadata.categories = Array.isArray(data.categories) ? data.categories : (categoryId ? [categoryId] : []);
|
||||
|
||||
@@ -455,7 +456,7 @@ export class ProductService {
|
||||
code: data.code,
|
||||
name: data.name,
|
||||
status: data.status || 'draft',
|
||||
family_id: family.id,
|
||||
family_id: family ? family.id : null,
|
||||
category_id: categoryId,
|
||||
brand_id: data.brand_id || data.brandId || data.brand,
|
||||
unit_id: data.unit_id || data.unitId || data.unit,
|
||||
@@ -468,7 +469,7 @@ export class ProductService {
|
||||
await product.update({ metadata }, { transaction });
|
||||
|
||||
const familyAttributesMap = new Map();
|
||||
if (family.attributeSet && Array.isArray(family.attributeSet.groups)) {
|
||||
if (family && family.attributeSet && Array.isArray(family.attributeSet.groups)) {
|
||||
for (const g of family.attributeSet.groups) {
|
||||
if (Array.isArray(g.attributes)) {
|
||||
for (const a of g.attributes) {
|
||||
@@ -654,7 +655,6 @@ export class ProductService {
|
||||
if (data.hasOwnProperty('country')) metadata.country = data.country;
|
||||
if (data.hasOwnProperty('hsn')) metadata.hsn = data.hsn;
|
||||
if (data.hasOwnProperty('type')) metadata.type = data.type;
|
||||
if (data.hasOwnProperty('shortDesc')) metadata.shortDesc = data.shortDesc;
|
||||
if (data.hasOwnProperty('description')) metadata.description = data.description;
|
||||
if (data.hasOwnProperty('categories')) {
|
||||
metadata.categories = Array.isArray(data.categories) ? data.categories : [];
|
||||
|
||||
@@ -8,20 +8,22 @@ export const createValidation = [
|
||||
.trim()
|
||||
.withMessage('Name must be a string'),
|
||||
body('family_id')
|
||||
.notEmpty()
|
||||
.withMessage('Product Family (family_id) is required')
|
||||
.optional({ checkFalsy: true })
|
||||
.isUUID()
|
||||
.withMessage('Product Family (family_id) must be a valid UUID'),
|
||||
body('category_id')
|
||||
.optional({ checkFalsy: true })
|
||||
.notEmpty()
|
||||
.withMessage('Category (category_id) is required')
|
||||
.isUUID()
|
||||
.withMessage('Category must be a valid UUID'),
|
||||
body('brand_id')
|
||||
.optional({ checkFalsy: true })
|
||||
.notEmpty()
|
||||
.withMessage('Brand (brand_id) is required')
|
||||
.isUUID()
|
||||
.withMessage('Brand must be a valid UUID'),
|
||||
body('unit_id')
|
||||
.optional({ checkFalsy: true })
|
||||
.notEmpty()
|
||||
.withMessage('Unit (unit_id) is required')
|
||||
.isUUID()
|
||||
.withMessage('Unit must be a valid UUID'),
|
||||
body('code')
|
||||
|
||||
@@ -2,6 +2,7 @@ import repository from './workflow.repository.js';
|
||||
import { models } from '../../shared/database/models.js';
|
||||
import { SocketService } from '../../shared/services/socket.service.js';
|
||||
import { AuditService } from '../../shared/services/audit.service.js';
|
||||
import { generateUniqueCode } from '../../utils/helpers/code.utils.js';
|
||||
|
||||
export class WorkflowService {
|
||||
async getAll(query = {}, context = {}) {
|
||||
@@ -116,21 +117,8 @@ export class WorkflowService {
|
||||
}
|
||||
|
||||
async create(data, context = {}) {
|
||||
if (!data.code || !data.code.trim()) {
|
||||
if (data.name) {
|
||||
data.code = data.name.toLowerCase().replace(/[^a-z0-9_]+/g, '_').replace(/^_+|_+$/g, '');
|
||||
}
|
||||
if (!data.code) {
|
||||
data.code = `wfk_${Date.now()}`;
|
||||
}
|
||||
}
|
||||
data.code = data.code.toLowerCase().trim();
|
||||
|
||||
// Check duplicate code
|
||||
const existing = await models.WorkflowRegistry.findOne({ where: { code: data.code } });
|
||||
if (existing) {
|
||||
throw new Error(`Workflow with code "${data.code}" already exists`);
|
||||
}
|
||||
const baseCode = data.code || data.name || 'workflow';
|
||||
data.code = await generateUniqueCode(models.WorkflowRegistry, baseCode, 'code');
|
||||
|
||||
const record = await repository.create({
|
||||
code: data.code,
|
||||
|
||||
@@ -9,7 +9,7 @@ class SocketServiceClass {
|
||||
init(server) {
|
||||
this.io = new Server(server, {
|
||||
cors: {
|
||||
origin: process.env.CORS_ORIGIN || '*',
|
||||
origin: process.env.CORS_ORIGIN ? (process.env.CORS_ORIGIN.includes(',') ? process.env.CORS_ORIGIN.split(',') : process.env.CORS_ORIGIN) : '*',
|
||||
methods: ['GET', 'POST']
|
||||
}
|
||||
});
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 237 KiB |
Reference in New Issue
Block a user