deploy
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
DATABASE_URL="file:./dev.db"
|
||||
ADMIN_EMAIL=admin@example.com
|
||||
# Default code for login
|
||||
DEFAULT_CODE=123456
|
||||
IS_DEFAULT_CODE_ENABLED=true
|
||||
PORT=3333
|
||||
# Токен для админ-запросов с фронта: Authorization: Bearer <значение>
|
||||
ADMIN_API_TOKEN=dev-secret-change-me
|
||||
# JWT для пользовательской авторизации (замени в проде)
|
||||
JWT_SECRET=dev-jwt-secret-change-me
|
||||
# Опционально: список origin для CORS через запятую (в dev можно не задавать)
|
||||
# CORS_ORIGIN=http://localhost:5173
|
||||
|
||||
# SMTP для отправки кода (если не задано — код логируется в консоль как [DEV])
|
||||
# SMTP_HOST=smtp.example.com
|
||||
# SMTP_PORT=587
|
||||
# SMTP_SECURE=false
|
||||
# SMTP_USER=user@example.com
|
||||
# SMTP_PASS=password
|
||||
# MAIL_FROM="Craftshop <no-reply@example.com>"
|
||||
+1
-2
@@ -4,10 +4,9 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node --env-file=.dev_env --watch src/index.js",
|
||||
"dev": "node --env-file=.env --watch src/index.js",
|
||||
"dev:classic": "node --watch src/index.js",
|
||||
"start": "node src/index.js",
|
||||
"start:dev_env": "node --env-file=.dev_env src/index.js",
|
||||
"db:migrate": "prisma migrate dev",
|
||||
"db:migrate:test": "node --env-file=.dev_env ./node_modules/prisma/build/index.js migrate deploy",
|
||||
"db:seed": "prisma db seed",
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Order" ADD COLUMN "deliveryCarrier" TEXT;
|
||||
@@ -116,6 +116,8 @@ model Order {
|
||||
status String @default("DRAFT")
|
||||
/// 'delivery' | 'pickup'
|
||||
deliveryType String @default("delivery")
|
||||
/// RUSSIAN_POST | OZON_PVZ | YANDEX_PVZ | FIVE_POST при deliveryType=delivery
|
||||
deliveryCarrier String?
|
||||
/// 'online' | 'on_pickup' — способ расчёта для заказа
|
||||
paymentMethod String @default("online")
|
||||
itemsSubtotalCents Int @default(0)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
export const DELIVERY_CARRIERS = ['RUSSIAN_POST', 'OZON_PVZ', 'YANDEX_PVZ', 'FIVE_POST']
|
||||
|
||||
/**
|
||||
* @param {unknown} value
|
||||
* @returns {value is typeof DELIVERY_CARRIERS[number]}
|
||||
*/
|
||||
export function isDeliveryCarrier(value) {
|
||||
return typeof value === 'string' && DELIVERY_CARRIERS.includes(value)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
export const ORDER_STATUSES = [
|
||||
'DRAFT',
|
||||
'DELIVERY_FEE_ADJUSTMENT',
|
||||
'PENDING_PAYMENT',
|
||||
'PAYMENT_VERIFICATION',
|
||||
'PAID',
|
||||
@@ -22,6 +23,8 @@ export function canTransitionAdminOrderStatus(order, next) {
|
||||
switch (from) {
|
||||
case 'DRAFT':
|
||||
return next === 'PENDING_PAYMENT' || next === 'CANCELLED'
|
||||
case 'DELIVERY_FEE_ADJUSTMENT':
|
||||
return next === 'CANCELLED'
|
||||
case 'PENDING_PAYMENT':
|
||||
return next === 'CANCELLED'
|
||||
case 'PAYMENT_VERIFICATION':
|
||||
|
||||
@@ -7,7 +7,9 @@ export async function registerAdminOrderRoutes(fastify) {
|
||||
{ preHandler: [fastify.verifyAdmin] },
|
||||
async () => {
|
||||
const attentionCount = await prisma.order.count({
|
||||
where: { status: { in: ['PENDING_PAYMENT', 'PAYMENT_VERIFICATION'] } },
|
||||
where: {
|
||||
status: { in: ['DELIVERY_FEE_ADJUSTMENT', 'PENDING_PAYMENT', 'PAYMENT_VERIFICATION'] },
|
||||
},
|
||||
})
|
||||
return { attentionCount }
|
||||
},
|
||||
@@ -57,6 +59,7 @@ export async function registerAdminOrderRoutes(fastify) {
|
||||
id: o.id,
|
||||
status: o.status,
|
||||
deliveryType: o.deliveryType,
|
||||
deliveryCarrier: o.deliveryCarrier,
|
||||
paymentMethod: o.paymentMethod,
|
||||
totalCents: o.totalCents,
|
||||
currency: o.currency,
|
||||
@@ -109,6 +112,37 @@ export async function registerAdminOrderRoutes(fastify) {
|
||||
},
|
||||
)
|
||||
|
||||
fastify.patch(
|
||||
'/api/admin/orders/:id/delivery-fee',
|
||||
{ preHandler: [fastify.verifyAdmin] },
|
||||
async (request, reply) => {
|
||||
const { id } = request.params
|
||||
const feeRaw = request.body?.deliveryFeeCents
|
||||
const parsed =
|
||||
typeof feeRaw === 'string' ? Number.parseInt(feeRaw, 10) : typeof feeRaw === 'number' ? feeRaw : NaN
|
||||
if (!Number.isInteger(parsed) || parsed < 0) {
|
||||
return reply.code(400).send({ error: 'deliveryFeeCents должно быть целым числом ≥ 0 (копейки)' })
|
||||
}
|
||||
|
||||
const existing = await prisma.order.findUnique({ where: { id } })
|
||||
if (!existing) return reply.code(404).send({ error: 'Заказ не найден' })
|
||||
if (existing.status !== 'DELIVERY_FEE_ADJUSTMENT') {
|
||||
return reply.code(409).send({ error: 'Корректировка доставки доступна только в статусе DELIVERY_FEE_ADJUSTMENT' })
|
||||
}
|
||||
|
||||
const totalCents = existing.itemsSubtotalCents + parsed
|
||||
const updated = await prisma.order.update({
|
||||
where: { id },
|
||||
data: {
|
||||
deliveryFeeCents: parsed,
|
||||
totalCents,
|
||||
status: 'PENDING_PAYMENT',
|
||||
},
|
||||
})
|
||||
return { item: updated }
|
||||
},
|
||||
)
|
||||
|
||||
fastify.post(
|
||||
'/api/admin/orders/:id/messages',
|
||||
{ preHandler: [fastify.verifyAdmin] },
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { issueEmailCode, normalizeEmail, verifyEmailCode } from '../lib/auth.js'
|
||||
import { isDeliveryCarrier } from '../lib/delivery-carrier.js'
|
||||
import { escapeHtml } from '../lib/escape-html.js'
|
||||
import { prisma } from '../lib/prisma.js'
|
||||
import { getOtherUploadMaxFileBytes } from '../lib/upload-limits.js'
|
||||
@@ -436,6 +437,24 @@ export async function registerAuthRoutes(fastify) {
|
||||
return reply.code(400).send({ error: 'deliveryType должен быть delivery | pickup' })
|
||||
}
|
||||
|
||||
const carrierRaw = request.body?.deliveryCarrier
|
||||
let deliveryCarrier = null
|
||||
if (deliveryType === 'delivery') {
|
||||
const carrierStr =
|
||||
carrierRaw === undefined || carrierRaw === null || carrierRaw === ''
|
||||
? ''
|
||||
: String(carrierRaw).trim()
|
||||
if (!isDeliveryCarrier(carrierStr)) {
|
||||
return reply
|
||||
.code(400)
|
||||
.send({
|
||||
error:
|
||||
'deliveryCarrier обязателен для доставки: RUSSIAN_POST | OZON_PVZ | YANDEX_PVZ | FIVE_POST',
|
||||
})
|
||||
}
|
||||
deliveryCarrier = carrierStr
|
||||
}
|
||||
|
||||
if (paymentMethod === 'on_pickup' && deliveryType !== 'pickup') {
|
||||
return reply.code(400).send({ error: 'Оплата при получении доступна только для самовывоза' })
|
||||
}
|
||||
@@ -468,9 +487,7 @@ export async function registerAuthRoutes(fastify) {
|
||||
}))
|
||||
|
||||
const itemsSubtotalCents = itemsPayload.reduce((sum, i) => sum + i.priceCentsSnapshot * i.qty, 0)
|
||||
const totalQty = itemsPayload.reduce((sum, i) => sum + i.qty, 0)
|
||||
const deliveryFeeCents =
|
||||
deliveryType === 'delivery' ? 50000 * Math.max(1, Math.ceil(totalQty / 2)) : 0
|
||||
const deliveryFeeCents = deliveryType === 'delivery' ? 50000 : 0
|
||||
const totalCents = itemsSubtotalCents + deliveryFeeCents
|
||||
|
||||
const addressSnapshotJson =
|
||||
@@ -488,7 +505,9 @@ export async function registerAuthRoutes(fastify) {
|
||||
lng: address.lng,
|
||||
})
|
||||
|
||||
const initialStatus = paymentMethod === 'on_pickup' ? 'IN_PROGRESS' : 'PENDING_PAYMENT'
|
||||
let initialStatus = 'PENDING_PAYMENT'
|
||||
if (paymentMethod === 'on_pickup') initialStatus = 'IN_PROGRESS'
|
||||
else if (deliveryType === 'delivery') initialStatus = 'DELIVERY_FEE_ADJUSTMENT'
|
||||
|
||||
let created
|
||||
try {
|
||||
@@ -511,6 +530,7 @@ export async function registerAuthRoutes(fastify) {
|
||||
userId,
|
||||
status: initialStatus,
|
||||
deliveryType,
|
||||
deliveryCarrier,
|
||||
paymentMethod,
|
||||
itemsSubtotalCents,
|
||||
deliveryFeeCents,
|
||||
@@ -703,6 +723,15 @@ export async function registerAuthRoutes(fastify) {
|
||||
return reply.code(409).send({ error: 'Для этого заказа оплата при получении — кнопка оплаты не нужна.' })
|
||||
}
|
||||
|
||||
if (order.status === 'DELIVERY_FEE_ADJUSTMENT') {
|
||||
return reply
|
||||
.code(409)
|
||||
.send({
|
||||
error:
|
||||
'Оплата станет доступна после корректировки стоимости доставки администратором.',
|
||||
})
|
||||
}
|
||||
|
||||
let nextStatus = order.status
|
||||
if (order.status === 'DRAFT') {
|
||||
await prisma.order.update({ where: { id }, data: { status: 'PENDING_PAYMENT' } })
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 393 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 321 KiB |
Reference in New Issue
Block a user