fix(site): критичные + честность текстов на test.food-market.kz (этап 1)
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker Public / Build + push Public (test + latest) (push) Waiting to run
Docker Public / Deploy :test → test.food-market.kz (push) Blocked by required conditions

1. /signup читает ?plan= и конфигурацию (общий модуль lib/pricing.ts): предвыбор тарифа,
   для «Бизнес» — конфигурация и цена (15 500 ₸ для 3/4/1); неизвестный план → «Старт».
2. Мобильная шапка: гамбургер + панель со всеми пунктами (aria-expanded, Esc/клик-вне, ≥44px).
3. og-картинки без ОФД/REST API/ABC — перегенерированы.
4. KB/блог: убраны несуществующие обещания (Kaspi Pay/Halyk/Visa, REST API, «Скачать установщик»,
   Kaspi Magazin Q3, маркировка, платные консультации, скидки 3/6/12, «Триал» как тариф).
   Противоречия сведены к оферте (удаление данных 30 дн, возврат по заявлению, Старт=2 сотр.).
5. /import: чужой HR-скриншот заменён на скриншот каталога продукта.
6. KB рендерился как сырой front-matter в code-блоке — развёрнут, дубль # убран.
7. Аналитика без реальных ID не подключается (нет data-id=REPLACE_ME).
8. robots.txt → рабочий /sitemap.xml.
9. Футер: дубль «Дорожная карта»/«Changelog» убран; 10. зоны нажатия футера + width/height у lazy-картинок.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
nns 2026-10-10 23:53:58 +05:00
parent 5cb2c5934c
commit b8e42e216c
21 changed files with 215 additions and 143 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View file

@ -1,3 +1,3 @@
User-agent: *
Allow: /
Sitemap: https://test.food-market.kz/sitemap-index.xml
Sitemap: https://test.food-market.kz/sitemap.xml

View file

