/** * Sprint 16 — flows 04 документы (8 flows): * 4.1 supply post+unpost остаток меняется на +qty потом обратно * 4.2 enter post+unpost (оприходование) * 4.3 retail-sale post → остаток -qty, FiscalNumber=null (None провайдер) * 4.4 retail-sale unpost восстанавливает остаток * 4.5 loss post+unpost — списание * 4.6 transfer post+unpost между двумя складами * 4.7 demand post (оптовая отгрузка) * 4.8 supplier-return post (возврат поставщику) */ import { expect, test } from '@playwright/test' import { request } from '../factories/api-client.js' import { Endpoints } from '../factories/types.js' import { OrgFactory } from '../factories/OrgFactory.js' async function stockOf(token: string, productId: string, storeId: string): Promise { const r = await request<{ items: Array<{ productId: string; storeId: string; quantity: number }> }>( `/api/inventory/stock?productId=${productId}&pageSize=100`, { token }, ) const row = r.items.find(x => x.productId === productId && x.storeId === storeId) return row ? Number(row.quantity) : 0 } test.describe('flow 04 — документы post/unpost', () => { test('4.1 supply post → stock +qty; unpost → откат @smoke', async () => { const b = await OrgFactory.for('doc41').withProducts(1).withCounterparties(1).build() const supplier = b.counterparties[0]! const product = b.products[0]! const draft = await request<{ id: string }>(Endpoints.supplies, { token: b.session.accessToken, body: { date: new Date().toISOString(), supplierId: supplier.id, storeId: b.refs.storeId, currencyId: b.refs.currencyId, lines: [{ productId: product.id, quantity: 50, unitPrice: 30 }], }, }) await request(`${Endpoints.supplies}/${draft.id}/post`, { token: b.session.accessToken, method: 'POST' }) expect(await stockOf(b.session.accessToken, product.id, b.refs.storeId)).toBe(50) await request(`${Endpoints.supplies}/${draft.id}/unpost`, { token: b.session.accessToken, method: 'POST' }) expect(await stockOf(b.session.accessToken, product.id, b.refs.storeId)).toBe(0) }) test('4.2 enter post+unpost — оприходование', async () => { const b = await OrgFactory.for('doc42').withProducts(1).build() const product = b.products[0]! const draft = await request<{ id: string }>(Endpoints.enters, { token: b.session.accessToken, body: { date: new Date().toISOString(), storeId: b.refs.storeId, currencyId: b.refs.currencyId, lines: [{ productId: product.id, quantity: 7, unitPrice: 25 }], }, }) await request(`${Endpoints.enters}/${draft.id}/post`, { token: b.session.accessToken, method: 'POST' }) expect(await stockOf(b.session.accessToken, product.id, b.refs.storeId)).toBe(7) await request(`${Endpoints.enters}/${draft.id}/unpost`, { token: b.session.accessToken, method: 'POST' }) expect(await stockOf(b.session.accessToken, product.id, b.refs.storeId)).toBe(0) }) test('4.3 retail-sale post → stock -qty', async () => { const b = await OrgFactory.for('doc43').withProducts(1).withSupplies(1).build() const product = b.products[0]! const stockBefore = await stockOf(b.session.accessToken, product.id, b.refs.storeId) expect(stockBefore).toBe(100) const draft = await request<{ id: string }>(Endpoints.retailSales, { token: b.session.accessToken, body: { date: new Date().toISOString(), storeId: b.refs.storeId, retailPointId: b.refs.retailPointId, currencyId: b.refs.currencyId, payment: 0, isReturn: false, lines: [{ productId: product.id, quantity: 3, unitPrice: 100, discount: 0, vatPercent: 12 }], subtotal: 300, discountTotal: 0, total: 300, paidCash: 300, paidCard: 0, }, }) await request(`${Endpoints.retailSales}/${draft.id}/post`, { token: b.session.accessToken, method: 'POST' }) expect(await stockOf(b.session.accessToken, product.id, b.refs.storeId)).toBe(97) }) test('4.4 retail-sale unpost восстанавливает остаток', async () => { const b = await OrgFactory.for('doc44').withProducts(1).withSupplies(1).build() const product = b.products[0]! const draft = await request<{ id: string }>(Endpoints.retailSales, { token: b.session.accessToken, body: { date: new Date().toISOString(), storeId: b.refs.storeId, retailPointId: b.refs.retailPointId, currencyId: b.refs.currencyId, payment: 0, isReturn: false, lines: [{ productId: product.id, quantity: 5, unitPrice: 100, discount: 0, vatPercent: 12 }], subtotal: 500, discountTotal: 0, total: 500, paidCash: 500, paidCard: 0, }, }) await request(`${Endpoints.retailSales}/${draft.id}/post`, { token: b.session.accessToken, method: 'POST' }) expect(await stockOf(b.session.accessToken, product.id, b.refs.storeId)).toBe(95) await request(`${Endpoints.retailSales}/${draft.id}/unpost`, { token: b.session.accessToken, method: 'POST' }) expect(await stockOf(b.session.accessToken, product.id, b.refs.storeId)).toBe(100) }) test('4.5 loss post+unpost — списание уменьшает/восстанавливает', async () => { const b = await OrgFactory.for('doc45').withProducts(1).withSupplies(1).build() const product = b.products[0]! const draft = await request<{ id: string }>(Endpoints.losses, { token: b.session.accessToken, body: { date: new Date().toISOString(), storeId: b.refs.storeId, currencyId: b.refs.currencyId, reason: 1, // LossReason.Expired lines: [{ productId: product.id, quantity: 2, unitCost: 50 }], }, }) await request(`${Endpoints.losses}/${draft.id}/post`, { token: b.session.accessToken, method: 'POST' }) expect(await stockOf(b.session.accessToken, product.id, b.refs.storeId)).toBe(98) await request(`${Endpoints.losses}/${draft.id}/unpost`, { token: b.session.accessToken, method: 'POST' }) expect(await stockOf(b.session.accessToken, product.id, b.refs.storeId)).toBe(100) }) test('4.6 transfer между двумя складами @smoke', async () => { const b = await OrgFactory.for('doc46').withProducts(1).withSupplies(1).build() const product = b.products[0]! // Создаём второй склад const target = await request<{ id: string }>('/api/catalog/stores', { token: b.session.accessToken, body: { name: 'Второй склад', code: 'S2', isMain: false, isActive: true }, }) const draft = await request<{ id: string }>(Endpoints.transfers, { token: b.session.accessToken, body: { date: new Date().toISOString(), fromStoreId: b.refs.storeId, toStoreId: target.id, currencyId: b.refs.currencyId, lines: [{ productId: product.id, quantity: 10 }], }, }) await request(`${Endpoints.transfers}/${draft.id}/post`, { token: b.session.accessToken, method: 'POST' }) expect(await stockOf(b.session.accessToken, product.id, b.refs.storeId)).toBe(90) expect(await stockOf(b.session.accessToken, product.id, target.id)).toBe(10) await request(`${Endpoints.transfers}/${draft.id}/unpost`, { token: b.session.accessToken, method: 'POST' }) expect(await stockOf(b.session.accessToken, product.id, b.refs.storeId)).toBe(100) }) test('4.7 demand post — оптовая отгрузка уменьшает остаток', async () => { const b = await OrgFactory.for('doc47').withProducts(1).withSupplies(1).withCounterparties(2).build() const product = b.products[0]! const customer = b.counterparties[1]! const draft = await request<{ id: string }>(Endpoints.demands, { token: b.session.accessToken, body: { date: new Date().toISOString(), customerId: customer.id, storeId: b.refs.storeId, currencyId: b.refs.currencyId, payment: 0, lines: [{ productId: product.id, quantity: 8, unitPrice: 90, discount: 0, vatPercent: 12 }], subtotal: 720, discountTotal: 0, total: 720, paidAmount: 720, }, }) await request(`${Endpoints.demands}/${draft.id}/post`, { token: b.session.accessToken, method: 'POST' }) expect(await stockOf(b.session.accessToken, product.id, b.refs.storeId)).toBe(92) }) test('4.8 supplier-return post — возврат поставщику', async () => { const b = await OrgFactory.for('doc48').withProducts(1).withSupplies(1).build() const product = b.products[0]! const supplier = b.counterparties[0]! const draft = await request<{ id: string }>(Endpoints.supplierReturns, { token: b.session.accessToken, body: { date: new Date().toISOString(), supplierId: supplier.id, storeId: b.refs.storeId, currencyId: b.refs.currencyId, lines: [{ productId: product.id, quantity: 4, unitPrice: 50 }], }, }) await request(`${Endpoints.supplierReturns}/${draft.id}/post`, { token: b.session.accessToken, method: 'POST' }) expect(await stockOf(b.session.accessToken, product.id, b.refs.storeId)).toBe(96) }) })