solved merge conflicts

This commit is contained in:
m7338ohd-debug
2026-07-20 11:41:16 +05:30
parent 44814a42a2
commit 4eb59e888e
16 changed files with 261 additions and 86 deletions
+12 -12
View File
@@ -49,13 +49,13 @@ export class BrandService {
action: 'CREATE',
resource: 'Brand',
resourceId: record.id,
userId: userContext.userId || 'system',
userId: context.userId || 'system',
details: data
});
// Notify tenant users
if (userContext.tenantId) {
NotificationService.notifyTenant(userContext.tenantId, userContext.userId, {
if (context.tenantId) {
NotificationService.notifyTenant(context.tenantId, context.userId, {
variant: 'brand',
action: 'created',
title: 'New brand added',
@@ -89,13 +89,13 @@ export class BrandService {
action: 'UPDATE',
resource: 'Brand',
resourceId: id,
userId: userContext.userId || 'system',
userId: context.userId || 'system',
details: data
});
// Notify tenant users
if (userContext.tenantId) {
NotificationService.notifyTenant(userContext.tenantId, userContext.userId, {
if (context.tenantId) {
NotificationService.notifyTenant(context.tenantId, context.userId, {
variant: 'brand',
action: 'updated',
title: 'Brand updated',
@@ -129,12 +129,12 @@ export class BrandService {
action: 'DELETE',
resource: 'Brand',
resourceId: id,
userId: userContext.userId || userContext.id || 'system'
userId: context.userId || context.id || 'system'
});
// Notify tenant users
if (userContext.tenantId) {
NotificationService.notifyTenant(userContext.tenantId, userContext.userId, {
if (context.tenantId) {
NotificationService.notifyTenant(context.tenantId, context.userId, {
variant: 'brand',
action: 'deleted',
title: 'Brand deleted',
@@ -162,12 +162,12 @@ export class BrandService {
action: 'ARCHIVE',
resource: 'Brand',
resourceId: id,
userId: userContext.userId || userContext.id || 'system'
userId: context.userId || context.id || 'system'
});
// Notify tenant users
if (userContext.tenantId) {
NotificationService.notifyTenant(userContext.tenantId, userContext.userId, {
if (context.tenantId) {
NotificationService.notifyTenant(context.tenantId, context.userId, {
variant: 'brand',
action: 'deleted',
title: 'Brand deleted',
@@ -40,6 +40,7 @@ export class CatalogController {
channelCount: raw.channelCount ?? 0,
attributeGroups: raw.attributeGroups ?? 0,
canDelete: raw.canDelete ?? true,
completeness: raw.completeness ?? 100,
lastUpdated: raw.updated_at || raw.updatedAt,
createdBy: raw.created_by || 'system'
};
@@ -24,11 +24,11 @@ export class Catalog extends Model {
as: 'variantAxes'
});
// M:N with AssetType for required assets
Catalog.belongsToMany(models.AssetType, {
// M:N with AssetFamily for required assets
Catalog.belongsToMany(models.AssetFamily, {
through: models.FamilyAssetRequirement,
foreignKey: 'family_id',
otherKey: 'asset_type_id',
otherKey: 'asset_family_id',
as: 'assetRequirements'
});
@@ -37,7 +37,7 @@ export class CatalogRepository {
]
},
{
model: models.AssetType,
model: models.AssetFamily,
as: 'assetRequirements',
through: { attributes: [] }
},
@@ -106,7 +106,7 @@ export class CatalogRepository {
]
},
{
model: models.AssetType,
model: models.AssetFamily,
as: 'assetRequirements',
through: { attributes: [] }
},
@@ -3,7 +3,6 @@ import { models, sequelize } 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 { ApiError } from '../../../utils/helpers/ApiError.utils.js';
export class CatalogService {
async attachCounts(record, transaction) {
@@ -66,6 +65,32 @@ export class CatalogService {
record.setDataValue('attributeGroups', attributeGroupsCount);
record.setDataValue('canDelete', productCount === 0);
// Calculate average completeness for the family
let completeness = 100;
if (productCount > 0) {
const products = await models.Product.findAll({
where: { family_id: id },
attributes: ['id'],
transaction
});
const productIds = products.map(p => p.id);
const completenessRecords = await models.ProductCompleteness.findAll({
where: {
product_id: productIds,
channel: 'default'
},
attributes: ['percentage'],
transaction
});
if (completenessRecords.length > 0) {
const totalPct = completenessRecords.reduce((sum, c) => sum + c.percentage, 0);
completeness = Math.round(totalPct / completenessRecords.length);
} else {
completeness = 0;
}
}
record.setDataValue('completeness', completeness);
// Load full Brand and Unit objects from completeness_rules IDs
const rules = record.completeness_rules || {};
let brandIds = rules.allowedBrands || [];
@@ -226,26 +251,23 @@ export class CatalogService {
variantAxes = uniqueAxes;
}
// 5. Asset Requirement Validation & Deduplication
// 5. Asset Family Validation & Deduplication
let assetRequirements = [];
if (data.assetRequirements && Array.isArray(data.assetRequirements)) {
const uniqueAssetTypes = [...new Set(data.assetRequirements)];
const existingAssetTypes = await models.AssetType.findAll({
where: { id: uniqueAssetTypes },
const uniqueAssetFamilies = [...new Set(data.assetRequirements)];
const existingAssetFamilies = await models.AssetFamily.findAll({
where: { id: uniqueAssetFamilies },
transaction
});
if (existingAssetTypes.length !== uniqueAssetTypes.length) {
throw new Error('One or more selected asset types do not exist');
if (existingAssetFamilies.length !== uniqueAssetFamilies.length) {
throw new Error('One or more selected asset families do not exist');
}
for (const at of existingAssetTypes) {
if (at.status !== 'active') {
throw new Error(`Asset type "${at.name}" is inactive`);
}
if (at.deleted_at || at.deletedAt) {
throw new Error(`Asset type "${at.name}" has been deleted`);
for (const af of existingAssetFamilies) {
if (af.status !== 'active') {
throw new Error(`Asset family "${af.name}" is inactive`);
}
}
assetRequirements = uniqueAssetTypes;
assetRequirements = uniqueAssetFamilies;
}
// 6. Channel Validation & Deduplication
@@ -337,7 +359,7 @@ export class CatalogService {
for (const assetTypeId of assetRequirements) {
await models.FamilyAssetRequirement.create({
family_id: record.id,
asset_type_id: assetTypeId
asset_family_id: assetTypeId
}, { transaction });
}
@@ -362,7 +384,7 @@ export class CatalogService {
action: 'CREATE',
resource: 'Catalog',
resourceId: record.id,
userId: userContext.userId || 'system',
userId: context.userId || 'system',
details: data
});
@@ -477,26 +499,23 @@ export class CatalogService {
variantAxes = uniqueAxes;
}
// 5. Asset Requirement Validation & Deduplication
// 5. Asset Family Validation & Deduplication
let assetRequirements = null;
if (data.assetRequirements && Array.isArray(data.assetRequirements)) {
const uniqueAssetTypes = [...new Set(data.assetRequirements)];
const existingAssetTypes = await models.AssetType.findAll({
where: { id: uniqueAssetTypes },
const uniqueAssetFamilies = [...new Set(data.assetRequirements)];
const existingAssetFamilies = await models.AssetFamily.findAll({
where: { id: uniqueAssetFamilies },
transaction
});
if (existingAssetTypes.length !== uniqueAssetTypes.length) {
throw new Error('One or more selected asset types do not exist');
if (existingAssetFamilies.length !== uniqueAssetFamilies.length) {
throw new Error('One or more selected asset families do not exist');
}
for (const at of existingAssetTypes) {
if (at.status !== 'active') {
throw new Error(`Asset type "${at.name}" is inactive`);
}
if (at.deleted_at || at.deletedAt) {
throw new Error(`Asset type "${at.name}" has been deleted`);
for (const af of existingAssetFamilies) {
if (af.status !== 'active') {
throw new Error(`Asset family "${af.name}" is inactive`);
}
}
assetRequirements = uniqueAssetTypes;
assetRequirements = uniqueAssetFamilies;
}
// 6. Channel Validation & Deduplication
@@ -601,7 +620,7 @@ export class CatalogService {
for (const assetTypeId of assetRequirements) {
await models.FamilyAssetRequirement.create({
family_id: id,
asset_type_id: assetTypeId
asset_family_id: assetTypeId
}, { transaction });
}
await AuditService.log({ action: 'ASSET_REQUIREMENTS_CHANGED', resource: 'Catalog', resourceId: id, userId: context.userId || 'system', details: assetRequirements });
@@ -676,7 +695,7 @@ export class CatalogService {
action: 'DELETE',
resource: 'Catalog',
resourceId: id,
userId: userContext.userId || 'system'
userId: context.userId || 'system'
});
return true;
@@ -11,7 +11,7 @@ export default (sequelize) => {
allowNull: false,
primaryKey: true
},
asset_type_id: {
asset_family_id: {
type: DataTypes.UUID,
allowNull: false,
primaryKey: true
@@ -199,8 +199,8 @@ export class CategorieService {
});
// Notify tenant users
if (userContext.tenantId) {
NotificationService.notifyTenant(userContext.tenantId, userContext.userId, {
if (context.tenantId) {
NotificationService.notifyTenant(context.tenantId, context.userId, {
variant: 'category',
action: 'updated',
title: 'Category updated',
@@ -262,12 +262,12 @@ export class CategorieService {
action: 'DELETE',
resource: 'Categorie',
resourceId: id,
userId: userContext.userId || 'system'
userId: context.userId || 'system'
});
// Notify tenant users
if (userContext.tenantId) {
NotificationService.notifyTenant(userContext.tenantId, userContext.userId, {
if (context.tenantId) {
NotificationService.notifyTenant(context.tenantId, context.userId, {
variant: 'category',
action: 'deleted',
title: 'Category deleted',
@@ -303,12 +303,12 @@ export class CategorieService {
action: 'ARCHIVE',
resource: 'Categorie',
resourceId: id,
userId: userContext.userId || 'system'
userId: context.userId || 'system'
});
// Notify tenant users
if (userContext.tenantId) {
NotificationService.notifyTenant(userContext.tenantId, userContext.userId, {
if (context.tenantId) {
NotificationService.notifyTenant(context.tenantId, context.userId, {
variant: 'category',
action: 'deleted',
title: 'Category deleted',
@@ -342,7 +342,7 @@ export class CategorieService {
action: 'RESTORE',
resource: 'Categorie',
resourceId: id,
userId: userContext.userId || 'system'
userId: context.userId || 'system'
});
return restored;
@@ -61,8 +61,8 @@ export class AssetFamilyService {
let dbReqIds = [];
try {
if (models.FamilyAssetRequirement) {
const reqs = await models.FamilyAssetRequirement.findAll({ where: { family_id: familyData.id } });
dbReqIds = reqs.map(r => r.asset_type_id).filter(Boolean);
// FamilyAssetRequirement links Catalog to AssetFamily, so it does not contain asset_type_ids
dbReqIds = [];
}
} catch (e) {
// ignore
+3 -3
View File
@@ -149,7 +149,7 @@ export class AssetService {
action: 'CREATE',
resource: 'Asset',
resourceId: record.id,
userId: userContext.userId || 'system',
userId: context.userId || 'system',
details: data
});
@@ -212,7 +212,7 @@ export class AssetService {
action: 'UPDATE',
resource: 'Asset',
resourceId: id,
userId: userContext.userId || 'system',
userId: context.userId || 'system',
details: data
});
@@ -347,7 +347,7 @@ export class AssetService {
action: 'DELETE',
resource: 'Asset',
resourceId: id,
userId: userContext.userId || 'system'
userId: context.userId || 'system'
});
return true;
@@ -30,7 +30,7 @@ export class CompletenessService {
]
},
{
model: models.AssetType,
model: models.AssetFamily,
as: 'assetRequirements'
},
{
@@ -80,10 +80,36 @@ export class CompletenessService {
}
const allAttributes = Array.from(attributesMap.values());
const requiredAttributes = allAttributes.filter(attr => attr.is_required);
const requiredAssetTypes = family.assetRequirements || [];
const requiredAssetFamilies = family.assetRequirements || [];
const requiredChannels = family.channels || [];
const totalCount = requiredAttributes.length + requiredAssetTypes.length + requiredChannels.length;
// Resolve required asset types dynamically across all assigned asset families
const requiredAssetTypes = [];
for (const assetFamily of requiredAssetFamilies) {
let typeIds = [];
const desc = assetFamily.description || '';
if (desc.includes('__AT_IDS__:')) {
const parts = desc.split('__AT_IDS__:');
typeIds = (parts[1] || '').split(',').map(s => s.trim()).filter(Boolean);
}
if (typeIds.length > 0) {
const types = await models.AssetType.findAll({
where: {
id: typeIds,
is_required: true
},
transaction
});
requiredAssetTypes.push(...types);
}
}
// Deduplicate required asset types
const uniqueRequiredAssetTypesMap = new Map();
requiredAssetTypes.forEach(at => uniqueRequiredAssetTypesMap.set(at.id, at));
const uniqueRequiredAssetTypes = Array.from(uniqueRequiredAssetTypesMap.values());
const totalCount = requiredAttributes.length + uniqueRequiredAssetTypes.length + requiredChannels.length;
let fulfilledCount = 0;
const missingAttributes = [];
@@ -102,7 +128,7 @@ export class CompletenessService {
// 2. Validate required assets
const assignedAssetTypeIds = (product.productAssets || []).map(pa => pa.asset?.assetType?.id).filter(Boolean);
for (const assetType of requiredAssetTypes) {
for (const assetType of uniqueRequiredAssetTypes) {
if (assignedAssetTypeIds.includes(assetType.id)) {
fulfilledCount++;
} else {
@@ -27,7 +27,7 @@ export class CompletenessResolverService {
const familyReqs = await models.FamilyAssetRequirement.findAll({
where: { family_id: product.family_id }
});
const mappedIds = familyReqs.map(r => r.asset_type_id);
const mappedIds = familyReqs.map(r => r.asset_family_id);
const assetFamilies = await models.AssetFamily.findAll({
where: { id: mappedIds }
@@ -46,8 +46,12 @@ export class CompletenessResolverService {
const assetFamiliesCompleteness = await Promise.all(
assetFamilies.map(async (family) => {
const afReqs = await models.FamilyAssetRequirement.findAll({ where: { family_id: family.id } });
const typeIds = afReqs.map(r => r.asset_type_id);
let typeIds = [];
const desc = family.description || '';
if (desc.includes('__AT_IDS__:')) {
const parts = desc.split('__AT_IDS__:');
typeIds = (parts[1] || '').split(',').map(s => s.trim()).filter(Boolean);
}
let types = [];
if (typeIds.length > 0) {
@@ -406,13 +406,13 @@ export class ProductService {
action: 'CREATE',
resource: 'Product',
resourceId: product.id,
userId: userContext.userId || 'system',
userId: context.userId || 'system',
details: data
});
// Notify tenant users
if (userContext.tenantId) {
NotificationService.notifyTenant(userContext.tenantId, userContext.userId, {
if (context.tenantId) {
NotificationService.notifyTenant(context.tenantId, context.userId, {
variant: 'product',
action: 'created',
title: 'New product created',
@@ -568,13 +568,13 @@ export class ProductService {
action: 'UPDATE',
resource: 'Product',
resourceId: id,
userId: userContext.userId || 'system',
userId: context.userId || 'system',
details: data
});
// Notify tenant users
if (userContext.tenantId) {
NotificationService.notifyTenant(userContext.tenantId, userContext.userId, {
if (context.tenantId) {
NotificationService.notifyTenant(context.tenantId, context.userId, {
variant: 'product',
action: 'updated',
title: 'Product updated',
@@ -605,12 +605,12 @@ export class ProductService {
action: 'DELETE',
resource: 'Product',
resourceId: id,
userId: userContext.userId || userContext.id || 'system'
userId: context.userId || context.id || 'system'
});
// Notify tenant users
if (userContext.tenantId) {
NotificationService.notifyTenant(userContext.tenantId, userContext.userId, {
if (context.tenantId) {
NotificationService.notifyTenant(context.tenantId, context.userId, {
variant: 'product',
action: 'deleted',
title: 'Product deleted',
@@ -637,12 +637,12 @@ export class ProductService {
action: 'ARCHIVE',
resource: 'Product',
resourceId: id,
userId: userContext.userId || userContext.id || 'system'
userId: context.userId || context.id || 'system'
});
// Notify tenant users
if (userContext.tenantId) {
NotificationService.notifyTenant(userContext.tenantId, userContext.userId, {
if (context.tenantId) {
NotificationService.notifyTenant(context.tenantId, context.userId, {
variant: 'product',
action: 'deleted',
title: 'Product deleted',