шдод олрол
This commit is contained in:
@@ -1,17 +1,13 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import { Link as RouterLink } from 'react-router-dom'
|
||||
import Alert from '@mui/material/Alert'
|
||||
import Box from '@mui/material/Box'
|
||||
import Button from '@mui/material/Button'
|
||||
import FormControl from '@mui/material/FormControl'
|
||||
import InputLabel from '@mui/material/InputLabel'
|
||||
import Link from '@mui/material/Link'
|
||||
import MenuItem from '@mui/material/MenuItem'
|
||||
import Select from '@mui/material/Select'
|
||||
import Stack from '@mui/material/Stack'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { useUnit } from 'effector-react'
|
||||
import { Link as RouterLink } from 'react-router-dom'
|
||||
import { postAdminOrderMessage, setAdminOrderStatus } from '@/entities/order/api/admin-order-api'
|
||||
import type { AdminOrderDetailResponse } from '@/entities/order/api/admin-order-api'
|
||||
import { deliveryCarrierLabelRu } from '@/shared/constants/delivery-carrier'
|
||||
@@ -165,31 +161,34 @@ export function OrderDetailContent({ detail, orderId }: { detail: AdminOrderDeta
|
||||
<DeliveryFeeAdjustmentForm key={detail.id} orderId={detail.id} deliveryFeeCents={detail.deliveryFeeCents} />
|
||||
)}
|
||||
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2} sx={{ alignItems: { sm: 'center' } }}>
|
||||
<FormControl size="small" sx={{ minWidth: 240 }}>
|
||||
<InputLabel id="next-status-label">Сменить статус</InputLabel>
|
||||
<Select
|
||||
labelId="next-status-label"
|
||||
label="Сменить статус"
|
||||
value=""
|
||||
onChange={(e) => {
|
||||
const next = String(e.target.value)
|
||||
if (!next) return
|
||||
statusMut.mutate(next)
|
||||
}}
|
||||
disabled={statusMut.isPending || nextStatuses.length === 0}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>Выберите…</em>
|
||||
</MenuItem>
|
||||
{nextStatuses.map((s) => (
|
||||
<MenuItem key={s} value={s}>
|
||||
{ORDER_STATUS_MAP[s] ?? s}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
<Box>
|
||||
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 700 }}>
|
||||
Быстрый переход статуса
|
||||
</Typography>
|
||||
{statusMut.isError && <Alert severity="error">Не удалось сменить статус</Alert>}
|
||||
{nextStatuses.length === 0 ? (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Статус финальный, смена недоступна
|
||||
</Typography>
|
||||
) : (
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={1.25}>
|
||||
{nextStatuses.map((nextStatus) => {
|
||||
const isCancelled = nextStatus === 'CANCELLED'
|
||||
return (
|
||||
<Button
|
||||
key={nextStatus}
|
||||
variant={isCancelled ? 'outlined' : 'contained'}
|
||||
color={isCancelled ? 'error' : 'primary'}
|
||||
disabled={statusMut.isPending}
|
||||
onClick={() => statusMut.mutate(nextStatus)}
|
||||
>
|
||||
{ORDER_STATUS_MAP[nextStatus] ?? nextStatus}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Typography variant="subtitle2" sx={{ mb: 1 }}>
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { useUnit } from 'effector-react'
|
||||
import { MemoryRouter } from 'react-router-dom'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { setAdminOrderStatus } from '@/entities/order/api/admin-order-api'
|
||||
import type { AdminOrderDetailResponse } from '@/entities/order/api/admin-order-api'
|
||||
import { ORDER_STATUS_MAP } from '@/shared/lib/order-status-data'
|
||||
import { OrderDetailContent } from '../OrderDetailContent'
|
||||
|
||||
vi.mock('@/entities/order/api/admin-order-api', () => ({
|
||||
setAdminOrderStatus: vi.fn(),
|
||||
postAdminOrderMessage: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('effector-react', () => ({
|
||||
useUnit: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/shared/ui/RichTextMessageEditor.lazy', () => ({
|
||||
RichTextMessageEditor: ({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: string
|
||||
onChange: (next: string) => void
|
||||
placeholder?: string
|
||||
disabled?: boolean
|
||||
}) => <textarea aria-label="Ответ админа" value={value} onChange={(e) => onChange(e.target.value)} />,
|
||||
}))
|
||||
|
||||
const setAdminOrderStatusMock = vi.mocked(setAdminOrderStatus)
|
||||
const useUnitMock = vi.mocked(useUnit)
|
||||
|
||||
function createDetail(overrides?: Partial<AdminOrderDetailResponse['item']>): AdminOrderDetailResponse['item'] {
|
||||
return {
|
||||
id: 'order-12345678',
|
||||
status: 'PENDING_PAYMENT',
|
||||
deliveryType: 'delivery',
|
||||
deliveryCarrier: null,
|
||||
paymentMethod: 'online',
|
||||
itemsSubtotalCents: 3000,
|
||||
deliveryFeeCents: 300,
|
||||
deliveryFeeLocked: true,
|
||||
totalCents: 3300,
|
||||
currency: 'RUB',
|
||||
addressSnapshotJson: null,
|
||||
comment: null,
|
||||
createdAt: '2026-05-28T10:00:00.000Z',
|
||||
updatedAt: '2026-05-28T10:00:00.000Z',
|
||||
user: {
|
||||
id: 'user-1',
|
||||
email: 'buyer@example.com',
|
||||
displayName: 'Покупатель',
|
||||
avatar: null,
|
||||
avatarStyle: null,
|
||||
},
|
||||
items: [
|
||||
{
|
||||
id: 'item-1',
|
||||
productId: 'product-1',
|
||||
qty: 1,
|
||||
titleSnapshot: 'Тестовый товар',
|
||||
priceCentsSnapshot: 3000,
|
||||
},
|
||||
],
|
||||
messages: [],
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
function createDeferred<T>() {
|
||||
let resolve!: (value: T | PromiseLike<T>) => void
|
||||
let reject!: (reason?: unknown) => void
|
||||
const promise = new Promise<T>((res, rej) => {
|
||||
resolve = res
|
||||
reject = rej
|
||||
})
|
||||
return { promise, resolve, reject }
|
||||
}
|
||||
|
||||
function renderComponent(detail: AdminOrderDetailResponse['item'], orderId = 'order-12345678') {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
})
|
||||
|
||||
return render(
|
||||
<MemoryRouter>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<OrderDetailContent detail={detail} orderId={orderId} />
|
||||
</QueryClientProvider>
|
||||
</MemoryRouter>,
|
||||
)
|
||||
}
|
||||
|
||||
describe('OrderDetailContent quick status transitions', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
useUnitMock.mockReturnValue(null)
|
||||
setAdminOrderStatusMock.mockResolvedValue(undefined)
|
||||
})
|
||||
|
||||
it('рендерит кнопки доступных переходов статуса', async () => {
|
||||
renderComponent(createDetail({ status: 'PENDING_PAYMENT', deliveryType: 'delivery' }))
|
||||
|
||||
expect(screen.getByText('Быстрый переход статуса')).toBeInTheDocument()
|
||||
expect(await screen.findByRole('button', { name: ORDER_STATUS_MAP.PAID })).toBeInTheDocument()
|
||||
const cancelledButton = screen.getByRole('button', { name: ORDER_STATUS_MAP.CANCELLED })
|
||||
expect(cancelledButton).toBeInTheDocument()
|
||||
expect(cancelledButton).toHaveClass('MuiButton-outlined')
|
||||
expect(cancelledButton).toHaveClass('MuiButton-colorError')
|
||||
})
|
||||
|
||||
it('по клику вызывает setAdminOrderStatus(orderId, статус)', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderComponent(createDetail({ status: 'PENDING_PAYMENT', deliveryType: 'delivery' }), 'order-click-test')
|
||||
|
||||
await user.click(await screen.findByRole('button', { name: ORDER_STATUS_MAP.PAID }))
|
||||
|
||||
expect(setAdminOrderStatusMock).toHaveBeenCalledWith('order-click-test', 'PAID')
|
||||
})
|
||||
|
||||
it('в pending состоянии дизейблит кнопки перехода', async () => {
|
||||
const user = userEvent.setup()
|
||||
const deferred = createDeferred<void>()
|
||||
setAdminOrderStatusMock.mockImplementationOnce(() => deferred.promise)
|
||||
|
||||
renderComponent(createDetail({ status: 'PENDING_PAYMENT', deliveryType: 'delivery' }))
|
||||
|
||||
await user.click(await screen.findByRole('button', { name: ORDER_STATUS_MAP.PAID }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('button', { name: ORDER_STATUS_MAP.PAID })).toBeDisabled()
|
||||
expect(screen.getByRole('button', { name: ORDER_STATUS_MAP.CANCELLED })).toBeDisabled()
|
||||
})
|
||||
|
||||
deferred.resolve(undefined)
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('button', { name: ORDER_STATUS_MAP.PAID })).not.toBeDisabled()
|
||||
})
|
||||
})
|
||||
|
||||
it('для финального статуса показывает сообщение без кнопок перехода', () => {
|
||||
renderComponent(createDetail({ status: 'CANCELLED' }))
|
||||
|
||||
expect(screen.getByText('Статус финальный, смена недоступна')).toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: ORDER_STATUS_MAP.PAID })).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: ORDER_STATUS_MAP.CANCELLED })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('показывает ошибку мутации и после завершения запроса снова даёт кликнуть', async () => {
|
||||
const user = userEvent.setup()
|
||||
const deferred = createDeferred<void>()
|
||||
setAdminOrderStatusMock.mockImplementationOnce(() => deferred.promise).mockResolvedValueOnce(undefined)
|
||||
|
||||
renderComponent(createDetail({ status: 'PENDING_PAYMENT', deliveryType: 'delivery' }))
|
||||
|
||||
const paidButton = await screen.findByRole('button', { name: ORDER_STATUS_MAP.PAID })
|
||||
await user.click(paidButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(paidButton).toBeDisabled()
|
||||
})
|
||||
|
||||
deferred.reject(new Error('request failed'))
|
||||
|
||||
const errorAlert = await screen.findByText('Не удалось сменить статус')
|
||||
expect(errorAlert).toBeInTheDocument()
|
||||
|
||||
await waitFor(() => {
|
||||
expect(paidButton).not.toBeDisabled()
|
||||
})
|
||||
|
||||
await user.click(paidButton)
|
||||
expect(setAdminOrderStatusMock).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
@@ -2,6 +2,7 @@ import { Fragment, useMemo, useState } from 'react'
|
||||
import Alert from '@mui/material/Alert'
|
||||
import Box from '@mui/material/Box'
|
||||
import Button from '@mui/material/Button'
|
||||
import Chip from '@mui/material/Chip'
|
||||
import FormControl from '@mui/material/FormControl'
|
||||
import InputLabel from '@mui/material/InputLabel'
|
||||
import MenuItem from '@mui/material/MenuItem'
|
||||
@@ -20,6 +21,7 @@ import { OrderDetailContent } from '@/features/order-detail/ui/OrderDetailConten
|
||||
import { ORDER_STATUSES } from '@/shared/constants/order'
|
||||
import { formatPriceRub } from '@/shared/lib/format-price'
|
||||
import { groupOrdersByStatus } from '@/shared/lib/group-orders-by-status'
|
||||
import { orderRequiresPriceApproval } from '@/shared/lib/order-requires-price-approval'
|
||||
import { ORDER_STATUS_MAP } from '@/shared/lib/order-status-data'
|
||||
import { AdminDialog } from '@/shared/ui/AdminDialog/AdminDialog'
|
||||
|
||||
@@ -133,20 +135,39 @@ export function AdminOrdersPage() {
|
||||
{ORDER_STATUS_MAP[group.statusCode] ?? group.statusCode} ({group.items.length})
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{group.items.map((o) => (
|
||||
<TableRow key={o.id} hover>
|
||||
<TableCell>{o.id.slice(-8)}</TableCell>
|
||||
<TableCell>{o.user.email}</TableCell>
|
||||
<TableCell>{new Date(o.createdAt).toLocaleString('ru-RU')}</TableCell>
|
||||
<TableCell>{formatPriceRub(o.totalCents)}</TableCell>
|
||||
<TableCell>{o.itemsCount}</TableCell>
|
||||
<TableCell align="right">
|
||||
<Button size="small" onClick={() => open(o.id)}>
|
||||
Открыть
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{group.items.map((o) => {
|
||||
const knownStatus = ORDER_STATUSES.includes(o.status as (typeof ORDER_STATUSES)[number])
|
||||
const deliveryFeeLocked = (o as typeof o & { deliveryFeeLocked?: boolean }).deliveryFeeLocked ?? true
|
||||
const showPriceApprovalChip =
|
||||
knownStatus &&
|
||||
orderRequiresPriceApproval({
|
||||
status: o.status as (typeof ORDER_STATUSES)[number],
|
||||
deliveryType: o.deliveryType,
|
||||
deliveryFeeLocked,
|
||||
})
|
||||
|
||||
return (
|
||||
<TableRow key={o.id} hover>
|
||||
<TableCell>
|
||||
<Stack direction="row" spacing={1} useFlexGap sx={{ alignItems: 'center', flexWrap: 'wrap' }}>
|
||||
<span>{o.id.slice(-8)}</span>
|
||||
{showPriceApprovalChip && (
|
||||
<Chip size="small" color="warning" variant="outlined" label="Цена не подтверждена" />
|
||||
)}
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell>{o.user.email}</TableCell>
|
||||
<TableCell>{new Date(o.createdAt).toLocaleString('ru-RU')}</TableCell>
|
||||
<TableCell>{formatPriceRub(o.totalCents)}</TableCell>
|
||||
<TableCell>{o.itemsCount}</TableCell>
|
||||
<TableCell align="right">
|
||||
<Button size="small" onClick={() => open(o.id)}>
|
||||
Открыть
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
</Fragment>
|
||||
))}
|
||||
{ordersQuery.isSuccess && items.length === 0 && (
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { fetchAdminOrders } from '@/entities/order/api/admin-order-api'
|
||||
import type { AdminOrderListItem } from '@/entities/order/api/admin-order-api'
|
||||
import { AdminOrdersPage } from '../AdminOrdersPage'
|
||||
|
||||
vi.mock('@/entities/order/api/admin-order-api', () => ({
|
||||
fetchAdminOrders: vi.fn(),
|
||||
fetchAdminOrder: vi.fn(),
|
||||
}))
|
||||
|
||||
const fetchAdminOrdersMock = vi.mocked(fetchAdminOrders)
|
||||
|
||||
function renderPage() {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AdminOrdersPage />
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
}
|
||||
|
||||
type AdminOrderListItemWithApproval = AdminOrderListItem & { deliveryFeeLocked?: boolean }
|
||||
|
||||
function createOrder(overrides?: Partial<AdminOrderListItemWithApproval>): AdminOrderListItemWithApproval {
|
||||
return {
|
||||
id: 'order-12345678',
|
||||
status: 'PENDING_PAYMENT',
|
||||
deliveryType: 'delivery' as const,
|
||||
deliveryCarrier: null,
|
||||
paymentMethod: 'online' as const,
|
||||
totalCents: 10000,
|
||||
currency: 'RUB',
|
||||
createdAt: '2026-05-28T10:00:00.000Z',
|
||||
updatedAt: '2026-05-28T10:00:00.000Z',
|
||||
user: { id: 'user-1', email: 'buyer@example.com' },
|
||||
itemsCount: 1,
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
function mockOrdersResponse(order: AdminOrderListItemWithApproval) {
|
||||
fetchAdminOrdersMock.mockResolvedValueOnce({
|
||||
items: [order],
|
||||
total: 1,
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
})
|
||||
}
|
||||
|
||||
describe('AdminOrdersPage', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('показывает бейдж для PENDING_PAYMENT + delivery + deliveryFeeLocked=false', async () => {
|
||||
mockOrdersResponse(createOrder({ deliveryFeeLocked: false }))
|
||||
|
||||
renderPage()
|
||||
|
||||
expect(await screen.findByText('Цена не подтверждена')).toBeInTheDocument()
|
||||
expect(screen.getByText('12345678')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('не показывает бейдж для PENDING_PAYMENT + pickup + deliveryFeeLocked=false', async () => {
|
||||
mockOrdersResponse(
|
||||
createOrder({
|
||||
id: 'order-87654321',
|
||||
deliveryType: 'pickup',
|
||||
deliveryFeeLocked: false,
|
||||
}),
|
||||
)
|
||||
|
||||
renderPage()
|
||||
|
||||
expect(await screen.findByText('87654321')).toBeInTheDocument()
|
||||
expect(screen.queryByText('Цена не подтверждена')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('не показывает бейдж для PAID + delivery + deliveryFeeLocked=false', async () => {
|
||||
mockOrdersResponse(
|
||||
createOrder({
|
||||
id: 'order-45671234',
|
||||
status: 'PAID',
|
||||
deliveryFeeLocked: false,
|
||||
}),
|
||||
)
|
||||
|
||||
renderPage()
|
||||
|
||||
expect(await screen.findByText('45671234')).toBeInTheDocument()
|
||||
expect(screen.queryByText('Цена не подтверждена')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('не показывает бейдж при отсутствии deliveryFeeLocked', async () => {
|
||||
mockOrdersResponse(
|
||||
createOrder({
|
||||
id: 'order-11223344',
|
||||
deliveryFeeLocked: undefined,
|
||||
}),
|
||||
)
|
||||
|
||||
renderPage()
|
||||
|
||||
expect(await screen.findByText('11223344')).toBeInTheDocument()
|
||||
expect(screen.queryByText('Цена не подтверждена')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('вызывает fetchAdminOrders на стартовом рендере', async () => {
|
||||
mockOrdersResponse(createOrder({ id: 'order-99887766', deliveryFeeLocked: true }))
|
||||
|
||||
renderPage()
|
||||
|
||||
await screen.findByText('99887766')
|
||||
expect(fetchAdminOrdersMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'
|
||||
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'
|
||||
import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'
|
||||
@@ -218,14 +218,13 @@ export function AdminSliderPage() {
|
||||
|
||||
const galleryItems: GalleryImageItem[] = galleryQuery.data?.items ?? []
|
||||
|
||||
const initialSlides = useMemo<SlideDraft[]>(() => {
|
||||
if (!sliderQuery.isSuccess) return []
|
||||
return sliderQuery.data.slides.map((s) => ({
|
||||
galleryImageId: s.galleryImageId,
|
||||
caption: s.caption,
|
||||
textColor: s.textColor || '#ffffff',
|
||||
}))
|
||||
}, [sliderQuery.isSuccess, sliderQuery.data?.slides])
|
||||
const initialSlides: SlideDraft[] = sliderQuery.isSuccess
|
||||
? sliderQuery.data.slides.map((s) => ({
|
||||
galleryImageId: s.galleryImageId,
|
||||
caption: s.caption,
|
||||
textColor: s.textColor || '#ffffff',
|
||||
}))
|
||||
: []
|
||||
|
||||
if (sliderQuery.isLoading || galleryQuery.isLoading) {
|
||||
return <Typography color="text.secondary">Загрузка…</Typography>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { orderRequiresPriceApproval } from '../order-requires-price-approval'
|
||||
|
||||
describe('orderRequiresPriceApproval', () => {
|
||||
it('returns true when pending payment delivery fee is not locked for delivery', () => {
|
||||
const result = orderRequiresPriceApproval({
|
||||
status: 'PENDING_PAYMENT',
|
||||
deliveryType: 'delivery',
|
||||
deliveryFeeLocked: false,
|
||||
})
|
||||
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it('returns false when status is not pending payment', () => {
|
||||
const result = orderRequiresPriceApproval({
|
||||
status: 'PAID',
|
||||
deliveryType: 'delivery',
|
||||
deliveryFeeLocked: false,
|
||||
})
|
||||
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false when delivery type is pickup', () => {
|
||||
const result = orderRequiresPriceApproval({
|
||||
status: 'PENDING_PAYMENT',
|
||||
deliveryType: 'pickup',
|
||||
deliveryFeeLocked: false,
|
||||
})
|
||||
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false when delivery fee is locked', () => {
|
||||
const result = orderRequiresPriceApproval({
|
||||
status: 'PENDING_PAYMENT',
|
||||
deliveryType: 'delivery',
|
||||
deliveryFeeLocked: true,
|
||||
})
|
||||
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { OrderStatus } from '@/shared/constants/order'
|
||||
|
||||
type OrderPriceApprovalCandidate = {
|
||||
status: OrderStatus
|
||||
deliveryType: 'delivery' | 'pickup'
|
||||
deliveryFeeLocked: boolean
|
||||
}
|
||||
|
||||
export function orderRequiresPriceApproval(order: OrderPriceApprovalCandidate): boolean {
|
||||
return order.status === 'PENDING_PAYMENT' && order.deliveryType === 'delivery' && order.deliveryFeeLocked === false
|
||||
}
|
||||
Reference in New Issue
Block a user