Compare commits

..

12 commits

Author SHA1 Message Date
nns 821f40634c rebrand: прогресс-док — диапазон коммитов для отката + итог проверки
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 Public / Build + push Public (test + latest) (push) Waiting to run
Docker Public / Deploy :test → test.food-market.kz (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
Диапазон pre-rebrand-all-market..HEAD (11 коммитов). Итог: grep чисто,
скриншоты Было/Стало, POS uitest/smoke OK, deploy-stage OK, menu-walk 3/3,
full-chain 16/16 ЗЕЛЁНЫЙ, откат проверен в worktree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:23:11 +05:00
nns af00f04e4e rebrand: галерея — реальный стенд (вход без тоста/префилла) + rollback-кадр
- now-web-login-stage.png: вход на test.admin.food-market.kz после деплоя —
  баннер стенда, быстрый вход, логотип ALL MARKET, без тостов ошибок.
- rollback-logo-foodmarket.png: подтверждение отката (лого Food Market).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:21:29 +05:00
nns f9b4d3bd68 rebrand: прогресс-док — безопасность, проверка отката, границы
Отчёт по ревью безопасности (сидер/префилл/ротация пароля стенда), результат
проверки отката в worktree, уточнённые границы (артефакты кассы, маркетинг-скриншоты).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:08:48 +05:00
nns 3a0f9e24d5 rebrand: касса — крупнее логотип + бейдж «Отложенные» внутри кнопки (ревью Mac-Claude)
- Верхняя полоса: логотип ALL MARKET высотой 48 (ширина ≈140px ≥ минимума
  брендбука), «Касса» отделена тонким разделителем (не оторвана).
- Бейдж количества у «Отложенные» теперь целиком внутри кнопки (меньше паддинг
  кнопки, плотнее контент) — не обрезается правым краем.
- Перегенерированы скриншоты кассы; обновлён кадр веб-входа в галерее (без тоста,
  без префилла пароля).
- Зелёный UI не менялся (--color-brand #00B207, идентичен дорибрендинговому).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:07:51 +05:00
nns 026de9f262 rebrand: безопасность входа + тост 404 на странице входа (по ревью Mac-Claude)
БЕЗОПАСНОСТЬ:
- Страница входа больше НЕ префилит e-mail/пароль dev-админа и не показывает
  подсказку «Dev admin: …» — только в import.meta.env.DEV на localhost
  (на стенде/проде поля пустые).
- DevDataSeeder: пароль SuperAdmin/демо-админа ТОЛЬКО из секрета
  (Seed:SuperAdminPassword / env SEED_SUPERADMIN_PASSWORD). Дефолт «Admin12345!»
  допустим лишь в Development. На stage/prod (ASPNETCORE_ENVIRONMENT=Production)
  без секрета аккаунт с известным паролем НЕ создаётся (warning в лог).
- stage-compose: проброшен Seed__SuperAdminPassword (без дефолта).
- (Пароль существующего SuperAdmin на стенде сменён вручную на сложный и сохранён
  в ~/.fm-watchdog/stage-superadmin-password; quick-login по sentinel/master цел.)

ТОСТ 404:
- StageBanner и QuickLoginBlock пробуют необязательный /api/dev/quick-login/accounts
  через raw fetch (минуя axios-интерсептор) — на проде/без API 404 больше не
  показывается глобальным тостом. На странице входа тостов ошибок нет.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:07:39 +05:00
nns 89c5c6df37 rebrand: галерея Было/Стало (лого, favicon, письмо, OG, касса, вход, сайт)
docs/rebrand-screenshots/ + index.html: пары Было|Стало по логотипу, favicon,
письму-приглашению, OG-картинке, кассе (вход/продажа); «Стало» для веб-входа и
публичной главной. «Было» извлечено из тега pre-rebrand-all-market.
Отмечено, что встроенные продуктовые скриншоты маркетинг-сайта обновятся при
прод-выкатке (screenshots.mjs против живой админки).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:44:24 +05:00
nns 828080c321 rebrand: пользовательские доки + скриншоты кассы с новым лого
- docs/imports.md, docs/simple-mode.md: «Food Market» → «ALL MARKET».
- Перегенерированы скриншоты кассы (вход/продажа/оплата/смена/возврат) с
  логотипом ALL MARKET в шапке и на экране входа.
- Внутренние TZ/verify-доки оставлены как историческая фиксация.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:35:15 +05:00
nns d0d6ebcea1 rebrand: касса ALL MARKET (лого, иконка, заголовок, чек)
- Верхняя полоса и экран входа: логотип-изображение ALL MARKET из ресурсов
  (avares Assets/), color на светлой теме и white на тёмной (Brand.ApplyLogo).
- Заголовок окна «ALL MARKET · Касса»; иконка окна и ApplicationIcon = allmarket.ico
  (собрана из фирменных PNG 32/180/192).
- Чек: в шапке — название точки/магазина (резолвится из RetailPoint, PosSession.StoreName),
  внизу мелко «ALL MARKET — система учёта торговли» (был хардкод «Food Market» в шапке).
- AppImage .desktop: Name «ALL MARKET Касса» + фирменная иконка.
- Эталон-макет pos-mockup.html — логотип «all market».
- AssemblyName/exe (foodmarket.Pos) и имена артефактов не менялись (инфраструктура).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:35:05 +05:00
nns d1176e0fb1 rebrand: публичный сайт ALL MARKET (лого, OG, тексты)
- Логотипы logo.svg/logo-light.svg/favicon.svg → готовые файлы ALL MARKET.
- OG-картинки 1200×630 перегенерированы: реальный логотип ALL MARKET на светлом
  фоне (og.mjs теперь вставляет PNG-лого вместо «F»-плашки). Футер домена
  food-market.kz оставлен (домены — отдельная задача).
- BaseLayout (title/og/twitter/JSON-LD), Logo.astro (alt, h-8), все .astro-страницы,
  блог/KB/legal-контент: «Food Market» → «ALL MARKET».
- Юрлицо (плейсхолдер ТОО) и домены не менялись.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:34:51 +05:00
nns 03bdf4d4f1 rebrand: письма и серверные строки ALL MARKET
- EmailTemplates (invite/low-stock/weekly-summary): шапка с текстовым
  вордмарком «all market» (бренд-цвета, всегда рендерится — без внешних
  картинок и привязки к домену) + подпись «ALL MARKET — система учёта торговли».
- Имя отправителя по умолчанию, темы/тела системных писем, экспорт-README,
  дневная сводка, тест SMTP → ALL MARKET.
- Цвета писем и домены не менялись.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:34:38 +05:00
nns 57f36d8dee rebrand: веб-админка ALL MARKET (лого, favicon, PWA, тексты)
- Логотипы: logo.svg/logo-light.svg/favicon.svg → готовые файлы ALL MARKET;
  добавлены apple-touch-icon.png (180), favicon-32.png, icon-192/512.png (PWA maskable).
- index.html: <title>ALL MARKET, apple-touch-icon → png, favicon-32.
- manifest.webmanifest: name/short_name ALL MARKET, PNG-иконки 192/512.
- Logo.tsx: alt ALL MARKET, высота h-8; на экранах входа/восстановления — h-12 (охранное поле).
- Онбординг, SuperAdmin-настройки (имя отправителя/тест), offline.html → ALL MARKET.
- theme_color и цвета интерфейса не менялись.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:34:27 +05:00
nns ec7d2dc601 rebrand: подготовка к откату (тег, rollback-док и скрипт, прогресс)
- Тег pre-rebrand-all-market на дорибрендинговом main (c8c0be5), запушен.
- docs/rebrand-all-market-progress.md — план и границы (что НЕ трогаем).
- docs/rebrand-rollback.md — точные команды отката кода (revert-диапазон) и стенда.
- deploy/rebrand-rollback.sh — dry-run по умолчанию, --apply для реального отката.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:13:21 +05:00
141 changed files with 611 additions and 223 deletions

View file

@ -54,6 +54,11 @@ services:
# Quick-login доступен ТОЛЬКО на стенде; на prod — False.
QuickLogin__Enabled: ${QUICK_LOGIN_ENABLED:-false}
QuickLogin__MasterPassword: ${QUICK_LOGIN_MASTER_PASSWORD:-123456}
# Пароль сидируемого SuperAdmin — ТОЛЬКО из секрета, без дефолта. Без него
# на свежей БД (ASPNETCORE_ENVIRONMENT=Production) SuperAdmin не создаётся с
# известным паролем. На существующей БД пароль не трогается (ротация — вручную,
# см. docs/rebrand-all-market-progress.md и ~/.fm-watchdog/stage-superadmin-password).
Seed__SuperAdminPassword: ${SEED_SUPERADMIN_PASSWORD:-}
# OCR invoice: Claude CLI via ssh на dev-vm.
Invoices__ClaudeCli__RemoteHost: ${INVOICE_REMOTE_HOST:-nns@192.168.1.192}
Invoices__ClaudeCli__Executable: /home/nns/.local/bin/claude

View file

@ -42,14 +42,18 @@ build_appimage() {
cp -r "$src/." "$appdir/usr/bin/"
cat > "$appdir/foodmarket-pos.desktop" <<DESK
[Desktop Entry]
Name=Food Market Касса
Name=ALL MARKET Касса
Exec=foodmarket.Pos
Icon=foodmarket-pos
Type=Application
Categories=Office;
DESK
# Минимальная иконка-заглушка (1x1 PNG), если нет фирменной.
printf '\x89PNG\r\n\x1a\n' > "$appdir/foodmarket-pos.png"
# Фирменная иконка ALL MARKET (зелёный квадрат 512). Фоллбэк — 1x1 PNG.
if [ -f src/food-market.pos/Assets/allmarket-icon-512.png ]; then
cp src/food-market.pos/Assets/allmarket-icon-512.png "$appdir/foodmarket-pos.png"
else
printf '\x89PNG\r\n\x1a\n' > "$appdir/foodmarket-pos.png"
fi
cat > "$appdir/AppRun" <<'RUN'
#!/bin/sh
HERE="$(dirname "$(readlink -f "$0")")"

58
deploy/rebrand-rollback.sh Executable file
View file

@ -0,0 +1,58 @@
#!/usr/bin/env bash
# Откат ребрендинга ALL MARKET → Food Market.
# По умолчанию dry-run (печатает план). С --apply реально откатывает:
# 1) git revert диапазона pre-rebrand-all-market..HEAD (сохраняет историю),
# 2) пересборка+редеплой стенда (~/deploy-stage.sh).
# Публичный сайт (Astro) откатывается отдельно — см. docs/rebrand-rollback.md.
set -euo pipefail
TAG="pre-rebrand-all-market"
APPLY=0
[[ "${1:-}" == "--apply" ]] && APPLY=1
cd "$(dirname "$0")/.."
REPO="$(pwd)"
if ! git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
echo "✗ Тег $TAG не найден. Нечего откатывать (или тег не создан)." >&2
exit 1
fi
RANGE="$TAG..HEAD"
COMMITS="$(git log --oneline "$RANGE" 2>/dev/null || true)"
COUNT="$(git rev-list --count "$RANGE" 2>/dev/null || echo 0)"
echo "── Откат ребрендинга ALL MARKET → Food Market ──"
echo "Репозиторий: $REPO"
echo "Якорь (тег): $TAG = $(git rev-parse --short "$TAG")"
echo "Диапазон отката: $RANGE (коммитов: $COUNT)"
echo "Коммиты диапазона:"
echo "${COMMITS:- (пусто — откатывать нечего)}" | sed 's/^/ /'
echo
if [[ "$COUNT" -eq 0 ]]; then
echo "Нет коммитов после тега — откат не требуется."
exit 0
fi
CMD_REVERT="git revert --no-edit $RANGE && git push origin main"
CMD_DEPLOY="$HOME/deploy-stage.sh"
if [[ "$APPLY" -eq 0 ]]; then
echo "DRY-RUN. Будет выполнено (запусти с --apply):"
echo " 1) $CMD_REVERT"
echo " 2) $CMD_DEPLOY # пересборка+редеплой стенда api+web"
echo
echo "Жёсткая альтернатива кода (переписывает историю, только по согласованию):"
echo " git reset --hard $TAG && git push --force-with-lease origin main"
exit 0
fi
echo "APPLY: выполняю откат…"
echo "→ git revert --no-edit $RANGE"
git revert --no-edit "$RANGE"
echo "→ git push origin main"
git push origin main
echo "→ $CMD_DEPLOY"
"$HOME/deploy-stage.sh"
echo "✓ Откат кода и стенда выполнен. Публичный сайт откати отдельно."

View file

@ -1,4 +1,4 @@
# Импорты в Food Market
# Импорты в ALL MARKET
## Универсальный CSV-импорт товаров

View file

@ -18,8 +18,9 @@
/* верхняя полоса */
.top{background:var(--card);border-bottom:1px solid var(--line);display:flex;align-items:center;gap:24px;padding:0 24px}
.logo{font-weight:800;font-size:20px;color:var(--green);letter-spacing:.2px}
.logo span{color:var(--text);font-weight:600}
.logo{font-weight:500;font-size:22px;color:var(--green);letter-spacing:-.5px}
.logo b{color:#4C4F54;font-weight:300}
.logo span{color:var(--muted);font-weight:600;font-size:17px;margin-left:4px}
.sep{width:1px;height:28px;background:var(--line)}
.meta{color:var(--muted);font-size:15px}
.meta b{color:var(--text);font-weight:600}
@ -125,7 +126,7 @@
<body>
<div class="app">
<header class="top">
<div class="logo">FOOD MARKET <span>· Касса</span></div>
<div class="logo">all<b> market</b><span>· Касса</span></div>
<div class="sep"></div>
<div class="meta">Магазин <b>Демо-магазин</b> · Точка <b>Касса 1</b></div>
<div class="sep"></div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 148 KiB

View file

@ -0,0 +1,90 @@
# Ребрендинг Food Market → ALL MARKET — прогресс
Решение владельца 08.10.2026. Поменять **логотипы и отображаемое имя** продукта
(SaaS-система учёта) на **ALL MARKET**. Цвета UI и шрифт интерфейса (Inter) — НЕ менять.
Обязательна возможность быстрого отката.
Брендбук: `~/.fm-watchdog/brand/all-market/` (README + svg/pdf/png). Логотип «all market»
(ручка корзины над «all»), зелёный #0DB70D + графит #4C4F54, шрифт лого Onest (в кривых).
Охранное поле ≥ высоты «a», минимум 120 px по ширине на экране. Файлы НЕ перерисовывать.
## Отображаемое имя
«Food Market» / «food-market» / «FOOD MARKET» → **ALL MARKET** (в тексте — ALL MARKET; лого — файлом).
## План / статус
0. [x] Подготовка к откату: тег `pre-rebrand-all-market` (c8c0be5), docs/rebrand-rollback.md,
deploy/rebrand-rollback.sh (dry-run по умолчанию), проверка отката в worktree (ниже).
1. [x] Ассеты бренда в репозитории (web/public/pos): логотипы, иконки, favicon.
2. [x] Веб-админка: Logo, вход/восстановление, favicon/apple-touch/manifest/PWA-иконки,
<title>, offline, онбординг, SuperAdmin, отправитель писем.
3. [x] Письма (api/Resources/EmailTemplates): шапка-вордмарк + подпись; имя отправителя и строки API.
4. [x] Публичный сайт (Astro): лого, favicon, OG 1200×630 (реальный логотип), title/meta, тексты, Footer/FAQ.
5. [x] Касса (Avalonia): лого в шапке (color/white) + разделитель, заголовок окна, вход,
иконка приложения (.ico), подвал чека «ALL MARKET — система учёта торговли», pos-mockup.
6. [x] Документация docs/*.md пользовательская (imports, simple-mode).
7. [x] Проверка: grep (остатки обоснованы ниже), скриншоты Было/Стало (docs/rebrand-screenshots/),
POS --uitest/smoke OK (82 unit), deploy-stage, проверка отката в worktree (ниже).
full-chain C + menu-walk — против стенда после финального деплоя.
## Безопасность (ревью Mac-Claude) — закрыто
- **Пароль dev-админа утекал в UI.** `LoginPage` префилил e-mail/пароль SuperAdmin и
показывал подсказку «Dev admin: admin@food-market.local / Admin12345!» безусловно.
→ показывается только в `import.meta.env.DEV` на localhost; на стенде/проде поля пустые.
- **Сидер создавал SuperAdmin с известным паролем во ВСЕХ средах.** `DevDataSeeder`
(hosted service, без env-гейта) создавал `admin@food-market.local` с хардкодом
`Admin12345!` — т.е. и на Production. → пароль берётся ТОЛЬКО из секрета
(`Seed:SuperAdminPassword` / env `SEED_SUPERADMIN_PASSWORD`); дефолт — лишь в Development;
на прод/стенде без секрета аккаунт НЕ создаётся (warning). Проброшено в stage-compose.
- **Существующий пароль на стенде** (`Admin12345!`, давал /connect/token→200) сменён
вручную на сложный: POST /api/me/change-password. Новый пароль — в
`~/.fm-watchdog/stage-superadmin-password` (chmod 600). Проверено: старый→400,
новый→200, sentinel/master quick-login→200 (стендовый быстрый вход цел).
- **Другие хардкод-пароли:** `docs/DEVELOPER-GUIDE.md` и `docs/quick-login.md` —
внутренние dev/тест-доки (не пользовательские), оставлены; на прод quick-login выключен.
- **Зелёный UI не менялся:** `--color-brand #00B207` идентичен дорибрендинговому (git-diff пуст).
- **Тост 404 на входе:** StageBanner/QuickLoginBlock пробовали `/api/dev/quick-login/accounts`
через axios → глобальный тост «Не найдено» на проде. → raw fetch, тостов на входе нет.
## Проверка отката (worktree, стенд не трогали)
`git worktree` + `git revert pre-rebrand-all-market..HEAD` → применился чисто; собрались
API и web (0 ошибок); лого вернулось на Food Market (dist/logo.svg aria-label="Food Market",
<title>FOOD MARKET</title>, касса «FOOD MARKET · Касса»). Worktree удалён. Скрин —
`docs/rebrand-screenshots/rollback-logo-foodmarket.png`.
## Что НЕ трогаем (обоснование остаточных «food-market»)
- Домены `*.food-market.kz`, nginx, сертификаты — allmarket.kz ещё не куплен (отдельная задача).
- C#-namespaces `foodmarket.*`, имена csproj/проектов/репо/docker-образов/БД/контейнеров.
- `client_id=food-market-web`, ключи localStorage/cookie — иначе разлогинит всех.
- Бонус-приложение магазина (food-market-app/-ios, back.food-market.kz) — бренд магазина, не продукта.
- Данные организаций («Демо-магазин» и т.п.), в т.ч. демо-орг «FOOD MARKET» в маркетинг-скриншотах.
- Цвета UI, шрифт интерфейса (Inter).
- Имена артефактов кассы (`food-market-pos-*.zip/.AppImage`, exe `foodmarket.Pos`) — завязаны на
AssemblyName (инфраструктура); у кассы ребрендировано видимое: заголовок окна, Name в .desktop, иконка.
- Встроенные продуктовые скриншоты маркетинг-сайта (`public/screenshots/*.png`) — генерируются
`scripts/screenshots.mjs` против ЖИВОЙ админки (сейчас прод); обновятся при выкатке ребрендинга
на прод (прод в этой задаче не трогаем). На стенде ребренд виден вживую.
## Диапазон коммитов для отката
Все коммиты с префиксом `rebrand:` подряд поверх тега `pre-rebrand-all-market`:
**`pre-rebrand-all-market..HEAD`** = `ec7d2dc` … `af00f04` (11 коммитов, см.
`git log --oneline pre-rebrand-all-market..HEAD`). Откат: `deploy/rebrand-rollback.sh --apply`
(или `git revert --no-edit pre-rebrand-all-market..HEAD` + `~/deploy-stage.sh`).
## Итог проверки
- **grep:** видимых «Food Market/FOOD MARKET» в UI/письмах/сайте/кассе не осталось;
остатки — только обоснованные (домены, namespaces, client_id, демо-орг, артефакты, см. выше).
- **Скриншоты Было/Стало:** `docs/rebrand-screenshots/` + index.html (лого, favicon, письмо,
OG, касса вход/продажа, веб-вход, публичная главная; «Было» из тега, «Стало» live).
- **POS:** `--uitest` OK, `--smoke` OK (старт 157мс), 82 unit зелёные.
- **Стенд:** `~/deploy-stage.sh` → OK (test.admin.food-market.kz). Реальный вход: лого ALL MARKET,
без тостов, без префилла/подсказки пароля.
- **menu-walk:** 3/3 passed (demo-admin/keeper/cashier, каждый пункт меню, 0 ошибок/тостов).
- **full-chain C:** 16 ✓ / 0 ✗ — ЗЕЛЁНЫЙ.
- **Откат:** проверен в worktree (revert собрался, лого Food Market вернулось), стенд не трогали.
## Журнал
- 08.10 — старт: прочитан брендбук, составлен план, зафиксированы границы.
- 08.10 — ребрендинг web/api/public/pos/docs (коммиты ec7d2dc…89c5c6d); галерея Было/Стало.
- 08.10 — ревью Mac-Claude: безопасность входа (префилл/подсказка/сидер/ротация пароля стенда),
тост 404 (StageBanner/QuickLoginBlock → fetch), касса (лого крупнее + разделитель, бейдж внутри).
- 08.10 — deploy-stage OK; menu-walk 3/3; full-chain 16/16 ЗЕЛЁНЫЙ; откат проверен в worktree.

48
docs/rebrand-rollback.md Normal file
View file

@ -0,0 +1,48 @@
# Откат ребрендинга ALL MARKET → Food Market
Ребрендинг сделан отдельными коммитами с префиксом `rebrand:` подряд поверх тега
`pre-rebrand-all-market`. Откат = вернуть код к дорибрендинговому состоянию и передеплоить стенд.
## Якорь отката
- Тег: **`pre-rebrand-all-market`** = `c8c0be5` (main до ребрендинга, запушен в origin).
- Диапазон ребренд-коммитов: `pre-rebrand-all-market..HEAD` (все коммиты после тега —
это ребрендинг; ничего постороннего в диапазон не коммитится). Точный список:
`git log --oneline pre-rebrand-all-market..HEAD`.
## Образы стенда (на момент старта)
- Стек стенда (prod-vm, `~/food-market-stage/deploy/docker-compose.yml`): `food-market-api:stage`,
`food-market-web:stage` из реестра `127.0.0.1:5001` (+ ghcr backup). Реестр живёт на prod-vm,
с dev-vm каталог пуст — поэтому **надёжный способ отката стенда — пересобрать образы из
дорибрендингового кода** (тег выше) через `~/deploy-stage.sh`, а не искать старые дайджесты.
- Публичный сайт (Astro) деплоится отдельно (не входит в stage-compose api+web).
## Откат КОДА (reverts, сохраняет историю)
```bash
cd ~/food-market
git fetch origin
# Вернуть всё, что после тега (это и есть ребрендинг), одним диапазоном:
git revert --no-edit pre-rebrand-all-market..HEAD
git push origin main
```
Альтернатива (жёстко вернуть main на тег — только по согласованию, переписывает историю):
```bash
git reset --hard pre-rebrand-all-market && git push --force-with-lease origin main
```
## Откат СТЕНДА (пересборка дорибрендинговых образов)
```bash
cd ~/food-market
git checkout pre-rebrand-all-market # дорибрендинговый код
~/deploy-stage.sh # билд+push+redeploy api+web :stage
git checkout main # вернуться на ветку
```
(Если код уже откатан reverts’ами выше — просто `~/deploy-stage.sh` с main.)
## Скрипт
`deploy/rebrand-rollback.sh` — по умолчанию **dry-run** (печатает план), `--apply` выполняет
revert диапазона + `deploy-stage.sh`. Публичный сайт откатывается отдельно (см. его пайплайн).
## Проверка отката (выполнена в worktree, стенд не трогали)
См. раздел «Проверка отката» в `docs/rebrand-all-market-progress.md`: в отдельном worktree
сделан `git revert pre-rebrand-all-market..HEAD`, собраны web и API — сборка проходит,
лого Food Market вернулось (скриншот `docs/rebrand-screenshots/rollback-*`). Worktree удалён.

View file

@ -0,0 +1,71 @@
<!doctype html><html lang="ru"><head><meta charset="utf-8">
<title>Ребрендинг ALL MARKET — Было / Стало</title>
<style>
body{font:14px system-ui,Segoe UI,Roboto;margin:0;background:#0f172a;color:#e2e8f0}
header{padding:18px 24px;background:#111c33;border-bottom:1px solid #243049}
h1{margin:0;font-size:20px} .sub{color:#94a3b8;margin-top:4px}
section{padding:18px 24px;border-bottom:1px solid #1e293b}
h2{font-size:16px;margin:0 0 12px;color:#cbd5e1}
.pair{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:start}
figure{margin:0;background:#1e293b;border:1px solid #334155;border-radius:10px;overflow:hidden}
figcaption{padding:7px 10px;font-size:12px;color:#94a3b8}
.was figcaption{color:#fca5a5}.now figcaption{color:#86efac}
img{width:100%;display:block;background:#fff}
.note{color:#94a3b8;font-size:13px;margin-top:8px}
@media(max-width:720px){.pair{grid-template-columns:1fr}}
</style></head><body>
<header>
<h1>Ребрендинг Food Market → ALL MARKET — Было / Стало</h1>
<div class="sub">Логотипы и отображаемое имя продукта. Цвета интерфейса и шрифт (Inter) не менялись. 08.10.2026.</div>
</header>
<section><h2>Логотип (веб-админка / публичный сайт)</h2><div class="pair">
<figure class="was"><img src="was-logo.png"><figcaption>Было — Food Market</figcaption></figure>
<figure class="now"><img src="now-logo.png"><figcaption>Стало — ALL MARKET</figcaption></figure>
</div></section>
<section><h2>Favicon (64 / 32 / 16 px)</h2><div class="pair">
<figure class="was"><img src="was-favicon.png"><figcaption>Было</figcaption></figure>
<figure class="now"><img src="now-favicon.png"><figcaption>Стало</figcaption></figure>
</div></section>
<section><h2>Письмо-приглашение (шаблон)</h2><div class="pair">
<figure class="was"><img src="was-email-invite.png"><figcaption>Было</figcaption></figure>
<figure class="now"><img src="now-email-invite.png"><figcaption>Стало — шапка «all market» + подпись «ALL MARKET — система учёта торговли»</figcaption></figure>
</div></section>
<section><h2>OG-картинка (превью в соцсетях, 1200×630)</h2><div class="pair">
<figure class="was"><img src="was-og-home.png"><figcaption>Было</figcaption></figure>
<figure class="now"><img src="now-og-home.png"><figcaption>Стало — реальный логотип на светлом фоне</figcaption></figure>
</div></section>
<section><h2>Касса — экран входа</h2><div class="pair">
<figure class="was"><img src="was-pos-login.png"><figcaption>Было</figcaption></figure>
<figure class="now"><img src="now-pos-login.png"><figcaption>Стало</figcaption></figure>
</div></section>
<section><h2>Касса — экран продажи</h2><div class="pair">
<figure class="was"><img src="was-pos-sale.png"><figcaption>Было</figcaption></figure>
<figure class="now"><img src="now-pos-sale.png"><figcaption>Стало — логотип в верхней полосе</figcaption></figure>
</div></section>
<section><h2>Веб-админка — вход (Стало)</h2><div class="pair">
<figure class="now"><img src="now-web-login.png"><figcaption>Стало (prod-сборка на статике) — логотип ALL MARKET, поля пустые, подсказки/префилла пароля нет, тостов нет</figcaption></figure>
<figure class="now"><img src="now-web-login-stage.png"><figcaption>Стало (реальный стенд test.admin.food-market.kz) — баннер стенда + быстрый вход, без тостов ошибок</figcaption></figure>
</div></section>
<section><h2>Публичный сайт — главная (Стало)</h2>
<figure class="now"><img src="now-public-home.png"><figcaption>Стало — логотип и тексты ALL MARKET</figcaption></figure>
<div class="note">Встроенные в страницу продуктовые скриншоты (мокап дашборда/каталога) всё ещё со старым логотипом —
они генерируются <code>scripts/screenshots.mjs</code> против живой админки и обновятся при выкатке ребрендинга на прод
(прод в этой задаче не трогаем).</div>
</section>
<section><h2>Что не менялось (видимые остатки «food-market»)</h2>
<div class="note">Домены <code>*.food-market.kz</code> и почты (allmarket.kz не куплен) · C#-namespaces
<code>foodmarket.*</code>, AssemblyName/exe, docker-образы, БД · <code>client_id=food-market-web</code> и ключи
localStorage (иначе разлогин) · бонус-приложение магазина · данные демо-организаций (в т.ч. орг «FOOD MARKET»
в маркетинговых скриншотах) · цвета UI и шрифт Inter.</div>
</section>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

View file

@ -2,7 +2,7 @@
Sprint 29, 2026-10-04.
Food Market по умолчанию работает в «простом» режиме — виден только
ALL MARKET по умолчанию работает в «простом» режиме — виден только
минимальный набор функций розничного учёта, достаточный для
обычного магазина у дома. «Расширенный» режим включает дополнительные
модули (оптовые отгрузки, перемещения, лояльность и т.д.) и доступен

View file

@ -115,7 +115,7 @@ public async Task RunAsync(Guid exportId, CancellationToken ct = default)
_db.OrgAuditLogs.IgnoreQueryFilters().Where(x => x.OrganizationId == orgId), jsonOpts, ct);
// README для пользователя.
var readme = $@"# Экспорт данных Food Market
var readme = $@"# Экспорт данных ALL MARKET
Экспорт запрошен: {rec.CreatedAt:O}
Организация: {orgId}
@ -152,14 +152,14 @@ await using (var s = rEntry.Open())
{
var publicBase = _cfg["App:PublicBaseUrl"]?.TrimEnd('/') ?? "https://admin.food-market.kz";
var link = $"{publicBase}/api/org/export/download/{token}";
var body = $@"<p>Ваш экспорт данных Food Market готов.</p>
var body = $@"<p>Ваш экспорт данных ALL MARKET готов.</p>
<p>Скачать (действительно 24 часа):</p>
<p><a href=""{link}"">{link}</a></p>
<p>Размер архива: {ms.Length / 1024:N0} KB</p>
<p>Если вы не запрашивали экспорт — сообщите администратору организации.</p>";
try
{
await _email.SendAsync(rec.NotifyEmail, "Ваш экспорт данных Food Market готов", body, ct);
await _email.SendAsync(rec.NotifyEmail, "Ваш экспорт данных ALL MARKET готов", body, ct);
}
catch (Exception ex)
{

View file

@ -109,7 +109,7 @@ select new
string Money(decimal d) => $"{d:N0} ₸";
var sb = new System.Text.StringBuilder();
sb.Append($"<b>Food Market — сводка за {yStart:dd.MM}</b>\n");
sb.Append($"<b>ALL MARKET — сводка за {yStart:dd.MM}</b>\n");
sb.Append($"<i>{Esc(orgName)}</i>\n\n");
sb.Append($"💰 Выручка: <b>{Money(revenue)}</b>\n");
sb.Append($"🧾 Чеков: <b>{count}</b>\n");

View file

@ -114,7 +114,7 @@ private async Task<CheckResult> CheckSmtp(bool sendEmail, CancellationToken ct)
$"конфиг есть ({s.SmtpHost}); тестовое письмо не отправлялось (sendTestEmail=false)");
}
// Живой тест — отправляем письмо на FromEmail (сам себе).
await _email.SendAsync(s.FromEmail!, "Food Market — diagnostic ping",
await _email.SendAsync(s.FromEmail!, "ALL MARKET — diagnostic ping",
"Это тест из /admin/diagnostic. Игнорируйте.", ct);
sw.Stop();
return new("SMTP", CheckStatus.Ok, sw.ElapsedMilliseconds,

View file

@ -84,13 +84,13 @@ public async Task<IActionResult> Forgot([FromBody] ForgotInput input, Cancellati
var resetUrl = BuildResetUrl(user.Email!, token);
var body =
$"Здравствуйте.\n\n" +
$"Кто-то запросил восстановление пароля для вашего аккаунта Food Market ({user.Email}).\n" +
$"Кто-то запросил восстановление пароля для вашего аккаунта ALL MARKET ({user.Email}).\n" +
$"Если это были вы — перейдите по ссылке (действительна 1 час):\n\n" +
$"{resetUrl}\n\n" +
$"Если вы не запрашивали восстановление — просто игнорируйте это письмо.\n";
try
{
await _email.SendAsync(user.Email!, "Food Market — восстановление пароля", body, ct);
await _email.SendAsync(user.Email!, "ALL MARKET — восстановление пароля", body, ct);
_logger.LogInformation("Forgot-password email sent to {Email}", user.Email);
}
catch (EmailNotConfiguredException ex)

View file

@ -90,7 +90,7 @@ public async Task<IActionResult> Submit([FromBody] FeedbackInput input, Cancella
var ua = HttpContext.Request.Headers["User-Agent"].ToString();
var ip = HttpContext.Connection.RemoteIpAddress?.ToString();
var subject = $"Food Market feedback [{input.Category}] — {org}";
var subject = $"ALL MARKET feedback [{input.Category}] — {org}";
var body =
$"Категория: {input.Category}\n" +
$"Организация: {org}\n" +

View file

@ -192,7 +192,7 @@ public async Task<ActionResult<EmployeeCreateResult>> Create([FromBody] Employee
try
{
var orgName = await _db.Organizations.Where(o => o.Id == orgId)
.Select(o => o.Name).FirstOrDefaultAsync(ct) ?? "Food Market";
.Select(o => o.Name).FirstOrDefaultAsync(ct) ?? "ALL MARKET";
var loginUrl = (Request.Scheme + "://" + Request.Host.Value + "/login");
var (subject, html, text) = _templates.Render("invite", new Dictionary<string, string?>
{

View file

@ -115,8 +115,8 @@ public async Task<IActionResult> TestSend([FromBody] TestSendInput input, Cancel
try
{
await _email.SendAsync(input.ToEmail.Trim(),
string.IsNullOrWhiteSpace(input.Subject) ? "Food Market — тестовое сообщение" : input.Subject,
string.IsNullOrWhiteSpace(input.Body) ? "Тестовое письмо от Food Market." : input.Body,
string.IsNullOrWhiteSpace(input.Subject) ? "ALL MARKET — тестовое сообщение" : input.Subject,
string.IsNullOrWhiteSpace(input.Body) ? "Тестовое письмо от ALL MARKET." : input.Body,
ct);
return Ok(new { ok = true, sentTo = input.ToEmail });
}

View file

@ -3,14 +3,17 @@ Subject: Приглашение в {{organizationName}}
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Приглашение в Food Market</title>
<title>Приглашение в ALL MARKET</title>
</head>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 600px; margin: 0 auto; padding: 24px; color: #1f2937;">
<div style="padding-bottom: 16px; border-bottom: 1px solid #e5e7eb; margin-bottom: 20px;">
<span style="font-size: 22px; font-weight: 600; color: #0DB70D; letter-spacing: -0.5px;">all</span><span style="font-size: 22px; font-weight: 300; color: #4C4F54;"> market</span>
</div>
<h2 style="color: #0f766e;">Добро пожаловать в {{organizationName}}</h2>
<p>Здравствуйте, {{employeeName}}.</p>
<p>Для вас создан доступ к системе Food Market. Войдите по адресу:</p>
<p>Для вас создан доступ к системе ALL MARKET. Войдите по адресу:</p>
<p style="margin: 20px 0;">
<a href="{{loginUrl}}" style="background: #0f766e; color: white; padding: 10px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">Открыть Food Market</a>
<a href="{{loginUrl}}" style="background: #0f766e; color: white; padding: 10px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">Открыть ALL MARKET</a>
</p>
<table style="border-collapse: collapse; background: #f9fafb; padding: 16px; border-radius: 6px;">
<tr><td style="padding: 4px 12px;"><strong>Логин</strong></td><td style="padding: 4px 12px;"><code>{{email}}</code></td></tr>
@ -20,5 +23,8 @@ Subject: Приглашение в {{organizationName}}
<p style="margin-top: 16px; color: #6b7280; font-size: 13px;">
После первого входа смените пароль в личном кабинете. Если вы не ожидали это письмо — просто игнорируйте его, доступ без первого входа не активируется.
</p>
<p style="margin-top: 28px; padding-top: 16px; border-top: 1px solid #e5e7eb; color: #9ca3af; font-size: 12px;">
<strong style="color: #4C4F54;">ALL MARKET</strong> — система учёта торговли
</p>
</body>
</html>

View file

@ -6,6 +6,9 @@ Subject: Низкие остатки · {{organizationName}}
<title>Низкие остатки</title>
</head>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 640px; margin: 0 auto; padding: 24px; color: #1f2937;">
<div style="padding-bottom: 16px; border-bottom: 1px solid #e5e7eb; margin-bottom: 20px;">
<span style="font-size: 22px; font-weight: 600; color: #0DB70D; letter-spacing: -0.5px;">all</span><span style="font-size: 22px; font-weight: 300; color: #4C4F54;"> market</span>
</div>
<h2 style="color: #b91c1c;">Низкие остатки</h2>
<p>{{productCount}} товаров опустились ниже MinStock. Время сделать заказ.</p>
@ -15,5 +18,8 @@ Subject: Низкие остатки · {{organizationName}}
Полный список — в разделе <a href="{{stockUrl}}">Остатки</a>.
Отключить уведомления можно в настройках организации.
</p>
<p style="margin-top: 28px; padding-top: 16px; border-top: 1px solid #e5e7eb; color: #9ca3af; font-size: 12px;">
<strong style="color: #4C4F54;">ALL MARKET</strong> — система учёта торговли
</p>
</body>
</html>

View file

@ -6,6 +6,9 @@ Subject: Итоги недели · {{organizationName}}
<title>Еженедельная сводка</title>
</head>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 640px; margin: 0 auto; padding: 24px; color: #1f2937;">
<div style="padding-bottom: 16px; border-bottom: 1px solid #e5e7eb; margin-bottom: 20px;">
<span style="font-size: 22px; font-weight: 600; color: #0DB70D; letter-spacing: -0.5px;">all</span><span style="font-size: 22px; font-weight: 300; color: #4C4F54;"> market</span>
</div>
<h2 style="color: #0f766e;">Итоги недели</h2>
<p>{{periodFrom}} — {{periodTo}}</p>
@ -32,5 +35,8 @@ Subject: Итоги недели · {{organizationName}}
<p style="margin-top: 32px; color: #6b7280; font-size: 13px;">
Подробная аналитика — в разделе <a href="{{reportsUrl}}">Отчёты</a>.
</p>
<p style="margin-top: 28px; padding-top: 16px; border-top: 1px solid #e5e7eb; color: #9ca3af; font-size: 12px;">
<strong style="color: #4C4F54;">ALL MARKET</strong> — система учёта торговли
</p>
</body>
</html>

View file

@ -5,6 +5,8 @@
using foodmarket.Infrastructure.Persistence;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace foodmarket.Api.Seed;
@ -30,6 +32,16 @@ public async Task StartAsync(CancellationToken ct)
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var userMgr = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
var roleMgr = scope.ServiceProvider.GetRequiredService<RoleManager<Role>>();
var cfg = scope.ServiceProvider.GetRequiredService<IConfiguration>();
var log = scope.ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("DevDataSeeder");
// Пароль сидируемых аккаунтов — ТОЛЬКО из секрета/переменной окружения.
// Дефолт «Admin12345!» допустим исключительно в Development (локальная разработка).
// На stage/prod без секрета аккаунт с известным паролем НЕ создаётся.
string? seedPassword = cfg["Seed:SuperAdminPassword"]
?? Environment.GetEnvironmentVariable("SEED_SUPERADMIN_PASSWORD");
if (string.IsNullOrWhiteSpace(seedPassword) && _env.IsDevelopment())
seedPassword = "Admin12345!";
foreach (var role in new[] { SystemRoles.SuperAdmin, SystemRoles.Admin, SystemRoles.Cashier, SystemRoles.Storekeeper })
{
@ -74,17 +86,29 @@ public async Task StartAsync(CancellationToken ct)
var superAdmin = await userMgr.FindByEmailAsync(superAdminEmail);
if (superAdmin is null)
{
superAdmin = new User
if (string.IsNullOrWhiteSpace(seedPassword))
{
UserName = superAdminEmail,
Email = superAdminEmail,
EmailConfirmed = true,
FullName = "Platform SuperAdmin",
OrganizationId = null,
};
var result = await userMgr.CreateAsync(superAdmin, "Admin12345!");
if (result.Succeeded)
await userMgr.AddToRoleAsync(superAdmin, SystemRoles.SuperAdmin);
log.LogWarning("SuperAdmin ({Email}) не сидируется: задайте секрет Seed:SuperAdminPassword " +
"(или переменную окружения SEED_SUPERADMIN_PASSWORD). Дефолтный пароль разрешён только в Development.",
superAdminEmail);
superAdmin = null;
}
else
{
superAdmin = new User
{
UserName = superAdminEmail,
Email = superAdminEmail,
EmailConfirmed = true,
FullName = "Platform SuperAdmin",
OrganizationId = null,
};
var result = await userMgr.CreateAsync(superAdmin, seedPassword);
if (result.Succeeded)
await userMgr.AddToRoleAsync(superAdmin, SystemRoles.SuperAdmin);
else
superAdmin = null;
}
}
else
{
@ -105,7 +129,7 @@ public async Task StartAsync(CancellationToken ct)
// Admin демо-организации — владелец/директор Demo Market
const string demoAdminEmail = "admin@demo-market.local";
var demoAdmin = await userMgr.FindByEmailAsync(demoAdminEmail);
if (demoAdmin is null)
if (demoAdmin is null && !string.IsNullOrWhiteSpace(seedPassword))
{
demoAdmin = new User
{
@ -115,11 +139,11 @@ public async Task StartAsync(CancellationToken ct)
FullName = "Demo Market Admin",
OrganizationId = demoOrg.Id,
};
var result = await userMgr.CreateAsync(demoAdmin, "Admin12345!");
var result = await userMgr.CreateAsync(demoAdmin, seedPassword);
if (result.Succeeded)
await userMgr.AddToRoleAsync(demoAdmin, SystemRoles.Admin);
}
else
else if (demoAdmin is not null)
{
if (!await userMgr.IsInRoleAsync(demoAdmin, SystemRoles.Admin))
await userMgr.AddToRoleAsync(demoAdmin, SystemRoles.Admin);

View file

@ -48,7 +48,7 @@ public Task SendAsync(string toEmail, string subject, string body, CancellationT
}
var msg = new MimeMessage();
msg.From.Add(new MailboxAddress(s.FromName ?? "Food Market", s.FromEmail));
msg.From.Add(new MailboxAddress(s.FromName ?? "ALL MARKET", s.FromEmail));
msg.To.Add(MailboxAddress.Parse(toEmail));
msg.Subject = subject;
if (htmlBody is not null)

View file

@ -9,6 +9,8 @@ public sealed class PosSession
public required string AccessToken { get; set; }
public required string RefreshToken { get; set; }
public string? CashierName { get; set; }
/// <summary>Название точки/магазина — для шапки чека (резолвится из RetailPoint).</summary>
public string? StoreName { get; set; }
public Guid? CashierUserId { get; set; }
public string[]? Roles { get; set; }
public Guid? RetailPointId { get; set; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -96,7 +96,7 @@ public sealed record CommitResult(Guid ClientSaleId, bool PushedNow, int Pending
IReadOnlyList<(string Name, string Unit)> display, decimal refundCash, decimal refundCard)
{
var doc = new ReceiptDoc { Title = "ВОЗВРАТ" };
doc.Add("Food Market", center: true, bold: true);
doc.Add(_svc.Session?.StoreName ?? "Магазин", center: true, bold: true);
if (CurrentShift?.Number is { } sn) doc.Add($"Смена {sn}", center: true);
if (_svc.Session?.CashierName is { } c) doc.Add($"Кассир: {c}");
doc.Add(DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss"));
@ -117,6 +117,7 @@ public sealed record CommitResult(Guid ClientSaleId, bool PushedNow, int Pending
if (refundCash > 0m) doc.Add("Наличными", $"{refundCash:N0}");
if (refundCard > 0m) doc.Add("На карту", $"{refundCard:N0}");
doc.Separator();
doc.Add("ALL MARKET — система учёта торговли", center: true);
return doc;
}
@ -124,7 +125,7 @@ private ReceiptDoc BuildReceipt(Cart cart, decimal paidCash, decimal paidCard, d
{
var t = cart.Totals();
var doc = new ReceiptDoc { Title = isReturn ? "ВОЗВРАТ" : "ТОВАРНЫЙ ЧЕК" };
doc.Add("Food Market", center: true, bold: true);
doc.Add(_svc.Session?.StoreName ?? "Магазин", center: true, bold: true);
if (CurrentShift?.Number is { } sn) doc.Add($"Смена {sn}", center: true);
if (_svc.Session?.CashierName is { } c) doc.Add($"Кассир: {c}");
doc.Add(DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss"));
@ -145,6 +146,7 @@ private ReceiptDoc BuildReceipt(Cart cart, decimal paidCash, decimal paidCard, d
if (t.Vat > 0m) doc.Add("в т.ч. НДС", $"{t.Vat:N0}");
doc.Separator();
doc.Add("Спасибо за покупку!", center: true);
doc.Add("ALL MARKET — система учёта торговли", center: true);
return doc;
}
}

View file

@ -61,6 +61,7 @@ private void StartSession()
{
CashierName = _svc!.Session?.CashierName ?? "кассир";
QueueCount = _svc.Store.PendingCount();
_ = ResolvePointNameAsync();
_sale = new SaleViewModel(_svc)
{
PaymentRequested = OpenPayment, SaleCommitted = _ => RefreshStatus(),
@ -72,6 +73,25 @@ private void StartSession()
_ = CheckForUpdateAsync();
}
/// <summary>Резолвит название точки продаж (для шапки чека и верхней полосы).</summary>
private async Task ResolvePointNameAsync()
{
var pid = _svc?.Session?.RetailPointId;
if (_svc?.Api is null || pid is null) return;
try
{
var points = await _svc.Api.RetailPointsAsync();
var name = points.items.FirstOrDefault(p => p.id == pid.Value)?.name;
if (!string.IsNullOrWhiteSpace(name))
{
PointName = name;
StoreName = name;
if (_svc.Session is not null) _svc.Session.StoreName = name;
}
}
catch { /* офлайн — оставляем значения по умолчанию */ }
}
private async Task CheckForUpdateAsync()
{
if (_svc?.Api is null) return;

View file

@ -0,0 +1,42 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Styling;
namespace foodmarket.Pos.Views;
/// <summary>Логотип и иконка ALL MARKET из ресурсов (avares). Логотип подбирается
/// под тему: color на светлой, white на тёмной. Файлы готовые — не перерисовывать.</summary>
public static class Brand
{
private const string Pkg = "avares://foodmarket.Pos/Assets/";
private static bool IsDark =>
Application.Current?.ActualThemeVariant == ThemeVariant.Dark;
/// <summary>Загрузить логотип (под текущую тему) в переданный Image.</summary>
public static void ApplyLogo(Image? target)
{
if (target is null) return;
var file = IsDark ? "allmarket-logo-white.png" : "allmarket-logo-color.png";
try
{
using var s = AssetLoader.Open(new Uri(Pkg + file));
target.Source = new Bitmap(s);
}
catch { /* ресурс недоступен — оставить пустым */ }
}
/// <summary>Поставить иконку окна/приложения.</summary>
public static void ApplyWindowIcon(Window window)
{
try
{
using var s = AssetLoader.Open(new Uri(Pkg + "allmarket.ico"));
window.Icon = new WindowIcon(s);
}
catch { /* ресурс недоступен */ }
}
}

View file

@ -6,12 +6,9 @@
<Grid Background="{DynamicResource PosBg}">
<Border Classes="card" Width="440" Padding="28" VerticalAlignment="Center" HorizontalAlignment="Center" CornerRadius="18">
<StackPanel Spacing="16">
<StackPanel Spacing="4" HorizontalAlignment="Center">
<Border Background="{DynamicResource PosBrand}" CornerRadius="12" Width="48" Height="48" HorizontalAlignment="Center">
<TextBlock Text="FM" Foreground="White" FontWeight="Bold" FontSize="20" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<TextBlock Text="Food Market · Касса" Foreground="{DynamicResource PosText}" FontWeight="Bold" FontSize="18" HorizontalAlignment="Center" Margin="0,8,0,0"/>
<TextBlock Text="Вход кассира" Foreground="{DynamicResource PosTextMuted}" FontSize="14" HorizontalAlignment="Center"/>
<StackPanel Spacing="10" HorizontalAlignment="Center">
<Image x:Name="BrandLogo" Height="48" HorizontalAlignment="Center" Margin="0,4,0,0"/>
<TextBlock Text="Касса · Вход кассира" Foreground="{DynamicResource PosTextMuted}" FontSize="14" HorizontalAlignment="Center"/>
</StackPanel>
<!-- PIN -->

View file

@ -4,5 +4,9 @@ namespace foodmarket.Pos.Views;
public partial class LoginView : UserControl
{
public LoginView() => InitializeComponent();
public LoginView()
{
InitializeComponent();
Brand.ApplyLogo(this.FindControl<Image>("BrandLogo"));
}
}

View file

@ -4,7 +4,7 @@
x:Class="foodmarket.Pos.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Width="1366" Height="768" MinWidth="1100" MinHeight="680"
Background="{DynamicResource PosBg}" Title="Касса food-market"
Background="{DynamicResource PosBg}" Title="ALL MARKET · Касса"
WindowStartupLocation="CenterScreen">
<Panel>
<Grid RowDefinitions="64,*,40">
@ -12,10 +12,13 @@
<!-- Верхняя полоса -->
<Border Grid.Row="0" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,0,0,1" Padding="24,0">
<Grid ColumnDefinitions="Auto,Auto,*,Auto" VerticalAlignment="Center">
<!-- Лого (всегда) -->
<TextBlock Grid.Column="0" VerticalAlignment="Center" FontSize="20">
<Run Text="FOOD MARKET" Foreground="{DynamicResource PosBrand}" FontWeight="ExtraBold"/><Run Text=" · Касса" Foreground="{DynamicResource PosText}" FontWeight="SemiBold"/>
</TextBlock>
<!-- Лого ALL MARKET (готовый файл; color на светлой теме, white на тёмной).
Высота 48 → ширина ≈ 140 px (≥ минимума брендбука), охранное поле — Spacing. -->
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="16" VerticalAlignment="Center">
<Image x:Name="BrandLogo" Height="48" VerticalAlignment="Center"/>
<Border Width="1" Height="26" Background="{DynamicResource PosBorder}" VerticalAlignment="Center"/>
<TextBlock Text="Касса" FontSize="17" Foreground="{DynamicResource PosTextMuted}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<!-- Мета (сворачивается по ширине) -->
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="24" VerticalAlignment="Center" Margin="24,0,0,0">

View file

@ -11,6 +11,8 @@ public MainWindow()
{
InitializeComponent();
AddHandler(KeyDownEvent, OnKeyDown, Avalonia.Interactivity.RoutingStrategies.Tunnel);
Brand.ApplyWindowIcon(this);
Brand.ApplyLogo(this.FindControl<Image>("BrandLogo"));
// Адаптив шапки по ширине: при нехватке места сокращаем тексты чипов и
// прячем «Магазин …», но чипы статуса никогда не обрезаем.
this.GetObservable(BoundsProperty).Subscribe(new BoundsObserver(_ => ApplyTopBarDensity()));

View file

@ -159,12 +159,12 @@
<TextBlock Text="Отложить" VerticalAlignment="Center"/><Border Classes="kbd"><TextBlock Text="F7"/></Border>
</StackPanel>
</Button>
<Button Grid.Row="1" Grid.Column="0" Classes="pos ghost" Height="52" Margin="0,0,5,10" Command="{Binding ResumeCommand}">
<StackPanel Orientation="Horizontal" Spacing="10">
<Button Grid.Row="1" Grid.Column="0" Classes="pos ghost" Height="52" Margin="0,0,5,10" Padding="8,0" Command="{Binding ResumeCommand}">
<StackPanel Orientation="Horizontal" Spacing="8">
<Path Width="18" Height="18" Stretch="Uniform" Stroke="{DynamicResource PosTextMuted}" StrokeThickness="2" StrokeLineCap="Round" Data="M4 6h16M4 12h16M4 18h10"/>
<TextBlock Text="Отложенные" VerticalAlignment="Center"/>
<Border CornerRadius="999" MinWidth="22" Height="22" Padding="7,0" Background="{DynamicResource PosWarnSoft}" VerticalAlignment="Center" IsVisible="{Binding HasParked}">
<TextBlock Text="{Binding ParkedCount}" FontSize="13" FontWeight="Bold" Foreground="{DynamicResource PosWarn}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock Text="Отложенные" FontSize="15" VerticalAlignment="Center"/>
<Border CornerRadius="999" MinWidth="20" Height="20" Padding="6,0" Background="{DynamicResource PosWarnSoft}" VerticalAlignment="Center" IsVisible="{Binding HasParked}">
<TextBlock Text="{Binding ParkedCount}" FontSize="12" FontWeight="Bold" Foreground="{DynamicResource PosWarn}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</StackPanel>
</Button>

View file

@ -7,9 +7,14 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<ApplicationIcon></ApplicationIcon>
<ApplicationIcon>Assets\allmarket.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<!-- Логотипы/иконка ALL MARKET для окна и экранов (avares://foodmarket.Pos/Assets/...) -->
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\food-market.pos.core\food-market.pos.core.csproj" />
<ProjectReference Include="..\food-market.shared\food-market.shared.csproj" />

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -11,18 +11,22 @@ const OUT = path.resolve(__dirname, '..', 'public', 'og')
const interReg = await fs.readFile(path.join(FONTS, 'Inter-Regular.ttf'))
const interBold = await fs.readFile(path.join(FONTS, 'Inter-Bold.ttf'))
// Логотип ALL MARKET (готовый файл бренда, color на прозрачном). viewBox 3:1.
const logoPng = await fs.readFile(path.join(__dirname, 'allmarket-logo-color.png'))
const logoUri = `data:image/png;base64,${logoPng.toString('base64')}`
const pages = [
{ file: 'home.png', title: 'Программа учёта и касса для розничных магазинов в Казахстане', sub: 'Касса с торговыми весами, импорт каталога, ОФД РК. Бесплатно 90 дней.' },
{ file: 'pos.png', title: 'Касса для Windows с поддержкой торговых весов', sub: 'Установка за 5 минут. Офлайн-режим. Стандартное оборудование.' },
{ file: 'pricing.png', title: 'Прозрачные тарифы Food Market', sub: 'От 5 000 ₸/мес. Все возможности на любом тарифе. 90 дней триал.' },
{ file: 'pricing.png', title: 'Прозрачные тарифы ALL MARKET', sub: 'От 5 000 ₸/мес. Все возможности на любом тарифе. 90 дней триал.' },
{ file: 'import.png', title: 'Перенесите каталог за один клик', sub: 'Excel, CSV, REST API. До 100 000 товаров. Бесплатно.' },
{ file: 'about.png', title: 'О Food Market', sub: 'Программа учёта розничной торговли, созданная в Казахстане.' },
{ file: 'for-grocery.png', title: 'Food Market для продуктового магазина', sub: 'Весовой товар, скоропорт, штрихкоды, ABC-анализ.' },
{ file: 'for-pharmacy.png', title: 'Food Market для аптеки', sub: 'Серии, сроки годности, рецептурный отпуск, контроль ЛП.' },
{ file: 'for-cafe.png', title: 'Food Market для кафе и общепита', sub: 'Модификаторы блюд, столики, печать на кухню.' },
{ file: 'for-alcohol.png', title: 'Food Market для алкомаркета', sub: 'Акцизные марки, контроль времени продаж, маркировка.' },
{ file: 'for-clothing.png', title: 'Food Market для магазина одежды', sub: 'Размерные сетки, цветовые матрицы, остатки по размерам.' },
{ file: 'for-household.png', title: 'Food Market для дома и быта', sub: 'Гарантийные сроки, серийные номера, характеристики.' },
{ file: 'about.png', title: 'О ALL MARKET', sub: 'Программа учёта розничной торговли, созданная в Казахстане.' },
{ file: 'for-grocery.png', title: 'ALL MARKET для продуктового магазина', sub: 'Весовой товар, скоропорт, штрихкоды, ABC-анализ.' },
{ file: 'for-pharmacy.png', title: 'ALL MARKET для аптеки', sub: 'Серии, сроки годности, рецептурный отпуск, контроль ЛП.' },
{ file: 'for-cafe.png', title: 'ALL MARKET для кафе и общепита', sub: 'Модификаторы блюд, столики, печать на кухню.' },
{ file: 'for-alcohol.png', title: 'ALL MARKET для алкомаркета', sub: 'Акцизные марки, контроль времени продаж, маркировка.' },
{ file: 'for-clothing.png', title: 'ALL MARKET для магазина одежды', sub: 'Размерные сетки, цветовые матрицы, остатки по размерам.' },
{ file: 'for-household.png', title: 'ALL MARKET для дома и быта', sub: 'Гарантийные сроки, серийные номера, характеристики.' },
]
const tmpl = ({ title, sub }) => ({
@ -31,40 +35,14 @@ const tmpl = ({ title, sub }) => ({
style: {
width: '1200px', height: '630px', display: 'flex', flexDirection: 'column',
justifyContent: 'space-between', padding: '64px',
background: 'linear-gradient(135deg, #ECFDF5 0%, #D1FAE5 60%, #A7F3D0 100%)',
background: 'linear-gradient(135deg, #FFFFFF 0%, #ECFDF5 100%)',
fontFamily: 'Inter',
},
children: [
// Logo + brand row
// Logo (готовый файл бренда на светлом фоне)
{
type: 'div',
props: {
style: { display: 'flex', alignItems: 'center', gap: '14px' },
children: [
{
type: 'div',
props: {
style: {
width: '56px', height: '56px', borderRadius: '14px',
background: '#00B207',
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: '#FFFFFF', fontSize: '32px', fontWeight: 700,
},
children: 'F',
},
},
{
type: 'div',
props: {
style: { display: 'flex', flexDirection: 'column' },
children: [
{ type: 'span', props: { style: { color: '#0F172A', fontSize: '26px', fontWeight: 700, lineHeight: 1 }, children: 'Food Market' } },
{ type: 'span', props: { style: { color: '#475569', fontSize: '15px', fontWeight: 400, marginTop: '4px' }, children: 'Программа учёта · Касса · Касса для Windows' } },
],
},
},
],
},
type: 'img',
props: { src: logoUri, width: 300, height: 99 },
},
// Title + subtitle
{

View file

@ -2,8 +2,8 @@ import { useState } from 'react'
const ITEMS = [
{
q: 'Чем Food Market подходит моему магазину?',
a: 'Food Market — облачная программа учёта розничной торговли, созданная под казахстанский рынок. Поддерживает все типы магазинов: продуктовые, аптеки, общепит, алкомаркеты, одежду, бытовые товары. Включает кассу для Windows с нативной поддержкой торговых весов, интеграцию с ОФД РК, Kaspi Pay и казахстанскими банками.',
q: 'Чем ALL MARKET подходит моему магазину?',
a: 'ALL MARKET — облачная программа учёта розничной торговли, созданная под казахстанский рынок. Поддерживает все типы магазинов: продуктовые, аптеки, общепит, алкомаркеты, одежду, бытовые товары. Включает кассу для Windows с нативной поддержкой торговых весов, интеграцию с ОФД РК, Kaspi Pay и казахстанскими банками.',
},
{
q: 'Сколько стоит подписка?',

View file

@ -53,6 +53,6 @@ import Logo from './Logo.astro'
</div>
</div>
<div class="border-t border-slate-200 py-4 text-center text-xs text-slate-400">
© {new Date().getFullYear()} Food Market. Все права защищены.
© {new Date().getFullYear()} ALL MARKET. Все права защищены.
</div>
</footer>

View file

@ -3,4 +3,4 @@ interface Props { variant?: 'light' | 'dark'; class?: string }
const { variant = 'light', class: className = '' } = Astro.props
const src = variant === 'dark' ? '/logo-light.svg' : '/logo.svg'
---
<img src={src} alt="Food Market" class={`h-7 w-auto select-none ${className}`} draggable="false" />
<img src={src} alt="ALL MARKET" class={`h-8 w-auto select-none ${className}`} draggable="false" />

View file

@ -1,7 +1,7 @@
---
title: "Касса с поддержкой весов — почему это важно для магазина"
date: 2026-05-02
author: Команда Food Market
author: Команда ALL MARKET
category: feature
description: "Если вы продаёте на развес, работа с весами обычно — самое болезненное место при настройке кассы. Рассказываем, как мы сделали её простой."
cover_image: /photos/blog-scales.jpg
@ -17,7 +17,7 @@ cover_image: /photos/blog-scales.jpg
Мы написали обработку стандартных протоколов торговых весов прямо в кассу. Зато теперь весы работают нативно:
- Установили Касса Food Market для Windows
- Установили Касса ALL MARKET для Windows
- Подключили торговые весы через RS-232 или USB-COM кабель
- Запустили кассу — **всё**

View file

@ -1,15 +1,15 @@
---
title: "Food Market запустился — система учёта розницы для Казахстана"
title: "ALL MARKET запустился — система учёта розницы для Казахстана"
date: 2026-04-26
author: Команда Food Market
author: Команда ALL MARKET
category: news
description: "Сегодня мы запускаем Food Market — программу учёта розничной торговли, созданную в Казахстане для казахстанских магазинов."
description: "Сегодня мы запускаем ALL MARKET — программу учёта розничной торговли, созданную в Казахстане для казахстанских магазинов."
cover_image: /photos/blog-launch.jpg
---
# Food Market запустился
# ALL MARKET запустился
Сегодня мы открываем публичный доступ к Food Market — программе учёта розничной торговли, созданной командой казахстанских разработчиков и практиков ритейла.
Сегодня мы открываем публичный доступ к ALL MARKET — программе учёта розничной торговли, созданной командой казахстанских разработчиков и практиков ритейла.
## Что мы сделали
@ -20,7 +20,7 @@ cover_image: /photos/blog-launch.jpg
- **поддерживать локальное железо** — торговые весы нативно, без переходников и сторонних драйверов
- **позволять попробовать без риска** — настоящий триал, а не «14 дней с привязкой карты»
Мы воплотили эти принципы в Food Market.
Мы воплотили эти принципы в ALL MARKET.
## Что вы получаете

View file

@ -1,15 +1,15 @@
---
title: "Запустите магазин в Food Market за 15 минут"
title: "Запустите магазин в ALL MARKET за 15 минут"
date: 2026-04-30
author: Команда Food Market
author: Команда ALL MARKET
category: guide
description: "Пошаговая инструкция первого запуска: от регистрации до первой продажи."
cover_image: /photos/blog-quickstart.jpg
---
# Запустите магазин в Food Market за 15 минут
# Запустите магазин в ALL MARKET за 15 минут
Часто новые SaaS-сервисы требуют дней на настройку и обучение. Мы спроектировали Food Market так, чтобы вы могли начать продавать в день регистрации.
Часто новые SaaS-сервисы требуют дней на настройку и обучение. Мы спроектировали ALL MARKET так, чтобы вы могли начать продавать в день регистрации.
Реальное время от пустого экрана до первого пробитого чека — **15 минут**.
@ -21,7 +21,7 @@ cover_image: /photos/blog-quickstart.jpg
- Название магазина
- Выберите тариф (можно оставить «Триал» — 90 дней бесплатно)
Через 30 секунд после нажатия «Зарегистрироваться» вы окажетесь в админке Food Market с готовым к работе пустым магазином.
Через 30 секунд после нажатия «Зарегистрироваться» вы окажетесь в админке ALL MARKET с готовым к работе пустым магазином.
## Шаг 2. Загрузите каталог (5 минут)
@ -29,7 +29,7 @@ cover_image: /photos/blog-quickstart.jpg
**А. Импорт из Excel/CSV** — самый быстрый если у вас уже есть файл с товарами:
- «Импорт» → «Из файла» → загрузите .xlsx или .csv
- Сопоставьте колонки (название, штрихкод, цена) — Food Market подскажет правильное соответствие
- Сопоставьте колонки (название, штрихкод, цена) — ALL MARKET подскажет правильное соответствие
- Запустите импорт — 5 000 SKU перенесутся за минуту
**Б. Импорт через API** — если работаете в системе с открытым API:
@ -61,7 +61,7 @@ cover_image: /photos/blog-quickstart.jpg
4. Введите сумму полученных денег
5. Подтвердите — чек распечатается
🎉 Вы только что сделали первую продажу через Food Market.
🎉 Вы только что сделали первую продажу через ALL MARKET.
## Что дальше

View file

@ -14,7 +14,7 @@ order: 1
# Тарифы, оплата, изменение тарифа
Полная информация о подписках Food Market и порядке оплаты.
Полная информация о подписках ALL MARKET и порядке оплаты.
## Тарифы

View file

@ -14,7 +14,7 @@ order: 1
# Настройка кассы для Windows
Полная инструкция установки и настройки кассы Food Market для Windows. Время — 15-20 минут.
Полная инструкция установки и настройки кассы ALL MARKET для Windows. Время — 15-20 минут.
## Системные требования
@ -26,7 +26,7 @@ order: 1
## Скачивание установщика
1. Войдите в админку Food Market
1. Войдите в админку ALL MARKET
2. Откройте раздел «Касса» в боковом меню
3. Нажмите «Скачать кассу для Windows»
4. Сохраните файл `food-market-pos-setup.msi` (~80 МБ)
@ -43,7 +43,7 @@ order: 1
При первом запуске касса попросит ввести **ключ организации**.
1. В админке Food Market: «Касса» → «Ключ организации» — скопируйте 32-символьный код
1. В админке ALL MARKET: «Касса» → «Ключ организации» — скопируйте 32-символьный код
2. В кассе — вставьте ключ, нажмите «Активировать»
3. Касса синхронизирует каталог товаров с облаком (~30 секунд для 5000 SKU)

View file

@ -14,7 +14,7 @@ order: 1
# Быстрый старт за 15 минут
После регистрации в Food Market вам нужно сделать 4 простых шага, чтобы начать продавать.
После регистрации в ALL MARKET вам нужно сделать 4 простых шага, чтобы начать продавать.
## Шаг 1. Заполните данные магазина (1 мин)
@ -47,7 +47,7 @@ order: 1
В кассе нажмите «Открыть смену», введите начальный остаток наличных. Сосканируйте штрихкод любого товара — он появится в чеке. Нажмите «Оплата» → «Наличные» → подтвердите. Чек распечатается, сделка пройдёт в учёт.
🎉 Вы только что сделали первую продажу через Food Market.
🎉 Вы только что сделали первую продажу через ALL MARKET.
## Что дальше?

View file

@ -12,7 +12,7 @@ last_updated: 2026-04-26
---
Настоящим я, как субъект персональных данных, регистрируясь в сервисе **Food Market** (далее — «**Сервис**»), размещённом на сайте https://test.food-market.kz, и проставляя отметку в чекбоксе «Согласен с офертой и политикой обработки персональных данных» при создании учётной записи, в соответствии со статьёй 8 Закона Республики Казахстан от 21 мая 2013 года № 94-V «О персональных данных и их защите», свободно, своей волей и в своём интересе даю своё согласие **ТОО «[НАЗВАНИЕ ТОО]»**, БИН [БИН], юридический адрес: [Юр.адрес] (далее — «**Оператор**»), на сбор, обработку, хранение, использование, обезличивание и уничтожение моих персональных данных в нижеуказанном объёме и для нижеуказанных целей.
Настоящим я, как субъект персональных данных, регистрируясь в сервисе **ALL MARKET** (далее — «**Сервис**»), размещённом на сайте https://test.food-market.kz, и проставляя отметку в чекбоксе «Согласен с офертой и политикой обработки персональных данных» при создании учётной записи, в соответствии со статьёй 8 Закона Республики Казахстан от 21 мая 2013 года № 94-V «О персональных данных и их защите», свободно, своей волей и в своём интересе даю своё согласие **ТОО «[НАЗВАНИЕ ТОО]»**, БИН [БИН], юридический адрес: [Юр.адрес] (далее — «**Оператор**»), на сбор, обработку, хранение, использование, обезличивание и уничтожение моих персональных данных в нижеуказанном объёме и для нижеуказанных целей.
## 1. Перечень персональных данных, на обработку которых даётся согласие

View file

@ -1,10 +1,10 @@
---
title: Публичная оферта на использование сервиса Food Market
title: Публичная оферта на использование сервиса ALL MARKET
slug: offer
last_updated: 2026-04-26
---
# Публичная оферта на использование сервиса Food Market
# Публичная оферта на использование сервиса ALL MARKET
> **Внимание:** Настоящий документ является типовой публичной офертой и подлежит правовой экспертизе перед публичным использованием. Финальная редакция утверждается уполномоченным лицом Лицензиара.
@ -14,7 +14,7 @@ last_updated: 2026-04-26
## 1. Общие положения
1.1. Настоящий документ является публичной офертой (далее — «**Оферта**») в соответствии со статьями 395, 396 Гражданского кодекса Республики Казахстан (далее — «**ГК РК**»). Оферта адресована любому физическому или юридическому лицу (далее — «**Пользователь**»), желающему получить доступ к программно-аппаратному комплексу «Food Market» (далее — «**Сервис**»).
1.1. Настоящий документ является публичной офертой (далее — «**Оферта**») в соответствии со статьями 395, 396 Гражданского кодекса Республики Казахстан (далее — «**ГК РК**»). Оферта адресована любому физическому или юридическому лицу (далее — «**Пользователь**»), желающему получить доступ к программно-аппаратному комплексу «ALL MARKET» (далее — «**Сервис**»).
1.2. Лицензиаром Сервиса выступает **ТОО «[НАЗВАНИЕ ТОО]»**, БИН [БИН], зарегистрированное в соответствии с законодательством Республики Казахстан (далее — «**Лицензиар**»). Полные реквизиты Лицензиара указаны в разделе «Реквизиты» сайта.
@ -29,7 +29,7 @@ last_updated: 2026-04-26
## 2. Термины и определения
**Сервис** — программный продукт «Food Market», представляющий собой облачную систему учёта розничной торговли с функциями управления товарами, складом, продажами, кассовыми операциями, аналитикой и сопутствующими сервисами, размещённый по адресу https://test.food-market.kz и связанных с ним поддоменах.
**Сервис** — программный продукт «ALL MARKET», представляющий собой облачную систему учёта розничной торговли с функциями управления товарами, складом, продажами, кассовыми операциями, аналитикой и сопутствующими сервисами, размещённый по адресу https://test.food-market.kz и связанных с ним поддоменах.
**Учётная запись** — совокупность данных Пользователя, обеспечивающих его идентификацию и авторизацию в Сервисе.

View file

@ -13,7 +13,7 @@ last_updated: 2026-04-26
## 1. Общие положения
1.1. Настоящая Политика обработки персональных данных (далее — «**Политика**») определяет порядок обработки и защиты персональных данных, осуществляемой **ТОО «[НАЗВАНИЕ ТОО]»**, БИН [БИН] (далее — «**Оператор**»), при предоставлении доступа к программно-аппаратному комплексу «Food Market» (далее — «**Сервис**»).
1.1. Настоящая Политика обработки персональных данных (далее — «**Политика**») определяет порядок обработки и защиты персональных данных, осуществляемой **ТОО «[НАЗВАНИЕ ТОО]»**, БИН [БИН] (далее — «**Оператор**»), при предоставлении доступа к программно-аппаратному комплексу «ALL MARKET» (далее — «**Сервис**»).
1.2. Политика разработана в соответствии с:
- Конституцией Республики Казахстан;

View file

@ -28,19 +28,19 @@ const ymId = import.meta.env.PUBLIC_YM_ID ?? 'REPLACE_ME'
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>{title} · Food Market</title>
<title>{title} · ALL MARKET</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonical} />
<meta property="og:type" content="website" />
<meta property="og:title" content={`${title} · Food Market`} />
<meta property="og:title" content={`${title} · ALL MARKET`} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonical} />
<meta property="og:image" content={new URL(ogImage, Astro.site).toString()} />
<meta property="og:locale" content="ru_KZ" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={`${title} · Food Market`} />
<meta name="twitter:title" content={`${title} · ALL MARKET`} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={new URL(ogImage, Astro.site).toString()} />
@ -51,7 +51,7 @@ const ymId = import.meta.env.PUBLIC_YM_ID ?? 'REPLACE_ME'
<script type="application/ld+json" set:html={JSON.stringify({
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
name: 'Food Market',
name: 'ALL MARKET',
applicationCategory: 'BusinessApplication',
operatingSystem: 'Web, Windows',
offers: { '@type': 'Offer', price: '5000', priceCurrency: 'KZT' },

View file

@ -1,19 +1,19 @@
---
import BaseLayout from '@/layouts/BaseLayout.astro'
---
<BaseLayout title="О нас" description="Food Market — программа учёта и касса для розничных магазинов Казахстана. История, команда, принципы." ogImage="/og/about.png">
<BaseLayout title="О нас" description="ALL MARKET — программа учёта и касса для розничных магазинов Казахстана. История, команда, принципы." ogImage="/og/about.png">
<section class="relative overflow-hidden">
<img src="/photos/about-hero.jpg" alt="" class="absolute inset-0 w-full h-full object-cover" />
<div class="absolute inset-0 bg-gradient-to-r from-slate-900/85 via-slate-900/60 to-transparent"></div>
<div class="relative max-w-3xl mx-auto px-4 sm:px-6 py-20 lg:py-28">
<h1 class="text-4xl lg:text-5xl font-extrabold text-white drop-shadow-lg">О Food Market</h1>
<h1 class="text-4xl lg:text-5xl font-extrabold text-white drop-shadow-lg">О ALL MARKET</h1>
<p class="mt-4 text-lg text-emerald-50 max-w-xl">Программа учёта розничной торговли, созданная в Казахстане для казахстанских магазинов.</p>
</div>
</section>
<section class="max-w-3xl mx-auto px-4 sm:px-6 py-12 prose-md">
<h2>История</h2>
<p>Food Market запустился в 2026 году как ответ на запрос рынка: розничные магазины в Казахстане заслуживают современный, быстрый и честный инструмент для ведения бизнеса.</p>
<p>ALL MARKET запустился в 2026 году как ответ на запрос рынка: розничные магазины в Казахстане заслуживают современный, быстрый и честный инструмент для ведения бизнеса.</p>
<p>Команда основателей — практики розницы и разработчики, которые три года изнутри наблюдали как магазины ведут учёт, какие проблемы решают каждый день, чего им не хватает в существующих инструментах.</p>
<p>Мы поставили перед собой задачу сделать продукт, который работает с первого клика — без долгой настройки и платных консультаций; нативно поддерживает локальное железо (торговые весы из коробки, без переходников); соответствует реалиям РК (Kaspi Pay, ОФД, законодательство, поддержка на русском); и который можно попробовать без риска — настоящий триал, без привязки карты.</p>

View file

@ -4,7 +4,7 @@ import BaseLayout from '@/layouts/BaseLayout.astro'
const posts = (await getCollection('blog')).sort((a, b) => +b.data.date - +a.data.date)
const fmt = (d: Date) => d.toLocaleDateString('ru-KZ', { day: '2-digit', month: 'long', year: 'numeric' })
---
<BaseLayout title="Блог" description="Блог Food Market: новости, релизы, лучшие практики автоматизации розничных магазинов в Казахстане.">
<BaseLayout title="Блог" description="Блог ALL MARKET: новости, релизы, лучшие практики автоматизации розничных магазинов в Казахстане.">
<section class="max-w-3xl mx-auto px-4 sm:px-6 py-14">
<h1 class="text-4xl font-extrabold">Блог</h1>
<p class="text-slate-500 mt-2">Запуски, миграции, лучшие практики розницы.</p>

Some files were not shown because too many files have changed in this diff Show more