upadted flow
This commit is contained in:
@@ -92,7 +92,7 @@ export class CatalogRepository {
|
||||
{
|
||||
model: models.Attribute,
|
||||
as: 'variantAxes',
|
||||
through: { attributes: [] },
|
||||
through: { attributes: ['id', 'required', 'display_order', 'active'] },
|
||||
required: false,
|
||||
include: [
|
||||
{
|
||||
|
||||
@@ -101,6 +101,22 @@ export class CatalogService {
|
||||
throw new ApiError(404, 'Product Family not found');
|
||||
}
|
||||
await this.attachCounts(record);
|
||||
|
||||
if (record.variantAxes && Array.isArray(record.variantAxes)) {
|
||||
for (const axisAttr of record.variantAxes) {
|
||||
const throughId = axisAttr.FamilyVariantAxis ? axisAttr.FamilyVariantAxis.id : null;
|
||||
if (throughId) {
|
||||
const suggestedVals = await models.FamilyVariantAxisValue.findAll({
|
||||
where: { axis_id: throughId },
|
||||
order: [['sort_order', 'ASC']]
|
||||
});
|
||||
axisAttr.setDataValue('suggestedValues', suggestedVals.map(v => v.value));
|
||||
} else {
|
||||
axisAttr.setDataValue('suggestedValues', []);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@@ -189,13 +205,15 @@ export class CatalogService {
|
||||
// 4. Variant Strategy Validation & Deduplication
|
||||
let variantAxes = [];
|
||||
if (data.variantAxes && Array.isArray(data.variantAxes)) {
|
||||
const uniqueAxes = [...new Set(data.variantAxes)];
|
||||
const attributesSet = new Set(attributes);
|
||||
const invalidAxes = uniqueAxes.filter(axis => !attributesSet.has(axis));
|
||||
const invalidAxes = data.variantAxes.filter(axis => {
|
||||
const attrId = typeof axis === 'string' ? axis : (axis.attributeId || axis.attribute_id || axis.id);
|
||||
return !attrId || !attributesSet.has(attrId);
|
||||
});
|
||||
if (invalidAxes.length > 0) {
|
||||
throw new Error('Variant axes must be a subset of family attributes');
|
||||
}
|
||||
variantAxes = uniqueAxes;
|
||||
variantAxes = data.variantAxes;
|
||||
}
|
||||
|
||||
// 5. Asset Family Validation & Deduplication
|
||||
@@ -271,13 +289,23 @@ export class CatalogService {
|
||||
throw new Error(`Total completeness rules weight must equal 100% (currently ${totalWeight}%)`);
|
||||
}
|
||||
|
||||
// Category Resolution
|
||||
const rawCategoryId = data.category || data.category_id || data.categoryId || null;
|
||||
let categoryId = null;
|
||||
if (rawCategoryId) {
|
||||
const catObj = await models.Categorie.findByPk(rawCategoryId, { transaction });
|
||||
if (catObj) {
|
||||
categoryId = catObj.id;
|
||||
}
|
||||
}
|
||||
|
||||
// 9. Save Catalog
|
||||
const createData = {
|
||||
code: data.code,
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
status: data.status || 'draft',
|
||||
category_id: null,
|
||||
category_id: categoryId,
|
||||
workflow_code: workflowCode,
|
||||
completeness_rules: completenessRules,
|
||||
attribute_set_id: attributeSetId
|
||||
@@ -295,11 +323,32 @@ export class CatalogService {
|
||||
}
|
||||
|
||||
// Save variant axis relationships
|
||||
for (const attributeId of variantAxes) {
|
||||
await models.FamilyVariantAxis.create({
|
||||
for (let i = 0; i < variantAxes.length; i++) {
|
||||
const item = variantAxes[i];
|
||||
const attrId = typeof item === 'string' ? item : (item.attributeId || item.attribute_id || item.id);
|
||||
const required = typeof item === 'object' && item.required !== undefined ? !!item.required : true;
|
||||
const active = typeof item === 'object' && item.active !== undefined ? !!item.active : true;
|
||||
const displayOrder = typeof item === 'object' && item.display_order !== undefined ? item.display_order : i;
|
||||
|
||||
const axisRecord = await models.FamilyVariantAxis.create({
|
||||
family_id: record.id,
|
||||
attribute_id: attributeId
|
||||
attribute_id: attrId,
|
||||
required,
|
||||
display_order: displayOrder,
|
||||
active
|
||||
}, { transaction });
|
||||
|
||||
const suggestedValues = typeof item === 'object' ? (item.suggestedValues || item.suggested_values || []) : [];
|
||||
for (let j = 0; j < suggestedValues.length; j++) {
|
||||
const valItem = suggestedValues[j];
|
||||
const valText = typeof valItem === 'string' ? valItem : valItem.value;
|
||||
if (!valText) continue;
|
||||
await models.FamilyVariantAxisValue.create({
|
||||
axis_id: axisRecord.id,
|
||||
value: valText,
|
||||
sort_order: typeof valItem === 'object' && valItem.sort_order !== undefined ? valItem.sort_order : j
|
||||
}, { transaction });
|
||||
}
|
||||
}
|
||||
|
||||
// Save asset requirements
|
||||
@@ -420,14 +469,16 @@ export class CatalogService {
|
||||
// 4. Variant Strategy Validation & Deduplication
|
||||
let variantAxes = null;
|
||||
if (data.variantAxes && Array.isArray(data.variantAxes)) {
|
||||
const uniqueAxes = [...new Set(data.variantAxes)];
|
||||
const targetAttributes = attributes || (await record.getAttributes({ transaction })).map(a => a.id);
|
||||
const attributesSet = new Set(targetAttributes);
|
||||
const invalidAxes = uniqueAxes.filter(axis => !attributesSet.has(axis));
|
||||
const invalidAxes = data.variantAxes.filter(axis => {
|
||||
const attrId = typeof axis === 'string' ? axis : (axis.attributeId || axis.attribute_id || axis.id);
|
||||
return !attrId || !attributesSet.has(attrId);
|
||||
});
|
||||
if (invalidAxes.length > 0) {
|
||||
throw new Error('Variant axes must be a subset of family attributes');
|
||||
}
|
||||
variantAxes = uniqueAxes;
|
||||
variantAxes = data.variantAxes;
|
||||
}
|
||||
|
||||
// 5. Asset Family Validation & Deduplication
|
||||
@@ -509,6 +560,22 @@ export class CatalogService {
|
||||
throw new Error(`Total completeness rules weight must equal 100% (currently ${totalWeight}%)`);
|
||||
}
|
||||
|
||||
// Category Resolution for Update
|
||||
const rawCategoryId = data.hasOwnProperty('category') ? data.category : (data.hasOwnProperty('category_id') ? data.category_id : (data.hasOwnProperty('categoryId') ? data.categoryId : undefined));
|
||||
let categoryId = undefined;
|
||||
if (rawCategoryId !== undefined) {
|
||||
if (rawCategoryId) {
|
||||
const catObj = await models.Categorie.findByPk(rawCategoryId, { transaction });
|
||||
if (catObj) {
|
||||
categoryId = catObj.id;
|
||||
} else {
|
||||
categoryId = null;
|
||||
}
|
||||
} else {
|
||||
categoryId = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 9. Update Catalog
|
||||
const updateData = {
|
||||
name: data.name || record.name,
|
||||
@@ -518,6 +585,9 @@ export class CatalogService {
|
||||
completeness_rules: completenessRules,
|
||||
attribute_set_id: attributeSetId
|
||||
};
|
||||
if (categoryId !== undefined) {
|
||||
updateData.category_id = categoryId;
|
||||
}
|
||||
|
||||
await record.update(updateData, { transaction });
|
||||
|
||||
@@ -535,12 +605,40 @@ export class CatalogService {
|
||||
}
|
||||
|
||||
if (variantAxes !== null) {
|
||||
// Find existing axis records to clean up suggested values
|
||||
const existingAxes = await models.FamilyVariantAxis.findAll({ where: { family_id: id }, transaction });
|
||||
const existingAxisIds = existingAxes.map(a => a.id);
|
||||
if (existingAxisIds.length > 0) {
|
||||
await models.FamilyVariantAxisValue.destroy({ where: { axis_id: existingAxisIds }, transaction });
|
||||
}
|
||||
await models.FamilyVariantAxis.destroy({ where: { family_id: id }, transaction });
|
||||
for (const attributeId of variantAxes) {
|
||||
await models.FamilyVariantAxis.create({
|
||||
|
||||
for (let i = 0; i < variantAxes.length; i++) {
|
||||
const item = variantAxes[i];
|
||||
const attrId = typeof item === 'string' ? item : (item.attributeId || item.attribute_id || item.id);
|
||||
const required = typeof item === 'object' && item.required !== undefined ? !!item.required : true;
|
||||
const active = typeof item === 'object' && item.active !== undefined ? !!item.active : true;
|
||||
const displayOrder = typeof item === 'object' && item.display_order !== undefined ? item.display_order : i;
|
||||
|
||||
const axisRecord = await models.FamilyVariantAxis.create({
|
||||
family_id: id,
|
||||
attribute_id: attributeId
|
||||
attribute_id: attrId,
|
||||
required,
|
||||
display_order: displayOrder,
|
||||
active
|
||||
}, { transaction });
|
||||
|
||||
const suggestedValues = typeof item === 'object' ? (item.suggestedValues || item.suggested_values || []) : [];
|
||||
for (let j = 0; j < suggestedValues.length; j++) {
|
||||
const valItem = suggestedValues[j];
|
||||
const valText = typeof valItem === 'string' ? valItem : valItem.value;
|
||||
if (!valText) continue;
|
||||
await models.FamilyVariantAxisValue.create({
|
||||
axis_id: axisRecord.id,
|
||||
value: valText,
|
||||
sort_order: typeof valItem === 'object' && valItem.sort_order !== undefined ? valItem.sort_order : j
|
||||
}, { transaction });
|
||||
}
|
||||
}
|
||||
await AuditService.log({ action: 'VARIANT_AXES_CHANGED', resource: 'Catalog', resourceId: id, userId: context.userId || 'system', details: variantAxes });
|
||||
}
|
||||
|
||||
@@ -39,7 +39,16 @@ export const createValidation = [
|
||||
.isArray()
|
||||
.withMessage('Allowed units must be an array')
|
||||
.custom((value) => value.every(val => typeof val === 'string'))
|
||||
.withMessage('Allowed units must be an array of unit IDs')
|
||||
.withMessage('Allowed units must be an array of unit IDs'),
|
||||
body('category')
|
||||
.optional({ nullable: true })
|
||||
.isString(),
|
||||
body('category_id')
|
||||
.optional({ nullable: true })
|
||||
.isString(),
|
||||
body('categoryId')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
];
|
||||
|
||||
export const updateValidation = [
|
||||
@@ -85,7 +94,16 @@ export const updateValidation = [
|
||||
.isArray()
|
||||
.withMessage('Allowed units must be an array')
|
||||
.custom((value) => value.every(val => typeof val === 'string'))
|
||||
.withMessage('Allowed units must be an array of unit IDs')
|
||||
.withMessage('Allowed units must be an array of unit IDs'),
|
||||
body('category')
|
||||
.optional({ nullable: true })
|
||||
.isString(),
|
||||
body('category_id')
|
||||
.optional({ nullable: true })
|
||||
.isString(),
|
||||
body('categoryId')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
];
|
||||
|
||||
export const deleteValidation = [
|
||||
|
||||
@@ -1,20 +1,52 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class FamilyVariantAxis extends Model {
|
||||
static associate(models) {}
|
||||
static associate(models) {
|
||||
FamilyVariantAxis.belongsTo(models.Catalog, {
|
||||
foreignKey: 'family_id',
|
||||
as: 'family'
|
||||
});
|
||||
FamilyVariantAxis.belongsTo(models.Attribute, {
|
||||
foreignKey: 'attribute_id',
|
||||
as: 'attribute'
|
||||
});
|
||||
FamilyVariantAxis.hasMany(models.FamilyVariantAxisValue, {
|
||||
foreignKey: 'axis_id',
|
||||
as: 'suggestedValues'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
FamilyVariantAxis.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
family_id: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
allowNull: false
|
||||
},
|
||||
attribute_id: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false
|
||||
},
|
||||
required: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
defaultValue: true
|
||||
},
|
||||
display_order: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
active: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class FamilyVariantAxisValue extends Model {
|
||||
static associate(models) {
|
||||
FamilyVariantAxisValue.belongsTo(models.FamilyVariantAxis, {
|
||||
foreignKey: 'axis_id',
|
||||
as: 'axis'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
FamilyVariantAxisValue.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
axis_id: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false
|
||||
},
|
||||
value: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false
|
||||
},
|
||||
sort_order: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'FamilyVariantAxisValue',
|
||||
tableName: 'family_variant_axis_values',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
return FamilyVariantAxisValue;
|
||||
};
|
||||
@@ -99,6 +99,39 @@ export class ProductController {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
getVariantValues = async (req, res, next) => {
|
||||
try {
|
||||
const data = await service.getVariantValues(req.params.id, req.context);
|
||||
return res.status(200).json({ success: true, data });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
saveVariantValues = async (req, res, next) => {
|
||||
try {
|
||||
const data = await service.saveVariantValues(req.params.id, req.body.values, req.context);
|
||||
return res.status(200).json({ success: true, data });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
generateVariants = async (req, res, next) => {
|
||||
try {
|
||||
const result = await service.generateVariants(req.params.id, req.body, req.context);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
variants: result.variants,
|
||||
generatedCount: result.generatedCount,
|
||||
warnings: result.warnings
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new ProductController();
|
||||
|
||||
|
||||
@@ -171,4 +171,27 @@ router.post(
|
||||
controller.restore
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:id/variant-values',
|
||||
authenticate,
|
||||
authorize(['products.items']),
|
||||
controller.getVariantValues
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/:id/variant-values',
|
||||
authenticate,
|
||||
authorize(['products.items']),
|
||||
controller.saveVariantValues
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/:id/generate-variants',
|
||||
authenticate,
|
||||
authorize(['products.items']),
|
||||
audit('GENERATE_VARIANTS'),
|
||||
controller.generateVariants
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
|
||||
@@ -850,6 +850,67 @@ export class ProductService {
|
||||
|
||||
return restored;
|
||||
}
|
||||
|
||||
async getVariantValues(productId, context = {}) {
|
||||
const values = await models.ProductVariantValue.findAll({
|
||||
where: { product_id: productId },
|
||||
include: [
|
||||
{
|
||||
model: models.Attribute,
|
||||
as: 'axis',
|
||||
attributes: ['id', 'code', 'name', 'type']
|
||||
}
|
||||
],
|
||||
order: [['sort_order', 'ASC'], ['created_at', 'ASC']]
|
||||
});
|
||||
return values;
|
||||
}
|
||||
|
||||
async saveVariantValues(productId, values = [], context = {}) {
|
||||
const transaction = await sequelize.transaction();
|
||||
try {
|
||||
await models.ProductVariantValue.destroy({
|
||||
where: { product_id: productId },
|
||||
transaction
|
||||
});
|
||||
|
||||
const records = [];
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
const item = values[i];
|
||||
if (!item.axis_id || !item.value) continue;
|
||||
const created = await models.ProductVariantValue.create({
|
||||
product_id: productId,
|
||||
axis_id: item.axis_id,
|
||||
value: item.value,
|
||||
sort_order: item.sort_order !== undefined ? item.sort_order : i
|
||||
}, { transaction });
|
||||
records.push(created);
|
||||
}
|
||||
|
||||
await transaction.commit();
|
||||
return records;
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async generateVariants(productId, data, context = {}) {
|
||||
// Import variantService dynamically to avoid circular dependencies
|
||||
const variantService = (await import('../../variants/variants/variant.service.js')).default;
|
||||
const result = await variantService.generateBatch({
|
||||
productId,
|
||||
axes: data.axes,
|
||||
skuTemplate: data.skuTemplate
|
||||
}, context);
|
||||
|
||||
return {
|
||||
variants: result.variants,
|
||||
generatedCount: result.created ? result.created.length : 0,
|
||||
warnings: result.skipped ? result.skipped.map(s => `Skipped combination ${JSON.stringify(s.combination)}: ${s.reason}`) : []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default new ProductService();
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class ProductVariantValue extends Model {
|
||||
static associate(models) {
|
||||
ProductVariantValue.belongsTo(models.Product, {
|
||||
foreignKey: 'product_id',
|
||||
as: 'product'
|
||||
});
|
||||
ProductVariantValue.belongsTo(models.Attribute, {
|
||||
foreignKey: 'axis_id',
|
||||
as: 'axis'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
ProductVariantValue.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
product_id: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false
|
||||
},
|
||||
axis_id: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false
|
||||
},
|
||||
value: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false
|
||||
},
|
||||
sort_order: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'ProductVariantValue',
|
||||
tableName: 'product_variant_values',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
return ProductVariantValue;
|
||||
};
|
||||
@@ -0,0 +1,169 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
// 1. Extend or recreate family_variant_axes to ensure id, required, display_order, active exist
|
||||
const familyAxesExists = await queryInterface.describeTable('family_variant_axes').catch(() => null);
|
||||
if (familyAxesExists) {
|
||||
if (!familyAxesExists.id) {
|
||||
await queryInterface.addColumn('family_variant_axes', 'id', {
|
||||
type: Sequelize.UUID,
|
||||
defaultValue: Sequelize.UUIDV4,
|
||||
allowNull: true
|
||||
});
|
||||
}
|
||||
if (!familyAxesExists.required) {
|
||||
await queryInterface.addColumn('family_variant_axes', 'required', {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true
|
||||
});
|
||||
}
|
||||
if (!familyAxesExists.display_order) {
|
||||
await queryInterface.addColumn('family_variant_axes', 'display_order', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
});
|
||||
}
|
||||
if (!familyAxesExists.active) {
|
||||
await queryInterface.addColumn('family_variant_axes', 'active', {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await queryInterface.createTable('family_variant_axes', {
|
||||
id: {
|
||||
type: Sequelize.UUID,
|
||||
defaultValue: Sequelize.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
family_id: {
|
||||
type: Sequelize.UUID,
|
||||
allowNull: false,
|
||||
references: { model: 'catalogs', key: 'id' },
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE'
|
||||
},
|
||||
attribute_id: {
|
||||
type: Sequelize.UUID,
|
||||
allowNull: false,
|
||||
references: { model: 'attributes', key: 'id' },
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT'
|
||||
},
|
||||
required: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true
|
||||
},
|
||||
display_order: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
active: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Create family_variant_axis_values table
|
||||
const familyAxisValuesExists = await queryInterface.describeTable('family_variant_axis_values').catch(() => null);
|
||||
if (!familyAxisValuesExists) {
|
||||
await queryInterface.createTable('family_variant_axis_values', {
|
||||
id: {
|
||||
type: Sequelize.UUID,
|
||||
defaultValue: Sequelize.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
axis_id: {
|
||||
type: Sequelize.UUID,
|
||||
allowNull: false
|
||||
},
|
||||
value: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: false
|
||||
},
|
||||
sort_order: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false
|
||||
}
|
||||
});
|
||||
await queryInterface.addIndex('family_variant_axis_values', ['axis_id'], { name: 'idx_fam_var_axis_val_axis' }).catch(() => null);
|
||||
}
|
||||
|
||||
// 3. Create product_variant_values table
|
||||
const productVariantValuesExists = await queryInterface.describeTable('product_variant_values').catch(() => null);
|
||||
if (!productVariantValuesExists) {
|
||||
await queryInterface.createTable('product_variant_values', {
|
||||
id: {
|
||||
type: Sequelize.UUID,
|
||||
defaultValue: Sequelize.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
product_id: {
|
||||
type: Sequelize.UUID,
|
||||
allowNull: false,
|
||||
references: { model: 'products', key: 'id' },
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE'
|
||||
},
|
||||
axis_id: {
|
||||
type: Sequelize.UUID,
|
||||
allowNull: false,
|
||||
references: { model: 'attributes', key: 'id' },
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT'
|
||||
},
|
||||
value: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: false
|
||||
},
|
||||
sort_order: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false
|
||||
}
|
||||
});
|
||||
await queryInterface.addIndex('product_variant_values', ['product_id'], { name: 'idx_prod_var_val_prod' }).catch(() => null);
|
||||
}
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('product_variant_values').catch(() => null);
|
||||
await queryInterface.dropTable('family_variant_axis_values').catch(() => null);
|
||||
}
|
||||
};
|
||||
@@ -18,11 +18,17 @@ const storage = multer.diskStorage({
|
||||
});
|
||||
|
||||
export const fileFilter = (req, file, cb) => {
|
||||
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf', 'text/csv', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
|
||||
if (allowedTypes.includes(file.mimetype)) {
|
||||
const allowedTypes = [
|
||||
'image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif', 'image/svg+xml', 'image/avif', 'image/bmp',
|
||||
'video/mp4', 'video/webm', 'video/quicktime',
|
||||
'application/pdf', 'text/csv', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.ms-excel', 'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
];
|
||||
if (allowedTypes.includes(file.mimetype) || file.mimetype.startsWith('image/') || file.mimetype.startsWith('video/')) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('Invalid file type'), false);
|
||||
cb(new Error(`Invalid file type: ${file.mimetype}`), false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -144,6 +144,7 @@ import attributeSetGroupModelInit from '../../features/attributes/attributeSets/
|
||||
// Product Family Bridge Models
|
||||
import familyAttributeModelInit from '../../features/catalogs/catalogs/familyAttribute.model.js';
|
||||
import familyVariantAxisModelInit from '../../features/catalogs/catalogs/familyVariantAxis.model.js';
|
||||
import familyVariantAxisValueModelInit from '../../features/catalogs/catalogs/familyVariantAxisValue.model.js';
|
||||
import familyAssetRequirementModelInit from '../../features/catalogs/catalogs/familyAssetRequirement.model.js';
|
||||
import familyChannelModelInit from '../../features/catalogs/catalogs/familyChannel.model.js';
|
||||
|
||||
@@ -173,6 +174,7 @@ import channelModelInit from '../../features/channels/channels/channel.model.js'
|
||||
import channelTypeModelInit from '../../features/channels/channelTypes/channelType.model.js';
|
||||
import workflowModelInit from '../../features/workflows/workflow.model.js';
|
||||
import productAttributeValueModelInit from '../../features/products/products/productAttributeValue.model.js';
|
||||
import productVariantValueModelInit from '../../features/products/products/productVariantValue.model.js';
|
||||
import productCompletenessModelInit from '../../features/products/products/productCompleteness.model.js';
|
||||
|
||||
export const initializeDatabaseModels = () => {
|
||||
@@ -206,6 +208,7 @@ export const initializeDatabaseModels = () => {
|
||||
// Product Family Bridges
|
||||
registerModel('FamilyAttribute', familyAttributeModelInit);
|
||||
registerModel('FamilyVariantAxis', familyVariantAxisModelInit);
|
||||
registerModel('FamilyVariantAxisValue', familyVariantAxisValueModelInit);
|
||||
registerModel('FamilyAssetRequirement', familyAssetRequirementModelInit);
|
||||
registerModel('FamilyChannel', familyChannelModelInit);
|
||||
|
||||
@@ -214,6 +217,7 @@ export const initializeDatabaseModels = () => {
|
||||
registerModel('ChannelType', channelTypeModelInit);
|
||||
registerModel('WorkflowRegistry', workflowModelInit);
|
||||
registerModel('ProductAttributeValue', productAttributeValueModelInit);
|
||||
registerModel('ProductVariantValue', productVariantValueModelInit);
|
||||
registerModel('ProductCompleteness', productCompletenessModelInit);
|
||||
|
||||
// Variants
|
||||
@@ -241,3 +245,4 @@ export const initializeDatabaseModels = () => {
|
||||
};
|
||||
|
||||
export { models, sequelize };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user