diff --git a/.env.local b/.env.local index 6c6f3f4..147fe9c 100644 --- a/.env.local +++ b/.env.local @@ -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 diff --git a/app.js b/app.js index a9c247a..02fbd44 100644 --- a/app.js +++ b/app.js @@ -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'] })); diff --git a/package-lock.json b/package-lock.json index 582fdbc..eb22e40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/src/features/attributes/attributeGroups/attributeGroup.service.js b/src/features/attributes/attributeGroups/attributeGroup.service.js index 2a95dae..3846865 100644 --- a/src/features/attributes/attributeGroups/attributeGroup.service.js +++ b/src/features/attributes/attributeGroups/attributeGroup.service.js @@ -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 }); diff --git a/src/features/attributes/attributeSets/attributeSet.service.js b/src/features/attributes/attributeSets/attributeSet.service.js index 4ace2e5..a5591c0 100644 --- a/src/features/attributes/attributeSets/attributeSet.service.js +++ b/src/features/attributes/attributeSets/attributeSet.service.js @@ -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 }); diff --git a/src/features/attributes/attributes/attribute.service.js b/src/features/attributes/attributes/attribute.service.js index eb0b029..6f0a945 100644 --- a/src/features/attributes/attributes/attribute.service.js +++ b/src/features/attributes/attributes/attribute.service.js @@ -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(); diff --git a/src/features/brands/brands/brand.service.js b/src/features/brands/brands/brand.service.js index 22f7a33..fc93472 100644 --- a/src/features/brands/brands/brand.service.js +++ b/src/features/brands/brands/brand.service.js @@ -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); diff --git a/src/features/brands/units/unit.service.js b/src/features/brands/units/unit.service.js index 7d92c96..9c26ba6 100644 --- a/src/features/brands/units/unit.service.js +++ b/src/features/brands/units/unit.service.js @@ -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); diff --git a/src/features/brands/units/unit.validation.js b/src/features/brands/units/unit.validation.js index a7e5f45..cf2cf0b 100644 --- a/src/features/brands/units/unit.validation.js +++ b/src/features/brands/units/unit.validation.js @@ -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') diff --git a/src/features/catalogs/catalogs/catalog.service.js b/src/features/catalogs/catalogs/catalog.service.js index 861fd91..fc66c4c 100644 --- a/src/features/catalogs/catalogs/catalog.service.js +++ b/src/features/catalogs/catalogs/catalog.service.js @@ -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, { diff --git a/src/features/categories/categories/categorie.service.js b/src/features/categories/categories/categorie.service.js index 2e7c0dd..38bd55f 100644 --- a/src/features/categories/categories/categorie.service.js +++ b/src/features/categories/categories/categorie.service.js @@ -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; diff --git a/src/features/channels/channelTypes/channelType.service.js b/src/features/channels/channelTypes/channelType.service.js index 5ea3ca0..5c0c5c2 100644 --- a/src/features/channels/channelTypes/channelType.service.js +++ b/src/features/channels/channelTypes/channelType.service.js @@ -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); diff --git a/src/features/channels/channels/channel.service.js b/src/features/channels/channels/channel.service.js index 7771a22..a97548f 100644 --- a/src/features/channels/channels/channel.service.js +++ b/src/features/channels/channels/channel.service.js @@ -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 diff --git a/src/features/media/assetFamilies/assetFamily.service.js b/src/features/media/assetFamilies/assetFamily.service.js index 95cbb3e..d1ea064 100644 --- a/src/features/media/assetFamilies/assetFamily.service.js +++ b/src/features/media/assetFamilies/assetFamily.service.js @@ -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); diff --git a/src/features/media/assetTypes/assetType.service.js b/src/features/media/assetTypes/assetType.service.js index 82d52be..56b8c95 100644 --- a/src/features/media/assetTypes/assetType.service.js +++ b/src/features/media/assetTypes/assetType.service.js @@ -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, diff --git a/src/features/products/products/product.service.js b/src/features/products/products/product.service.js index 44105c7..e30a7de 100644 --- a/src/features/products/products/product.service.js +++ b/src/features/products/products/product.service.js @@ -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 : []; diff --git a/src/features/products/products/product.validation.js b/src/features/products/products/product.validation.js index 9c10e10..be791ca 100644 --- a/src/features/products/products/product.validation.js +++ b/src/features/products/products/product.validation.js @@ -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') diff --git a/src/features/workflows/workflow.service.js b/src/features/workflows/workflow.service.js index 0f312a6..289ba9f 100644 --- a/src/features/workflows/workflow.service.js +++ b/src/features/workflows/workflow.service.js @@ -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, diff --git a/src/shared/services/socket.service.js b/src/shared/services/socket.service.js index 8a260e0..9116a22 100644 --- a/src/shared/services/socket.service.js +++ b/src/shared/services/socket.service.js @@ -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'] } }); diff --git a/uploads/file-1785236926450-844648962.jpg b/uploads/file-1785236926450-844648962.jpg new file mode 100644 index 0000000..a5f3ea5 Binary files /dev/null and b/uploads/file-1785236926450-844648962.jpg differ