/** * Sprint 27 — cross-feature: ОФД Mock + RetailSale + Reports + ABC. * * Сценарий: * 1. Owner включает Provider=Mock через PUT /api/organization/fiscal. * 2. Делает приёмку 100 шт product А. * 3. Проводит 50 розничных продаж (1 шт/чек, разные продукты по rotation). * 4. Каждый чек получает `FiscalNumber=MOCK-…` после post (через Mock- * провайдер; деталь идемпотентна — повторный post тот же номер). * 5. Sales-отчёт за день показывает 50 транзакций + сумма ≈ ожидаемой. * 6. ABC-отчёт классифицирует наш ОДИН товар в класс A (>80% выручки), * потому что больше товаров не продаём. */ import { expect, test } from '@playwright/test' import { request } from '../regression/factories/api-client.js' import { Endpoints } from '../regression/factories/types.js' import { OrgFactory } from '../regression/factories/OrgFactory.js' test.describe('27.4 OFD Mock + RetailSale + Reports', () => { test('Provider=Mock, 50 продаж имеют MOCK-FiscalNumber, отчёт и ABC учитывают их', async () => { test.setTimeout(180_000) const org = await OrgFactory.for('s27ofd') .withProducts(3) .withCounterparties(1) .withSupplies(1) // 100 шт каждого товара .build() const tok = org.session.accessToken const headers = { Authorization: `Bearer ${tok}` } // ── 1. Включаем Mock-провайдер. await request('/api/organization/fiscal', { method: 'PUT', token: tok, body: { provider: 1, // Mock newApiKey: null, newApiSecret: null, cashboxUniqueNumber: 'MOCK-CASHBOX-001', apiBaseUrl: null, }, }) const fs = await request<{ provider: number; providerName: string }>( '/api/organization/fiscal', { token: tok }, ) expect(fs.provider).toBe(1) // ── 2. Проводим 50 продаж по одному штуку product[0]. const product = org.products[0] const today = new Date() const created: string[] = [] const N = 50 for (let i = 0; i < N; i++) { const saleInput = { date: today.toISOString(), storeId: org.refs.storeId, retailPointId: org.refs.retailPointId ?? null, customerId: null, currencyId: org.refs.currencyId, payment: 1, // Cash paidCash: 100, paidCard: 0, notes: `s27 sale #${i}`, lines: [ { productId: product.id, quantity: 1, unitPrice: 100, discount: 0, vatPercent: 0, }, ], } const createRes = await request<{ id: string; number: string }>( '/api/sales/retail', { token: tok, body: saleInput }, ) await request(`/api/sales/retail/${createRes.id}/post`, { token: tok, body: {}, }) created.push(createRes.id) } expect(created.length).toBe(N) // ── 3. Проверяем FiscalNumber у каждой проданной (берём первые 5 в выборку). let mockCount = 0 for (const id of created.slice(0, 5)) { const sale = await request<{ fiscalNumber: string | null; fiscalQrCode: string | null }>( `/api/sales/retail/${id}`, { token: tok }, ) if (sale.fiscalNumber?.startsWith('MOCK-')) mockCount++ } expect(mockCount, '5/5 первых чеков имеют MOCK-FiscalNumber').toBe(5) // ── 4. Sales-отчёт за день: 50 транзакций или ≥ 50 (если предыдущие // прогоны оставили данные). И revenue ≥ 5000 (50 × 100). const fromDate = new Date(today) fromDate.setHours(0, 0, 0, 0) const toDate = new Date(today) toDate.setHours(23, 59, 59, 999) type SalesRow = { key: string; label: string; revenue: number; transactions: number } const report = await request( `/api/reports/sales?from=${fromDate.toISOString()}&to=${toDate.toISOString()}&groupBy=period:day`, { token: tok }, ) const totalTx = report.reduce((s, r) => s + r.transactions, 0) const totalRevenue = report.reduce((s, r) => s + Number(r.revenue), 0) expect(totalTx, 'есть ≥50 транзакций').toBeGreaterThanOrEqual(N) expect(totalRevenue).toBeGreaterThanOrEqual(N * 100) // ── 5. ABC-отчёт: наш товар = класс A (мы продаём только его). type AbcRow = { productId: string; abcClass: string; share: number } const abc = await request( `/api/reports/abc?from=${fromDate.toISOString()}&to=${toDate.toISOString()}&metric=revenue`, { token: tok }, ) const ourRow = abc.find(x => x.productId === product.id) expect(ourRow, 'наш товар присутствует в ABC').toBeTruthy() expect(ourRow!.abcClass).toBe('A') expect(Number(ourRow!.share)).toBeGreaterThan(0.5) }) })