Compare commits
No commits in common. "821f40634cc28725a60366df267c9f83a9294168" and "c8c0be56b447dc1ccc9a756a4bd0bcefdebc98eb" have entirely different histories.
821f40634c
...
c8c0be56b4
|
|
@ -54,11 +54,6 @@ services:
|
||||||
# Quick-login доступен ТОЛЬКО на стенде; на prod — False.
|
# Quick-login доступен ТОЛЬКО на стенде; на prod — False.
|
||||||
QuickLogin__Enabled: ${QUICK_LOGIN_ENABLED:-false}
|
QuickLogin__Enabled: ${QUICK_LOGIN_ENABLED:-false}
|
||||||
QuickLogin__MasterPassword: ${QUICK_LOGIN_MASTER_PASSWORD:-123456}
|
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.
|
# OCR invoice: Claude CLI via ssh на dev-vm.
|
||||||
Invoices__ClaudeCli__RemoteHost: ${INVOICE_REMOTE_HOST:-nns@192.168.1.192}
|
Invoices__ClaudeCli__RemoteHost: ${INVOICE_REMOTE_HOST:-nns@192.168.1.192}
|
||||||
Invoices__ClaudeCli__Executable: /home/nns/.local/bin/claude
|
Invoices__ClaudeCli__Executable: /home/nns/.local/bin/claude
|
||||||
|
|
|
||||||
|
|
@ -42,18 +42,14 @@ build_appimage() {
|
||||||
cp -r "$src/." "$appdir/usr/bin/"
|
cp -r "$src/." "$appdir/usr/bin/"
|
||||||
cat > "$appdir/foodmarket-pos.desktop" <<DESK
|
cat > "$appdir/foodmarket-pos.desktop" <<DESK
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Name=ALL MARKET Касса
|
Name=Food Market Касса
|
||||||
Exec=foodmarket.Pos
|
Exec=foodmarket.Pos
|
||||||
Icon=foodmarket-pos
|
Icon=foodmarket-pos
|
||||||
Type=Application
|
Type=Application
|
||||||
Categories=Office;
|
Categories=Office;
|
||||||
DESK
|
DESK
|
||||||
# Фирменная иконка ALL MARKET (зелёный квадрат 512). Фоллбэк — 1x1 PNG.
|
# Минимальная иконка-заглушка (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"
|
printf '\x89PNG\r\n\x1a\n' > "$appdir/foodmarket-pos.png"
|
||||||
fi
|
|
||||||
cat > "$appdir/AppRun" <<'RUN'
|
cat > "$appdir/AppRun" <<'RUN'
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
HERE="$(dirname "$(readlink -f "$0")")"
|
HERE="$(dirname "$(readlink -f "$0")")"
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
#!/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 "✓ Откат кода и стенда выполнен. Публичный сайт откати отдельно."
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Импорты в ALL MARKET
|
# Импорты в Food Market
|
||||||
|
|
||||||
## Универсальный CSV-импорт товаров
|
## Универсальный CSV-импорт товаров
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,8 @@
|
||||||
|
|
||||||
/* верхняя полоса */
|
/* верхняя полоса */
|
||||||
.top{background:var(--card);border-bottom:1px solid var(--line);display:flex;align-items:center;gap:24px;padding:0 24px}
|
.top{background:var(--card);border-bottom:1px solid var(--line);display:flex;align-items:center;gap:24px;padding:0 24px}
|
||||||
.logo{font-weight:500;font-size:22px;color:var(--green);letter-spacing:-.5px}
|
.logo{font-weight:800;font-size:20px;color:var(--green);letter-spacing:.2px}
|
||||||
.logo b{color:#4C4F54;font-weight:300}
|
.logo span{color:var(--text);font-weight:600}
|
||||||
.logo span{color:var(--muted);font-weight:600;font-size:17px;margin-left:4px}
|
|
||||||
.sep{width:1px;height:28px;background:var(--line)}
|
.sep{width:1px;height:28px;background:var(--line)}
|
||||||
.meta{color:var(--muted);font-size:15px}
|
.meta{color:var(--muted);font-size:15px}
|
||||||
.meta b{color:var(--text);font-weight:600}
|
.meta b{color:var(--text);font-weight:600}
|
||||||
|
|
@ -126,7 +125,7 @@
|
||||||
<body>
|
<body>
|
||||||
<div class="app">
|
<div class="app">
|
||||||
<header class="top">
|
<header class="top">
|
||||||
<div class="logo">all<b> market</b><span>· Касса</span></div>
|
<div class="logo">FOOD MARKET <span>· Касса</span></div>
|
||||||
<div class="sep"></div>
|
<div class="sep"></div>
|
||||||
<div class="meta">Магазин <b>Демо-магазин</b> · Точка <b>Касса 1</b></div>
|
<div class="meta">Магазин <b>Демо-магазин</b> · Точка <b>Касса 1</b></div>
|
||||||
<div class="sep"></div>
|
<div class="sep"></div>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 148 KiB |
|
|
@ -1,90 +0,0 @@
|
||||||
# Ребрендинг 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.
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
# Откат ребрендинга 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 удалён.
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
<!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>
|
|
||||||
|
Before Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 6 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 880 KiB |
|
Before Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 151 KiB |
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Sprint 29, 2026-10-04.
|
Sprint 29, 2026-10-04.
|
||||||
|
|
||||||
ALL MARKET по умолчанию работает в «простом» режиме — виден только
|
Food Market по умолчанию работает в «простом» режиме — виден только
|
||||||
минимальный набор функций розничного учёта, достаточный для
|
минимальный набор функций розничного учёта, достаточный для
|
||||||
обычного магазина у дома. «Расширенный» режим включает дополнительные
|
обычного магазина у дома. «Расширенный» режим включает дополнительные
|
||||||
модули (оптовые отгрузки, перемещения, лояльность и т.д.) и доступен
|
модули (оптовые отгрузки, перемещения, лояльность и т.д.) и доступен
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ public async Task RunAsync(Guid exportId, CancellationToken ct = default)
|
||||||
_db.OrgAuditLogs.IgnoreQueryFilters().Where(x => x.OrganizationId == orgId), jsonOpts, ct);
|
_db.OrgAuditLogs.IgnoreQueryFilters().Where(x => x.OrganizationId == orgId), jsonOpts, ct);
|
||||||
|
|
||||||
// README для пользователя.
|
// README для пользователя.
|
||||||
var readme = $@"# Экспорт данных ALL MARKET
|
var readme = $@"# Экспорт данных Food Market
|
||||||
|
|
||||||
Экспорт запрошен: {rec.CreatedAt:O}
|
Экспорт запрошен: {rec.CreatedAt:O}
|
||||||
Организация: {orgId}
|
Организация: {orgId}
|
||||||
|
|
@ -152,14 +152,14 @@ await using (var s = rEntry.Open())
|
||||||
{
|
{
|
||||||
var publicBase = _cfg["App:PublicBaseUrl"]?.TrimEnd('/') ?? "https://admin.food-market.kz";
|
var publicBase = _cfg["App:PublicBaseUrl"]?.TrimEnd('/') ?? "https://admin.food-market.kz";
|
||||||
var link = $"{publicBase}/api/org/export/download/{token}";
|
var link = $"{publicBase}/api/org/export/download/{token}";
|
||||||
var body = $@"<p>Ваш экспорт данных ALL MARKET готов.</p>
|
var body = $@"<p>Ваш экспорт данных Food Market готов.</p>
|
||||||
<p>Скачать (действительно 24 часа):</p>
|
<p>Скачать (действительно 24 часа):</p>
|
||||||
<p><a href=""{link}"">{link}</a></p>
|
<p><a href=""{link}"">{link}</a></p>
|
||||||
<p>Размер архива: {ms.Length / 1024:N0} KB</p>
|
<p>Размер архива: {ms.Length / 1024:N0} KB</p>
|
||||||
<p>Если вы не запрашивали экспорт — сообщите администратору организации.</p>";
|
<p>Если вы не запрашивали экспорт — сообщите администратору организации.</p>";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _email.SendAsync(rec.NotifyEmail, "Ваш экспорт данных ALL MARKET готов", body, ct);
|
await _email.SendAsync(rec.NotifyEmail, "Ваш экспорт данных Food Market готов", body, ct);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ select new
|
||||||
string Money(decimal d) => $"{d:N0} ₸";
|
string Money(decimal d) => $"{d:N0} ₸";
|
||||||
|
|
||||||
var sb = new System.Text.StringBuilder();
|
var sb = new System.Text.StringBuilder();
|
||||||
sb.Append($"<b>ALL MARKET — сводка за {yStart:dd.MM}</b>\n");
|
sb.Append($"<b>Food Market — сводка за {yStart:dd.MM}</b>\n");
|
||||||
sb.Append($"<i>{Esc(orgName)}</i>\n\n");
|
sb.Append($"<i>{Esc(orgName)}</i>\n\n");
|
||||||
sb.Append($"💰 Выручка: <b>{Money(revenue)}</b>\n");
|
sb.Append($"💰 Выручка: <b>{Money(revenue)}</b>\n");
|
||||||
sb.Append($"🧾 Чеков: <b>{count}</b>\n");
|
sb.Append($"🧾 Чеков: <b>{count}</b>\n");
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ private async Task<CheckResult> CheckSmtp(bool sendEmail, CancellationToken ct)
|
||||||
$"конфиг есть ({s.SmtpHost}); тестовое письмо не отправлялось (sendTestEmail=false)");
|
$"конфиг есть ({s.SmtpHost}); тестовое письмо не отправлялось (sendTestEmail=false)");
|
||||||
}
|
}
|
||||||
// Живой тест — отправляем письмо на FromEmail (сам себе).
|
// Живой тест — отправляем письмо на FromEmail (сам себе).
|
||||||
await _email.SendAsync(s.FromEmail!, "ALL MARKET — diagnostic ping",
|
await _email.SendAsync(s.FromEmail!, "Food Market — diagnostic ping",
|
||||||
"Это тест из /admin/diagnostic. Игнорируйте.", ct);
|
"Это тест из /admin/diagnostic. Игнорируйте.", ct);
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
return new("SMTP", CheckStatus.Ok, sw.ElapsedMilliseconds,
|
return new("SMTP", CheckStatus.Ok, sw.ElapsedMilliseconds,
|
||||||
|
|
|
||||||
|
|
@ -84,13 +84,13 @@ public async Task<IActionResult> Forgot([FromBody] ForgotInput input, Cancellati
|
||||||
var resetUrl = BuildResetUrl(user.Email!, token);
|
var resetUrl = BuildResetUrl(user.Email!, token);
|
||||||
var body =
|
var body =
|
||||||
$"Здравствуйте.\n\n" +
|
$"Здравствуйте.\n\n" +
|
||||||
$"Кто-то запросил восстановление пароля для вашего аккаунта ALL MARKET ({user.Email}).\n" +
|
$"Кто-то запросил восстановление пароля для вашего аккаунта Food Market ({user.Email}).\n" +
|
||||||
$"Если это были вы — перейдите по ссылке (действительна 1 час):\n\n" +
|
$"Если это были вы — перейдите по ссылке (действительна 1 час):\n\n" +
|
||||||
$"{resetUrl}\n\n" +
|
$"{resetUrl}\n\n" +
|
||||||
$"Если вы не запрашивали восстановление — просто игнорируйте это письмо.\n";
|
$"Если вы не запрашивали восстановление — просто игнорируйте это письмо.\n";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _email.SendAsync(user.Email!, "ALL MARKET — восстановление пароля", body, ct);
|
await _email.SendAsync(user.Email!, "Food Market — восстановление пароля", body, ct);
|
||||||
_logger.LogInformation("Forgot-password email sent to {Email}", user.Email);
|
_logger.LogInformation("Forgot-password email sent to {Email}", user.Email);
|
||||||
}
|
}
|
||||||
catch (EmailNotConfiguredException ex)
|
catch (EmailNotConfiguredException ex)
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ public async Task<IActionResult> Submit([FromBody] FeedbackInput input, Cancella
|
||||||
var ua = HttpContext.Request.Headers["User-Agent"].ToString();
|
var ua = HttpContext.Request.Headers["User-Agent"].ToString();
|
||||||
var ip = HttpContext.Connection.RemoteIpAddress?.ToString();
|
var ip = HttpContext.Connection.RemoteIpAddress?.ToString();
|
||||||
|
|
||||||
var subject = $"ALL MARKET feedback [{input.Category}] — {org}";
|
var subject = $"Food Market feedback [{input.Category}] — {org}";
|
||||||
var body =
|
var body =
|
||||||
$"Категория: {input.Category}\n" +
|
$"Категория: {input.Category}\n" +
|
||||||
$"Организация: {org}\n" +
|
$"Организация: {org}\n" +
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ public async Task<ActionResult<EmployeeCreateResult>> Create([FromBody] Employee
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var orgName = await _db.Organizations.Where(o => o.Id == orgId)
|
var orgName = await _db.Organizations.Where(o => o.Id == orgId)
|
||||||
.Select(o => o.Name).FirstOrDefaultAsync(ct) ?? "ALL MARKET";
|
.Select(o => o.Name).FirstOrDefaultAsync(ct) ?? "Food Market";
|
||||||
var loginUrl = (Request.Scheme + "://" + Request.Host.Value + "/login");
|
var loginUrl = (Request.Scheme + "://" + Request.Host.Value + "/login");
|
||||||
var (subject, html, text) = _templates.Render("invite", new Dictionary<string, string?>
|
var (subject, html, text) = _templates.Render("invite", new Dictionary<string, string?>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -115,8 +115,8 @@ public async Task<IActionResult> TestSend([FromBody] TestSendInput input, Cancel
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _email.SendAsync(input.ToEmail.Trim(),
|
await _email.SendAsync(input.ToEmail.Trim(),
|
||||||
string.IsNullOrWhiteSpace(input.Subject) ? "ALL MARKET — тестовое сообщение" : input.Subject,
|
string.IsNullOrWhiteSpace(input.Subject) ? "Food Market — тестовое сообщение" : input.Subject,
|
||||||
string.IsNullOrWhiteSpace(input.Body) ? "Тестовое письмо от ALL MARKET." : input.Body,
|
string.IsNullOrWhiteSpace(input.Body) ? "Тестовое письмо от Food Market." : input.Body,
|
||||||
ct);
|
ct);
|
||||||
return Ok(new { ok = true, sentTo = input.ToEmail });
|
return Ok(new { ok = true, sentTo = input.ToEmail });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,14 @@ Subject: Приглашение в {{organizationName}}
|
||||||
<html lang="ru">
|
<html lang="ru">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Приглашение в ALL MARKET</title>
|
<title>Приглашение в Food Market</title>
|
||||||
</head>
|
</head>
|
||||||
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 600px; margin: 0 auto; padding: 24px; color: #1f2937;">
|
<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>
|
<h2 style="color: #0f766e;">Добро пожаловать в {{organizationName}}</h2>
|
||||||
<p>Здравствуйте, {{employeeName}}.</p>
|
<p>Здравствуйте, {{employeeName}}.</p>
|
||||||
<p>Для вас создан доступ к системе ALL MARKET. Войдите по адресу:</p>
|
<p>Для вас создан доступ к системе Food Market. Войдите по адресу:</p>
|
||||||
<p style="margin: 20px 0;">
|
<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;">Открыть ALL MARKET</a>
|
<a href="{{loginUrl}}" style="background: #0f766e; color: white; padding: 10px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">Открыть Food Market</a>
|
||||||
</p>
|
</p>
|
||||||
<table style="border-collapse: collapse; background: #f9fafb; padding: 16px; border-radius: 6px;">
|
<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>
|
<tr><td style="padding: 4px 12px;"><strong>Логин</strong></td><td style="padding: 4px 12px;"><code>{{email}}</code></td></tr>
|
||||||
|
|
@ -23,8 +20,5 @@ Subject: Приглашение в {{organizationName}}
|
||||||
<p style="margin-top: 16px; color: #6b7280; font-size: 13px;">
|
<p style="margin-top: 16px; color: #6b7280; font-size: 13px;">
|
||||||
После первого входа смените пароль в личном кабинете. Если вы не ожидали это письмо — просто игнорируйте его, доступ без первого входа не активируется.
|
После первого входа смените пароль в личном кабинете. Если вы не ожидали это письмо — просто игнорируйте его, доступ без первого входа не активируется.
|
||||||
</p>
|
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,6 @@ Subject: Низкие остатки · {{organizationName}}
|
||||||
<title>Низкие остатки</title>
|
<title>Низкие остатки</title>
|
||||||
</head>
|
</head>
|
||||||
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 640px; margin: 0 auto; padding: 24px; color: #1f2937;">
|
<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>
|
<h2 style="color: #b91c1c;">Низкие остатки</h2>
|
||||||
<p>{{productCount}} товаров опустились ниже MinStock. Время сделать заказ.</p>
|
<p>{{productCount}} товаров опустились ниже MinStock. Время сделать заказ.</p>
|
||||||
|
|
||||||
|
|
@ -18,8 +15,5 @@ Subject: Низкие остатки · {{organizationName}}
|
||||||
Полный список — в разделе <a href="{{stockUrl}}">Остатки</a>.
|
Полный список — в разделе <a href="{{stockUrl}}">Остатки</a>.
|
||||||
Отключить уведомления можно в настройках организации.
|
Отключить уведомления можно в настройках организации.
|
||||||
</p>
|
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,6 @@ Subject: Итоги недели · {{organizationName}}
|
||||||
<title>Еженедельная сводка</title>
|
<title>Еженедельная сводка</title>
|
||||||
</head>
|
</head>
|
||||||
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 640px; margin: 0 auto; padding: 24px; color: #1f2937;">
|
<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>
|
<h2 style="color: #0f766e;">Итоги недели</h2>
|
||||||
<p>{{periodFrom}} — {{periodTo}}</p>
|
<p>{{periodFrom}} — {{periodTo}}</p>
|
||||||
|
|
||||||
|
|
@ -35,8 +32,5 @@ Subject: Итоги недели · {{organizationName}}
|
||||||
<p style="margin-top: 32px; color: #6b7280; font-size: 13px;">
|
<p style="margin-top: 32px; color: #6b7280; font-size: 13px;">
|
||||||
Подробная аналитика — в разделе <a href="{{reportsUrl}}">Отчёты</a>.
|
Подробная аналитика — в разделе <a href="{{reportsUrl}}">Отчёты</a>.
|
||||||
</p>
|
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
using foodmarket.Infrastructure.Persistence;
|
using foodmarket.Infrastructure.Persistence;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace foodmarket.Api.Seed;
|
namespace foodmarket.Api.Seed;
|
||||||
|
|
||||||
|
|
@ -32,16 +30,6 @@ public async Task StartAsync(CancellationToken ct)
|
||||||
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
var userMgr = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
|
var userMgr = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
|
||||||
var roleMgr = scope.ServiceProvider.GetRequiredService<RoleManager<Role>>();
|
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 })
|
foreach (var role in new[] { SystemRoles.SuperAdmin, SystemRoles.Admin, SystemRoles.Cashier, SystemRoles.Storekeeper })
|
||||||
{
|
{
|
||||||
|
|
@ -85,15 +73,6 @@ public async Task StartAsync(CancellationToken ct)
|
||||||
const string superAdminEmail = "admin@food-market.local";
|
const string superAdminEmail = "admin@food-market.local";
|
||||||
var superAdmin = await userMgr.FindByEmailAsync(superAdminEmail);
|
var superAdmin = await userMgr.FindByEmailAsync(superAdminEmail);
|
||||||
if (superAdmin is null)
|
if (superAdmin is null)
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(seedPassword))
|
|
||||||
{
|
|
||||||
log.LogWarning("SuperAdmin ({Email}) не сидируется: задайте секрет Seed:SuperAdminPassword " +
|
|
||||||
"(или переменную окружения SEED_SUPERADMIN_PASSWORD). Дефолтный пароль разрешён только в Development.",
|
|
||||||
superAdminEmail);
|
|
||||||
superAdmin = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
superAdmin = new User
|
superAdmin = new User
|
||||||
{
|
{
|
||||||
|
|
@ -103,12 +82,9 @@ public async Task StartAsync(CancellationToken ct)
|
||||||
FullName = "Platform SuperAdmin",
|
FullName = "Platform SuperAdmin",
|
||||||
OrganizationId = null,
|
OrganizationId = null,
|
||||||
};
|
};
|
||||||
var result = await userMgr.CreateAsync(superAdmin, seedPassword);
|
var result = await userMgr.CreateAsync(superAdmin, "Admin12345!");
|
||||||
if (result.Succeeded)
|
if (result.Succeeded)
|
||||||
await userMgr.AddToRoleAsync(superAdmin, SystemRoles.SuperAdmin);
|
await userMgr.AddToRoleAsync(superAdmin, SystemRoles.SuperAdmin);
|
||||||
else
|
|
||||||
superAdmin = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -129,7 +105,7 @@ public async Task StartAsync(CancellationToken ct)
|
||||||
// Admin демо-организации — владелец/директор Demo Market
|
// Admin демо-организации — владелец/директор Demo Market
|
||||||
const string demoAdminEmail = "admin@demo-market.local";
|
const string demoAdminEmail = "admin@demo-market.local";
|
||||||
var demoAdmin = await userMgr.FindByEmailAsync(demoAdminEmail);
|
var demoAdmin = await userMgr.FindByEmailAsync(demoAdminEmail);
|
||||||
if (demoAdmin is null && !string.IsNullOrWhiteSpace(seedPassword))
|
if (demoAdmin is null)
|
||||||
{
|
{
|
||||||
demoAdmin = new User
|
demoAdmin = new User
|
||||||
{
|
{
|
||||||
|
|
@ -139,11 +115,11 @@ public async Task StartAsync(CancellationToken ct)
|
||||||
FullName = "Demo Market Admin",
|
FullName = "Demo Market Admin",
|
||||||
OrganizationId = demoOrg.Id,
|
OrganizationId = demoOrg.Id,
|
||||||
};
|
};
|
||||||
var result = await userMgr.CreateAsync(demoAdmin, seedPassword);
|
var result = await userMgr.CreateAsync(demoAdmin, "Admin12345!");
|
||||||
if (result.Succeeded)
|
if (result.Succeeded)
|
||||||
await userMgr.AddToRoleAsync(demoAdmin, SystemRoles.Admin);
|
await userMgr.AddToRoleAsync(demoAdmin, SystemRoles.Admin);
|
||||||
}
|
}
|
||||||
else if (demoAdmin is not null)
|
else
|
||||||
{
|
{
|
||||||
if (!await userMgr.IsInRoleAsync(demoAdmin, SystemRoles.Admin))
|
if (!await userMgr.IsInRoleAsync(demoAdmin, SystemRoles.Admin))
|
||||||
await userMgr.AddToRoleAsync(demoAdmin, SystemRoles.Admin);
|
await userMgr.AddToRoleAsync(demoAdmin, SystemRoles.Admin);
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ public Task SendAsync(string toEmail, string subject, string body, CancellationT
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg = new MimeMessage();
|
var msg = new MimeMessage();
|
||||||
msg.From.Add(new MailboxAddress(s.FromName ?? "ALL MARKET", s.FromEmail));
|
msg.From.Add(new MailboxAddress(s.FromName ?? "Food Market", s.FromEmail));
|
||||||
msg.To.Add(MailboxAddress.Parse(toEmail));
|
msg.To.Add(MailboxAddress.Parse(toEmail));
|
||||||
msg.Subject = subject;
|
msg.Subject = subject;
|
||||||
if (htmlBody is not null)
|
if (htmlBody is not null)
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,6 @@ public sealed class PosSession
|
||||||
public required string AccessToken { get; set; }
|
public required string AccessToken { get; set; }
|
||||||
public required string RefreshToken { get; set; }
|
public required string RefreshToken { get; set; }
|
||||||
public string? CashierName { get; set; }
|
public string? CashierName { get; set; }
|
||||||
/// <summary>Название точки/магазина — для шапки чека (резолвится из RetailPoint).</summary>
|
|
||||||
public string? StoreName { get; set; }
|
|
||||||
public Guid? CashierUserId { get; set; }
|
public Guid? CashierUserId { get; set; }
|
||||||
public string[]? Roles { get; set; }
|
public string[]? Roles { get; set; }
|
||||||
public Guid? RetailPointId { get; set; }
|
public Guid? RetailPointId { get; set; }
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
|
@ -96,7 +96,7 @@ public sealed record CommitResult(Guid ClientSaleId, bool PushedNow, int Pending
|
||||||
IReadOnlyList<(string Name, string Unit)> display, decimal refundCash, decimal refundCard)
|
IReadOnlyList<(string Name, string Unit)> display, decimal refundCash, decimal refundCard)
|
||||||
{
|
{
|
||||||
var doc = new ReceiptDoc { Title = "ВОЗВРАТ" };
|
var doc = new ReceiptDoc { Title = "ВОЗВРАТ" };
|
||||||
doc.Add(_svc.Session?.StoreName ?? "Магазин", center: true, bold: true);
|
doc.Add("Food Market", center: true, bold: true);
|
||||||
if (CurrentShift?.Number is { } sn) doc.Add($"Смена {sn}", center: true);
|
if (CurrentShift?.Number is { } sn) doc.Add($"Смена {sn}", center: true);
|
||||||
if (_svc.Session?.CashierName is { } c) doc.Add($"Кассир: {c}");
|
if (_svc.Session?.CashierName is { } c) doc.Add($"Кассир: {c}");
|
||||||
doc.Add(DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss"));
|
doc.Add(DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss"));
|
||||||
|
|
@ -117,7 +117,6 @@ public sealed record CommitResult(Guid ClientSaleId, bool PushedNow, int Pending
|
||||||
if (refundCash > 0m) doc.Add("Наличными", $"{refundCash:N0}");
|
if (refundCash > 0m) doc.Add("Наличными", $"{refundCash:N0}");
|
||||||
if (refundCard > 0m) doc.Add("На карту", $"{refundCard:N0}");
|
if (refundCard > 0m) doc.Add("На карту", $"{refundCard:N0}");
|
||||||
doc.Separator();
|
doc.Separator();
|
||||||
doc.Add("ALL MARKET — система учёта торговли", center: true);
|
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,7 +124,7 @@ private ReceiptDoc BuildReceipt(Cart cart, decimal paidCash, decimal paidCard, d
|
||||||
{
|
{
|
||||||
var t = cart.Totals();
|
var t = cart.Totals();
|
||||||
var doc = new ReceiptDoc { Title = isReturn ? "ВОЗВРАТ" : "ТОВАРНЫЙ ЧЕК" };
|
var doc = new ReceiptDoc { Title = isReturn ? "ВОЗВРАТ" : "ТОВАРНЫЙ ЧЕК" };
|
||||||
doc.Add(_svc.Session?.StoreName ?? "Магазин", center: true, bold: true);
|
doc.Add("Food Market", center: true, bold: true);
|
||||||
if (CurrentShift?.Number is { } sn) doc.Add($"Смена {sn}", center: true);
|
if (CurrentShift?.Number is { } sn) doc.Add($"Смена {sn}", center: true);
|
||||||
if (_svc.Session?.CashierName is { } c) doc.Add($"Кассир: {c}");
|
if (_svc.Session?.CashierName is { } c) doc.Add($"Кассир: {c}");
|
||||||
doc.Add(DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss"));
|
doc.Add(DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss"));
|
||||||
|
|
@ -146,7 +145,6 @@ private ReceiptDoc BuildReceipt(Cart cart, decimal paidCash, decimal paidCard, d
|
||||||
if (t.Vat > 0m) doc.Add("в т.ч. НДС", $"{t.Vat:N0}");
|
if (t.Vat > 0m) doc.Add("в т.ч. НДС", $"{t.Vat:N0}");
|
||||||
doc.Separator();
|
doc.Separator();
|
||||||
doc.Add("Спасибо за покупку!", center: true);
|
doc.Add("Спасибо за покупку!", center: true);
|
||||||
doc.Add("ALL MARKET — система учёта торговли", center: true);
|
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,6 @@ private void StartSession()
|
||||||
{
|
{
|
||||||
CashierName = _svc!.Session?.CashierName ?? "кассир";
|
CashierName = _svc!.Session?.CashierName ?? "кассир";
|
||||||
QueueCount = _svc.Store.PendingCount();
|
QueueCount = _svc.Store.PendingCount();
|
||||||
_ = ResolvePointNameAsync();
|
|
||||||
_sale = new SaleViewModel(_svc)
|
_sale = new SaleViewModel(_svc)
|
||||||
{
|
{
|
||||||
PaymentRequested = OpenPayment, SaleCommitted = _ => RefreshStatus(),
|
PaymentRequested = OpenPayment, SaleCommitted = _ => RefreshStatus(),
|
||||||
|
|
@ -73,25 +72,6 @@ private void StartSession()
|
||||||
_ = CheckForUpdateAsync();
|
_ = 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()
|
private async Task CheckForUpdateAsync()
|
||||||
{
|
{
|
||||||
if (_svc?.Api is null) return;
|
if (_svc?.Api is null) return;
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
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 { /* ресурс недоступен */ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,9 +6,12 @@
|
||||||
<Grid Background="{DynamicResource PosBg}">
|
<Grid Background="{DynamicResource PosBg}">
|
||||||
<Border Classes="card" Width="440" Padding="28" VerticalAlignment="Center" HorizontalAlignment="Center" CornerRadius="18">
|
<Border Classes="card" Width="440" Padding="28" VerticalAlignment="Center" HorizontalAlignment="Center" CornerRadius="18">
|
||||||
<StackPanel Spacing="16">
|
<StackPanel Spacing="16">
|
||||||
<StackPanel Spacing="10" HorizontalAlignment="Center">
|
<StackPanel Spacing="4" HorizontalAlignment="Center">
|
||||||
<Image x:Name="BrandLogo" Height="48" HorizontalAlignment="Center" Margin="0,4,0,0"/>
|
<Border Background="{DynamicResource PosBrand}" CornerRadius="12" Width="48" Height="48" HorizontalAlignment="Center">
|
||||||
<TextBlock Text="Касса · Вход кассира" Foreground="{DynamicResource PosTextMuted}" FontSize="14" 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>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- PIN -->
|
<!-- PIN -->
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,5 @@ namespace foodmarket.Pos.Views;
|
||||||
|
|
||||||
public partial class LoginView : UserControl
|
public partial class LoginView : UserControl
|
||||||
{
|
{
|
||||||
public LoginView()
|
public LoginView() => InitializeComponent();
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
Brand.ApplyLogo(this.FindControl<Image>("BrandLogo"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
x:Class="foodmarket.Pos.Views.MainWindow"
|
x:Class="foodmarket.Pos.Views.MainWindow"
|
||||||
x:DataType="vm:MainWindowViewModel"
|
x:DataType="vm:MainWindowViewModel"
|
||||||
Width="1366" Height="768" MinWidth="1100" MinHeight="680"
|
Width="1366" Height="768" MinWidth="1100" MinHeight="680"
|
||||||
Background="{DynamicResource PosBg}" Title="ALL MARKET · Касса"
|
Background="{DynamicResource PosBg}" Title="Касса food-market"
|
||||||
WindowStartupLocation="CenterScreen">
|
WindowStartupLocation="CenterScreen">
|
||||||
<Panel>
|
<Panel>
|
||||||
<Grid RowDefinitions="64,*,40">
|
<Grid RowDefinitions="64,*,40">
|
||||||
|
|
@ -12,13 +12,10 @@
|
||||||
<!-- Верхняя полоса -->
|
<!-- Верхняя полоса -->
|
||||||
<Border Grid.Row="0" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,0,0,1" Padding="24,0">
|
<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">
|
<Grid ColumnDefinitions="Auto,Auto,*,Auto" VerticalAlignment="Center">
|
||||||
<!-- Лого ALL MARKET (готовый файл; color на светлой теме, white на тёмной).
|
<!-- Лого (всегда) -->
|
||||||
Высота 48 → ширина ≈ 140 px (≥ минимума брендбука), охранное поле — Spacing. -->
|
<TextBlock Grid.Column="0" VerticalAlignment="Center" FontSize="20">
|
||||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="16" VerticalAlignment="Center">
|
<Run Text="FOOD MARKET" Foreground="{DynamicResource PosBrand}" FontWeight="ExtraBold"/><Run Text=" · Касса" Foreground="{DynamicResource PosText}" FontWeight="SemiBold"/>
|
||||||
<Image x:Name="BrandLogo" Height="48" VerticalAlignment="Center"/>
|
</TextBlock>
|
||||||
<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">
|
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="24" VerticalAlignment="Center" Margin="24,0,0,0">
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
AddHandler(KeyDownEvent, OnKeyDown, Avalonia.Interactivity.RoutingStrategies.Tunnel);
|
AddHandler(KeyDownEvent, OnKeyDown, Avalonia.Interactivity.RoutingStrategies.Tunnel);
|
||||||
Brand.ApplyWindowIcon(this);
|
|
||||||
Brand.ApplyLogo(this.FindControl<Image>("BrandLogo"));
|
|
||||||
// Адаптив шапки по ширине: при нехватке места сокращаем тексты чипов и
|
// Адаптив шапки по ширине: при нехватке места сокращаем тексты чипов и
|
||||||
// прячем «Магазин …», но чипы статуса никогда не обрезаем.
|
// прячем «Магазин …», но чипы статуса никогда не обрезаем.
|
||||||
this.GetObservable(BoundsProperty).Subscribe(new BoundsObserver(_ => ApplyTopBarDensity()));
|
this.GetObservable(BoundsProperty).Subscribe(new BoundsObserver(_ => ApplyTopBarDensity()));
|
||||||
|
|
|
||||||
|
|
@ -159,12 +159,12 @@
|
||||||
<TextBlock Text="Отложить" VerticalAlignment="Center"/><Border Classes="kbd"><TextBlock Text="F7"/></Border>
|
<TextBlock Text="Отложить" VerticalAlignment="Center"/><Border Classes="kbd"><TextBlock Text="F7"/></Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Grid.Row="1" Grid.Column="0" Classes="pos ghost" Height="52" Margin="0,0,5,10" Padding="8,0" Command="{Binding ResumeCommand}">
|
<Button Grid.Row="1" Grid.Column="0" Classes="pos ghost" Height="52" Margin="0,0,5,10" Command="{Binding ResumeCommand}">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||||
<Path Width="18" Height="18" Stretch="Uniform" Stroke="{DynamicResource PosTextMuted}" StrokeThickness="2" StrokeLineCap="Round" Data="M4 6h16M4 12h16M4 18h10"/>
|
<Path Width="18" Height="18" Stretch="Uniform" Stroke="{DynamicResource PosTextMuted}" StrokeThickness="2" StrokeLineCap="Round" Data="M4 6h16M4 12h16M4 18h10"/>
|
||||||
<TextBlock Text="Отложенные" FontSize="15" VerticalAlignment="Center"/>
|
<TextBlock Text="Отложенные" VerticalAlignment="Center"/>
|
||||||
<Border CornerRadius="999" MinWidth="20" Height="20" Padding="6,0" Background="{DynamicResource PosWarnSoft}" VerticalAlignment="Center" IsVisible="{Binding HasParked}">
|
<Border CornerRadius="999" MinWidth="22" Height="22" Padding="7,0" Background="{DynamicResource PosWarnSoft}" VerticalAlignment="Center" IsVisible="{Binding HasParked}">
|
||||||
<TextBlock Text="{Binding ParkedCount}" FontSize="12" FontWeight="Bold" Foreground="{DynamicResource PosWarn}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
<TextBlock Text="{Binding ParkedCount}" FontSize="13" FontWeight="Bold" Foreground="{DynamicResource PosWarn}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,9 @@
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||||
<ApplicationIcon>Assets\allmarket.ico</ApplicationIcon>
|
<ApplicationIcon></ApplicationIcon>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<!-- Логотипы/иконка ALL MARKET для окна и экранов (avares://foodmarket.Pos/Assets/...) -->
|
|
||||||
<AvaloniaResource Include="Assets\**" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\food-market.pos.core\food-market.pos.core.csproj" />
|
<ProjectReference Include="..\food-market.pos.core\food-market.pos.core.csproj" />
|
||||||
<ProjectReference Include="..\food-market.shared\food-market.shared.csproj" />
|
<ProjectReference Include="..\food-market.shared\food-market.shared.csproj" />
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
|
@ -11,22 +11,18 @@ const OUT = path.resolve(__dirname, '..', 'public', 'og')
|
||||||
const interReg = await fs.readFile(path.join(FONTS, 'Inter-Regular.ttf'))
|
const interReg = await fs.readFile(path.join(FONTS, 'Inter-Regular.ttf'))
|
||||||
const interBold = await fs.readFile(path.join(FONTS, 'Inter-Bold.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 = [
|
const pages = [
|
||||||
{ file: 'home.png', title: 'Программа учёта и касса для розничных магазинов в Казахстане', sub: 'Касса с торговыми весами, импорт каталога, ОФД РК. Бесплатно 90 дней.' },
|
{ file: 'home.png', title: 'Программа учёта и касса для розничных магазинов в Казахстане', sub: 'Касса с торговыми весами, импорт каталога, ОФД РК. Бесплатно 90 дней.' },
|
||||||
{ file: 'pos.png', title: 'Касса для Windows с поддержкой торговых весов', sub: 'Установка за 5 минут. Офлайн-режим. Стандартное оборудование.' },
|
{ file: 'pos.png', title: 'Касса для Windows с поддержкой торговых весов', sub: 'Установка за 5 минут. Офлайн-режим. Стандартное оборудование.' },
|
||||||
{ file: 'pricing.png', title: 'Прозрачные тарифы ALL MARKET', sub: 'От 5 000 ₸/мес. Все возможности на любом тарифе. 90 дней триал.' },
|
{ file: 'pricing.png', title: 'Прозрачные тарифы Food Market', sub: 'От 5 000 ₸/мес. Все возможности на любом тарифе. 90 дней триал.' },
|
||||||
{ file: 'import.png', title: 'Перенесите каталог за один клик', sub: 'Excel, CSV, REST API. До 100 000 товаров. Бесплатно.' },
|
{ file: 'import.png', title: 'Перенесите каталог за один клик', sub: 'Excel, CSV, REST API. До 100 000 товаров. Бесплатно.' },
|
||||||
{ file: 'about.png', title: 'О ALL MARKET', sub: 'Программа учёта розничной торговли, созданная в Казахстане.' },
|
{ file: 'about.png', title: 'О Food Market', sub: 'Программа учёта розничной торговли, созданная в Казахстане.' },
|
||||||
{ file: 'for-grocery.png', title: 'ALL MARKET для продуктового магазина', sub: 'Весовой товар, скоропорт, штрихкоды, ABC-анализ.' },
|
{ file: 'for-grocery.png', title: 'Food Market для продуктового магазина', sub: 'Весовой товар, скоропорт, штрихкоды, ABC-анализ.' },
|
||||||
{ file: 'for-pharmacy.png', title: 'ALL MARKET для аптеки', sub: 'Серии, сроки годности, рецептурный отпуск, контроль ЛП.' },
|
{ file: 'for-pharmacy.png', title: 'Food Market для аптеки', sub: 'Серии, сроки годности, рецептурный отпуск, контроль ЛП.' },
|
||||||
{ file: 'for-cafe.png', title: 'ALL MARKET для кафе и общепита', sub: 'Модификаторы блюд, столики, печать на кухню.' },
|
{ file: 'for-cafe.png', title: 'Food Market для кафе и общепита', sub: 'Модификаторы блюд, столики, печать на кухню.' },
|
||||||
{ file: 'for-alcohol.png', title: 'ALL MARKET для алкомаркета', sub: 'Акцизные марки, контроль времени продаж, маркировка.' },
|
{ file: 'for-alcohol.png', title: 'Food Market для алкомаркета', sub: 'Акцизные марки, контроль времени продаж, маркировка.' },
|
||||||
{ file: 'for-clothing.png', title: 'ALL MARKET для магазина одежды', sub: 'Размерные сетки, цветовые матрицы, остатки по размерам.' },
|
{ file: 'for-clothing.png', title: 'Food Market для магазина одежды', sub: 'Размерные сетки, цветовые матрицы, остатки по размерам.' },
|
||||||
{ file: 'for-household.png', title: 'ALL MARKET для дома и быта', sub: 'Гарантийные сроки, серийные номера, характеристики.' },
|
{ file: 'for-household.png', title: 'Food Market для дома и быта', sub: 'Гарантийные сроки, серийные номера, характеристики.' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const tmpl = ({ title, sub }) => ({
|
const tmpl = ({ title, sub }) => ({
|
||||||
|
|
@ -35,14 +31,40 @@ const tmpl = ({ title, sub }) => ({
|
||||||
style: {
|
style: {
|
||||||
width: '1200px', height: '630px', display: 'flex', flexDirection: 'column',
|
width: '1200px', height: '630px', display: 'flex', flexDirection: 'column',
|
||||||
justifyContent: 'space-between', padding: '64px',
|
justifyContent: 'space-between', padding: '64px',
|
||||||
background: 'linear-gradient(135deg, #FFFFFF 0%, #ECFDF5 100%)',
|
background: 'linear-gradient(135deg, #ECFDF5 0%, #D1FAE5 60%, #A7F3D0 100%)',
|
||||||
fontFamily: 'Inter',
|
fontFamily: 'Inter',
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
// Logo (готовый файл бренда на светлом фоне)
|
// Logo + brand row
|
||||||
{
|
{
|
||||||
type: 'img',
|
type: 'div',
|
||||||
props: { src: logoUri, width: 300, height: 99 },
|
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' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// Title + subtitle
|
// Title + subtitle
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import { useState } from 'react'
|
||||||
|
|
||||||
const ITEMS = [
|
const ITEMS = [
|
||||||
{
|
{
|
||||||
q: 'Чем ALL MARKET подходит моему магазину?',
|
q: 'Чем Food Market подходит моему магазину?',
|
||||||
a: 'ALL MARKET — облачная программа учёта розничной торговли, созданная под казахстанский рынок. Поддерживает все типы магазинов: продуктовые, аптеки, общепит, алкомаркеты, одежду, бытовые товары. Включает кассу для Windows с нативной поддержкой торговых весов, интеграцию с ОФД РК, Kaspi Pay и казахстанскими банками.',
|
a: 'Food Market — облачная программа учёта розничной торговли, созданная под казахстанский рынок. Поддерживает все типы магазинов: продуктовые, аптеки, общепит, алкомаркеты, одежду, бытовые товары. Включает кассу для Windows с нативной поддержкой торговых весов, интеграцию с ОФД РК, Kaspi Pay и казахстанскими банками.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
q: 'Сколько стоит подписка?',
|
q: 'Сколько стоит подписка?',
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,6 @@ import Logo from './Logo.astro'
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="border-t border-slate-200 py-4 text-center text-xs text-slate-400">
|
<div class="border-t border-slate-200 py-4 text-center text-xs text-slate-400">
|
||||||
© {new Date().getFullYear()} ALL MARKET. Все права защищены.
|
© {new Date().getFullYear()} Food Market. Все права защищены.
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,4 @@ interface Props { variant?: 'light' | 'dark'; class?: string }
|
||||||
const { variant = 'light', class: className = '' } = Astro.props
|
const { variant = 'light', class: className = '' } = Astro.props
|
||||||
const src = variant === 'dark' ? '/logo-light.svg' : '/logo.svg'
|
const src = variant === 'dark' ? '/logo-light.svg' : '/logo.svg'
|
||||||
---
|
---
|
||||||
<img src={src} alt="ALL MARKET" class={`h-8 w-auto select-none ${className}`} draggable="false" />
|
<img src={src} alt="Food Market" class={`h-7 w-auto select-none ${className}`} draggable="false" />
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
title: "Касса с поддержкой весов — почему это важно для магазина"
|
title: "Касса с поддержкой весов — почему это важно для магазина"
|
||||||
date: 2026-05-02
|
date: 2026-05-02
|
||||||
author: Команда ALL MARKET
|
author: Команда Food Market
|
||||||
category: feature
|
category: feature
|
||||||
description: "Если вы продаёте на развес, работа с весами обычно — самое болезненное место при настройке кассы. Рассказываем, как мы сделали её простой."
|
description: "Если вы продаёте на развес, работа с весами обычно — самое болезненное место при настройке кассы. Рассказываем, как мы сделали её простой."
|
||||||
cover_image: /photos/blog-scales.jpg
|
cover_image: /photos/blog-scales.jpg
|
||||||
|
|
@ -17,7 +17,7 @@ cover_image: /photos/blog-scales.jpg
|
||||||
|
|
||||||
Мы написали обработку стандартных протоколов торговых весов прямо в кассу. Зато теперь весы работают нативно:
|
Мы написали обработку стандартных протоколов торговых весов прямо в кассу. Зато теперь весы работают нативно:
|
||||||
|
|
||||||
- Установили Касса ALL MARKET для Windows
|
- Установили Касса Food Market для Windows
|
||||||
- Подключили торговые весы через RS-232 или USB-COM кабель
|
- Подключили торговые весы через RS-232 или USB-COM кабель
|
||||||
- Запустили кассу — **всё**
|
- Запустили кассу — **всё**
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
---
|
---
|
||||||
title: "ALL MARKET запустился — система учёта розницы для Казахстана"
|
title: "Food Market запустился — система учёта розницы для Казахстана"
|
||||||
date: 2026-04-26
|
date: 2026-04-26
|
||||||
author: Команда ALL MARKET
|
author: Команда Food Market
|
||||||
category: news
|
category: news
|
||||||
description: "Сегодня мы запускаем ALL MARKET — программу учёта розничной торговли, созданную в Казахстане для казахстанских магазинов."
|
description: "Сегодня мы запускаем Food Market — программу учёта розничной торговли, созданную в Казахстане для казахстанских магазинов."
|
||||||
cover_image: /photos/blog-launch.jpg
|
cover_image: /photos/blog-launch.jpg
|
||||||
---
|
---
|
||||||
|
|
||||||
# ALL MARKET запустился
|
# Food Market запустился
|
||||||
|
|
||||||
Сегодня мы открываем публичный доступ к ALL MARKET — программе учёта розничной торговли, созданной командой казахстанских разработчиков и практиков ритейла.
|
Сегодня мы открываем публичный доступ к Food Market — программе учёта розничной торговли, созданной командой казахстанских разработчиков и практиков ритейла.
|
||||||
|
|
||||||
## Что мы сделали
|
## Что мы сделали
|
||||||
|
|
||||||
|
|
@ -20,7 +20,7 @@ cover_image: /photos/blog-launch.jpg
|
||||||
- **поддерживать локальное железо** — торговые весы нативно, без переходников и сторонних драйверов
|
- **поддерживать локальное железо** — торговые весы нативно, без переходников и сторонних драйверов
|
||||||
- **позволять попробовать без риска** — настоящий триал, а не «14 дней с привязкой карты»
|
- **позволять попробовать без риска** — настоящий триал, а не «14 дней с привязкой карты»
|
||||||
|
|
||||||
Мы воплотили эти принципы в ALL MARKET.
|
Мы воплотили эти принципы в Food Market.
|
||||||
|
|
||||||
## Что вы получаете
|
## Что вы получаете
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
---
|
---
|
||||||
title: "Запустите магазин в ALL MARKET за 15 минут"
|
title: "Запустите магазин в Food Market за 15 минут"
|
||||||
date: 2026-04-30
|
date: 2026-04-30
|
||||||
author: Команда ALL MARKET
|
author: Команда Food Market
|
||||||
category: guide
|
category: guide
|
||||||
description: "Пошаговая инструкция первого запуска: от регистрации до первой продажи."
|
description: "Пошаговая инструкция первого запуска: от регистрации до первой продажи."
|
||||||
cover_image: /photos/blog-quickstart.jpg
|
cover_image: /photos/blog-quickstart.jpg
|
||||||
---
|
---
|
||||||
|
|
||||||
# Запустите магазин в ALL MARKET за 15 минут
|
# Запустите магазин в Food Market за 15 минут
|
||||||
|
|
||||||
Часто новые SaaS-сервисы требуют дней на настройку и обучение. Мы спроектировали ALL MARKET так, чтобы вы могли начать продавать в день регистрации.
|
Часто новые SaaS-сервисы требуют дней на настройку и обучение. Мы спроектировали Food Market так, чтобы вы могли начать продавать в день регистрации.
|
||||||
|
|
||||||
Реальное время от пустого экрана до первого пробитого чека — **15 минут**.
|
Реальное время от пустого экрана до первого пробитого чека — **15 минут**.
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ cover_image: /photos/blog-quickstart.jpg
|
||||||
- Название магазина
|
- Название магазина
|
||||||
- Выберите тариф (можно оставить «Триал» — 90 дней бесплатно)
|
- Выберите тариф (можно оставить «Триал» — 90 дней бесплатно)
|
||||||
|
|
||||||
Через 30 секунд после нажатия «Зарегистрироваться» вы окажетесь в админке ALL MARKET с готовым к работе пустым магазином.
|
Через 30 секунд после нажатия «Зарегистрироваться» вы окажетесь в админке Food Market с готовым к работе пустым магазином.
|
||||||
|
|
||||||
## Шаг 2. Загрузите каталог (5 минут)
|
## Шаг 2. Загрузите каталог (5 минут)
|
||||||
|
|
||||||
|
|
@ -29,7 +29,7 @@ cover_image: /photos/blog-quickstart.jpg
|
||||||
|
|
||||||
**А. Импорт из Excel/CSV** — самый быстрый если у вас уже есть файл с товарами:
|
**А. Импорт из Excel/CSV** — самый быстрый если у вас уже есть файл с товарами:
|
||||||
- «Импорт» → «Из файла» → загрузите .xlsx или .csv
|
- «Импорт» → «Из файла» → загрузите .xlsx или .csv
|
||||||
- Сопоставьте колонки (название, штрихкод, цена) — ALL MARKET подскажет правильное соответствие
|
- Сопоставьте колонки (название, штрихкод, цена) — Food Market подскажет правильное соответствие
|
||||||
- Запустите импорт — 5 000 SKU перенесутся за минуту
|
- Запустите импорт — 5 000 SKU перенесутся за минуту
|
||||||
|
|
||||||
**Б. Импорт через API** — если работаете в системе с открытым API:
|
**Б. Импорт через API** — если работаете в системе с открытым API:
|
||||||
|
|
@ -61,7 +61,7 @@ cover_image: /photos/blog-quickstart.jpg
|
||||||
4. Введите сумму полученных денег
|
4. Введите сумму полученных денег
|
||||||
5. Подтвердите — чек распечатается
|
5. Подтвердите — чек распечатается
|
||||||
|
|
||||||
🎉 Вы только что сделали первую продажу через ALL MARKET.
|
🎉 Вы только что сделали первую продажу через Food Market.
|
||||||
|
|
||||||
## Что дальше
|
## Что дальше
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ order: 1
|
||||||
|
|
||||||
# Тарифы, оплата, изменение тарифа
|
# Тарифы, оплата, изменение тарифа
|
||||||
|
|
||||||
Полная информация о подписках ALL MARKET и порядке оплаты.
|
Полная информация о подписках Food Market и порядке оплаты.
|
||||||
|
|
||||||
## Тарифы
|
## Тарифы
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ order: 1
|
||||||
|
|
||||||
# Настройка кассы для Windows
|
# Настройка кассы для Windows
|
||||||
|
|
||||||
Полная инструкция установки и настройки кассы ALL MARKET для Windows. Время — 15-20 минут.
|
Полная инструкция установки и настройки кассы Food Market для Windows. Время — 15-20 минут.
|
||||||
|
|
||||||
## Системные требования
|
## Системные требования
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ order: 1
|
||||||
|
|
||||||
## Скачивание установщика
|
## Скачивание установщика
|
||||||
|
|
||||||
1. Войдите в админку ALL MARKET
|
1. Войдите в админку Food Market
|
||||||
2. Откройте раздел «Касса» в боковом меню
|
2. Откройте раздел «Касса» в боковом меню
|
||||||
3. Нажмите «Скачать кассу для Windows»
|
3. Нажмите «Скачать кассу для Windows»
|
||||||
4. Сохраните файл `food-market-pos-setup.msi` (~80 МБ)
|
4. Сохраните файл `food-market-pos-setup.msi` (~80 МБ)
|
||||||
|
|
@ -43,7 +43,7 @@ order: 1
|
||||||
|
|
||||||
При первом запуске касса попросит ввести **ключ организации**.
|
При первом запуске касса попросит ввести **ключ организации**.
|
||||||
|
|
||||||
1. В админке ALL MARKET: «Касса» → «Ключ организации» — скопируйте 32-символьный код
|
1. В админке Food Market: «Касса» → «Ключ организации» — скопируйте 32-символьный код
|
||||||
2. В кассе — вставьте ключ, нажмите «Активировать»
|
2. В кассе — вставьте ключ, нажмите «Активировать»
|
||||||
3. Касса синхронизирует каталог товаров с облаком (~30 секунд для 5000 SKU)
|
3. Касса синхронизирует каталог товаров с облаком (~30 секунд для 5000 SKU)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ order: 1
|
||||||
|
|
||||||
# Быстрый старт за 15 минут
|
# Быстрый старт за 15 минут
|
||||||
|
|
||||||
После регистрации в ALL MARKET вам нужно сделать 4 простых шага, чтобы начать продавать.
|
После регистрации в Food Market вам нужно сделать 4 простых шага, чтобы начать продавать.
|
||||||
|
|
||||||
## Шаг 1. Заполните данные магазина (1 мин)
|
## Шаг 1. Заполните данные магазина (1 мин)
|
||||||
|
|
||||||
|
|
@ -47,7 +47,7 @@ order: 1
|
||||||
|
|
||||||
В кассе нажмите «Открыть смену», введите начальный остаток наличных. Сосканируйте штрихкод любого товара — он появится в чеке. Нажмите «Оплата» → «Наличные» → подтвердите. Чек распечатается, сделка пройдёт в учёт.
|
В кассе нажмите «Открыть смену», введите начальный остаток наличных. Сосканируйте штрихкод любого товара — он появится в чеке. Нажмите «Оплата» → «Наличные» → подтвердите. Чек распечатается, сделка пройдёт в учёт.
|
||||||
|
|
||||||
🎉 Вы только что сделали первую продажу через ALL MARKET.
|
🎉 Вы только что сделали первую продажу через Food Market.
|
||||||
|
|
||||||
## Что дальше?
|
## Что дальше?
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ last_updated: 2026-04-26
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Настоящим я, как субъект персональных данных, регистрируясь в сервисе **ALL MARKET** (далее — «**Сервис**»), размещённом на сайте https://test.food-market.kz, и проставляя отметку в чекбоксе «Согласен с офертой и политикой обработки персональных данных» при создании учётной записи, в соответствии со статьёй 8 Закона Республики Казахстан от 21 мая 2013 года № 94-V «О персональных данных и их защите», свободно, своей волей и в своём интересе даю своё согласие **ТОО «[НАЗВАНИЕ ТОО]»**, БИН [БИН], юридический адрес: [Юр.адрес] (далее — «**Оператор**»), на сбор, обработку, хранение, использование, обезличивание и уничтожение моих персональных данных в нижеуказанном объёме и для нижеуказанных целей.
|
Настоящим я, как субъект персональных данных, регистрируясь в сервисе **Food Market** (далее — «**Сервис**»), размещённом на сайте https://test.food-market.kz, и проставляя отметку в чекбоксе «Согласен с офертой и политикой обработки персональных данных» при создании учётной записи, в соответствии со статьёй 8 Закона Республики Казахстан от 21 мая 2013 года № 94-V «О персональных данных и их защите», свободно, своей волей и в своём интересе даю своё согласие **ТОО «[НАЗВАНИЕ ТОО]»**, БИН [БИН], юридический адрес: [Юр.адрес] (далее — «**Оператор**»), на сбор, обработку, хранение, использование, обезличивание и уничтожение моих персональных данных в нижеуказанном объёме и для нижеуказанных целей.
|
||||||
|
|
||||||
## 1. Перечень персональных данных, на обработку которых даётся согласие
|
## 1. Перечень персональных данных, на обработку которых даётся согласие
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
---
|
||||||
title: Публичная оферта на использование сервиса ALL MARKET
|
title: Публичная оферта на использование сервиса Food Market
|
||||||
slug: offer
|
slug: offer
|
||||||
last_updated: 2026-04-26
|
last_updated: 2026-04-26
|
||||||
---
|
---
|
||||||
|
|
||||||
# Публичная оферта на использование сервиса ALL MARKET
|
# Публичная оферта на использование сервиса Food Market
|
||||||
|
|
||||||
> **Внимание:** Настоящий документ является типовой публичной офертой и подлежит правовой экспертизе перед публичным использованием. Финальная редакция утверждается уполномоченным лицом Лицензиара.
|
> **Внимание:** Настоящий документ является типовой публичной офертой и подлежит правовой экспертизе перед публичным использованием. Финальная редакция утверждается уполномоченным лицом Лицензиара.
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ last_updated: 2026-04-26
|
||||||
|
|
||||||
## 1. Общие положения
|
## 1. Общие положения
|
||||||
|
|
||||||
1.1. Настоящий документ является публичной офертой (далее — «**Оферта**») в соответствии со статьями 395, 396 Гражданского кодекса Республики Казахстан (далее — «**ГК РК**»). Оферта адресована любому физическому или юридическому лицу (далее — «**Пользователь**»), желающему получить доступ к программно-аппаратному комплексу «ALL MARKET» (далее — «**Сервис**»).
|
1.1. Настоящий документ является публичной офертой (далее — «**Оферта**») в соответствии со статьями 395, 396 Гражданского кодекса Республики Казахстан (далее — «**ГК РК**»). Оферта адресована любому физическому или юридическому лицу (далее — «**Пользователь**»), желающему получить доступ к программно-аппаратному комплексу «Food Market» (далее — «**Сервис**»).
|
||||||
|
|
||||||
1.2. Лицензиаром Сервиса выступает **ТОО «[НАЗВАНИЕ ТОО]»**, БИН [БИН], зарегистрированное в соответствии с законодательством Республики Казахстан (далее — «**Лицензиар**»). Полные реквизиты Лицензиара указаны в разделе «Реквизиты» сайта.
|
1.2. Лицензиаром Сервиса выступает **ТОО «[НАЗВАНИЕ ТОО]»**, БИН [БИН], зарегистрированное в соответствии с законодательством Республики Казахстан (далее — «**Лицензиар**»). Полные реквизиты Лицензиара указаны в разделе «Реквизиты» сайта.
|
||||||
|
|
||||||
|
|
@ -29,7 +29,7 @@ last_updated: 2026-04-26
|
||||||
|
|
||||||
## 2. Термины и определения
|
## 2. Термины и определения
|
||||||
|
|
||||||
**Сервис** — программный продукт «ALL MARKET», представляющий собой облачную систему учёта розничной торговли с функциями управления товарами, складом, продажами, кассовыми операциями, аналитикой и сопутствующими сервисами, размещённый по адресу https://test.food-market.kz и связанных с ним поддоменах.
|
**Сервис** — программный продукт «Food Market», представляющий собой облачную систему учёта розничной торговли с функциями управления товарами, складом, продажами, кассовыми операциями, аналитикой и сопутствующими сервисами, размещённый по адресу https://test.food-market.kz и связанных с ним поддоменах.
|
||||||
|
|
||||||
**Учётная запись** — совокупность данных Пользователя, обеспечивающих его идентификацию и авторизацию в Сервисе.
|
**Учётная запись** — совокупность данных Пользователя, обеспечивающих его идентификацию и авторизацию в Сервисе.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ last_updated: 2026-04-26
|
||||||
|
|
||||||
## 1. Общие положения
|
## 1. Общие положения
|
||||||
|
|
||||||
1.1. Настоящая Политика обработки персональных данных (далее — «**Политика**») определяет порядок обработки и защиты персональных данных, осуществляемой **ТОО «[НАЗВАНИЕ ТОО]»**, БИН [БИН] (далее — «**Оператор**»), при предоставлении доступа к программно-аппаратному комплексу «ALL MARKET» (далее — «**Сервис**»).
|
1.1. Настоящая Политика обработки персональных данных (далее — «**Политика**») определяет порядок обработки и защиты персональных данных, осуществляемой **ТОО «[НАЗВАНИЕ ТОО]»**, БИН [БИН] (далее — «**Оператор**»), при предоставлении доступа к программно-аппаратному комплексу «Food Market» (далее — «**Сервис**»).
|
||||||
|
|
||||||
1.2. Политика разработана в соответствии с:
|
1.2. Политика разработана в соответствии с:
|
||||||
- Конституцией Республики Казахстан;
|
- Конституцией Республики Казахстан;
|
||||||
|
|
|
||||||
|
|
@ -28,19 +28,19 @@ const ymId = import.meta.env.PUBLIC_YM_ID ?? 'REPLACE_ME'
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<title>{title} · ALL MARKET</title>
|
<title>{title} · Food Market</title>
|
||||||
<meta name="description" content={description} />
|
<meta name="description" content={description} />
|
||||||
<link rel="canonical" href={canonical} />
|
<link rel="canonical" href={canonical} />
|
||||||
|
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:title" content={`${title} · ALL MARKET`} />
|
<meta property="og:title" content={`${title} · Food Market`} />
|
||||||
<meta property="og:description" content={description} />
|
<meta property="og:description" content={description} />
|
||||||
<meta property="og:url" content={canonical} />
|
<meta property="og:url" content={canonical} />
|
||||||
<meta property="og:image" content={new URL(ogImage, Astro.site).toString()} />
|
<meta property="og:image" content={new URL(ogImage, Astro.site).toString()} />
|
||||||
<meta property="og:locale" content="ru_KZ" />
|
<meta property="og:locale" content="ru_KZ" />
|
||||||
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
<meta name="twitter:title" content={`${title} · ALL MARKET`} />
|
<meta name="twitter:title" content={`${title} · Food Market`} />
|
||||||
<meta name="twitter:description" content={description} />
|
<meta name="twitter:description" content={description} />
|
||||||
<meta name="twitter:image" content={new URL(ogImage, Astro.site).toString()} />
|
<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({
|
<script type="application/ld+json" set:html={JSON.stringify({
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'ALL MARKET',
|
name: 'Food Market',
|
||||||
applicationCategory: 'BusinessApplication',
|
applicationCategory: 'BusinessApplication',
|
||||||
operatingSystem: 'Web, Windows',
|
operatingSystem: 'Web, Windows',
|
||||||
offers: { '@type': 'Offer', price: '5000', priceCurrency: 'KZT' },
|
offers: { '@type': 'Offer', price: '5000', priceCurrency: 'KZT' },
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
---
|
---
|
||||||
import BaseLayout from '@/layouts/BaseLayout.astro'
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
---
|
---
|
||||||
<BaseLayout title="О нас" description="ALL MARKET — программа учёта и касса для розничных магазинов Казахстана. История, команда, принципы." ogImage="/og/about.png">
|
<BaseLayout title="О нас" description="Food Market — программа учёта и касса для розничных магазинов Казахстана. История, команда, принципы." ogImage="/og/about.png">
|
||||||
<section class="relative overflow-hidden">
|
<section class="relative overflow-hidden">
|
||||||
<img src="/photos/about-hero.jpg" alt="" class="absolute inset-0 w-full h-full object-cover" />
|
<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="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">
|
<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">О ALL MARKET</h1>
|
<h1 class="text-4xl lg:text-5xl font-extrabold text-white drop-shadow-lg">О Food Market</h1>
|
||||||
<p class="mt-4 text-lg text-emerald-50 max-w-xl">Программа учёта розничной торговли, созданная в Казахстане для казахстанских магазинов.</p>
|
<p class="mt-4 text-lg text-emerald-50 max-w-xl">Программа учёта розничной торговли, созданная в Казахстане для казахстанских магазинов.</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="max-w-3xl mx-auto px-4 sm:px-6 py-12 prose-md">
|
<section class="max-w-3xl mx-auto px-4 sm:px-6 py-12 prose-md">
|
||||||
<h2>История</h2>
|
<h2>История</h2>
|
||||||
<p>ALL MARKET запустился в 2026 году как ответ на запрос рынка: розничные магазины в Казахстане заслуживают современный, быстрый и честный инструмент для ведения бизнеса.</p>
|
<p>Food Market запустился в 2026 году как ответ на запрос рынка: розничные магазины в Казахстане заслуживают современный, быстрый и честный инструмент для ведения бизнеса.</p>
|
||||||
<p>Команда основателей — практики розницы и разработчики, которые три года изнутри наблюдали как магазины ведут учёт, какие проблемы решают каждый день, чего им не хватает в существующих инструментах.</p>
|
<p>Команда основателей — практики розницы и разработчики, которые три года изнутри наблюдали как магазины ведут учёт, какие проблемы решают каждый день, чего им не хватает в существующих инструментах.</p>
|
||||||
<p>Мы поставили перед собой задачу сделать продукт, который работает с первого клика — без долгой настройки и платных консультаций; нативно поддерживает локальное железо (торговые весы из коробки, без переходников); соответствует реалиям РК (Kaspi Pay, ОФД, законодательство, поддержка на русском); и который можно попробовать без риска — настоящий триал, без привязки карты.</p>
|
<p>Мы поставили перед собой задачу сделать продукт, который работает с первого клика — без долгой настройки и платных консультаций; нативно поддерживает локальное железо (торговые весы из коробки, без переходников); соответствует реалиям РК (Kaspi Pay, ОФД, законодательство, поддержка на русском); и который можно попробовать без риска — настоящий триал, без привязки карты.</p>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 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' })
|
const fmt = (d: Date) => d.toLocaleDateString('ru-KZ', { day: '2-digit', month: 'long', year: 'numeric' })
|
||||||
---
|
---
|
||||||
<BaseLayout title="Блог" description="Блог ALL MARKET: новости, релизы, лучшие практики автоматизации розничных магазинов в Казахстане.">
|
<BaseLayout title="Блог" description="Блог Food Market: новости, релизы, лучшие практики автоматизации розничных магазинов в Казахстане.">
|
||||||
<section class="max-w-3xl mx-auto px-4 sm:px-6 py-14">
|
<section class="max-w-3xl mx-auto px-4 sm:px-6 py-14">
|
||||||
<h1 class="text-4xl font-extrabold">Блог</h1>
|
<h1 class="text-4xl font-extrabold">Блог</h1>
|
||||||
<p class="text-slate-500 mt-2">Запуски, миграции, лучшие практики розницы.</p>
|
<p class="text-slate-500 mt-2">Запуски, миграции, лучшие практики розницы.</p>
|
||||||
|
|
|
||||||