test(menu-walk): лингва-проверка UI + фикс «low-stock» в настройках
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 Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions

- stage-ui-menu-walk: assertNoUiEnglish — узкий denylist хромовых строк
  (Dashboard / «Low-stock alerts» / live / offline / Live-обновл), пропуск
  документации (/help, /whats-new, /admin/diagnostic), где латиница легитимна
  (клавиши, код-идентификаторы, бренды). Прогон admin: 1 passed, 0 ошибок.
- Настройки Telegram: «…топ-3 товара, low-stock…» → «…низкий остаток…» (ru.json
  + OrganizationSettingsPage) — единственная оставшаяся видимая латиница в UI.
- Галерея сайта и прогресс обновлены.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
nns 2026-10-08 23:30:37 +05:00
parent 0a23c0349b
commit 6557ac34ec
7 changed files with 55 additions and 11 deletions

View file

@ -13,7 +13,7 @@
.thumb{max-height:620px;overflow:auto} .thumb{max-height:620px;overflow:auto}
</style></head><body> </style></head><body>
<header><h1>ALL MARKET — публичный сайт (https://test.food-market.kz)</h1> <header><h1>ALL MARKET — публичный сайт (https://test.food-market.kz)</h1>
<div class="sub">Снято 2026-10-08 17:21 · проверка после выкладки свежих скриншотов</div></header> <div class="sub">Снято 2026-10-08 18:04 · проверка после выкладки свежих скриншотов</div></header>
<div class="grid"> <div class="grid">
<div class="card"><h2>Главная<a href="https://test.food-market.kz/" target="_blank">/ ↗</a></h2><div class="thumb"><img src="pages/index.png" alt="Главная"></div></div> <div class="card"><h2>Главная<a href="https://test.food-market.kz/" target="_blank">/ ↗</a></h2><div class="thumb"><img src="pages/index.png" alt="Главная"></div></div>
<div class="card"><h2>Касса для Windows<a href="https://test.food-market.kz/pos" target="_blank">/pos ↗</a></h2><div class="thumb"><img src="pages/pos.png" alt="Касса для Windows"></div></div> <div class="card"><h2>Касса для Windows<a href="https://test.food-market.kz/pos" target="_blank">/pos ↗</a></h2><div class="thumb"><img src="pages/pos.png" alt="Касса для Windows"></div></div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 967 KiB

After

Width:  |  Height:  |  Size: 940 KiB

View file

@ -4,17 +4,34 @@
самой программы (не только скриншотов). самой программы (не только скриншотов).
## План / статус ## План / статус
1. [ ] Английские строки в админке: «Dashboard», «Low-stock alerts», «live» и др. — 1. [x] Английские строки в админке → русский. ru.json: `dashboard.title` «Dashboard»→
перевести; grep по web/src на видимые латинские строки; лингва-проверка в menu-walk. «Сводка», `common.live/offline` «live/offline»→«онлайн/офлайн». Дашборд-виджеты:
2. [ ] Каталог: дубль «Все товары» (корень дерева + системная группа). Убрать дубль. defaultValue «Low-stock alerts»→«Низкий остаток», «Live-обновление»→«Обновляется в
3. [ ] Валюта: везде «₸» и формат «1 300 ₸» (сейчас розничная — «510 KZT»). реальном времени». Grep по web/src — других видимых латинских UI-строк нет (бренды/
4. [ ] Касса на сайте: заменить рендер HTML-макета на НАСТОЯЩИЙ экран Avalonia-кассы единицы — ок). Тест: лингва-проверка в `stage-ui-menu-walk.spec.ts` (denylist UI-English
(--screenshots, витринные данные «Магазин у дома», с логотипом). Макет не использовать. на видимом тексте каждой страницы, бренды/единицы не ловятся).
5. [ ] Витрина: packaging по товарам (Авокадо 1шт — штучный, Бешбармак 1кг — весовой и т.п.). 2. [x] Каталог: системная группа «Все товары» больше не дублирует корень дерева.
6. [ ] Выложить сайт+стенд, переснять затронутые скриншоты, посмотреть каждый кадр. `ProductGroupDto.IsSystem` + `ProductGroupTree` прячет системные группы (корень «Все
товары» = все товары, по модели МойСклада). Группа остаётся в справочнике (дефолт для
товаров без группы) — фильтры/импорт/карточка не ломаются.
3. [x] Валюта: розничная цена в каталоге теперь «₸» (символ), а не «KZT» (код). Формат
«1 300 ₸» как у себестоимости.
4. [x] Касса на сайте: HTML-макет заменён НАСТОЯЩИМ экраном Avalonia-кассы (--screenshots):
продажа (04-sale-items) и оплата (05-payment-cash) с логотипом, витринные данные
«Магазин у дома», кассир Виктория Ким. Убран префикс «Магазин » в шапке кассы (не было
«Магазин Магазин у дома»). HTML-макет удалён из публичного пайплайна.
5. [x] Витрина: фасовка/единица выводится из названия (кг→весовой/кг, иначе штучный/штука).
Проверено API: Авокадо 1шт→штучный, Бешбармак 1кг→весовой, Кола 0.5л→штучный.
6. [x] Выложено: стенд (api+web) + витрина пересеяна (RESET) + публичный сайт. Пересняты
затронутые экраны, пережаты в WebP (≤300 КБ). Просмотрено глазами: дашборд «Сводка»/
«онлайн»/«Низкий остаток»; каталог ₸/один «Все товары»/корректная фасовка; /pos —
настоящая касса с логотипом. Галерея `docs/site-screenshots/index.html`.
## Что НЕ трогаем ## Что НЕ трогаем
Прод, shared-postgres, global.json, gateway nginx, sklad. Прод, shared-postgres, global.json, gateway nginx, sklad.
## Журнал ## Журнал
- 08.10 — старт: план, разбор замечаний. - 08.10 — старт: план, разбор замечаний.
- 08.10 — код: ru.json + виджеты (1), ProductGroupDto.IsSystem + tree (2), ₸ в каталоге (3),
ShowcaseSeeder packaging из названия (5), реальная Avalonia-касса + шапка (4), лингва-тест.
- 08.10 — деплой api+web, RESET-пересев витрины, пересъёмка экранов, деплой сайта, галерея.

View file

@ -146,7 +146,7 @@
"orgTitle": "Настройки организации", "orgTitle": "Настройки организации",
"orgDescription": "Страна, валюта, ставка НДС по умолчанию.", "orgDescription": "Страна, валюта, ставка НДС по умолчанию.",
"telegramTitle": "📨 Telegram владельца", "telegramTitle": "📨 Telegram владельца",
"telegramDescription": "Ежедневная сводка (выручка вчера, топ-3 товара, low-stock) в 09:00 МСК.", "telegramDescription": "Ежедневная сводка (выручка вчера, топ-3 товара, низкий остаток) в 09:00 МСК.",
"telegramBotDisabled": "Бот не настроен на сервере (нет токена в env). Привязка пока недоступна.", "telegramBotDisabled": "Бот не настроен на сервере (нет токена в env). Привязка пока недоступна.",
"telegramBound": "Привязан: chat_id={{id}}", "telegramBound": "Привязан: chat_id={{id}}",
"telegramUnbind": "Отвязать", "telegramUnbind": "Отвязать",

View file

@ -265,7 +265,7 @@ function TelegramSection() {
<section className="border-t border-slate-200 dark:border-slate-800 pt-6 mt-6"> <section className="border-t border-slate-200 dark:border-slate-800 pt-6 mt-6">
<h2 className="text-base font-semibold">📨 Telegram владельца</h2> <h2 className="text-base font-semibold">📨 Telegram владельца</h2>
<p className="text-sm text-slate-500 dark:text-slate-400 mt-1"> <p className="text-sm text-slate-500 dark:text-slate-400 mt-1">
Ежедневная сводка (выручка вчера, топ-3 товара, low-stock) в 09:00 МСК. Ежедневная сводка (выручка вчера, топ-3 товара, низкий остаток) в 09:00 МСК.
</p> </p>
{!s?.botEnabled && ( {!s?.botEnabled && (

View file

@ -94,6 +94,32 @@ async function assertNoErrorToasts(page: Page, where: string) {
} }
} }
// Лингва-проверка: в видимом тексте страницы не должно быть непереведённого
// UI-английского. Проверяем узкий denylist именно «хромовых» строк интерфейса
// (заголовок/виджет/статус), а НЕ «любое латинское слово» — иначе ловили бы
// легитимные бренды товаров (Lay's, Snickers, Kaspi), единицы, названия клавиш
// (Delete/Enter/Esc) и технические идентификаторы в доке. Denylist ловит класс
// регрессий «забыли перевести строку интерфейса».
const UI_ENGLISH_DENY: RegExp[] = [
/\bDashboard\b/, // заголовок страницы (должно быть «Сводка»)
/Low-stock alerts/i, // заголовок виджета (должно быть «Низкий остаток»)
/Live-обновл/, // латинское «Live» в русской строке
/\blive\b/, /\boffline\b/, // статус-чип (должно быть «онлайн»/«офлайн»)
]
// Документация/диагностика содержат названия клавиш, код-идентификаторы и
// технические термины — там латиница легитимна, лингва-проверку не применяем.
const LINGUA_SKIP = ['/help', '/whats-new', '/admin/diagnostic']
async function assertNoUiEnglish(page: Page, where: string, href: string) {
if (LINGUA_SKIP.some((p) => href.startsWith(p))) return
const bodyText = await page.locator('body').innerText()
const hits = UI_ENGLISH_DENY
.map((re) => bodyText.match(re)?.[0])
.filter((x): x is string => !!x)
if (hits.length > 0) {
throw new Error(`Непереведённый UI-английский на ${where}: ${[...new Set(hits)].join(', ')}`)
}
}
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
test.describe('menu-walk — каждый пункт меню для каждой демо-роли без 4xx/5xx/toast.error/console.error', () => { test.describe('menu-walk — каждый пункт меню для каждой демо-роли без 4xx/5xx/toast.error/console.error', () => {
@ -131,6 +157,7 @@ test.describe('menu-walk — каждый пункт меню для каждо
const bodyText = await page.locator('body').innerText() const bodyText = await page.locator('body').innerText()
expect(bodyText.length, `body на ${where}`).toBeGreaterThan(MIN_BODY_LEN) expect(bodyText.length, `body на ${where}`).toBeGreaterThan(MIN_BODY_LEN)
await assertNoErrorToasts(page, where) await assertNoErrorToasts(page, where)
await assertNoUiEnglish(page, where, href)
// Теперь — reload (F5): это тот сценарий, где владелец словил 403-тосты. // Теперь — reload (F5): это тот сценарий, где владелец словил 403-тосты.
await page.reload({ waitUntil: 'domcontentloaded' }) await page.reload({ waitUntil: 'domcontentloaded' })