Add catalog option sets

This commit is contained in:
Ruslan Bakiev
2026-04-09 17:10:52 +07:00
parent 2cd8d0b612
commit 0103c3fb8a
4 changed files with 130 additions and 15 deletions

View File

@@ -0,0 +1,14 @@
-- AlterTable
ALTER TABLE "CatalogProductTypeSetting" ADD COLUMN "colorOptions" TEXT[] DEFAULT ARRAY[]::TEXT[],
ADD COLUMN "labelOptions" TEXT[] DEFAULT ARRAY[]::TEXT[],
ADD COLUMN "lengthOptionsM" INTEGER[] DEFAULT ARRAY[]::INTEGER[],
ADD COLUMN "sleeveOptions" TEXT[] DEFAULT ARRAY[]::TEXT[],
ADD COLUMN "thicknessOptionsMicron" INTEGER[] DEFAULT ARRAY[]::INTEGER[],
ADD COLUMN "widthOptionsMm" INTEGER[] DEFAULT ARRAY[]::INTEGER[];
-- AlterTable
ALTER TABLE "CatalogProductTypeSetting" ADD COLUMN "colorOptions" TEXT[] DEFAULT ARRAY[]::TEXT[],
ADD COLUMN "labelOptions" TEXT[] DEFAULT ARRAY[]::TEXT[],
ADD COLUMN "lengthOptionsM" INTEGER[] DEFAULT ARRAY[]::INTEGER[],
ADD COLUMN "sleeveOptions" TEXT[] DEFAULT ARRAY[]::TEXT[],
ADD COLUMN "thicknessOptionsMicron" INTEGER[] DEFAULT ARRAY[]::INTEGER[],
ADD COLUMN "widthOptionsMm" INTEGER[] DEFAULT ARRAY[]::INTEGER[];

View File

