Add catalog product type settings
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "CatalogProductTypeSetting" (
|
||||
"id" TEXT NOT NULL,
|
||||
"productType" TEXT NOT NULL,
|
||||
"showQuantityPerBox" BOOLEAN NOT NULL DEFAULT false,
|
||||
"allowCustomLength" BOOLEAN NOT NULL DEFAULT false,
|
||||
"customLengthMinM" INTEGER,
|
||||
"customLengthMaxM" INTEGER,
|
||||
"customLengthStepM" INTEGER,
|
||||
"allowCustomSleeveBrand" BOOLEAN NOT NULL DEFAULT false,
|
||||
"allowCustomLabel" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "CatalogProductTypeSetting_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "CatalogProductTypeSetting_productType_key" ON "CatalogProductTypeSetting"("productType");
|
||||
|
||||
@@ -190,6 +190,20 @@ model Product {
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model CatalogProductTypeSetting {
|
||||
id String @id @default(cuid())
|
||||
productType String @unique
|
||||
showQuantityPerBox Boolean @default(false)
|
||||
allowCustomLength Boolean @default(false)
|
||||
customLengthMinM Int?
|
||||
customLengthMaxM Int?
|
||||
customLengthStepM Int?
|
||||
allowCustomSleeveBrand Boolean @default(false)
|
||||
allowCustomLabel Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Cart {
|
||||
id String @id @default(cuid())
|
||||
userId String @unique
|
||||
|
||||
113
src/resolvers.js
113
src/resolvers.js
@@ -352,6 +352,19 @@ function normalizeQuantityValue(value) {
|
||||
return Math.floor(normalized);
|
||||
}
|
||||
|
||||
function normalizeOptionalPositiveInt(value) {
|
||||
if (value == null || value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalized = Number(value);
|
||||
if (!Number.isInteger(normalized) || normalized <= 0) {
|
||||
throw new Error('Catalog setting values must be positive integers.');
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function isCounterpartyProfileComplete(profile) {
|
||||
if (!profile) {
|
||||
return false;
|
||||
@@ -418,6 +431,91 @@ function defaultCartParameters(product) {
|
||||
};
|
||||
}
|
||||
|
||||
function presentCatalogProductTypeSetting(setting) {
|
||||
return {
|
||||
productType: setting.productType,
|
||||
showQuantityPerBox: Boolean(setting.showQuantityPerBox),
|
||||
allowCustomLength: Boolean(setting.allowCustomLength),
|
||||
customLengthMinM: setting.customLengthMinM ?? null,
|
||||
customLengthMaxM: setting.customLengthMaxM ?? null,
|
||||
customLengthStepM: setting.customLengthStepM ?? null,
|
||||
allowCustomSleeveBrand: Boolean(setting.allowCustomSleeveBrand),
|
||||
allowCustomLabel: Boolean(setting.allowCustomLabel),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeCatalogProductTypeSettingInput(input) {
|
||||
const productType = normalizeText(input.productType);
|
||||
if (!productType) {
|
||||
throw new Error('Product type is required.');
|
||||
}
|
||||
|
||||
const allowCustomLength = Boolean(input.allowCustomLength);
|
||||
const customLengthMinM = allowCustomLength ? normalizeOptionalPositiveInt(input.customLengthMinM) : null;
|
||||
const customLengthMaxM = allowCustomLength ? normalizeOptionalPositiveInt(input.customLengthMaxM) : null;
|
||||
const customLengthStepM = allowCustomLength ? normalizeOptionalPositiveInt(input.customLengthStepM) : null;
|
||||
|
||||
if (allowCustomLength) {
|
||||
if (customLengthMinM == null || customLengthMaxM == null || customLengthStepM == null) {
|
||||
throw new Error('Length customization requires min, max, and step values.');
|
||||
}
|
||||
|
||||
if (customLengthMinM > customLengthMaxM) {
|
||||
throw new Error('Length min must be less than or equal to max.');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
productType,
|
||||
showQuantityPerBox: Boolean(input.showQuantityPerBox),
|
||||
allowCustomLength,
|
||||
customLengthMinM,
|
||||
customLengthMaxM,
|
||||
customLengthStepM,
|
||||
allowCustomSleeveBrand: Boolean(input.allowCustomSleeveBrand),
|
||||
allowCustomLabel: Boolean(input.allowCustomLabel),
|
||||
};
|
||||
}
|
||||
|
||||
async function listCatalogProductTypeSettings(prisma) {
|
||||
const [products, persistedSettings] = await Promise.all([
|
||||
prisma.product.findMany({
|
||||
where: {
|
||||
isActive: true,
|
||||
NOT: {
|
||||
productType: null,
|
||||
},
|
||||
},
|
||||
select: { productType: true },
|
||||
distinct: ['productType'],
|
||||
orderBy: { productType: 'asc' },
|
||||
}),
|
||||
prisma.catalogProductTypeSetting.findMany({
|
||||
orderBy: { productType: 'asc' },
|
||||
}),
|
||||
]);
|
||||
|
||||
const productTypes = new Set([
|
||||
...products.map((item) => normalizeText(item.productType)).filter(Boolean),
|
||||
...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),
|
||||
}));
|
||||
}
|
||||
|
||||
const cartInclude = {
|
||||
items: {
|
||||
orderBy: [{ updatedAt: 'desc' }, { createdAt: 'desc' }],
|
||||
@@ -864,6 +962,8 @@ export const resolvers = {
|
||||
orderBy: { name: 'asc' },
|
||||
}),
|
||||
|
||||
catalogProductTypeSettings: async (_, __, context) => listCatalogProductTypeSettings(context.prisma),
|
||||
|
||||
order: async (_, { id }, context) => {
|
||||
const user = requireUser(context);
|
||||
const order = await context.prisma.order.findUnique({
|
||||
@@ -1440,6 +1540,19 @@ export const resolvers = {
|
||||
});
|
||||
},
|
||||
|
||||
upsertCatalogProductTypeSetting: async (_, { input }, context) => {
|
||||
requireManagerAccess(context);
|
||||
const payload = normalizeCatalogProductTypeSettingInput(input);
|
||||
|
||||
const persisted = await context.prisma.catalogProductTypeSetting.upsert({
|
||||
where: { productType: payload.productType },
|
||||
update: payload,
|
||||
create: payload,
|
||||
});
|
||||
|
||||
return presentCatalogProductTypeSetting(persisted);
|
||||
},
|
||||
|
||||
addProductToCart: async (_, { productId }, context) => {
|
||||
const user = requireUser(context);
|
||||
const normalizedProductId = normalizeText(productId);
|
||||
|
||||
@@ -248,6 +248,17 @@ type Product {
|
||||
availableInWarehouses: [ProductWarehouseBalance!]!
|
||||
}
|
||||
|
||||
type CatalogProductTypeSetting {
|
||||
productType: String!
|
||||
showQuantityPerBox: Boolean!
|
||||
allowCustomLength: Boolean!
|
||||
customLengthMinM: Int
|
||||
customLengthMaxM: Int
|
||||
customLengthStepM: Int
|
||||
allowCustomSleeveBrand: Boolean!
|
||||
allowCustomLabel: Boolean!
|
||||
}
|
||||
|
||||
type CartItem {
|
||||
id: ID!
|
||||
productId: ID!
|
||||
@@ -411,6 +422,7 @@ type Query {
|
||||
integrationSyncDashboard: IntegrationSyncDashboard!
|
||||
managerNotificationHistory(userId: ID!, channel: MessengerType!, limit: Int = 50): [NotificationHistoryItem!]!
|
||||
clientProducts: [Product!]!
|
||||
catalogProductTypeSettings: [CatalogProductTypeSetting!]!
|
||||
order(id: ID!): Order
|
||||
myOrders: [Order!]!
|
||||
myCurrentOrders: [Order!]!
|
||||
@@ -491,6 +503,17 @@ input UpdateCartItemQuantityInput {
|
||||
quantity: Float!
|
||||
}
|
||||
|
||||
input UpsertCatalogProductTypeSettingInput {
|
||||
productType: String!
|
||||
showQuantityPerBox: Boolean!
|
||||
allowCustomLength: Boolean!
|
||||
customLengthMinM: Int
|
||||
customLengthMaxM: Int
|
||||
customLengthStepM: Int
|
||||
allowCustomSleeveBrand: Boolean!
|
||||
allowCustomLabel: Boolean!
|
||||
}
|
||||
|
||||
input ReadyOrderItemInput {
|
||||
productId: ID!
|
||||
quantity: Float!
|
||||
@@ -554,6 +577,7 @@ type Mutation {
|
||||
connectMessenger(input: ConnectMessengerInput!): MessengerConnection!
|
||||
deleteMyMessengerConnection(connectionId: ID!): Boolean!
|
||||
upsertMyCounterpartyProfile(input: UpsertMyCounterpartyProfileInput!): CounterpartyProfile!
|
||||
upsertCatalogProductTypeSetting(input: UpsertCatalogProductTypeSettingInput!): CatalogProductTypeSetting!
|
||||
addProductToCart(productId: ID!): Cart!
|
||||
updateCartItemQuantity(input: UpdateCartItemQuantityInput!): Cart!
|
||||
removeCartItem(productId: ID!): Cart!
|
||||
|
||||
Reference in New Issue
Block a user