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
- MoneyMath: вынесены чистые формулы (статус оплаты, ожидаемая наличность, разница смены, остаток счёта, долг поставщика, защита от переплаты); сервис/SuppliesController/MoneyReportsController теперь зовут их. - UnitTests/MoneyMathTests: 25 кейсов на формулы + money-флаги RolePermissions. - IntegrationTests/MoneyModuleTests (реальный Postgres, 7 [Fact]): мультитенантность (Б не видит счета А), инвариант остатка, частичная→полная оплата приёмки, переплата→409, долг поставщика, продажа без смены→409 + после open→ok, расчёт expected/difference при закрытии смены. - ApiActor: helpers CreateRetailPoint/CreateMoneyAccount/CreatePostedSupply. - tests/e2e/scrape-moysklad.ts — скрейпер для docs/money-moysklad-reference.md. Прогон: UnitTests 25/25 ✓, IntegrationTests money 7/7 ✓ (33s, Testcontainers). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
75 lines
3.5 KiB
TypeScript
75 lines
3.5 KiB
TypeScript
// Scrape MoySklad REST API docs for sections relevant to the money module.
|
||
// Headless-Chromium нужен потому что doc SPA рендерит разделы JS-ом.
|
||
// Запуск: cd tests/e2e && npx tsx scrape-moysklad.ts
|
||
import { chromium } from "playwright";
|
||
import { writeFile, mkdir } from "node:fs/promises";
|
||
import { dirname } from "node:path";
|
||
|
||
const targets: { slug: string; label: string }[] = [
|
||
{ slug: "#/documents/payment-in", label: "Входящий платёж (безнал)" },
|
||
{ slug: "#/documents/payment-out", label: "Исходящий платёж (безнал)" },
|
||
{ slug: "#/documents/cashin", label: "Приходный ордер (наличные)" },
|
||
{ slug: "#/documents/cashout", label: "Расходный ордер (наличные)" },
|
||
{ slug: "#/documents/retaildrawercashin", label: "Внесение денег в кассу" },
|
||
{ slug: "#/documents/retaildrawercashout", label: "Выплата денег из кассы" },
|
||
{ slug: "#/documents/retailshift", label: "Розничная смена" },
|
||
{ slug: "#/documents/counterpartyadjustment", label: "Корректировка взаиморасчётов" },
|
||
{ slug: "#/reports/report-money", label: "Отчёт «Деньги»" },
|
||
{ slug: "#/reports/report-counterparty", label: "Показатели контрагентов" },
|
||
{ slug: "#/dictionaries/organization", label: "Юрлицо / счета юрлица" },
|
||
];
|
||
|
||
const BASE = "https://dev.moysklad.ru/doc/api/remap/1.2/";
|
||
const OUT = "/tmp/moysklad-dump";
|
||
|
||
async function dumpOne(page: import("playwright").Page, slug: string, label: string): Promise<string> {
|
||
const url = BASE + slug;
|
||
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 45_000 });
|
||
// SPA рендер — ждём, пока в DOM появятся заголовки раздела.
|
||
await page.waitForTimeout(2500);
|
||
// Снимаем контент самой длинной «статьи» (у них разметка меняется от раздела).
|
||
const text = await page.evaluate(() => {
|
||
// Пытаемся найти самую большую семантическую зону — main/article или просто body.
|
||
const candidates = Array.from(document.querySelectorAll("main, article, section, .markdown-section, .content, body"));
|
||
let best: Element = document.body;
|
||
let bestLen = 0;
|
||
for (const el of candidates) {
|
||
const len = (el as HTMLElement).innerText?.length ?? 0;
|
||
if (len > bestLen) {
|
||
bestLen = len;
|
||
best = el;
|
||
}
|
||
}
|
||
return (best as HTMLElement).innerText;
|
||
});
|
||
return `=== ${label} (${slug}) ===\nURL: ${url}\n\n${text}\n\n`;
|
||
}
|
||
|
||
async function main() {
|
||
const browser = await chromium.launch({ headless: true });
|
||
const ctx = await browser.newContext({ viewport: { width: 1400, height: 900 }, locale: "ru-RU" });
|
||
const page = await ctx.newPage();
|
||
await mkdir(OUT, { recursive: true });
|
||
|
||
for (const t of targets) {
|
||
const safe = t.slug.replace(/[^a-z0-9]+/gi, "_");
|
||
console.log("→", t.slug);
|
||
try {
|
||
const dump = await dumpOne(page, t.slug, t.label);
|
||
const path = `${OUT}/${safe}.txt`;
|
||
await mkdir(dirname(path), { recursive: true });
|
||
await writeFile(path, dump, "utf8");
|
||
console.log(" wrote", path, dump.length, "chars");
|
||
} catch (e: any) {
|
||
console.error(" FAILED", t.slug, e?.message);
|
||
}
|
||
}
|
||
|
||
await browser.close();
|
||
}
|
||
|
||
main().catch((e) => {
|
||
console.error(e);
|
||
process.exit(1);
|
||
});
|