@ -16,12 +16,12 @@ const logoPng = await fs.readFile(path.join(__dirname, 'allmarket-logo-color.png
const logoUri = `data:image/png;base64,${logoPng.toString('base64')}`
const pages = [
{ file: 'home.png', title: 'Программа учёта и касса для розничных магазинов в Казахстане', sub: 'Касса с торговыми весами, импорт каталога, ОФД РК. Бесплатно 90 дней.' },
{ file: 'home.png', title: 'Программа учёта и касса для розничных магазинов в Казахстане', sub: 'Касса с торговыми весами и импортом каталога. Бесплатно 90 дней.' },
{ file: 'pos.png', title: 'Касса для Windows с поддержкой торговых весов', sub: 'Установка за 5 минут. Офлайн-режим. Стандартное оборудование.' },
{ file: 'pricing.png', title: 'Прозрачные тарифы ALL MARKET', sub: 'От 5 000 ₸/мес. Все возможности на любом тарифе. 90 дней триал.' },
{ file: 'import.png', title: 'Перенесите каталог за один клик', sub: 'Excel, CSV, REST API. До 100 000 товаров. Бесплатно.' },
{ file: 'import.png', title: 'Перенесите каталог за один клик', sub: 'Excel, CSV, 1С, МойСклад. До 100 000 товаров. Бесплатно.' },
{ file: 'about.png', title: 'О ALL MARKET', sub: 'Программа учёта розничной торговли, созданная в Казахстане.' },
{ file: 'for-grocery.png', title: 'ALL MARKET для продуктового магазина', sub: 'Весовой товар, скоропорт, штрихкоды, ABC-анализ.' },
{ file: 'for-grocery.png', title: 'ALL MARKET для продуктового магазина', sub: 'Весовой товар, скоропорт, штрихкоды, учёт остатков.' },
{ file: 'for-pharmacy.png', title: 'ALL MARKET для аптеки', sub: 'Серии, сроки годности, рецептурный отпуск, контроль ЛП.' },
{ file: 'for-cafe.png', title: 'ALL MARKET для кафе и общепита', sub: 'Модификаторы блюд, столики, печать на кухню.' },
{ file: 'for-alcohol.png', title: 'ALL MARKET для алкомаркета', sub: 'Акцизные марки, контроль времени продаж, маркировка.' },

View file

@ -1,9 +1,10 @@
import { useMemo, useState } from 'react'
import { PRICING, businessPrice, fmtKzt } from '@/lib/pricing'
const BASE = 10000
const PER_SHOP = 2000
const PER_CASH = 500
const PER_WAREHOUSE = 500
const BASE = PRICING.base
const PER_SHOP = PRICING.perShop
const PER_CASH = PRICING.perCash
const PER_WAREHOUSE = PRICING.perWarehouse
interface SliderProps {
label: string; value: number; min: number; max: number; onChange: (v: number) => void; suffix?: string
@ -31,12 +32,7 @@ export default function BusinessTariffBuilder() {
const [warehouses, setWarehouses] = useState(1)
const [employees, setEmployees] = useState(5)
const total = useMemo(() => {
return BASE
+ Math.max(0, shops - 1) * PER_SHOP
+ Math.max(0, cashes - 1) * PER_CASH
+ Math.max(0, warehouses - 1) * PER_WAREHOUSE
}, [shops, cashes, warehouses])
const total = useMemo(() => businessPrice({ shops, cashes, warehouses }), [shops, cashes, warehouses])
const fmt = new Intl.NumberFormat('ru')
const signupHref = `/signup?plan=business&shops=${shops}&cashes=${cashes}&warehouses=${warehouses}&employees=${employees}`

View file

@ -12,33 +12,32 @@ import Logo from './Logo.astro'
<div>
<h3 class="text-xs font-semibold uppercase text-slate-500 mb-2">Продукт</h3>
<ul class="space-y-1.5">
<li><a href="/features" class="text-slate-700 hover:text-brand">Возможности</a></li>
<li><a href="/pos" class="text-slate-700 hover:text-brand">Касса</a></li>
<li><a href="/import" class="text-slate-700 hover:text-brand">Импорт каталога</a></li>
<li><a href="/for-grocery" class="text-slate-700 hover:text-brand">Для продуктовых</a></li>
<li><a href="/integrations" class="text-slate-700 hover:text-brand">Перенос и интеграции</a></li>
<li><a href="/pricing" class="text-slate-700 hover:text-brand">Тарифы</a></li>
<li><a href="/changelog" class="text-slate-700 hover:text-brand">Дорожная карта</a></li>
<li><a href="/features" class="block py-2 text-slate-700 hover:text-brand">Возможности</a></li>
<li><a href="/pos" class="block py-2 text-slate-700 hover:text-brand">Касса</a></li>
<li><a href="/import" class="block py-2 text-slate-700 hover:text-brand">Импорт каталога</a></li>
<li><a href="/for-grocery" class="block py-2 text-slate-700 hover:text-brand">Для продуктовых</a></li>
<li><a href="/integrations" class="block py-2 text-slate-700 hover:text-brand">Перенос и интеграции</a></li>
<li><a href="/pricing" class="block py-2 text-slate-700 hover:text-brand">Тарифы</a></li>
</ul>
</div>
<div>
<h3 class="text-xs font-semibold uppercase text-slate-500 mb-2">Ресурсы</h3>
<ul class="space-y-1.5">
<li><a href="/kb" class="text-slate-700 hover:text-brand">База знаний</a></li>
<li><a href="/blog" class="text-slate-700 hover:text-brand">Блог</a></li>
<li><a href="/status" class="text-slate-700 hover:text-brand">Status</a></li>
<li><a href="/changelog" class="text-slate-700 hover:text-brand">Changelog</a></li>
<li><a href="/kb" class="block py-2 text-slate-700 hover:text-brand">База знаний</a></li>
<li><a href="/blog" class="block py-2 text-slate-700 hover:text-brand">Блог</a></li>
<li><a href="/status" class="block py-2 text-slate-700 hover:text-brand">Status</a></li>
<li><a href="/changelog" class="block py-2 text-slate-700 hover:text-brand">Changelog</a></li>
</ul>
</div>
<div>
<h3 class="text-xs font-semibold uppercase text-slate-500 mb-2">Компания</h3>
<ul class="space-y-1.5">
<li><a href="/about" class="text-slate-700 hover:text-brand">О нас</a></li>
<li><a href="/contacts" class="text-slate-700 hover:text-brand">Контакты</a></li>
<li><a href="/legal/offer" class="text-slate-700 hover:text-brand">Оферта</a></li>
<li><a href="/legal/privacy" class="text-slate-700 hover:text-brand">Политика ПДн</a></li>
<li><a href="/legal/consent" class="text-slate-700 hover:text-brand">Согласие ПДн</a></li>
<li><a href="/legal/requisites" class="text-slate-700 hover:text-brand">Реквизиты</a></li>
<li><a href="/about" class="block py-2 text-slate-700 hover:text-brand">О нас</a></li>
<li><a href="/contacts" class="block py-2 text-slate-700 hover:text-brand">Контакты</a></li>
<li><a href="/legal/offer" class="block py-2 text-slate-700 hover:text-brand">Оферта</a></li>
<li><a href="/legal/privacy" class="block py-2 text-slate-700 hover:text-brand">Политика ПДн</a></li>
<li><a href="/legal/consent" class="block py-2 text-slate-700 hover:text-brand">Согласие ПДн</a></li>
<li><a href="/legal/requisites" class="block py-2 text-slate-700 hover:text-brand">Реквизиты</a></li>
</ul>
</div>
</div>

View file

@ -1,24 +1,81 @@
---
import Logo from './Logo.astro'
const APP_URL = import.meta.env.PUBLIC_APP_URL || 'https://admin.food-market.kz'
const nav = [
{ href: '/features', label: 'Возможности' },
{ href: '/pos', label: 'Касса' },
{ href: '/for-grocery', label: 'Для продуктовых' },
{ href: '/import', label: 'Импорт' },
{ href: '/pricing', label: 'Тарифы' },
{ href: '/kb', label: 'База знаний' },
{ href: '/blog', label: 'Блог' },
]
---
<header class="sticky top-0 z-40 bg-white/95 backdrop-blur border-b border-slate-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 h-14 flex items-center justify-between gap-4">
<a href="/" class="flex items-center gap-2"><Logo /></a>
<nav class="hidden lg:flex items-center gap-1 text-sm">
<a href="/features" class="px-3 py-2 hover:text-brand transition">Возможности</a>
<a href="/pos" class="px-3 py-2 hover:text-brand transition">Касса</a>
<a href="/for-grocery" class="px-3 py-2 hover:text-brand transition">Для продуктовых</a>
<a href="/import" class="px-3 py-2 hover:text-brand transition">Импорт</a>
<a href="/pricing" class="px-3 py-2 hover:text-brand transition">Тарифы</a>
<a href="/kb" class="px-3 py-2 hover:text-brand transition">База знаний</a>
<a href="/blog" class="px-3 py-2 hover:text-brand transition">Блог</a>
{nav.map((i) => <a href={i.href} class="px-3 py-2 hover:text-brand transition">{i.label}</a>)}
</nav>
<div class="flex items-center gap-2">
<a href={`${APP_URL}/login`} class="px-3 py-1.5 text-sm text-slate-700 hover:text-brand">Войти</a>
<a href="/signup" class="px-3 py-1.5 text-sm font-medium bg-brand text-white rounded-md hover:bg-brand-hover">Начать бесплатно</a>
<a href={`${APP_URL}/login`} class="px-3 min-h-[44px] flex items-center text-sm text-slate-700 hover:text-brand">Войти</a>
<a href="/signup" class="px-3 min-h-[44px] flex items-center text-sm font-medium bg-brand text-white rounded-md hover:bg-brand-hover">Начать бесплатно</a>
<!-- Гамбургер: только на планшете/мобильном (десктоп-меню скрыто < lg) -->
<button
id="mobile-menu-btn"
type="button"
class="lg:hidden inline-flex items-center justify-center w-11 h-11 -mr-1 rounded-md text-slate-700 hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-brand"
aria-label="Меню"
aria-controls="mobile-menu"
aria-expanded="false"
>
<svg id="mobile-menu-icon-open" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="4" y1="7" x2="20" y2="7"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="17" x2="20" y2="17"/></svg>
<svg id="mobile-menu-icon-close" class="hidden" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg>
</button>
</div>
</div>
<!-- Мобильная панель: все пункты десктоп-меню + входы. Зоны нажатия ≥ 44px. -->
<nav id="mobile-menu" class="hidden lg:hidden border-t border-slate-200 bg-white" aria-label="Мобильное меню">
<div class="max-w-7xl mx-auto px-2 sm:px-4 py-2">
{nav.map((i) => <a href={i.href} class="flex items-center min-h-[44px] px-3 rounded-md text-[15px] text-slate-800 hover:bg-slate-100 hover:text-brand">{i.label}</a>)}
<div class="mt-2 pt-2 border-t border-slate-100 grid grid-cols-2 gap-2">
<a href={`${APP_URL}/login`} class="flex items-center justify-center min-h-[44px] px-3 rounded-md text-sm font-medium border border-slate-300 text-slate-700 hover:bg-slate-50">Войти</a>
<a href="/signup" class="flex items-center justify-center min-h-[44px] px-3 rounded-md text-sm font-medium bg-brand text-white hover:bg-brand-hover">Начать бесплатно</a>
</div>
</div>
</nav>
</header>
<script>
const btn = document.getElementById('mobile-menu-btn')
const menu = document.getElementById('mobile-menu')
const iconOpen = document.getElementById('mobile-menu-icon-open')
const iconClose = document.getElementById('mobile-menu-icon-close')
if (btn && menu) {
const setOpen = (open: boolean) => {
menu.classList.toggle('hidden', !open)
btn.setAttribute('aria-expanded', String(open))
iconOpen?.classList.toggle('hidden', open)
iconClose?.classList.toggle('hidden', !open)
}
btn.addEventListener('click', (e) => {
e.stopPropagation()
setOpen(menu.classList.contains('hidden'))
})
// закрытие по тапу вне меню
document.addEventListener('click', (e) => {
if (menu.classList.contains('hidden')) return
const t = e.target as Node
if (!menu.contains(t) && !btn.contains(t)) setOpen(false)
})
// закрытие по Esc
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && !menu.classList.contains('hidden')) { setOpen(false); (btn as HTMLButtonElement).focus() }
})
// при переходе к десктопной ширине — сбросить состояние
window.matchMedia('(min-width: 1024px)').addEventListener('change', (m) => { if (m.matches) setOpen(false) })
}
</script>

View file

@ -1,6 +1,34 @@
import { useState } from 'react'
import { validateEmail, validatePassword, validatePhone } from '@/lib/validation'
import { PhoneInput } from '@/components/PhoneInput'
import { PRICING, businessPrice, clamp, fmtKzt, type BusinessConfig } from '@/lib/pricing'
const VALID_PLANS = ['start', 'business', 'network'] as const
type PlanId = (typeof VALID_PLANS)[number]
/** Читаем ?plan= и параметры конфигуратора из URL на клиенте (сайт статический —
* серверного query нет). Неизвестный план → «Старт». Параметры конфигурации
* клампуются к границам конструктора. */
function readUrlSelection(fallbackPlan: string): { plan: PlanId; config: BusinessConfig; hasConfig: boolean } {
const planFallback: PlanId = (VALID_PLANS as readonly string[]).includes(fallbackPlan) ? (fallbackPlan as PlanId) : 'start'
let plan = planFallback
const cfg: BusinessConfig = { shops: PRICING.shops.min, cashes: PRICING.cashes.min, warehouses: PRICING.warehouses.min, employees: PRICING.employees.min }
let hasConfig = false
if (typeof window !== 'undefined') {
const q = new URLSearchParams(window.location.search)
const p = q.get('plan')
plan = p && (VALID_PLANS as readonly string[]).includes(p) ? (p as PlanId) : 'start'
const num = (k: string, rng: { min: number; max: number }) => {
const v = q.get(k); if (v == null || v === '') return undefined
hasConfig = true; return clamp(Number(v), rng.min, rng.max)
}
cfg.shops = num('shops', PRICING.shops) ?? cfg.shops
cfg.cashes = num('cashes', PRICING.cashes) ?? cfg.cashes
cfg.warehouses = num('warehouses', PRICING.warehouses) ?? cfg.warehouses
cfg.employees = num('employees', PRICING.employees) ?? cfg.employees
}
return { plan, config: cfg, hasConfig }
}
const APP_URL = (import.meta.env.PUBLIC_APP_URL as string | undefined) ?? 'https://admin.food-market.kz'
const API_URL = APP_URL
@ -15,11 +43,16 @@ interface Props {
type FieldErrors = Partial<Record<'email' | 'password' | 'orgName' | 'phone' | 'agree', string>>
export default function SignupForm({ defaultPlan = 'start' }: Props) {
// Разбор ?plan= и конфигурации из URL — один раз при инициализации (клиент).
const [sel] = useState(() => readUrlSelection(defaultPlan))
const [config] = useState<BusinessConfig>(sel.config)
const hasConfig = sel.hasConfig
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [orgName, setOrgName] = useState('')
const [phone, setPhone] = useState('')
const [plan, setPlan] = useState(defaultPlan)
const [plan, setPlan] = useState<string>(sel.plan)
const businessTotal = businessPrice(config)
const [agree, setAgree] = useState(false)
const [busy, setBusy] = useState(false)
const [error, setError] = useState<string | null>(null)
@ -45,7 +78,15 @@ export default function SignupForm({ defaultPlan = 'start' }: Props) {
const res = await fetch(`${API_URL}/api/auth/signup`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password, organizationName: orgName, phone: phone || null, plan }),
// plan сервер принимает (SignupInput.Plan). Параметры конфигурации «Бизнес»
// отправляем вперёд-совместимо; текущий контракт их игнорирует (решение сервера —
// принимать ли — отдельно, см. отчёт site-fixes).
body: JSON.stringify({
email, password, organizationName: orgName, phone: phone || null, plan,
...(plan === 'business' && hasConfig
? { shops: config.shops, cashes: config.cashes, warehouses: config.warehouses, employees: config.employees }
: {}),
}),
})
if (!res.ok) {
const j = await res.json().catch(() => ({}))
@ -154,8 +195,8 @@ export default function SignupForm({ defaultPlan = 'start' }: Props) {
<div className="text-sm font-medium mb-2">Тариф</div>
<div className="grid grid-cols-3 gap-2">
{[
{ id: 'start', name: 'Старт', price: '5 000 ₸' },
{ id: 'business', name: 'Бизнес', price: 'от 10 000 ₸' },
{ id: 'start', name: 'Старт', price: fmtKzt(PRICING.startPrice) },
{ id: 'business', name: 'Бизнес', price: hasConfig ? `${fmtKzt(businessTotal)}/мес` : 'от 10 000 ₸' },
{ id: 'network', name: 'Сеть', price: 'по запросу' },
].map((p) => (
<label key={p.id} className={`p-2.5 rounded-md border cursor-pointer text-center text-sm ${plan === p.id ? 'border-brand bg-brand/5' : 'border-slate-200'}`}>
@ -165,6 +206,21 @@ export default function SignupForm({ defaultPlan = 'start' }: Props) {
</label>
))}
</div>
{plan === 'business' && hasConfig && (
<div className="mt-3 rounded-md border border-brand/30 bg-brand/5 p-3 text-sm">
<div className="flex items-baseline justify-between">
<span className="font-medium">Ваша конфигурация «Бизнес»</span>
<span className="font-bold">{fmtKzt(businessTotal)}/мес</span>
</div>
<div className="mt-1.5 text-xs text-slate-600 grid grid-cols-2 gap-x-4 gap-y-0.5">
<span>Магазинов: <b>{config.shops}</b></span>
<span>Касс: <b>{config.cashes}</b></span>
<span>Складов: <b>{config.warehouses}</b></span>
<span>Сотрудников: <b>{config.employees}</b></span>
</div>
<p className="mt-1.5 text-[11px] text-slate-400">Изменить — в <a href="/pricing" className="underline">конструкторе тарифов</a>.</p>
</div>
)}
</div>
<div>

View file

@ -40,4 +40,4 @@ cover_image: /photos/blog-scales.jpg
- **Дешевле в эксплуатации** — не платите за сторонние модули интеграции
- **Проще обучение кассиров** — они работают как привыкли, никакой «сложной системы»
[Скачать кассу →](/pos)
[Подробнее о кассе →](/pos)

View file

@ -35,10 +35,9 @@ cover_image: /photos/blog-launch.jpg
Сейчас мы открываем регистрацию и принимаем первых клиентов. Будем активно собирать обратную связь и улучшать продукт каждую неделю.
В ближайших планах:
- Интеграция с Kaspi Magazin (Q3 2026)
- Интеграция с Kaspi Magazin
- Marketplace интеграции (Wildberries, Ozon — Q4 2026)
- Мобильное приложение для руководителя
- Готовая архитектура для будущей маркировки в РК
[Зарегистрироваться →](/signup)

View file

@ -19,7 +19,7 @@ cover_image: /photos/blog-quickstart.jpg
- Email — это будет ваш логин
- Пароль (минимум 8 символов)
- Название магазина
- Выберите тариф (можно оставить «Триал» — 90 дней бесплатно)
- Выберите тариф (Старт, Бизнес или Сеть) — первые 90 дней бесплатно
Через 30 секунд после нажатия «Зарегистрироваться» вы окажетесь в админке ALL MARKET с готовым к работе пустым магазином.
@ -42,7 +42,7 @@ cover_image: /photos/blog-quickstart.jpg
## Шаг 3. Установите кассу (5 минут)
В админке: «Касса» → «Скачать установщик».
Приложение кассы для Windows — скоро (появится в разделе «Касса»).
1. Запустите .msi файл — нажмите «Далее» три раза
2. Введите ключ организации (показан в админке)
@ -57,7 +57,7 @@ cover_image: /photos/blog-quickstart.jpg
В кассе:
1. «Открыть смену» — введите начальный остаток наличных
2. Сосканируйте штрихкод любого товара — он появится в чеке
3. «Оплата» → выберите тип (Наличные / Kaspi / Карта)
3. «Оплата» → выберите тип (наличные или карта)
4. Введите сумму полученных денег
5. Подтвердите — чек распечатается
@ -68,7 +68,6 @@ cover_image: /photos/blog-quickstart.jpg
После первой продажи:
- Добавьте остальных кассиров — «Сотрудники» → «Добавить»
- Настройте программу лояльности — «Лояльность» → «Создать программу»
- Подключите Kaspi Pay для приёма безнала — «Интеграции» → «Kaspi»
- Запустите акции и скидки — «Маркетинг» → «Акции»
Полная документация в нашей [Базе знаний](/kb).

View file

@ -4,16 +4,6 @@ category: Биллинг
order: 4
---
```markdown
---
title: Тарифы, оплата, изменение тарифа
slug: billing
section: account
order: 1
---
# Тарифы, оплата, изменение тарифа
Полная информация о подписках ALL MARKET и порядке оплаты.
## Тарифы
@ -21,8 +11,9 @@ order: 1
См. полную таблицу на [странице тарифов](/pricing).
Кратко:
- **Триал** — 90 дней, бесплатно, без карты, все возможности
- **Старт** — 5 000 тг/мес, 1 магазин, 1 касса, 1 склад
Все тарифы — первые 90 дней бесплатно, без банковской карты.
- **Старт** — 5 000 тг/мес, 1 магазин, 1 касса, 1 склад, до 2 сотрудников
- **Бизнес** — от 10 000 тг/мес, конструктор (до 3 магазинов / 5 касс / 5 складов)
- **Сеть** — по запросу, без ограничений
@ -30,11 +21,7 @@ order: 1
### Для физических лиц и ИП
- Kaspi Pay (карта или счёт)
- Halyk Bank, Jusan, Forte и другие казахстанские банки
- Visa / Mastercard
Оплата проходит мгновенно. Доступ продлевается на оплаченный период.
Способ оплаты подписки уточняется — свяжитесь с нами: support@food-market.kz. Приём оплат картой онлайн — в разработке.
### Для юридических лиц
@ -43,15 +30,6 @@ order: 1
- Срок оплаты — 5 рабочих дней с даты выставления счёта
- Доступ продлевается после поступления средств на расчётный счёт
## Скидки за длительные периоды
При оплате авансом:
- 3 месяца: −5%
- 6 месяцев: −10%
- 12 месяцев: −20%
Скидки применяются автоматически при выборе периода в форме оплаты.
## Изменение тарифа
В любой момент через «Настройки» → «Подписка».
@ -78,17 +56,14 @@ order: 1
- Импорт и экспорт работают
Через 30 дней без оплаты:
- Учётная запись помечается на удаление
- Email с финальным предупреждением
- Через 60 дней без оплаты — данные удаляются
- Контент сохраняется 30 календарных дней (см. оферту, п. 5.3/11.3), после чего может быть удалён
## Возврат средств
Возврат за неиспользованный период возможен:
- В течение первых 14 дней с момента оплаты — без обоснования
- После 14 дней — пропорционально неиспользованному периоду
Возврат денежных средств за неиспользованный период осуществляется в случаях, предусмотренных законодательством РК, по письменному заявлению (см. оферту, п. 6.5).
Заявка на возврат: support@food-market.kz с указанием причины и реквизитов для возврата.
Заявка: support@food-market.kz с указанием причины и реквизитов для возврата.
## История платежей и счёта
@ -106,5 +81,3 @@ order: 1
- Выделенный менеджер
Заявка: sales@food-market.kz
```

View file

@ -4,16 +4,6 @@ category: Общее
order: 5
---
```markdown
---
title: Частые вопросы
slug: faq
section: general
order: 99
---
# Частые вопросы
## Регистрация и триал
### Сколько длится триал?
@ -31,7 +21,7 @@ order: 99
## Тарифы и оплата
### Сколько стоит самый дешёвый тариф?
Старт — 5 000 тг/мес. Включает 1 магазин, 1 кассу, 1 склад, без лимита товаров и пользователей.
Старт — 5 000 тг/мес. Включает 1 магазин, 1 кассу, 1 склад, до 2 сотрудников, без лимита товаров.
### Что входит в тариф Бизнес?
Конструктор: до 3 магазинов, до 5 касс, до 5 складов. Цена считается по формуле base 10 000 + 2 000 за магазин + 500 за кассу + 500 за склад.
@ -76,7 +66,7 @@ order: 99
## Импорт и миграция
### Можно перенести данные из другой системы учёта?
Да. Поддерживаем импорт через Excel/CSV-файл, через стандартный REST API и через выгрузку 1С. Подробнее — на странице [/import](/import).
Да. Поддерживаем импорт из файла Excel/CSV, выгрузку 1С (сохранённую в CSV) и перенос из МойСклад по токену. Подробнее — на странице [/import](/import).
### Какой объём каталога переносится?
До 100 000 товаров за один импорт. Каталог 10 000 позиций обычно переносится за 5–10 минут.
@ -98,5 +88,4 @@ order: 99
Базовая поддержка бесплатна для всех тарифов. Приоритетная поддержка с SLA — в тарифах Бизнес и Сеть.
### Есть ли обучение и внедрение?
Платные консультации 5 000 тг/час. Большинство клиентов справляются самостоятельно с помощью нашей базы знаний и видео-уроков.
```
Большинство клиентов справляются самостоятельно с помощью базы знаний. Если нужна помощь с настройкой или импортом — напишите в поддержку, поможем.

View file

@ -4,16 +4,6 @@ category: Касса
order: 3
---
```markdown
---
title: Настройка кассы для Windows
slug: pos-setup
section: cash-register
order: 1
---
# Настройка кассы для Windows
Полная инструкция установки и настройки кассы ALL MARKET для Windows. Время — 15-20 минут.
## Системные требования
@ -28,7 +18,7 @@ order: 1
1. Войдите в админку ALL MARKET
2. Откройте раздел «Касса» в боковом меню
3. Нажмите «Скачать кассу для Windows»
3. Приложение кассы для Windows — скоро (кнопка появится в разделе «Касса»)
4. Сохраните файл `food-market-pos-setup.msi` (~80 МБ)
## Установка
@ -82,7 +72,7 @@ order: 1
1. Нажмите «Открыть смену»
2. Введите начальный остаток наличных в кассе
3. Найдите товар поиском или сканером
4. Нажмите «Оплата» → выберите тип оплаты (Наличные / Kaspi / Карта)
4. Нажмите «Оплата» → выберите тип оплаты (наличные или карта)
5. Введите сумму полученных денег → подтвердите
6. Чек распечатается, сделка пройдёт в учёт
@ -107,5 +97,3 @@ order: 1
## Поддержка
support@food-market.kz — отвечаем в течение часа в рабочее время.
```

View file

@ -4,16 +4,6 @@ category: Начало работы
order: 1
---
```markdown
---
title: Быстрый старт за 15 минут
slug: quickstart
section: getting-started
order: 1
---
# Быстрый старт за 15 минут
После регистрации в ALL MARKET вам нужно сделать 4 простых шага, чтобы начать продавать.
## Шаг 1. Заполните данные магазина (1 мин)
@ -36,7 +26,7 @@ order: 1
## Шаг 3. Установите кассу (5 мин)
«Касса» → «Скачать установщик». Запустите .msi файл, нажмите «Далее» три раза. После установки введите ключ организации (показан в разделе «Касса»).
Приложение кассы для Windows — скоро (следите за разделом «Касса» в админке). После установки нужно будет ввести ключ организации из раздела «Касса».
Подключите оборудование:
- Электронные торговые весы — через RS-232 или USB-COM
@ -51,8 +41,5 @@ order: 1
## Что дальше?
- Добавить остальных кассиров и сотрудников: [Управление пользователями](/kb/users)
- Настроить скидки и программу лояльности: [Лояльность и скидки](/kb/loyalty)
- Добавить остальных кассиров и сотрудников — в админке «Настройки» → «Сотрудники»
- Перенести каталог из старой системы: [Импорт каталога](/import)
```

View file

@ -57,11 +57,9 @@ const ymId = import.meta.env.PUBLIC_YM_ID ?? 'REPLACE_ME'
offers: { '@type': 'Offer', price: '5000', priceCurrency: 'KZT' },
})} />
{/* Sprint 20: analytics placeholders. См. docs/analytics.md.
Скрипты подключаются только при заданных env-vars; иначе
в HTML остаётся data-id="REPLACE_ME" — служит маркером
«не настроено» при ручной проверке view-source. */}
{gaId !== 'REPLACE_ME' ? (
{/* Аналитика подключается ТОЛЬКО при реальных env-ID (PUBLIC_GA_ID / PUBLIC_YM_ID).
Без них НИЧЕГО не выводим — никаких заглушек data-id="REPLACE_ME" в HTML. */}
{gaId !== 'REPLACE_ME' && (
<>
<script async src={`https://www.googletagmanager.com/gtag/js?id=${gaId}`}></script>
<script set:html={`
@ -71,11 +69,9 @@ const ymId = import.meta.env.PUBLIC_YM_ID ?? 'REPLACE_ME'
gtag('config', '${gaId}', { anonymize_ip: true });
`} />
</>
) : (
<script data-analytics="google" data-id="REPLACE_ME" data-doc="docs/analytics.md" />
)}
{ymId !== 'REPLACE_ME' ? (
{ymId !== 'REPLACE_ME' && (
<script set:html={`
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();
@ -84,8 +80,6 @@ const ymId = import.meta.env.PUBLIC_YM_ID ?? 'REPLACE_ME'
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
ym(${ymId}, "init", {clickmap:true,trackLinks:true,accurateTrackBounce:true});
`} />
) : (
<script data-analytics="yandex-metrika" data-id="REPLACE_ME" data-doc="docs/analytics.md" />
)}
</head>
<body class="min-h-screen flex flex-col">

View file

@ -0,0 +1,35 @@
// Единый расчёт цены тарифа «Бизнес» — ОДИН источник правды для конструктора
// (BusinessTariffBuilder), формы регистрации (SignupForm) и страницы /pricing.
// База включает первый магазин/кассу/склад; далее — надбавка за каждый сверх первого.
export const PRICING = {
base: 10000,
perShop: 2000,
perCash: 500,
perWarehouse: 500,
// границы конфигуратора «Бизнес»
shops: { min: 1, max: 3 },
cashes: { min: 1, max: 5 },
warehouses: { min: 1, max: 5 },
employees: { min: 5, max: 10 },
startPrice: 5000,
} as const
export interface BusinessConfig { shops: number; cashes: number; warehouses: number; employees?: number }
export function businessPrice(c: BusinessConfig): number {
const s = clamp(c.shops, PRICING.shops.min, PRICING.shops.max)
const k = clamp(c.cashes, PRICING.cashes.min, PRICING.cashes.max)
const w = clamp(c.warehouses, PRICING.warehouses.min, PRICING.warehouses.max)
return PRICING.base
+ Math.max(0, s - 1) * PRICING.perShop
+ Math.max(0, k - 1) * PRICING.perCash
+ Math.max(0, w - 1) * PRICING.perWarehouse
}
export function clamp(n: number, min: number, max: number): number {
if (!Number.isFinite(n)) return min
return Math.min(max, Math.max(min, Math.round(n)))
}
const KZT = new Intl.NumberFormat('ru')
export const fmtKzt = (n: number): string => `${KZT.format(n)} ₸`

View file

@ -35,7 +35,7 @@ const faq = [
---
<BaseLayout title="Импорт каталога" description="Перенесите товары, контрагентов, остатки и штрихкоды из вашей системы за минуты. CSV, 1С, МойСклад." ogImage="/og/import.png">
<section class="relative overflow-hidden">
<img src="/photos/import-hero.jpg" alt="" class="absolute inset-0 w-full h-full object-cover" />
<img src="/screenshots/catalog.webp" srcset="/screenshots/catalog.webp 1440w, /screenshots/catalog@2x.webp 2880w" sizes="100vw" width="1440" height="900" alt="Каталог товаров в ALL MARKET" 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-slate-900/30"></div>
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 py-20 lg:py-28">
<div class="max-w-2xl">

View file

@ -86,7 +86,7 @@ const modules = [
sizes="(max-width: 1024px) 100vw, 1100px"
width="1440" height="900"
alt="Каталог товаров ALL MARKET: список товаров с группами, штрихкодами, себестоимостью и розничной ценой"
class="w-full block rounded-b-md" loading="lazy" />
class="w-full block rounded-b-md" width="1440" height="900" loading="lazy" />
</div>
</section>
@ -111,7 +111,7 @@ const modules = [
sizes="(max-width: 1024px) 100vw, 600px"
width="1920" height="1080"
alt="Окно продажи кассы ALL MARKET: чек с товарами, избранные позиции, итоговая сумма и горячие клавиши"
class="w-full block rounded-md" loading="lazy" />
class="w-full block rounded-md" width="1440" height="810" loading="lazy" />
</figure>
</div>
</section>

View file

@ -54,7 +54,7 @@ const steps = [
sizes="(max-width: 1024px) 100vw, 600px"
width="1920" height="1080"
alt="Окно продажи кассы ALL MARKET: чек с товарами, избранные позиции, итоговая сумма и горячие клавиши"
class="w-full block rounded-md" loading="lazy" />
class="w-full block rounded-md" width="1440" height="810" loading="lazy" />
<figcaption class="text-center text-sm text-slate-500 mt-3">Экран продажи</figcaption>
</figure>
<figure class="rounded-xl border border-slate-200 bg-slate-50 p-3 shadow-lg">
@ -64,7 +64,7 @@ const steps = [
sizes="(max-width: 1024px) 100vw, 600px"
width="1920" height="1080"
alt="Окно оплаты кассы ALL MARKET: выбор способа оплаты — наличные, карта, смешанная — и расчёт сдачи"
class="w-full block rounded-md" loading="lazy" />
class="w-full block rounded-md" width="1440" height="810" loading="lazy" />
<figcaption class="text-center text-sm text-slate-500 mt-3">Оплата и расчёт сдачи</figcaption>
</figure>
</div>