Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Build + push API (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Owner caught on stage (2026-06-07 18:57):
1) POST /api/purchases/invoices/upload → 500: migration Phase31a создала
invoice_uploads.UpdatedAt как NOT NULL, но конвенция проекта — DateTime?
nullable + стамп только для Modified в AppDbContext.StampTimestamps.
EF на INSERT отправлял UpdatedAt=NULL → 23502. Фикс: Phase31b DROP NOT NULL
+ Phase31b для invoice_aliases.
2) Фоновые 403 → тост «Нет доступа» при F5 на любой странице:
- SavedPresets фетчил /api/user/presets (RequiresAdvancedMode) из любой
страницы с фильтрами в простом режиме → 403;
- useOnboardingProgress фетчил /api/organization/employees (Admin only)
для кладовщика/кассира на '/' → 403.
Фикс:
- api.ts: GET-403 silent, тост только на действия юзера (POST/PUT/DELETE);
- SavedPresets gated на advancedMode (useMe + enabled);
- useOnboardingProgress canSeeEmployees-gate.
3) tests/food-market.IntegrationTests/InvoiceUploadHttpTests.cs — HTTP-прогон
POST /upload через Testcontainers-Postgres (дыра: s31 pipeline tests
проверяли matcher, но ни разу не инсертили в invoice_uploads).
4) tests/e2e/scenarios/stage-ui-menu-walk.spec.ts — regress: каждая демо-роль,
каждый пункт меню, F5, assert 0 unexpected 4xx/5xx + 0 error-тостов +
0 console.error. В quality-watchdog step 9 с 4-часовой каденцией.
5) tests/e2e/scenarios/stage-ui-invoice-upload.spec.ts — demo-keeper
загружает реальное фото накладной (tests/fixtures/invoice-sample.jpg,
сгенерирована Playwright-ом из HTML, 1800×2400 JPEG) → ждёт recognition
→ экран проверки. При не-настроенном Claude CLI тест fail'ится с ясным
сообщением (это тоже баг).
Прогон локально 4/4 (InvoicePipelineTests 2/2 + InvoiceUploadHttpTests 2/2).
docs/hotfix-invoice500-403-progress.md — ход работы 1-4/5.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
94 lines
4.5 KiB
TypeScript
94 lines
4.5 KiB
TypeScript
// Генератор фикстуры «фото накладной»: рендерит HTML-таблицу через
|
||
// Playwright Chromium → JPEG → кладёт в tests/fixtures/invoice-sample.jpg.
|
||
//
|
||
// Запуск: pnpm exec tsx scripts/make-invoice-fixture.ts
|
||
//
|
||
// Фикстуру коммитим в репо (однажды сгенерирована — используется всеми
|
||
// e2e-прогонами, в т.ч. в quality-watchdog). Пересобирать при изменении
|
||
// HTML.
|
||
import { chromium } from 'playwright'
|
||
import { writeFileSync, mkdirSync } from 'node:fs'
|
||
import { dirname, resolve } from 'node:path'
|
||
import { fileURLToPath } from 'node:url'
|
||
|
||
const __dir = dirname(fileURLToPath(import.meta.url))
|
||
const OUT = resolve(__dir, '..', '..', 'fixtures', 'invoice-sample.jpg')
|
||
|
||
const html = `<!doctype html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<style>
|
||
* { box-sizing: border-box }
|
||
body { font-family: Helvetica, Arial, sans-serif; color: #111; margin: 0; padding: 24px; background: white }
|
||
.meta { display: flex; justify-content: space-between; margin-bottom: 18px; font-size: 13px }
|
||
.title { font-size: 22px; font-weight: 700; margin: 6px 0 14px }
|
||
.sub { font-size: 13px; color: #444; margin-bottom: 20px }
|
||
table { width: 100%; border-collapse: collapse; font-size: 12px }
|
||
th, td { border: 1px solid #333; padding: 6px 8px; vertical-align: top }
|
||
th { background: #eee; text-align: left; font-weight: 700 }
|
||
td.n { text-align: right; font-variant-numeric: tabular-nums }
|
||
tfoot td { font-weight: 700 }
|
||
.footnote { margin-top: 18px; font-size: 11px; color: #333 }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="meta">
|
||
<div>
|
||
<div><b>Поставщик:</b> ТОО "Тестовый Поставщик"</div>
|
||
<div><b>БИН:</b> 123456789012</div>
|
||
<div><b>Адрес:</b> г. Алматы, ул. Абая 100</div>
|
||
</div>
|
||
<div style="text-align: right">
|
||
<div><b>Накладная № Т-00042</b></div>
|
||
<div><b>Дата:</b> 03.10.2026</div>
|
||
<div><b>Валюта:</b> KZT</div>
|
||
</div>
|
||
</div>
|
||
<div class="title">Товарная накладная</div>
|
||
<div class="sub">Покупатель: ТОО "Food Market Demo" · Склад: Центральный</div>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>№</th>
|
||
<th>Наименование</th>
|
||
<th>Штрихкод</th>
|
||
<th>Код пост.</th>
|
||
<th>Ед.</th>
|
||
<th class="n">Кол-во</th>
|
||
<th class="n">Цена</th>
|
||
<th class="n">Сумма</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td>1</td><td>Молоко «Домик в деревне» 3.2% 950мл</td><td>4870022200015</td><td>MLK-095</td><td>шт</td><td class="n">24</td><td class="n">420.00</td><td class="n">10080.00</td></tr>
|
||
<tr><td>2</td><td>Хлеб «Деревенский» 500г</td><td>4870022200022</td><td>BRD-500</td><td>шт</td><td class="n">30</td><td class="n">180.00</td><td class="n">5400.00</td></tr>
|
||
<tr><td>3</td><td>Яйцо куриное С1, лоток 10шт</td><td>4870022200039</td><td>EGG-10</td><td>уп</td><td class="n">15</td><td class="n">720.00</td><td class="n">10800.00</td></tr>
|
||
<tr><td>4</td><td>Сахар-песок 1кг «Рафинад»</td><td>4870022200046</td><td>SUG-1K</td><td>кг</td><td class="n">40</td><td class="n">360.00</td><td class="n">14400.00</td></tr>
|
||
<tr><td>5</td><td>Масло подсолн. рафинир. 1л</td><td>4870022200053</td><td>OIL-1L</td><td>шт</td><td class="n">12</td><td class="n">980.00</td><td class="n">11760.00</td></tr>
|
||
</tbody>
|
||
<tfoot>
|
||
<tr><td colspan="7">ИТОГО:</td><td class="n">52440.00</td></tr>
|
||
</tfoot>
|
||
</table>
|
||
<div class="footnote">
|
||
Отпустил: Иванов И. И. _______________ · Принял: _______________ ·
|
||
М.П.
|
||
</div>
|
||
</body>
|
||
</html>`
|
||
|
||
async function main() {
|
||
mkdirSync(dirname(OUT), { recursive: true })
|
||
const browser = await chromium.launch()
|
||
const context = await browser.newContext({ viewport: { width: 900, height: 1200 }, deviceScaleFactor: 2 })
|
||
const page = await context.newPage()
|
||
await page.setContent(html, { waitUntil: 'load' })
|
||
const buffer = await page.screenshot({ fullPage: true, type: 'jpeg', quality: 92 })
|
||
writeFileSync(OUT, buffer)
|
||
console.log('fixture written:', OUT, buffer.length, 'bytes')
|
||
await context.close()
|
||
await browser.close()
|
||
}
|
||
main().catch((e) => { console.error(e); process.exit(1) })
|