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
|
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 {
|
model Cart {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
userId String @unique
|
userId String @unique
|
||||||
|
|||||||
113
src/resolvers.js
113
src/resolvers.js
@@ -352,6 +352,19 @@ function normalizeQuantityValue(value) {
|
|||||||
return Math.floor(normalized);
|
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) {
|
function isCounterpartyProfileComplete(profile) {
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
return false;
|
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 = {
|
const cartInclude = {
|
||||||
items: {
|
items: {
|
||||||
orderBy: [{ updatedAt: 'desc' }, { createdAt: 'desc' }],
|
orderBy: [{ updatedAt: 'desc' }, { createdAt: 'desc' }],
|
||||||
@@ -864,6 +962,8 @@ export const resolvers = {
|
|||||||
orderBy: { name: 'asc' },
|
orderBy: { name: 'asc' },
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
catalogProductTypeSettings: async (_, __, context) => listCatalogProductTypeSettings(context.prisma),
|
||||||
|
|
||||||
order: async (_, { id }, context) => {
|
order: async (_, { id }, context) => {
|
||||||
const user = requireUser(context);
|
const user = requireUser(context);
|
||||||
const order = await context.prisma.order.findUnique({
|
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) => {
|
addProductToCart: async (_, { productId }, context) => {
|
||||||
const user = requireUser(context);
|
const user = requireUser(context);
|
||||||
const normalizedProductId = normalizeText(productId);
|
const normalizedProductId = normalizeText(productId);
|
||||||
|
|||||||
@@ -248,6 +248,17 @@ type Product {
|
|||||||
availableInWarehouses: [ProductWarehouseBalance!]!
|
availableInWarehouses: [ProductWarehouseBalance!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CatalogProductTypeSetting {
|
||||||
|
productType: String!
|
||||||
|
showQuantityPerBox: Boolean!
|
||||||
|
allowCustomLength: Boolean!
|
||||||
|
customLengthMinM: Int
|
||||||
|
customLengthMaxM: Int
|
||||||
|
customLengthStepM: Int
|
||||||
|
allowCustomSleeveBrand: Boolean!
|
||||||
|
allowCustomLabel: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
type CartItem {
|
type CartItem {
|
||||||
id: ID!
|
id: ID!
|
||||||
productId: ID!
|
productId: ID!
|
||||||
@@ -411,6 +422,7 @@ type Query {
|
|||||||
integrationSyncDashboard: IntegrationSyncDashboard!
|
integrationSyncDashboard: IntegrationSyncDashboard!
|
||||||
managerNotificationHistory(userId: ID!, channel: MessengerType!, limit: Int = 50): [NotificationHistoryItem!]!
|
managerNotificationHistory(userId: ID!, channel: MessengerType!, limit: Int = 50): [NotificationHistoryItem!]!
|
||||||
clientProducts: [Product!]!
|
clientProducts: [Product!]!
|
||||||
|
catalogProductTypeSettings: [CatalogProductTypeSetting!]!
|
||||||
order(id: ID!): Order
|
order(id: ID!): Order
|
||||||
myOrders: [Order!]!
|
myOrders: [Order!]!
|
||||||
myCurrentOrders: [Order!]!
|
myCurrentOrders: [Order!]!
|
||||||
@@ -491,6 +503,17 @@ input UpdateCartItemQuantityInput {
|
|||||||
quantity: Float!
|
quantity: Float!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input UpsertCatalogProductTypeSettingInput {
|
||||||
|
productType: String!
|
||||||
|
showQuantityPerBox: Boolean!
|
||||||
|
allowCustomLength: Boolean!
|
||||||
|
customLengthMinM: Int
|
||||||
|
customLengthMaxM: Int
|
||||||
|
customLengthStepM: Int
|
||||||
|
allowCustomSleeveBrand: Boolean!
|
||||||
|
allowCustomLabel: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
input ReadyOrderItemInput {
|
input ReadyOrderItemInput {
|
||||||
productId: ID!
|
productId: ID!
|
||||||
quantity: Float!
|
quantity: Float!
|
||||||
@@ -554,6 +577,7 @@ type Mutation {
|
|||||||
connectMessenger(input: ConnectMessengerInput!): MessengerConnection!
|
connectMessenger(input: ConnectMessengerInput!): MessengerConnection!
|
||||||
deleteMyMessengerConnection(connectionId: ID!): Boolean!
|
deleteMyMessengerConnection(connectionId: ID!): Boolean!
|
||||||
upsertMyCounterpartyProfile(input: UpsertMyCounterpartyProfileInput!): CounterpartyProfile!
|
upsertMyCounterpartyProfile(input: UpsertMyCounterpartyProfileInput!): CounterpartyProfile!
|
||||||
|
upsertCatalogProductTypeSetting(input: UpsertCatalogProductTypeSettingInput!): CatalogProductTypeSetting!
|
||||||
addProductToCart(productId: ID!): Cart!
|
addProductToCart(productId: ID!): Cart!
|
||||||
updateCartItemQuantity(input: UpdateCartItemQuantityInput!): Cart!
|
updateCartItemQuantity(input: UpdateCartItemQuantityInput!): Cart!
|
||||||
removeCartItem(productId: ID!): Cart!
|
removeCartItem(productId: ID!): Cart!
|
||||||
|
|||||||
Reference in New Issue
Block a user