@@ -200,6 +200,12 @@ model CatalogProductTypeSetting {
customLengthStepM Int?
allowCustomSleeveBrand Boolean @default(false)
allowCustomLabel Boolean @default(false)
widthOptionsMm Int[] @default([])
lengthOptionsM Int[] @default([])
thicknessOptionsMicron Int[] @default([])
sleeveOptions String[] @default([])
colorOptions String[] @default([])
labelOptions String[] @default([])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

View File

@@ -441,6 +441,57 @@ function presentCatalogProductTypeSetting(setting) {
customLengthStepM: setting.customLengthStepM ?? null,
allowCustomSleeveBrand: Boolean(setting.allowCustomSleeveBrand),
allowCustomLabel: Boolean(setting.allowCustomLabel),
widthOptionsMm: [...(setting.widthOptionsMm ?? [])],
lengthOptionsM: [...(setting.lengthOptionsM ?? [])],
thicknessOptionsMicron: [...(setting.thicknessOptionsMicron ?? [])],
sleeveOptions: [...(setting.sleeveOptions ?? [])],
colorOptions: [...(setting.colorOptions ?? [])],
labelOptions: [...(setting.labelOptions ?? [])],
};
}
function normalizePositiveIntList(values) {
return [...new Set((values ?? [])
.map((value) => normalizeOptionalPositiveInt(value))
.filter((value) => value !== null))]
.sort((a, b) => a - b);
}
function normalizeTextList(values) {
return [...new Set((values ?? [])
.map((value) => normalizeText(value))
.filter(Boolean))]
.sort((a, b) => a.localeCompare(b, 'ru'));
}
function collectProductTypeOptionDefaults(products) {
const colorOptions = [];
const labelOptions = [];
for (const product of products) {
for (const tag of product.tags ?? []) {
const normalizedTag = normalizeText(tag);
if (!normalizedTag) {
continue;
}
if (['прозрачный', 'коричневый', 'белый', 'черный', 'желтый', 'зеленый', 'красный', 'синий', 'оранжевый', 'красно-белый'].includes(normalizedTag)) {
colorOptions.push(normalizedTag);
}
if (['хрупкое', 'подарок', 'акция'].includes(normalizedTag)) {
labelOptions.push(normalizedTag);
}
}
}
return {
widthOptionsMm: normalizePositiveIntList(products.map((product) => product.widthMm)),
lengthOptionsM: normalizePositiveIntList(products.map((product) => product.lengthM)),
thicknessOptionsMicron: normalizePositiveIntList(products.map((product) => product.thicknessMicron)),
sleeveOptions: normalizeTextList(products.map((product) => product.sleeveBrand)),
colorOptions: normalizeTextList(colorOptions),
labelOptions: normalizeTextList(labelOptions),
};
}
@@ -474,6 +525,12 @@ function normalizeCatalogProductTypeSettingInput(input) {
customLengthStepM,
allowCustomSleeveBrand: Boolean(input.allowCustomSleeveBrand),
allowCustomLabel: Boolean(input.allowCustomLabel),
widthOptionsMm: normalizePositiveIntList(input.widthOptionsMm),
lengthOptionsM: normalizePositiveIntList(input.lengthOptionsM),
thicknessOptionsMicron: normalizePositiveIntList(input.thicknessOptionsMicron),
sleeveOptions: normalizeTextList(input.sleeveOptions),
colorOptions: normalizeTextList(input.colorOptions),
labelOptions: normalizeTextList(input.labelOptions),
};
}
@@ -486,34 +543,60 @@ async function listCatalogProductTypeSettings(prisma) {
productType: null,
},
},
select: { productType: true },
distinct: ['productType'],
orderBy: { productType: 'asc' },
select: {
productType: true,
widthMm: true,
lengthM: true,
thicknessMicron: true,
sleeveBrand: true,
tags: true,
},
orderBy: [{ productType: 'asc' }, { name: 'asc' }],
}),
prisma.catalogProductTypeSetting.findMany({
orderBy: { productType: 'asc' },
}),
]);
const productsByType = new Map();
for (const product of products) {
const productType = normalizeText(product.productType);
if (!productType) {
continue;
}
const existing = productsByType.get(productType);
if (existing) {
existing.push(product);
} else {
productsByType.set(productType, [product]);
}
}
const productTypes = new Set([
...products.map((item) => normalizeText(item.productType)).filter(Boolean),
...productsByType.keys(),
...persistedSettings.map((item) => item.productType),
]);
const settingsByType = new Map(persistedSettings.map((item) => [item.productType, item]));
return [...productTypes]
.sort((a, b) => a.localeCompare(b, 'ru'))
.map((productType) => presentCatalogProductTypeSetting({
productType,
showQuantityPerBox: false,
allowCustomLength: false,
customLengthMinM: null,
customLengthMaxM: null,
customLengthStepM: null,
allowCustomSleeveBrand: false,
allowCustomLabel: false,
...settingsByType.get(productType),
}));
.map((productType) => {
const persisted = settingsByType.get(productType);
const fallbackOptions = collectProductTypeOptionDefaults(productsByType.get(productType) ?? []);
return presentCatalogProductTypeSetting(persisted ?? {
productType,
showQuantityPerBox: false,
allowCustomLength: false,
customLengthMinM: null,
customLengthMaxM: null,
customLengthStepM: null,
allowCustomSleeveBrand: false,
allowCustomLabel: false,
...fallbackOptions,
});
});
}
const cartInclude = {

View File

@@ -257,6 +257,12 @@ type CatalogProductTypeSetting {
customLengthStepM: Int
allowCustomSleeveBrand: Boolean!
allowCustomLabel: Boolean!
widthOptionsMm: [Int!]!
lengthOptionsM: [Int!]!
thicknessOptionsMicron: [Int!]!
sleeveOptions: [String!]!
colorOptions: [String!]!
labelOptions: [String!]!
}
type CartItem {
@@ -512,6 +518,12 @@ input UpsertCatalogProductTypeSettingInput {
customLengthStepM: Int
allowCustomSleeveBrand: Boolean!
allowCustomLabel: Boolean!
widthOptionsMm: [Int!]!
lengthOptionsM: [Int!]!
thicknessOptionsMicron: [Int!]!
sleeveOptions: [String!]!
colorOptions: [String!]!
labelOptions: [String!]!
}
input ReadyOrderItemInput {