update goods
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
-- RedefineProductTable
|
||||
-- Set quantity = 0 for made-to-order products before dropping inStock
|
||||
UPDATE Product SET quantity = 0 WHERE inStock = 0;
|
||||
|
||||
-- Drop inStock and leadTimeDays columns
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Product" (
|
||||
"id" TEXT PRIMARY KEY NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"shortDescription" TEXT,
|
||||
"description" TEXT,
|
||||
"quantity" INTEGER NOT NULL DEFAULT 0,
|
||||
"materials" TEXT NOT NULL DEFAULT '[]',
|
||||
"priceCents" INTEGER NOT NULL,
|
||||
"imageUrl" TEXT,
|
||||
"published" BOOLEAN NOT NULL DEFAULT false,
|
||||
"categoryId" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Product_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Product" ("createdAt", "description", "id", "imageUrl", "materials", "priceCents", "published", "quantity", "shortDescription", "slug", "title", "updatedAt", "categoryId") SELECT "createdAt", "description", "id", "imageUrl", "materials", "priceCents", "published", "quantity", "shortDescription", "slug", "title", "updatedAt", "categoryId" FROM "Product";
|
||||
DROP TABLE "Product";
|
||||
ALTER TABLE "new_Product" RENAME TO "Product";
|
||||
CREATE UNIQUE INDEX "Product_slug_key" ON "Product"("slug");
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -30,8 +30,6 @@ model Product {
|
||||
priceCents Int
|
||||
imageUrl String?
|
||||
published Boolean @default(false)
|
||||
inStock Boolean @default(true)
|
||||
leadTimeDays Int?
|
||||
category Category @relation(fields: [categoryId], references: [id], onDelete: Restrict)
|
||||
categoryId String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { getOrCreateUnspecifiedCategory } from '../../lib/default-category.js'
|
||||
import { upsertGalleryImagesByUrls } from '../../lib/gallery.js'
|
||||
import { prisma } from '../../lib/prisma.js'
|
||||
import {
|
||||
@@ -11,15 +10,13 @@ import { persistMultipartImages } from '../../lib/upload-images.js'
|
||||
const CREATE_PRODUCT_SCHEMA = {
|
||||
body: {
|
||||
type: 'object',
|
||||
required: ['title', 'priceCents'],
|
||||
required: ['title', 'priceCents', 'quantity', 'categoryId'],
|
||||
properties: {
|
||||
title: { type: 'string', minLength: 1 },
|
||||
slug: { type: 'string' },
|
||||
categoryId: { type: 'string' },
|
||||
categoryId: { type: 'string', minLength: 1 },
|
||||
priceCents: { type: 'number', minimum: 0 },
|
||||
quantity: { type: 'number', minimum: 0, nullable: true },
|
||||
inStock: { type: 'boolean' },
|
||||
leadTimeDays: { type: 'number', minimum: 1, nullable: true },
|
||||
quantity: { type: 'number', minimum: 0 },
|
||||
shortDescription: { type: 'string', nullable: true },
|
||||
description: { type: 'string', nullable: true },
|
||||
materials: { anyOf: [{ type: 'array', items: { type: 'string' } }, { type: 'string' }] },
|
||||
@@ -36,11 +33,9 @@ const PATCH_PRODUCT_SCHEMA = {
|
||||
properties: {
|
||||
title: { type: 'string', minLength: 1 },
|
||||
slug: { type: 'string' },
|
||||
categoryId: { type: 'string' },
|
||||
categoryId: { type: 'string', minLength: 1 },
|
||||
priceCents: { type: 'number', minimum: 0 },
|
||||
quantity: { type: 'number', minimum: 0, nullable: true },
|
||||
inStock: { type: 'boolean' },
|
||||
leadTimeDays: { type: 'number', minimum: 1, nullable: true },
|
||||
quantity: { type: 'number', minimum: 0 },
|
||||
shortDescription: { type: 'string', nullable: true },
|
||||
description: { type: 'string', nullable: true },
|
||||
materials: { anyOf: [{ type: 'array', items: { type: 'string' } }, { type: 'string' }] },
|
||||
@@ -101,52 +96,33 @@ export async function registerAdminProductRoutes(fastify) {
|
||||
return
|
||||
}
|
||||
const slug = String(body.slug ?? '').trim() || request.server.slugify(title) || `item-${Date.now()}`
|
||||
let categoryId = String(body.categoryId ?? '').trim()
|
||||
const categoryId = String(body.categoryId ?? '').trim()
|
||||
if (!categoryId) {
|
||||
categoryId = (await getOrCreateUnspecifiedCategory()).id
|
||||
} else {
|
||||
const cat = await prisma.category.findUnique({ where: { id: categoryId } })
|
||||
if (!cat) {
|
||||
reply.code(400).send({ error: 'Категория не найдена' })
|
||||
return
|
||||
}
|
||||
reply.code(400).send({ error: 'Укажите категорию' })
|
||||
return
|
||||
}
|
||||
const cat = await prisma.category.findUnique({ where: { id: categoryId } })
|
||||
if (!cat) {
|
||||
reply.code(400).send({ error: 'Категория не найдена' })
|
||||
return
|
||||
}
|
||||
const priceCents = Number(body.priceCents)
|
||||
if (!Number.isFinite(priceCents) || priceCents < 0) {
|
||||
reply.code(400).send({ error: 'Некорректная цена (priceCents ≥ 0)' })
|
||||
return
|
||||
}
|
||||
const inStock = body.inStock === undefined || body.inStock === null ? true : Boolean(body.inStock)
|
||||
const leadTimeDaysRaw = body.leadTimeDays
|
||||
const leadTimeDays =
|
||||
leadTimeDaysRaw === undefined || leadTimeDaysRaw === null || leadTimeDaysRaw === '' ? null : Number(leadTimeDaysRaw)
|
||||
if (!inStock) {
|
||||
if (!Number.isFinite(leadTimeDays) || leadTimeDays <= 0) {
|
||||
reply.code(400).send({ error: 'Если "под заказ", укажите срок исполнения (дней) > 0' })
|
||||
return
|
||||
}
|
||||
}
|
||||
const exists = await prisma.product.findUnique({ where: { slug } })
|
||||
if (exists) {
|
||||
reply.code(409).send({ error: 'Такой slug уже занят' })
|
||||
return
|
||||
}
|
||||
|
||||
let quantity = 0
|
||||
if (!inStock) {
|
||||
quantity = 1
|
||||
} else {
|
||||
if (body.quantity === undefined || body.quantity === null || body.quantity === '') {
|
||||
reply.code(400).send({ error: 'Укажите количество' })
|
||||
return
|
||||
}
|
||||
const n = Number(body.quantity)
|
||||
if (!Number.isFinite(n) || n < 0) {
|
||||
reply.code(400).send({ error: 'Некорректное количество (quantity ≥ 0)' })
|
||||
return
|
||||
}
|
||||
quantity = Math.floor(n)
|
||||
const n = Number(body.quantity)
|
||||
if (!Number.isFinite(n) || n < 0) {
|
||||
reply.code(400).send({ error: 'Некорректное количество (quantity ≥ 0)' })
|
||||
return
|
||||
}
|
||||
const quantity = Math.floor(n)
|
||||
|
||||
const product = await prisma.product.create({
|
||||
data: {
|
||||
@@ -159,8 +135,6 @@ export async function registerAdminProductRoutes(fastify) {
|
||||
priceCents: Math.round(priceCents),
|
||||
imageUrl: body.imageUrl ? String(body.imageUrl) : null,
|
||||
published: Boolean(body.published),
|
||||
inStock,
|
||||
leadTimeDays: inStock ? null : Math.round(leadTimeDays),
|
||||
categoryId,
|
||||
images: Array.isArray(body.imageUrls)
|
||||
? {
|
||||
@@ -209,12 +183,7 @@ export async function registerAdminProductRoutes(fastify) {
|
||||
data.description = body.description ? String(body.description) : null
|
||||
}
|
||||
if (body.quantity !== undefined) {
|
||||
const v = body.quantity
|
||||
if (v === null || v === '') {
|
||||
reply.code(400).send({ error: 'Укажите количество' })
|
||||
return
|
||||
}
|
||||
const n = Number(v)
|
||||
const n = Number(body.quantity)
|
||||
if (!Number.isFinite(n) || n < 0) {
|
||||
reply.code(400).send({ error: 'Некорректное количество (quantity ≥ 0)' })
|
||||
return
|
||||
@@ -237,42 +206,17 @@ export async function registerAdminProductRoutes(fastify) {
|
||||
}
|
||||
if (body.published !== undefined) data.published = Boolean(body.published)
|
||||
if (body.categoryId !== undefined) {
|
||||
const raw = body.categoryId
|
||||
if (raw === null || raw === '') {
|
||||
data.categoryId = (await getOrCreateUnspecifiedCategory()).id
|
||||
} else {
|
||||
const cid = String(raw).trim()
|
||||
const cat = await prisma.category.findUnique({ where: { id: cid } })
|
||||
if (!cat) {
|
||||
reply.code(400).send({ error: 'Категория не найдена' })
|
||||
return
|
||||
}
|
||||
data.categoryId = cid
|
||||
}
|
||||
}
|
||||
|
||||
if (body.inStock !== undefined) data.inStock = Boolean(body.inStock)
|
||||
if (body.leadTimeDays !== undefined) {
|
||||
const v = body.leadTimeDays
|
||||
const n = v === null || v === '' ? null : Number(v)
|
||||
if (n !== null && (!Number.isFinite(n) || n <= 0)) {
|
||||
reply.code(400).send({ error: 'Срок исполнения должен быть числом дней > 0' })
|
||||
const cid = String(body.categoryId).trim()
|
||||
if (!cid) {
|
||||
reply.code(400).send({ error: 'Укажите категорию' })
|
||||
return
|
||||
}
|
||||
data.leadTimeDays = n === null ? null : Math.round(n)
|
||||
}
|
||||
|
||||
const nextInStock = data.inStock ?? existing.inStock
|
||||
const nextLead = data.leadTimeDays ?? existing.leadTimeDays
|
||||
if (!nextInStock && (!Number.isFinite(nextLead) || nextLead === null || nextLead <= 0)) {
|
||||
reply.code(400).send({ error: 'Если "под заказ", укажите срок исполнения (дней) > 0' })
|
||||
return
|
||||
}
|
||||
if (nextInStock && data.leadTimeDays !== undefined) {
|
||||
data.leadTimeDays = null
|
||||
}
|
||||
if (!nextInStock) {
|
||||
data.quantity = 1
|
||||
const cat = await prisma.category.findUnique({ where: { id: cid } })
|
||||
if (!cat) {
|
||||
reply.code(400).send({ error: 'Категория не найдена' })
|
||||
return
|
||||
}
|
||||
data.categoryId = cid
|
||||
}
|
||||
|
||||
const imagesUpdate =
|
||||
@@ -312,4 +256,3 @@ export async function registerAdminProductRoutes(fastify) {
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ const PUBLIC_PRODUCTS_QUERY_SCHEMA = {
|
||||
properties: {
|
||||
categorySlug: { type: 'string' },
|
||||
q: { type: 'string' },
|
||||
availability: { type: 'string', enum: ['all', 'in_stock', 'made_to_order'] },
|
||||
sort: { type: 'string', enum: ['', 'price_asc', 'price_desc'] },
|
||||
page: { type: 'integer', minimum: 1 },
|
||||
pageSize: { type: 'integer', minimum: 1, maximum: 100 },
|
||||
@@ -84,8 +83,6 @@ export async function registerPublicCatalogRoutes(fastify) {
|
||||
const { categorySlug } = request.query
|
||||
const qRaw = request.query?.q
|
||||
const q = typeof qRaw === 'string' ? qRaw.trim() : ''
|
||||
const availabilityRaw = request.query?.availability
|
||||
const availability = typeof availabilityRaw === 'string' ? availabilityRaw.trim() : ''
|
||||
|
||||
const sortRaw = request.query?.sort
|
||||
const sort = typeof sortRaw === 'string' ? sortRaw : ''
|
||||
@@ -113,14 +110,6 @@ export async function registerPublicCatalogRoutes(fastify) {
|
||||
if (q) {
|
||||
where.OR = [{ title: { contains: q } }, { shortDescription: { contains: q } }]
|
||||
}
|
||||
if (availability === 'in_stock') {
|
||||
where.inStock = true
|
||||
where.quantity = { gt: 0 }
|
||||
} else if (availability === 'made_to_order') {
|
||||
where.inStock = false
|
||||
} else if (availability && availability !== 'all') {
|
||||
return reply.code(400).send({ error: 'availability должен быть all | in_stock | made_to_order' })
|
||||
}
|
||||
const applyPriceFilter = !(priceMin !== null && priceMax !== null && priceMin === 0 && priceMax === 0)
|
||||
|
||||
if (applyPriceFilter && (priceMin !== null || priceMax !== null)) {
|
||||
|
||||
Reference in New Issue
Block a user