correction

This commit is contained in:
MohamedHasan07
2026-07-21 17:01:52 +05:30
parent 56138fd780
commit be9d3b969f
13 changed files with 253 additions and 147 deletions
@@ -19,7 +19,7 @@ export default (sequelize) => {
allowNull: false
},
changed_by: {
type: DataTypes.UUID,
type: DataTypes.INTEGER,
allowNull: true
},
changes: {
@@ -19,7 +19,7 @@ export default (sequelize) => {
allowNull: false
},
changed_by: {
type: DataTypes.UUID,
type: DataTypes.INTEGER,
allowNull: true
},
changes: {
@@ -5,13 +5,13 @@ function serializeAttribute(attr) {
const json = attr.toJSON ? attr.toJSON() : { ...attr };
// Mapping to camelCase
json.isRequired = json.is_required ?? false;
json.isUnique = json.is_unique ?? false;
json.isLocalizable = json.is_localizable ?? false;
json.isVariantEligible = json.is_variant_eligible ?? false;
json.isSearchable = json.is_searchable ?? false;
json.isFilterable = json.is_filterable ?? false;
json.isChannelSpecific = json.is_channel_specific ?? false;
json.isRequired = json.is_required ?? json.isRequired ?? false;
json.isUnique = json.is_unique ?? json.isUnique ?? false;
json.isLocalizable = json.is_localizable ?? json.isLocalizable ?? false;
json.isVariantEligible = json.is_variant_eligible ?? json.isVariantEligible ?? false;
json.isSearchable = json.is_searchable ?? json.isSearchable ?? false;
json.isFilterable = json.is_filterable ?? json.isFilterable ?? false;
json.isChannelSpecific = json.is_channel_specific ?? json.isChannelSpecific ?? false;
json.minLength = json.min_length ?? 0;
json.maxLength = json.max_length ?? 255;
json.regexPattern = json.regex_pattern ?? null;
@@ -119,15 +119,15 @@ export default (sequelize) => {
defaultValue: 0
},
created_by: {
type: DataTypes.UUID,
type: DataTypes.INTEGER,
allowNull: true
},
updated_by: {
type: DataTypes.UUID,
type: DataTypes.INTEGER,
allowNull: true
},
deleted_by: {
type: DataTypes.UUID,
type: DataTypes.INTEGER,
allowNull: true
},
help_text: {
@@ -19,7 +19,7 @@ export default (sequelize) => {
allowNull: false
},
changed_by: {
type: DataTypes.UUID,
type: DataTypes.INTEGER,
allowNull: true
},
changes: {
@@ -1,49 +1,49 @@
import { Model, DataTypes } from 'sequelize';
export class Role extends Model {
static associate(models) {}
static associate(models) { }
}
export default (sequelize) => {
Role.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
tenant_id: {
type: DataTypes.INTEGER,
allowNull: true
type: DataTypes.INTEGER,
allowNull: true
},
role_code: {
type: DataTypes.STRING(50),
allowNull: false
type: DataTypes.STRING(50),
allowNull: false
},
role_name: {
type: DataTypes.STRING(100),
allowNull: false
type: DataTypes.STRING(100),
allowNull: false
},
description: {
type: DataTypes.TEXT
type: DataTypes.TEXT
},
role_type: {
type: DataTypes.STRING(20),
defaultValue: 'tenant',
allowNull: false
type: DataTypes.STRING(20),
defaultValue: 'tenant',
allowNull: false
},
is_system_role: {
type: DataTypes.BOOLEAN,
defaultValue: false
type: DataTypes.BOOLEAN,
defaultValue: false
},
status: {
type: DataTypes.BOOLEAN,
defaultValue: true
type: DataTypes.BOOLEAN,
defaultValue: true
},
created_by: {
type: DataTypes.INTEGER
type: DataTypes.INTEGER
},
updated_by: {
type: DataTypes.INTEGER
type: DataTypes.INTEGER
}
}, {
sequelize,
@@ -7,6 +7,13 @@ export class RoleRepository {
const tenantFilter = context.userType !== 'platform' && context.tenantId
? { tenant_id: context.tenantId }
: {};
const { where: optionsWhere, ...restOptions } = options;
const whereClause = {
...(optionsWhere || {}),
...tenantFilter
};
return await models.Role.findAll({
include: [
{
@@ -15,11 +22,8 @@ export class RoleRepository {
through: { attributes: ['can_view', 'can_create', 'can_edit', 'can_delete', 'can_alter', 'can_import', 'can_export'] }
}
],
...options,
where: {
...(options.where || {}),
...tenantFilter
}
...restOptions,
where: whereClause
});
}
@@ -48,7 +52,7 @@ export class RoleRepository {
roleData.tenant_id = context.tenantId;
}
const role = await models.Role.create(roleData, { transaction });
if (permissions && permissions.length > 0) {
const validPermissions = permissions.filter(p => p.node_id != null);
if (validPermissions.length > 0) {
@@ -66,7 +70,7 @@ export class RoleRepository {
await models.RolePermission.bulkCreate(rolePermissions, { transaction });
}
}
await transaction.commit();
return await this.findById(role.id, {}, context);
} catch (error) {
@@ -137,9 +141,9 @@ export class RoleRepository {
// Delete associations first
await models.RolePermission.destroy({ where: { role_id: id }, transaction });
await models.UserRole.destroy({ where: { role_id: id }, transaction });
await role.destroy({ transaction });
await transaction.commit();
return true;
} catch (error) {
@@ -1,6 +1,7 @@
import repository from './role.repository.js';
import { models } from '../../../shared/database/models.js';
import { ApiError } from '../../../utils/helpers/ApiError.utils.js';
import { Op } from 'sequelize';
export class RoleService {
async getAll(query = {}, context = {}) {
@@ -8,13 +9,17 @@ export class RoleService {
}
async getByTenant(tenantId, context = {}) {
if (!tenantId || tenantId === 'platform' || tenantId === 'null' || tenantId === 'undefined') {
return await repository.findAll({ where: { tenant_id: null } }, context);
}
if (tenantId === 'all') {
return await repository.findAll({}, context);
}
const numericId = isNaN(Number(tenantId)) ? tenantId : Number(tenantId);
return await repository.findAll({
where: tenantId
? { tenant_id: tenantId }
: { tenant_id: null }
where: {
tenant_id: numericId
}
}, context);
}
@@ -60,6 +65,15 @@ export class RoleService {
}
async delete(id, context = {}) {
const role = await repository.findById(id, {}, context);
if (!role) {
throw new ApiError(404, 'Role not found');
}
if (role.is_system_role || role.role_code === 'SUPER_ADMIN' || role.role_code === 'SUPERADMIN' || role.role_name?.toLowerCase().includes('super admin')) {
throw new ApiError(403, 'Forbidden: Super Admin and System roles cannot be deleted');
}
const deleted = await repository.delete(id, context);
if (!deleted) {
throw new ApiError(404, 'Role not found');
@@ -82,6 +82,22 @@ export class UserService {
}
async delete(id, context = {}) {
const user = await repository.findById(id, {}, context);
if (!user) {
throw new ApiError(404, 'User not found');
}
const isSuperAdmin = user.email === 'admin@admin.com' || (user.roles && user.roles.some(r =>
r.role_code === 'SUPER_ADMIN' ||
r.role_code === 'SUPERADMIN' ||
r.role_name?.toLowerCase().includes('super admin') ||
r.is_system_role
));
if (isSuperAdmin) {
throw new ApiError(403, 'Forbidden: Super Admin user cannot be deleted');
}
const deleted = await repository.delete(id, context);
if (!deleted) {
throw new ApiError(404, 'User not found');
+83 -102
View File
@@ -9,53 +9,83 @@ export class CatalogService {
if (!record) return null;
const id = record.id;
// Count associated products
const productCount = await models.Product.count({
where: { family_id: id },
transaction
});
// Run primary counts and relation queries in parallel
const [
productCount,
attributeCount,
variantAxisCount,
assetRequirementCount,
channelCount,
attributeRelations
] = await Promise.all([
models.Product.count({ where: { family_id: id }, transaction }),
models.FamilyAttribute.count({ where: { family_id: id }, transaction }),
models.FamilyVariantAxis.count({ where: { family_id: id }, transaction }),
models.FamilyAssetRequirement.count({ where: { family_id: id }, transaction }),
models.FamilyChannel.count({ where: { family_id: id }, transaction }),
models.FamilyAttribute.findAll({ where: { family_id: id }, transaction })
]);
// Count associated attributes
const attributeCount = await models.FamilyAttribute.count({
where: { family_id: id },
transaction
});
// Count associated variant axes
const variantAxisCount = await models.FamilyVariantAxis.count({
where: { family_id: id },
transaction
});
// Count associated asset requirements
const assetRequirementCount = await models.FamilyAssetRequirement.count({
where: { family_id: id },
transaction
});
// Count associated channels
const channelCount = await models.FamilyChannel.count({
where: { family_id: id },
transaction
});
// Count unique attribute groups
const attributeRelations = await models.FamilyAttribute.findAll({
where: { family_id: id },
transaction
});
const attributeIds = attributeRelations.map(ar => ar.attribute_id);
let attributeGroupsCount = 0;
if (attributeIds.length > 0) {
const groupRelations = await models.AttributeGroupAttribute.findAll({
where: { attribute_id: attributeIds },
attributes: ['group_id'],
raw: true,
transaction
});
const uniqueGroups = new Set(groupRelations.map(gr => gr.group_id));
attributeGroupsCount = uniqueGroups.size;
// Extract allowedBrands and allowedUnits from completeness_rules
const rules = record.completeness_rules || {};
let brandIds = rules.allowedBrands || [];
if (typeof brandIds === 'string') {
try { brandIds = JSON.parse(brandIds); } catch (e) { brandIds = []; }
}
let unitIds = rules.allowedUnits || [];
if (typeof unitIds === 'string') {
try { unitIds = JSON.parse(unitIds); } catch (e) { unitIds = []; }
}
// Run sub-queries concurrently via Promise.all
const [groupRelations, completenessRecords, allowedBrands, allowedUnits] = await Promise.all([
attributeIds.length > 0
? models.AttributeGroupAttribute.findAll({
where: { attribute_id: attributeIds },
attributes: ['group_id'],
raw: true,
transaction
})
: Promise.resolve([]),
productCount > 0
? models.Product.findAll({
where: { family_id: id },
attributes: ['id'],
transaction
}).then(products => {
const pIds = products.map(p => p.id);
return pIds.length > 0
? models.ProductCompleteness.findAll({
where: { product_id: pIds, channel: 'default' },
attributes: ['percentage'],
transaction
})
: Promise.resolve([]);
})
: Promise.resolve([]),
Array.isArray(brandIds) && brandIds.length > 0
? models.Brand.findAll({
where: { id: brandIds },
attributes: ['id', 'name', 'code', 'status'],
transaction
})
: Promise.resolve([]),
Array.isArray(unitIds) && unitIds.length > 0
? models.Unit.findAll({
where: { id: unitIds },
attributes: ['id', 'name', 'code', 'symbol', 'status'],
transaction
})
: Promise.resolve([])
]);
const uniqueGroups = new Set(groupRelations.map(gr => gr.group_id));
const attributeGroupsCount = uniqueGroups.size;
record.setDataValue('productCount', productCount);
record.setDataValue('attributeCount', attributeCount);
@@ -65,63 +95,16 @@ 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;
}
if (productCount > 0 && completenessRecords.length > 0) {
const totalPct = completenessRecords.reduce((sum, c) => sum + c.percentage, 0);
completeness = Math.round(totalPct / completenessRecords.length);
} else if (productCount > 0) {
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 || [];
if (typeof brandIds === 'string') {
try { brandIds = JSON.parse(brandIds); } catch (e) { brandIds = []; }
}
if (Array.isArray(brandIds) && brandIds.length > 0) {
const allowedBrands = await models.Brand.findAll({
where: { id: brandIds },
attributes: ['id', 'name', 'code', 'status'],
transaction
});
record.setDataValue('allowedBrands', allowedBrands);
} else {
record.setDataValue('allowedBrands', []);
}
let unitIds = rules.allowedUnits || [];
if (typeof unitIds === 'string') {
try { unitIds = JSON.parse(unitIds); } catch (e) { unitIds = []; }
}
if (Array.isArray(unitIds) && unitIds.length > 0) {
const allowedUnits = await models.Unit.findAll({
where: { id: unitIds },
attributes: ['id', 'name', 'code', 'symbol', 'status'],
transaction
});
record.setDataValue('allowedUnits', allowedUnits);
} else {
record.setDataValue('allowedUnits', []);
}
record.setDataValue('allowedBrands', allowedBrands);
record.setDataValue('allowedUnits', allowedUnits);
return record;
}
@@ -135,10 +118,8 @@ export class CatalogService {
// Find all records
const records = await repository.findAll({ where }, context);
// Attach counts
for (const record of records) {
await this.attachCounts(record);
}
// Attach counts concurrently using Promise.all
await Promise.all(records.map(record => this.attachCounts(record)));
return records;
}
@@ -0,0 +1,91 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
// 1. Alter attributes table
const attributesTable = await queryInterface.describeTable('attributes').catch(() => null);
if (attributesTable) {
if (attributesTable.created_by) {
await queryInterface.removeColumn('attributes', 'created_by');
}
if (attributesTable.updated_by) {
await queryInterface.removeColumn('attributes', 'updated_by');
}
if (attributesTable.deleted_by) {
await queryInterface.removeColumn('attributes', 'deleted_by');
}
await queryInterface.addColumn('attributes', 'created_by', {
type: Sequelize.INTEGER,
allowNull: true
});
await queryInterface.addColumn('attributes', 'updated_by', {
type: Sequelize.INTEGER,
allowNull: true
});
await queryInterface.addColumn('attributes', 'deleted_by', {
type: Sequelize.INTEGER,
allowNull: true
});
}
// 2. Alter history tables
const historyTables = ['attribute_history', 'attribute_set_history', 'attribute_group_history'];
for (const table of historyTables) {
const tableInfo = await queryInterface.describeTable(table).catch(() => null);
if (tableInfo) {
if (tableInfo.changed_by) {
await queryInterface.removeColumn(table, 'changed_by');
}
await queryInterface.addColumn(table, 'changed_by', {
type: Sequelize.INTEGER,
allowNull: true
});
}
}
},
down: async (queryInterface, Sequelize) => {
// Revert attributes table
const attributesTable = await queryInterface.describeTable('attributes').catch(() => null);
if (attributesTable) {
if (attributesTable.created_by) {
await queryInterface.removeColumn('attributes', 'created_by');
}
if (attributesTable.updated_by) {
await queryInterface.removeColumn('attributes', 'updated_by');
}
if (attributesTable.deleted_by) {
await queryInterface.removeColumn('attributes', 'deleted_by');
}
await queryInterface.addColumn('attributes', 'created_by', {
type: Sequelize.UUID,
allowNull: true
});
await queryInterface.addColumn('attributes', 'updated_by', {
type: Sequelize.UUID,
allowNull: true
});
await queryInterface.addColumn('attributes', 'deleted_by', {
type: Sequelize.UUID,
allowNull: true
});
}
// Revert history tables
const historyTables = ['attribute_history', 'attribute_set_history', 'attribute_group_history'];
for (const table of historyTables) {
const tableInfo = await queryInterface.describeTable(table).catch(() => null);
if (tableInfo) {
if (tableInfo.changed_by) {
await queryInterface.removeColumn(table, 'changed_by');
}
await queryInterface.addColumn(table, 'changed_by', {
type: Sequelize.UUID,
allowNull: true
});
}
}
}
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB