Custom roles is staff governance, so it belongs with the team — added a "Roles &
permissions" tab to the HR screen (owner-only) rendering the existing
CustomRolesPanel, and removed the Settings → Team → Custom Roles leaf/group.
fa/en/ar label added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a "سفارشها" (Orders) nav page listing closed orders by day (date +
branch filter, paged), each with reprint actions:
- چاپ فاکتور → customer receipt
- فیش آشپزخانه → kitchen ticket (all stations)
- one button per print station (e.g. Bar) → reprints only that station's items
Backend: the kitchen print endpoint gains an optional ?stationId= to reprint a
single station; PrintKitchenTicketAsync filters its station groups accordingly
(NO_STATION_ITEMS when that station has nothing on the order). Nav gated by
ViewOrders (visible to branch staff too). fa/en/ar strings added.
Note: local backend build couldn't run (NU1301 — NuGet restore network timeout);
dashboard typecheck is clean and the C# changes are minimal — CI builds via the
Nexus mirror.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Logs showed the raw User ID (ActorName was almost never stored) and an English
role enum. Now:
- AuditController resolves each entry's actor to the employee's CURRENT full name
and localized role at read time (joins Employees with IgnoreQueryFilters, so it
also names soft-deleted staff and fixes all historical rows — no migration).
- The audit table renders "Full name (Role)" with the role localized (fa/en/ar);
the name is a button that opens an employee-details dialog.
- New EmployeeDetailsDialog: fetches the employee and shows name, role, phone,
base salary, and an "Open in HR" link; handles removed staff gracefully.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Visiting /login while authenticated now redirects to the app (/pos) instead of
showing the login form again. Guarded on _hasHydrated so the not-yet-rehydrated
(null) session isn't misread, and renders a brief "redirecting" state instead of
flashing the form. fa/en/ar strings added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Complements the separate kitchen/bar printers with an on-screen split. The live
order DTO now carries each item's prep station (MenuItem → Category →
KitchenStation), and the KDS shows station tabs (All / Kitchen / Bar / …) that
appear only once ≥2 stations are in play. Selecting a station shows just the
tickets — and just the items within each ticket — for that station, so bar staff
see drinks and kitchen staff see food. Single-station cafés see the board
unchanged. fa/en/ar strings added.
Note: order status is still per-order (one advance button); the split is for
viewing/printing, not per-item status.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The print engine already routed items to per-station printers (MenuCategory →
KitchenStation.PrinterIp, falling back to the branch kitchen printer) and prints
the customer receipt to the receipt printer — but there was no UI to set it up.
This exposes it:
- Settings → "Kitchen & bar printers": create/edit/delete print stations, each
with its own printer IP/port, with a per-station test print (gated by
ManageKitchenStations).
- Menu category editor: a "Print station" dropdown to route each category to a
station (food → Kitchen, drinks → Bar); no station = branch kitchen printer.
Result: kitchen and bar tickets print on separate printers, while the customer
factor/receipt keeps printing on the receipt printer. fa/en/ar strings added.
No backend/migration changes — purely wiring the existing capability.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirrors the expanded backend catalog on the client: the Permission type and the
custom-role permission matrix now expose all ~80 capabilities grouped into 16
sections (admin, branches, menu, inventory, taxes, staff, tables, orders,
register, queue/kitchen, delivery, customers, coupons, marketing, reports,
expenses), each with fa/en/ar labels. Nav visibility now maps each page to its
View permission; taxes & branches become permission-driven (managers can view),
leaving billing as the sole hard owner-only nav gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New Settings → "Notifications & sound" leaf to make the alert channels
changeable: toggle sound (+ picker with live preview + volume slider),
enable desktop notifications (permission flow + test button), toggle the
tab unread badge and in-app toasts. Strings added for fa/en/ar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Owner can define named custom roles (e.g. Barista, Supervisor) with
color, description, and a fine-grained permission set (21 permissions
across 7 categories: admin, menu, staff, customer, reports, ops, kitchen)
- Employee assigned a custom role gets its permissions embedded in the
JWT at login (customPerms claim) and parsed by TenantMiddleware —
overrides the static EmployeeRole matrix for all API permission checks
- New endpoints: GET/POST/PATCH/DELETE /api/cafes/{id}/custom-roles and
PUT /api/cafes/{id}/employees/{id}/custom-role for assignment
- Dashboard Settings → Team & Staff → Custom Roles panel with grouped
checkbox matrix, group-level toggles, color preset picker, CRUD forms,
and employee-count display; translations in fa/en/ar
- EF migration adds CustomRoles table + nullable CustomRoleId FK on Employees
- POS slip now shows per-item notes on both thermal print and bill preview
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Platform admins can generate a permanent recovery key per café (admin
panel → Cafés). The café Owner uses it to sign in when OTP access is lost;
once authenticated, all server-side data syncs as normal (data is per-café
on the server, the device only caches it).
Backend:
- Cafe.RecoveryKeyHash (SHA-256, unique index) + RecoveryKeyCreatedAt; migration
- RecoveryKeyGenerator util: MZ-XXXXX-XXXXX-XXXXX-XXXXX, ~190-bit entropy,
stored as SHA-256 (API-token pattern — raw key shown once, never retrievable)
- Admin: POST/DELETE /api/admin/cafes/{id}/recovery-key (key returned once);
café list now reports HasRecoveryKey + RecoveryKeyCreatedAt
- Login: POST /api/auth/login-key → exact-hash lookup → resolves café Owner →
issues normal JWT; rate-limited (auth-otp), suspended/no-owner guarded, logged
Admin UI: per-café generate / regenerate / revoke with one-time reveal + copy.
Dashboard login: discreet "ورود با کلید بازیابی" link → key field. fa/en/ar.
86 tests pass; all tsc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The platform no longer sells SMS. Each café saves its OWN Kavenegar API
key + sender line (new Cafes columns + migration) and campaigns are sent
and billed through that account.
Backend:
- GET/PUT /sms/settings (Manager/Owner; key echoed masked, verified
against the provider before saving)
- campaign + balance use the café's credentials; SMS_NOT_CONFIGURED
error when missing; plan-tier SMS gating removed everywhere
(PlanLimitChecker, SmsMarketingService, billing status)
- platform Kavenegar config stays ONLY for login OTPs (env/DB)
- design-time DbContext factory so `dotnet ef migrations add` works
without booting the host
Dashboard:
- SMS screen: provider-settings card, not-configured callout, campaign
form disabled until configured; quota bar removed (usage stays as info)
- subscription screen + plan comparison no longer show SMS limits
Admin panel:
- Kavenegar/SMS section removed from integrations (request field now
optional; stored OTP config untouched)
- SMS limit field removed from the plan editor
- nav label "درگاه و پیامک" → "درگاه پرداخت و AI"
fa/en/ar translations. 86 tests pass; all tsc clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The merchant plan page hard-coded 4 tiers, prices and a feature matrix
that drifted from the admin-editable platform catalog (Starter tier
missing, stale prices/features). PlanComparison and CheckoutScreen now
consume /platform/plans + new /platform/features-catalog:
- columns = active plans by SortOrder (incl. Starter), names from
DisplayNameFa/En, prices from MonthlyPriceToman
- limit rows from PlanLimitsData (int.MaxValue → "نامحدود")
- feature rows from the feature catalog, ticked via FeatureKeys
- checkout validates the ?plan= param against isBillableOnline and
prices from the catalog — no more client-side price constants
fa/en/ar limit-row labels added.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backend:
- POST /orders/{id}/payments/corrections (Manager/Owner): void wrong
payments (marked Refunded, never deleted) and/or record replacements
atomically; mandatory reason; requires an open register shift; full
before/after written to the immutable audit trail.
- GET /orders/closed?date= — closed orders of one Iran-calendar day,
paged, the browsing surface for corrections.
- CalculateExpectedCash now subtracts cash refunds so corrections keep
the drawer expectation honest.
Dashboard (reports screen now has three tabs):
- عملکرد و سود: existing KPIs/charts + new day-by-day breakdown table
(orders, revenue, expenses, net profit per Jalali day).
- اصلاح سند: closed-orders browser with payment chips + correction
dialog (void checkboxes, replacement rows, live balance, reason).
- گزارش عملیات: filterable audit-log viewer (category, Jalali range,
branch) with expandable structured details.
fa/en/ar translations included. 86 backend tests pass; dashboard tsc clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The sidebar had 22 items in 5 accordion groups, all defaulting closed:
first visit showed five vague headers and zero destinations, there was
no Dashboard/Home link at all, and rare pages (taxes, subscription) had
equal weight with POS. Restructured around usage frequency:
- Flat primary (always visible, no header): Dashboard, POS, Tables,
Kitchen, Queue, Reservations, Menu, Reports
- Two collapsible groups: Customers & marketing (crm, coupons, sms,
reviews, discover) and Café management (inventory, expenses, shifts,
taxes, hr, branches)
- Footer utility icons: settings, subscription, support
- Removed "notifications" from the nav (duplicate of the topbar bell)
Other fixes folded in:
- Deleted [locale]/page.tsx which redirected "/" to /pos — it made the
POS exit button a no-op loop and left OverviewScreen unreachable.
"/" now renders the overview home; login still lands on /pos.
- Branch gating moved from group-level to an item whitelist
(BRANCH_ALLOWED_NAV_KEYS) — also closes the hole where branch
accounts could deep-link to /reports etc. past the RouteGuard.
- RouteGuard now checks footer items too (subscription stays gated).
- revalidate=300 on the locale layout: Next emitted s-maxage=31536000
and the WCDN edge kept serving year-old HTML shells after deploys.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Previously the only Employee records were the Owner (created at café signup) and
one Manager per branch — there was no way to add a waiter/cashier/chef. Adds it.
Backend:
- POST /api/cafes/{cafeId}/employees (HrController). Owner/Manager only; creating a
Manager requires Owner; Owner cannot be created here. Validates name/phone/role,
enforces one-employee-per-phone, validates branch belongs to the café, and can
optionally set username/password login in the same step (same hashing + uniqueness
as the credentials endpoint). Returns EmployeeSummaryDto.
Dashboard:
- New "Team" tab on the HR screen (now the default): employee roster (name, role,
phone, base salary) + an "Add employee" button (owner/manager) opening an inline
form — name, phone, role, optional branch, optional base salary, optional login.
- Role labels + all form strings in fa/en/ar.
86 API tests pass; dashboard tsc + build clean.
Builds on the outbox engine to take the whole dashboard offline in one place
instead of wiring 114 mutation sites individually.
Frontend (single chokepoint = the API client):
- offline-write: any write auto-queues to the outbox on offline/network failure
and returns an optimistic value; the online path is unchanged apart from an
Idempotency-Key header (so even online retries de-dup). entityType is derived
from the URL; POSTs get a remappable local id.
- client.doWrite unifies POST/PUT/PATCH/DELETE through this path. WriteOptions
gains `offline: "queue" | "reject" | "manual"`.
- Guardrails: auth / billing / payments / SMS / exports are online-only and throw
OFFLINE_UNAVAILABLE offline rather than queueing (no queued double-charges or
surprise SMS blasts). use-api-error resolves the friendly localized message
(fa/en/ar).
- submit-order opts out ("manual") to keep its richer local-Order mock; shared
helpers de-duplicated into offline-write.
- Request persistent storage on mount so unsynced writes survive eviction.
Backend:
- IdempotencyCleanupJob: daily purge of idempotency records older than 7 days
(the table now gets a row per keyed write). Registered in Hangfire. No migration.
86 API tests pass; dashboard tsc + build clean.
Before, buying a plan immediately switched the tier and stacked the duration.
Now a purchase made while the café still has paid coverage is QUEUED to start
when the current coverage ends, and the owner can cancel a queued one.
Model:
- SubscriptionPayment gains EffectiveFrom/EffectiveTo; status gains Scheduled
(paid, queued) and Cancelled. EF migration AddSubscriptionScheduling (nullable).
BillingService:
- On payment completion, compute coverage end (latest of active expiry + furthest
queued period). If it is in the future → Scheduled (queued, café tier/expiry
untouched); else activate immediately as before. Periods chain correctly.
- GetStatusAsync lazily promotes any due queued period to active, and returns the
queue (QueuedPlans).
- CancelQueuedAsync cancels a Scheduled period (owner-only) and re-packs the queue
so later periods slide earlier. Active prepaid plan is never cut short; no
automatic refund (manual, per product decision).
- Confirmation SMS distinguishes "activated until X" vs "queued, starts X".
API: BillingStatusDto.QueuedPlans + DELETE /api/billing/queued/{paymentId}.
Dashboard:
- Subscription screen shows a "Queued subscriptions" card (tier, window, cancel
with confirm).
- Checkout shows "you already have an active subscription — this will start on
{date}" when the café is still covered.
- i18n fa/en/ar.
81 API tests pass; dashboard typechecks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Delete (every manageable entity that only had "add" now has delete):
- Ingredients (warehouse): new DELETE /inventory/ingredients/{id} (soft-delete via
the global DeletedAt filter — no FK trouble with recipes/movements) + NoOp stub +
trash button in the materials cards.
- Reservations: new DELETE /reservations/{id} (soft-delete) + per-card delete button.
- Coupons & Customers: backend DELETE already existed; wired delete buttons in the UI.
- Shared ConfirmDialog component used by all delete flows (RTL-aware).
- Audit result: tables/branches/taxes/kitchen-stations/expenses/menu/terminals already
had delete; HR has no "add" so no delete needed; shifts intentionally excluded
(financial open/close records, not add-style entities).
Koja visibility:
- New Cafe.ShowOnKoja flag, default TRUE (DB default true so existing cafés stay
listed). Discover query now filters IsVerified && !Deleted && ShowOnKoja.
- public-profile GET/PUT expose showOnKoja; dashboard public-profile panel has an
on-by-default toggle that persists immediately. Platform IsVerified gate unchanged.
- EF migration AddCafeShowOnKoja (defaultValue: true).
Also: added the missing errors.generic i18n key (fa/en/ar) so useApiError's fallback
resolves instead of rendering the literal "errors.generic". 81 API tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add DELETE /api/cafes/{cafeId}/menu/items/{id} (DeleteItemAsync soft-delete,
mirroring the existing category delete) — item delete had no backend route.
- Dashboard menu admin: destructive "delete" action in the item and category
edit modals, behind a shared confirm dialog (AlertDialog). Deleting the
selected category falls back to "all items".
- Fix the availability ToggleSwitch in RTL: force dir="ltr" so the knob's
translate-x stays inside the track instead of escaping on the right
(same fix as the admin-panel toggles).
- i18n: deleteItem/deleteCategory confirm + success strings (fa/en/ar).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lets the POS agent and the QR/app customer attach a free-text note to each
order line (e.g. "no tomato", "extra hot") that reaches the kitchen/bar.
- Backend already supported it (OrderItem.Notes persists; CreateOrderItemRequest
and OrderItemDto carry Notes; LiveOrderDto items include it) — this wires the UI.
- cart.store: add setNotes(menuItemId, notes); notes already travel in
getPendingLines and round-trip via hydrateFromOrder.
- POS pos-screen: a note input under each cart line.
- QR guest menu: a note input under each cart line (QrCartLine.note).
- KDS: render the note prominently under each item so kitchen/bar sees it.
- i18n: pos.itemNotePlaceholder + qrMenu.itemNote (fa/ar/en).
Note: notes are captured on items being added; editing a note on an
already-submitted line is out of scope (no pending delta to re-send).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Error toasts surfaced the raw English backend message. Added an errors namespace (fa/ar/en) keyed by error code + a useApiError() resolver that maps ApiClientError.code to the localized message (fallback to a localized generic). Wired into menu, tables, demo banner, and subscription checkout; hardened getErrorMessage so it never returns the raw backend message.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Previously the subscribe mutation had no onError handler, so any
payment initiation failure (wrong merchant ID, ZarinPal API error,
disabled payment method) would silently re-enable the button with
no user feedback. Now errors are shown below the Pay button.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sidebar:
- All groups start collapsed on first load (v4 storage key resets old state)
- Opening one group closes all others (accordion)
- Navigating to a section opens only that section's group
Koja slug:
- SlugHelper: Persian->Latin transliteration, slug validation
- Registration accepts optional custom slug; auto-derives from cafe name
- Slug can be updated from dashboard Settings -> Profile
- Settings PATCH validates uniqueness (SLUG_TAKEN) and format (INVALID_SLUG)
- koja.meezi.ir/{slug} now redirects to /fa/cafe/{slug} (short URL support)
Bug fix:
- SupportTicketService: cafeId/status filters applied before Select() projection
to fix EF "could not be translated" crash on the support tickets page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap the POS terminal in the sidebar + topbar layout via a nested
fullscreen layout, and make the sidebar collapse to an icon-only rail
with a persisted toggle so operators keep navigation on the POS screen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduce an OTP input box on login/register, surface user roles and a
cafe chooser, add a dashboard switch button in the POS screen, and
register OTP validators explicitly to survive Docker layer caching.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add OrderTypePicker screen: Table / Counter / Takeaway cards shown when no
active session, replacing the old always-visible table board
- Move PosTableBoard into a modal overlay (opens on Table selection or
"Assign Table" for counter orders)
- Add orderType field + setOrderType action to cart store
- Counter and Takeaway orders no longer require a table to submit
- Add "Assign Table →" button in cart for counter orders with active session
- Rewrite category tabs as horizontal scrollable row (no wrapping)
- Larger product cards with 4:3 thumbnail + quantity badge overlay
- Bigger quantity controls (h-8 w-8) and "New order" back button in header
- Add i18n keys for order types in en/fa/ar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>