From 4eda6d0f81d35b42d56c601aef49d4ec5440ed02 Mon Sep 17 00:00:00 2001 From: "@kirill.komarov" Date: Mon, 11 May 2026 15:14:35 +0500 Subject: [PATCH] deploy --- .../src/entities/order/api/admin-order-api.ts | 6 + client/src/entities/order/api/order-api.ts | 3 + .../pages/admin-orders/ui/AdminOrdersPage.tsx | 120 ++++++++++++++++++ client/src/pages/checkout/ui/CheckoutPage.tsx | 33 +++-- .../pages/me/ui/sections/OrderDetailPage.tsx | 33 +++-- .../src/shared/constants/delivery-carrier.ts | 20 +++ client/src/shared/constants/order.ts | 3 + .../src/shared/lib/order-address-snapshot.ts | 18 +++ client/src/shared/lib/order-status-labels.ts | 1 + scripts/deploy-ssh.sh | 6 +- server/.dev_env | 20 --- server/package.json | 3 +- .../migration.sql | 2 + server/prisma/schema.prisma | 2 + server/src/lib/delivery-carrier.js | 9 ++ server/src/lib/order-status.js | 3 + server/src/routes/api/admin-orders.js | 36 +++++- server/src/routes/auth.js | 37 +++++- .../4c8444e8-b0b4-451f-b493-e85db9659d85.png | Bin 0 -> 402211 bytes .../69c7eee1-ec9a-4667-ae21-c883b325b135.png | Bin 0 -> 328517 bytes 20 files changed, 299 insertions(+), 56 deletions(-) create mode 100644 client/src/shared/constants/delivery-carrier.ts create mode 100644 client/src/shared/lib/order-address-snapshot.ts delete mode 100644 server/.dev_env create mode 100644 server/prisma/migrations/20260511093350_order_delivery_carrier/migration.sql create mode 100644 server/src/lib/delivery-carrier.js create mode 100644 server/uploads/4c8444e8-b0b4-451f-b493-e85db9659d85.png create mode 100644 server/uploads/69c7eee1-ec9a-4667-ae21-c883b325b135.png diff --git a/client/src/entities/order/api/admin-order-api.ts b/client/src/entities/order/api/admin-order-api.ts index 363417f..d2aa8ed 100644 --- a/client/src/entities/order/api/admin-order-api.ts +++ b/client/src/entities/order/api/admin-order-api.ts @@ -4,6 +4,7 @@ export type AdminOrderListItem = { id: string status: string deliveryType: 'delivery' | 'pickup' + deliveryCarrier?: string | null paymentMethod?: 'online' | 'on_pickup' totalCents: number currency: string @@ -25,6 +26,7 @@ export type AdminOrderDetailResponse = { id: string status: string deliveryType: 'delivery' | 'pickup' + deliveryCarrier?: string | null paymentMethod?: 'online' | 'on_pickup' itemsSubtotalCents: number deliveryFeeCents: number @@ -77,6 +79,10 @@ export async function setAdminOrderStatus(id: string, status: string): Promise { + await apiClient.patch(`admin/orders/${id}/delivery-fee`, { deliveryFeeCents }) +} + export async function postAdminOrderMessage(id: string, text: string): Promise { await apiClient.post(`admin/orders/${id}/messages`, { text }) } diff --git a/client/src/entities/order/api/order-api.ts b/client/src/entities/order/api/order-api.ts index 4452164..e68d79e 100644 --- a/client/src/entities/order/api/order-api.ts +++ b/client/src/entities/order/api/order-api.ts @@ -1,4 +1,5 @@ import { apiClient } from '@/shared/api/client' +import type { DeliveryCarrierCode } from '@/shared/constants/delivery-carrier' export type OrderListItem = { id: string @@ -19,6 +20,7 @@ export type OrderDetailResponse = { id: string status: string deliveryType: 'delivery' | 'pickup' + deliveryCarrier?: DeliveryCarrierCode | null paymentMethod?: OrderPaymentMethod itemsSubtotalCents: number deliveryFeeCents: number @@ -47,6 +49,7 @@ export type OrderDetailResponse = { export async function createOrder(body: { deliveryType: 'delivery' | 'pickup' + deliveryCarrier?: DeliveryCarrierCode | null paymentMethod?: OrderPaymentMethod addressId?: string | null comment?: string | null diff --git a/client/src/pages/admin-orders/ui/AdminOrdersPage.tsx b/client/src/pages/admin-orders/ui/AdminOrdersPage.tsx index e295cc3..748743a 100644 --- a/client/src/pages/admin-orders/ui/AdminOrdersPage.tsx +++ b/client/src/pages/admin-orders/ui/AdminOrdersPage.tsx @@ -22,18 +22,63 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { fetchAdminOrder, fetchAdminOrders, + patchAdminOrderDeliveryFee, postAdminOrderMessage, setAdminOrderStatus, } from '@/entities/order/api/admin-order-api' +import { deliveryCarrierLabelRu } from '@/shared/constants/delivery-carrier' import { ORDER_STATUSES, getAdminNextOrderStatuses } from '@/shared/constants/order' import { formatPriceRub } from '@/shared/lib/format-price' import { groupOrdersByStatus } from '@/shared/lib/group-orders-by-status' import { invalidateQueryKeys } from '@/shared/lib/invalidate-query-keys' +import { parseOrderAddressSnapshot } from '@/shared/lib/order-address-snapshot' import { orderStatusLabelRu } from '@/shared/lib/order-status-labels' import { ChatMessageBubble } from '@/shared/ui/ChatMessageBubble' import { OrderMessageBody } from '@/shared/ui/OrderMessageBody' import { RichTextMessageEditor } from '@/shared/ui/RichTextMessageEditor' +function DeliveryFeeAdjustmentForm({ orderId, deliveryFeeCents }: { orderId: string; deliveryFeeCents: number }) { + const qc = useQueryClient() + const [rub, setRub] = useState(() => String(deliveryFeeCents / 100)) + const feeMut = useMutation({ + mutationFn: () => patchAdminOrderDeliveryFee(orderId, Math.round(Number.parseFloat(rub) * 100)), + onSuccess: async () => { + await invalidateQueryKeys(qc, [ + ['admin', 'orders'], + ['admin', 'orders', 'detail'], + ['admin', 'orders', 'summary'], + ]) + }, + }) + + return ( + + setRub(e.target.value)} + inputProps={{ min: 0, step: 1 }} + sx={{ width: { xs: '100%', sm: 200 } }} + /> + + + ) +} + export function AdminOrdersPage() { const qc = useQueryClient() const [q, setQ] = useState('') @@ -96,6 +141,11 @@ export function AdminOrdersPage() { const detail = orderDetailQuery.data?.item const canSendMessage = msg.replace(/<[^>]*>/g, ' ').trim().length > 0 + const deliverySnapshot = useMemo( + () => (detail?.deliveryType === 'delivery' ? parseOrderAddressSnapshot(detail.addressSnapshotJson) : null), + [detail], + ) + const nextStatuses = useMemo(() => { if (!detail) return [] return getAdminNextOrderStatuses(detail.status, detail.deliveryType ?? 'delivery') @@ -211,8 +261,78 @@ export function AdminOrdersPage() { Получение: {detail.deliveryType === 'pickup' ? 'самовывоз' : 'доставка'} {(detail.paymentMethod ?? 'online') === 'on_pickup' ? ' · оплата при получении' : ' · онлайн-оплата'} + {detail.deliveryType === 'delivery' && deliveryCarrierLabelRu(detail.deliveryCarrier) && ( + <> · служба: {deliveryCarrierLabelRu(detail.deliveryCarrier)} + )} + {detail.deliveryType === 'delivery' && ( + + + Адрес и получатель (на момент заказа) + + {deliverySnapshot ? ( + + {deliverySnapshot.label?.trim() && ( + + Метка: {deliverySnapshot.label} + + )} + + + Адрес: + {' '} + {deliverySnapshot.addressLine ?? '—'} + + + + Получатель: + {' '} + {deliverySnapshot.recipientName ?? '—'} + + + + Телефон: + {' '} + {deliverySnapshot.recipientPhone ?? '—'} + + {deliverySnapshot.comment?.trim() && ( + + Комментарий к адресу: {deliverySnapshot.comment} + + )} + + ) : ( + + Данные адреса в заказе отсутствуют или не распознаны. + + )} + + )} + + {detail.status === 'DELIVERY_FEE_ADJUSTMENT' && ( + + Укажите итоговую стоимость доставки (₽). После сохранения заказ получит статус « + {orderStatusLabelRu('PENDING_PAYMENT')}», и клиент сможет оплатить с учётом этой суммы. + + )} + + {detail.status === 'DELIVERY_FEE_ADJUSTMENT' && ( + + )} + Сменить статус diff --git a/client/src/pages/checkout/ui/CheckoutPage.tsx b/client/src/pages/checkout/ui/CheckoutPage.tsx index 6e12a6a..46a663f 100644 --- a/client/src/pages/checkout/ui/CheckoutPage.tsx +++ b/client/src/pages/checkout/ui/CheckoutPage.tsx @@ -19,6 +19,7 @@ import { Link as RouterLink, useNavigate } from 'react-router-dom' import { fetchMyCart } from '@/entities/cart/api/cart-api' import { createOrder } from '@/entities/order/api/order-api' import { fetchMyAddresses } from '@/entities/user/api/address-api' +import { DELIVERY_CARRIER_OPTIONS, type DeliveryCarrierCode } from '@/shared/constants/delivery-carrier' import { formatPriceRub } from '@/shared/lib/format-price' import { $user } from '@/shared/model/auth' @@ -28,6 +29,7 @@ export function CheckoutPage() { const navigate = useNavigate() const [deliveryType, setDeliveryType] = useState<'delivery' | 'pickup'>('delivery') const [pickupPayment, setPickupPayment] = useState<'online' | 'on_pickup'>('online') + const [deliveryCarrier, setDeliveryCarrier] = useState('RUSSIAN_POST') const [addressId, setAddressId] = useState('') const [comment, setComment] = useState('') @@ -50,6 +52,7 @@ export function CheckoutPage() { mutationFn: () => createOrder({ deliveryType, + deliveryCarrier: deliveryType === 'delivery' ? deliveryCarrier : null, paymentMethod: deliveryType === 'delivery' ? 'online' : pickupPayment, addressId: deliveryType === 'delivery' ? selectedAddressId : null, comment: comment.trim() || null, @@ -74,9 +77,7 @@ export function CheckoutPage() { const items = cartQuery.data?.items ?? [] const itemsSubtotalCents = items.reduce((s, x) => s + x.product.priceCents * x.qty, 0) - const totalQty = items.reduce((s, x) => s + x.qty, 0) - const deliveryFeeCents = - deliveryType === 'delivery' && items.length > 0 ? 50000 * Math.max(1, Math.ceil(totalQty / 2)) : 0 + const deliveryFeeCents = deliveryType === 'delivery' && items.length > 0 ? 50000 : 0 const total = itemsSubtotalCents + deliveryFeeCents const addresses = addressesQuery.data?.items ?? [] const hasOverLimit = items.some((x) => x.qty > (x.product.inStock ? x.product.quantity : 1)) @@ -153,6 +154,23 @@ export function CheckoutPage() { {deliveryType === 'delivery' && ( <> + + + Служба доставки + + { + const v = String(e.target.value) as DeliveryCarrierCode + setDeliveryCarrier(v) + }} + > + {DELIVERY_CARRIER_OPTIONS.map((o) => ( + } label={o.label} /> + ))} + + + Адрес доставки