{{ userDisplayName }}
{{ user?.role }} · v{{ APP_VERSION }}
Drinks have no choices yet (milk, hot or iced, shots). They only appear after you set them up.
{{ selectedCategory === 'favorites' ? 'Favorites' : selectedCategory === 'popular' ? 'Popular' : (selectedCategoryData?.name || '') }}
No products found
Current Order
#{{ currentOrderNumber }}{{ item.name }} ${{ item.price.toFixed(2) }} each
Note: {{ item.note }}
Drinks have no choices yet (milk, hot or iced, shots). They only appear after you set them up.
Search Results
{{ selectedCategoryData.name }}
{{ registerView === 'categories' ? 'Categories' : 'Favorites' }}
{{ selectedCategory === 'favorites' ? 'Favorites' : selectedCategory === 'popular' ? 'Popular' : (selectedCategoryData?.name || '') }}
No products found
Current Order
#{{ currentOrderNumber }}{{ getCustomerDisplayName(selectedCustomer) }}
Resident (10% off)
Cart is empty
{{ item.name }}
Note: {{ item.note }}
Orders
Review and update orders{{ selectedLocation ? ' at ' + selectedLocation.name : '' }}.
{{ openTickets.length }} unpaid {{ openTickets.length === 1 ? 'ticket' : 'tickets' }}, ${{ openTicketsOwed.toFixed(2) }} still to collect. They are pinned to the top.
{{ ordersFilterLabel }}
{{ ordersRangeLabel }}{{ selectedLocation.name }}
#{{ order.order_number }}
{{ order.customer_name }}
{{ getOrderAge(order.created_at) }}
{{ order.order_type?.replace('_', ' ') }}
- {{ l.quantity }} x {{ l.name }} Note: {{ l.note }}
| Order | Customer | Type | Items | Total | Status | Payment | Actions |
|---|---|---|---|---|---|---|---|
|
#{{ order.order_number }} VRS Check items {{ getOrderAge(order.created_at) }} |
{{ order.customer_name }} {{ order.delivery_name }} — | {{ order.order_type?.replace('_', ' ') }} | {{ order.item_count }} items | ${{ parseFloat(order.total).toFixed(2) }} | {{ order.status }} | Unpaid {{ order.payment_status }} |
|
{{ ordersEmptyTitle }}
Try another status or date to find orders.
Showing {{ ordersFilterWord }} orders for {{ ordersRangeLabel }}.
Products
Showing {{ filteredManageProducts.length }} of {{ products.length }} products
| Product | Category | Price | Shows on | Status | Actions | |
|---|---|---|---|---|---|---|
|
{{ product.name }} {{ product.name_es }}
{{ parseFloat(product.inventory_stock || 0).toFixed(1) }} {{ product.inventory_unit }}
|
{{ product.category_name }} | ${{ parseFloat(product.price).toFixed(2) }} | {{ product.is_active ? 'Active' : 'Inactive' }} |
|
Nothing in {{ productManageCategoryData.name }} yet.
No products in {{ productManageCategoryData.name }} match “{{ productManageSearch }}”.
No products found
Categories
Drag to reorder. Order affects display on POS.
{{ category.name }}
{{ category.name_es || '—' }}
Testing
Try the new things and say what happened. A "didn't work" with a note is the most useful thing you can give us.
Nothing to test right now.
{{ cl.name }}
{{ cl.environment }} Signed offby {{ cl.signed_off_by_name || 'someone' }} on {{ formatShoppingDate(cl.signed_off_at) }}
{{ cl.pass }} worked · {{ cl.fail }} broken · {{ cl.untested }} not tried yet
{{ qaOpenChecklist.name }}
{{ qaOpenChecklist.intro }}
{{ qaOpenChecklist.pass }} worked · {{ qaOpenChecklist.fail }} broken · {{ qaOpenChecklist.untested }} not tried yet
{{ section.name }}
{{ item.title }}
{{ item.expectation }}
| Who | Check | Result | When |
|---|---|---|---|
| {{ h.by_name || 'Someone' }} |
{{ h.title }} “{{ h.note }}” |
{{ h.result === 'pass' ? 'Worked' : (h.result === 'fail' ? 'Broke' : 'Skipped') }} | {{ formatQaTime(h.created_at) }} |
Things to Buy
Anyone can add. A manager marks it purchased.
Showing {{ filteredShoppingItems.length }} of {{ shoppingItems.length }}
| Item {{ shoppingSortArrow('name') }} | Where | Asked for {{ shoppingSortArrow('created_at') }} | Cost | Status {{ shoppingSortArrow('status') }} | Actions |
|---|---|---|---|---|---|
|
Urgent
{{ item.name }} {{ item.notes }} |
{{ item.location_name || '—' }} |
{{ item.requested_by_name || 'Someone' }} {{ formatShoppingDate(item.created_at) }} |
${{ parseFloat(item.actual_cost).toFixed(2) }} ~${{ parseFloat(item.estimated_cost).toFixed(2) }} — |
{{ item.status === 'open' ? 'Needed' : (item.status === 'purchased' ? 'Bought' : 'Dropped') }}
{{ item.purchased_by_name }}, {{ formatShoppingDate(item.purchased_at) }} |
Manager only
|
Nothing on the list. When something breaks or runs out, add it here.
Nothing matches those filters.
Kitchen
Prepare batches, manage stock and wrap up the day.
Produce Batch
Make prepared items
Receive Stock
Log deliveries
Record Waste
Log spoiled or damaged items
Closing Count
End-of-day inventory check
Activity Log
Review kitchen activity
Select Recipe
No production recipes configured yet. Tap "Create Recipe" to add one.
{{ kitchenRecipeEditId ? 'Edit Recipe' : 'Create Recipe' }}
A batch increases this product's stock at the location where it's produced.
{{ ing._name }}
{{ ing._unit }}
{{ kitchenRecipeDetail.name }}
Makes {{ kitchenRecipeDetail.output_quantity }} per batch
Ingredients needed at {{ selectedLocation.name }}
Record Waste
Closing Count
{{ item.name }}
Expected: {{ parseFloat(item.expected_quantity).toFixed(1) }} {{ item.unit }}
No prepared items to count.
Receive Stock
Activity Log
{{ entry.item_name }} — {{ parseFloat(entry.quantity_produced).toFixed(1) }} {{ entry.unit }} ({{ entry.recipe_name }}, x{{ entry.batch_multiplier }}) {{ entry.linked_product_name }} — {{ Math.abs(parseFloat(entry.quantity_change)).toFixed(1) }} {{ entry.unit }} ({{ entry.waste_reason }})
By {{ entry.performed_by }}
{{ new Date(entry.entry_date).toLocaleDateString() }}
{{ new Date(entry.entry_date).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) }}
Exp: {{ new Date(entry.expires_at).toLocaleDateString() }}
No activity found{{ kitchenLogFilter !== 'all' ? ' for this filter' : '' }}.
TIME'S UP!
{{ bakingSession.steps[bakingSession.current_step - 1]?.title }}
Baking
{{ recipe.name }}
{{ recipe.output_quantity }} {{ recipe.output_unit }} of {{ recipe.output_item_name }}
No production recipes found. Create recipes in the Kitchen section first.
{{ bakingSelectedRecipe.name }} — Steps
Total time: {{ bakingSelectedRecipe.total_time || 0 }} min · {{ (bakingSelectedRecipe.steps || []).length }} steps
{{ step.title }}
{{ step.description }}
No steps yet. Add steps below.
Add Step
{{ bakingSession.recipe_name }}
Batch: {{ bakingSession.batch_multiplier }}x
{{ bakingSession.steps[bakingSession.current_step - 1].title }}
{{ bakingSession.steps[bakingSession.current_step - 1].description }}
{{ bakingSession.steps[bakingSession.current_step - 1].duration_minutes }} min total
Ingredients ({{ bakingSession.batch_multiplier }}x batch)
{{ (user.role === 'admin' || user.role === 'manager') ? 'My Shift Today' : "Today's Baking" }}
{{ bakingMyShift.status }}No shifts scheduled for this date
{{ shift.user_name }} ({{ shift.user_role }})
| Recipe | User | Started | Status | Multiplier |
|---|---|---|---|---|
| {{ s.recipe_name }} | {{ s.user_name }} | {{ new Date(s.started_at).toLocaleString() }} | {{ s.status }} | {{ s.batch_multiplier }}x |
No baking sessions yet.
{{ bakingSelectedShift.user_name }}'s Shift
No items in this shift.
{{ bakingEditingShift ? 'Edit Shift' : 'Create Shift' }}
Shift Items
No recipes found
No items added yet. Search recipes above to add.
Scheduled time exceeds shift duration
Inventory
Products and ingredients across all locations
| Product {{ sortIndicator('product_name') }} | Category {{ sortIndicator('category_name') }} | {{ loc.name }} {{ sortIndicator('loc_' + loc.id) }} | Total {{ sortIndicator('total') }} | Actions |
|---|---|---|---|---|
|
{{ row.product_name }} inactive ${{ parseFloat(row.price).toFixed(2) }} |
{{ row.category_name || '—' }} | {{ row.quantities[loc.id]?.quantity ?? 0 }} | {{ row.total }} | |
| No products yet. Add some via the Products tab. No products match the current filters. |
| Ingredient {{ sortIndicator('name') }} | Unit {{ sortIndicator('unit') }} | Supplier {{ sortIndicator('supplier') }} | {{ loc.name }} {{ sortIndicator('loc_' + loc.id) }} | Total {{ sortIndicator('total') }} | Actions |
|---|---|---|---|---|---|
|
{{ row.name }} PAR {{ row.par_min }}–{{ row.par_target }} |
{{ row.unit }} | {{ row.supplier || '—' }} | {{ formatQty(row.quantities[loc.id]?.quantity ?? 0, row.unit) }} | {{ formatQty(row.total, row.unit) }} | |
| No ingredients yet. Click "+ Add Ingredient" above. No ingredients match the current filters. |
| When | Product | From | To | Qty | By |
|---|---|---|---|---|---|
| {{ new Date(t.transferred_at).toLocaleString() }} | {{ t.product_name }} | {{ t.from_location_name }} | {{ t.to_location_name }} | {{ t.quantity }} | {{ [t.user_first_name, t.user_last_name].filter(Boolean).join(' ') || '—' }} |
| No transfers logged yet. | |||||
| When | Kind | Item | Location | Action | Before → After | Δ | By | Notes |
|---|---|---|---|---|---|---|---|---|
| {{ new Date(a.created_at).toLocaleString() }} | {{ a.entity_type }} | {{ a.entity_name || ('#' + a.entity_id) }} | {{ a.location_name || '—' }} | {{ a.action_type }} | {{ parseFloat(a.qty_before).toFixed(2) }} → {{ parseFloat(a.qty_after).toFixed(2) }} | {{ parseFloat(a.qty_delta) > 0 ? '+' : '' }}{{ parseFloat(a.qty_delta).toFixed(2) }} | {{ a.user_name || '—' }} | {{ a.notes || '' }} |
| No adjustments logged yet. | ||||||||
{{ parReportSummary.critical || 0 }}
Critical
{{ parReportSummary.low || 0 }}
Low
{{ parReportSummary.ok || 0 }}
OK
| Item | On Hand | PAR Min | Target | Order Qty | Status |
|---|---|---|---|---|---|
|
{{ item.name }} {{ item.category_tag || '' }} {{ item.supplier ? '/ ' + item.supplier : '' }} |
{{ parseFloat(item.quantity_on_hand).toFixed(1) }} {{ item.unit }} | {{ parseFloat(item.par_min).toFixed(1) }} | {{ parseFloat(item.par_target).toFixed(1) }} | {{ item.suggested_order_qty > 0 ? parseFloat(item.suggested_order_qty).toFixed(1) + ' ' + item.unit : '—' }} ({{ item.suggested_purchase_units }} {{ item.purchase_unit }}s) | {{ item.status }} |
{{ transferMode === 'get' ? 'Get From Main' : 'Send Back to Main' }}
{{ transferMode === 'get' ? 'Move stock from Main to ' + (selectedLocation?.name || 'here') + '.' : 'Move stock from ' + (selectedLocation?.name || 'here') + ' back to Main.' }}
{{ row.product_name }}
{{ row.kind }} · Source has {{ row.source_qty }} · already at dest: {{ row.dest_qty }}
Adjust stock
{{ adjustForm.type === 'product' ? 'Product' : 'Ingredient' }}: {{ adjustForm.name }}
Current: {{ formatQty(adjustCurrentQty, adjustForm.unit) }} · After: {{ formatQty(adjustPreview, adjustForm.unit) }}
Pictures (optional, up to 3)
Take a screenshot of the problem first, then tap Add a picture and choose it from your photos.
{{ bugError }}
What we send with it
The screen you are on, the app version, your location and name, the type of device, and the last few errors the app saw. Never a password, a PIN or card numbers.
You have not sent anything yet. When you do, you will see what happened to it here.
-
{{ BUG_STATUS[r.status]?.label || r.status }} New {{ bugAgo(r.created_at) }} · v{{ r.app_version }}
{{ r.message }}
{{ r.file_count }} {{ r.file_count === 1 ? 'picture' : 'pictures' }} sent
From the team
{{ r.reply }}
Live in version {{ r.fixed_in_version }}. You are on {{ APP_VERSION }}.
Restock {{ restockTarget.name }}
On hand: {{ restockTarget.quantity_on_hand }} {{ restockTarget.unit }}
{{ confirmConfig.title }}
{{ confirmConfig.message }}
Add a Time Entry
For someone who never punched in. This is recorded in the audit log under your name.
Leave empty only if they are still on the clock right now.
{{ addTimeEntryError }}
{{ scheduleForm.id ? 'Edit Shift' : 'Add a Shift' }}
Rota weeks run Friday to Thursday, the same as a pay period. No more than {{ SCHEDULE_MAX_CONCURRENT }} people on at the same time.
No staff loaded yet.
{{ scheduleDateIssue }}
This shift is on a week already worked. You can correct it here or delete it — but moving it to another day means picking one inside the open rota{{ scheduleEditableRange.min ? ', ' + scheduleEditableRange.label : '' }}.
New shifts go on the open days: {{ scheduleEditableRange.label }}.
Some days in that stretch are locked by a closed pay period and can't take a shift.
That is a fourth person at {{ formatTime12h(scheduleConflict.at) }}
Already on: {{ scheduleConflict.names.join(', ') }}. Only {{ SCHEDULE_MAX_CONCURRENT }} can be on at once, so saving this will be rejected — move a time or drop someone first.
{{ schedulePersonConflict.name }} is already on at that time
Their existing shift that day runs {{ formatTime12h(schedulePersonConflict.start) }} – {{ formatTime12h(schedulePersonConflict.end) }} at {{ schedulePersonConflict.location }}. Nobody can be booked twice over the same hours, so saving this will be rejected — change the times, or edit that shift instead of adding a second one.
{{ scheduleError }}
{{ ingredientForm.id ? 'Edit' : 'Add' }} Ingredient
Customers
| Customer | Contact | Resident | Orders | Visits | Total Spent | Actions |
|---|---|---|---|---|---|---|
|
{{ getCustomerDisplayName(customer) }} {{ customer.first_name }} {{ customer.last_name }} |
{{ customer.email || '—' }} {{ formatDisplayPhone(customer.phone) || '—' }} |
— | 0 |
{{ customer.loyalty_visits }} visits
{{ customer.loyalty_rewards_lifetime }} earned
Free item earned! {{ customer.loyalty_visits_month || 0 }} this month |
${{ parseFloat(customer.total_spent || 0).toFixed(2) }} |
Dashboard
Today's Revenue
${{ dashboardData?.today?.total_revenue?.toFixed(2) || '0.00' }}
Orders Today
{{ dashboardData?.today?.total_orders || 0 }}
Active Orders
{{ dashboardData?.active_orders || 0 }}
Low Stock Items
{{ dashboardData?.low_stock_count || 0 }}
Top Products Today
{{ product.quantity }} sold
${{ parseFloat(product.revenue).toFixed(2) }}
AI Insights
AI-powered business intelligence
API Key Required
Add your Anthropic API key in Settings to enable AI insights.
Latest Insight - {{ aiLatestInsight.insight_date }}
{{ aiLatestInsight.insight_type }} analysis | Confidence: {{ Math.round((aiLatestInsight.confidence_score || 0) * 100) }}%
{{ aiLatestInsight.full_analysis?.summary || aiLatestInsight.summary }}
Sales Analysis
{{ aiLatestInsight.full_analysis?.sales_analysis?.overview }}
Trends
- • {{ trend }}
Peak Hours
Weather Impact
{{ aiLatestInsight.full_analysis?.weather_correlation?.impact }}
- • {{ p }}
Product Recommendations
Feature These
Consider Promoting
Watch List
Action Items
{{ item.action }}
{{ item.reason }}
No action items
Anomalies Detected
- {{ a }}
Forecast & Preparations
{{ aiLatestInsight.full_analysis.forecasting.outlook }}
- • {{ p }}
No Insights Yet
Generate your first AI insight to get started with business intelligence.
Business Notes
Notes are included as context for AI analysis (events, promotions, weather notes, etc.)
No business notes yet. Add notes to give the AI context about your business.
{{ note.title }}
{{ note.note_type }}{{ note.content }}
{{ note.note_date }} | by {{ note.author_first_name || 'System' }}
Loyalty Program
Manage rewards rules and view reports
Enrolled
{{ loyaltyReport.enrolled_customers }}
Active This Month
{{ loyaltyReport.active_this_month }}
Rewards Issued
{{ loyaltyReport.rewards_issued_this_month }}
Redeemed ($)
${{ (loyaltyReport.redemption_value_this_month || 0).toFixed(2) }}
Top Visitors
| Rule | Type | Reward | Active | Actions |
|---|---|---|---|---|
|
{{ rule.name }} {{ rule.reward_description }} |
Every {{ rule.trigger_count }} visits Surprise ({{ (rule.surprise_probability * 100).toFixed(0) }}%) Category ({{ rule.trigger_count }}) {{ rule.rule_type }} | Free Item ${{ parseFloat(rule.reward_value).toFixed(2) }} off {{ rule.reward_value }}% off | {{ rule.is_active ? 'Yes' : 'No' }} |
No loyalty rules configured.
Settings
Vieques Room Service link
VRS sends its delivery orders here, signed with a shared secret. A secret is set. No secret yet, so VRS orders are refused.
Try the delivery light
Sends a pretend delivery to the location you are working at. Then open the register there: the Delivery light comes on within 15 seconds. Open it and press Start this delivery to turn it off. This only works on beta and local, never on live.
Copy this now. It is shown once and cannot be read again.
{{ vrsSecretShown }}
Then give it to VRS as its TAZA_SECRET without pasting it into chat. Making a new one stops the old one working.
Payroll Deductions
Everything is off until you switch it on, and nothing is withheld from anyone while it is off. These rates are yours to confirm with the accountant — they are starting points, not advice. Employer rows are recorded for cost reporting and never come off anyone's pay.
Per-employee — set each person's rate on their staff record from their Form 499 R-4.1, not here. Switch this on to include the line on stubs.
Changing a rate only affects payroll generated after the change. Stubs already issued keep the rate that was in force when they were run.
Business Information
POS Settings
Sales Tax (State + Municipality)
Each order line is taxed by its category's bracket. State rate varies; municipality rate applies to everything.
Categories auto-suggest a bracket from their name when you create them (e.g., "Beer & Seltzers" → Alcohol). You can override per category.
Order Time Alerts
Order age color: green → yellow (warning) → red (urgent)
Payment Methods
Cash, Card, Venmo and ATH Móvil are always available at the register. Card means a payment you took on your own reader or phone; Taza records it on the order.
AI Insights
Required for AI insights. Get one at console.anthropic.com
Optional. Enables weather correlation analysis.
Locations
{{ locationForm.id ? 'Edit Location' : 'Add Location' }}
/menu?loc=slug
Account Settings
Change Password
Change PIN
Stock at {{ selectedLocation?.name || 'this location' }}
What you have on hand right now.
| Product | On Hand | |
|---|---|---|
|
{{ row.product_name }} {{ row.category_name || '' }} |
{{ row.quantity }} | |
| Loading… No products match "{{ myStockSearch }}" Nothing in stock at this location yet. | ||
My account
Your profile, location and workspace preferences.
{{ userDisplayName }}
{{ user?.email }}
{{ user?.role }}Working location
{{ selectedLocation?.name || 'No location selected' }}
Choose the location you're working at.
Your PIN
{{ myPin.pin }}
{{ myPin.reason }}
Your PIN is used to quickly sign in at the terminal.
Version {{ APP_VERSION }}
Register layout
How the register shows products on this device. Each person keeps their own choice.
Sound effects
Button clicks and the finish chime on this device.
Order screen cues
Make orders readable from across the counter. Kept on this device.
Color the choices
HOT in red with a flame, ICED in blue with a cube, "No tomato" in red with a ban sign.
Item pictures
Icons on a color tint are the default and are quicker to read. Photos show uploaded product pictures instead.
Faint ingredient pictures Experiment
A very light picture behind an order line (lavender, coconut, coffee...). Try it and tell us if it helps.
Workspace theme
Choose a look for your POS.
Dark cards
Dark page titles and section labels with light text.
Applies to your workspace appearance.
Time Clock
Clocked in since {{ new Date(timeClockStatus.entry?.clock_in_at).toLocaleTimeString() }}
Not clocked in
| Date | Clock In | Clock Out | Hours | Status |
|---|---|---|---|---|
| {{ new Date(entry.clock_in_at).toLocaleDateString() }} | {{ new Date(entry.clock_in_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) }} | {{ entry.clock_out_at ? new Date(entry.clock_out_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) : '—' }} | {{ entry.clock_out_at ? (((new Date(entry.clock_out_at) - new Date(entry.clock_in_at)) / 3600000) - (entry.break_minutes || 0) / 60).toFixed(1) + 'h' : '—' }} | {{ entry.status === 'closed' ? 'Ready to Review' : entry.status }} |
| No entries this week | ||||
{{ myPayView === 'mtd' ? 'Month to Date' : 'Year to Date' }}
Upcoming Payroll
| Period | Hours | Rate | Est. Gross | Status |
|---|---|---|---|---|
| {{ item.start_date }} — {{ item.end_date }} | {{ Number(item.hours_worked).toFixed(1) }}h | ${{ Number(item.rate).toFixed(2) }}/hr | ${{ Number(item.gross_amount).toFixed(2) }} | due |
| Since last payroll | {{ Number(myPaySummary.since_last_payroll?.hours || 0).toFixed(1) }}h | ${{ Number(myPaySummary.pay_rate || 0).toFixed(2) }}/hr | ${{ Number(myPaySummary.since_last_payroll?.estimated || 0).toFixed(2) }} | estimate |
| No hours since last payroll | ||||
Payment History
| Period | Amount | Method | Reference | Date |
|---|---|---|---|---|
| {{ row.start_date }} — {{ row.end_date }} | ${{ Number(row.amount).toFixed(2) }} | {{ row.method || '—' }} | {{ row.reference || '—' }} | {{ row.date ? new Date(row.date).toLocaleDateString() : '—' }} |
| No payments recorded yet | ||||
| Staff | Where | Date | In | Out | Hours | Status | Actions |
|---|---|---|---|---|---|---|---|
| {{ entry.nickname || entry.first_name }} | {{ entry.location_name || '—' }} | {{ new Date(entry.clock_in_at).toLocaleDateString() }} | {{ new Date(entry.clock_in_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) }} | {{ entry.clock_out_at ? new Date(entry.clock_out_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) : '—' }} | {{ entry.clock_out_at ? (((new Date(entry.clock_out_at) - new Date(entry.clock_in_at)) / 3600000) - (entry.break_minutes || 0) / 60).toFixed(1) + 'h' : '—' }} | {{ timeEntryStatusLabel(entry) }} |
|
| No entries to review | |||||||
Edit Time Entry
Payroll
| Period | Employees | Total Gross | Total Paid | Status | Actions |
|---|---|---|---|---|---|
| {{ period.start_date }} — {{ period.end_date }} | {{ period.employee_count || 0 }} | ${{ Number(period.total_gross || 0).toFixed(2) }} | ${{ Number(period.total_paid || 0).toFixed(2) }} | {{ period.status }} | |
| No pay periods yet | |||||
{{ payrollSelectedPeriod.start_date }} — {{ payrollSelectedPeriod.end_date }}
Created by {{ payrollSelectedPeriod.created_by_name }}
{{ payrollWarnings.length }} thing{{ payrollWarnings.length === 1 ? '' : 's' }} to look at
Nothing below was added or deducted automatically. Decide each one, adjust the hours if needed, then re-generate.
- {{ w.employee }} — {{ w.date }} — {{ w.message }}
| Employee | Hours | Rate | Gross | Deductions | Net | Notes | Status | Actions |
|---|---|---|---|---|---|---|---|---|
| {{ item.nickname || item.first_name }} {{ item.last_name || '' }} |
{{ Number(item.hours_worked).toFixed(1) }}h
{{ Number(item.regular_hours).toFixed(1) }} reg · {{ Number(item.overtime_hours).toFixed(1) }} OT ×{{ Number(item.ot_multiplier).toFixed(1) }} · {{ Number(item.seventh_day_hours).toFixed(1) }} 7th day
|
${{ Number(item.rate).toFixed(2) }}/hr | ${{ Number(item.gross_amount).toFixed(2) }} |
−${{ Number(item.deductions_total).toFixed(2) }}
—
{{ di ? ' · ' : '' }}{{ d.label }}
|
${{ Number(item.net_amount || item.gross_amount).toFixed(2) }}
Net is higher than gross. Edit and save this item again, or Start Over.
|
{{ item.notes || '' }} | {{ item.status }} | |
| No items. Click Generate to create payroll. | ||||||||
Summary
{{ payrollSelectedPeriod.status === 'processing' ? 'Employee Signature' : 'Manager Signature' }}
Checking this period… Pre-flight checks unavailable {{ payrollPreflightBlocking.length }} thing{{ payrollPreflightBlocking.length === 1 ? '' : 's' }} to fix before you can generate Ready to generate — {{ payrollPreflightWarnings.length }} thing{{ payrollPreflightWarnings.length === 1 ? '' : 's' }} worth a look Everything checks out — ready to generate
The readiness check did not answer. Nothing is blocked — but look over the hours yourself before generating.
These hours cannot be priced correctly yet. Fix each one, then Re-check.
- {{ c.user_name }} — {{ preflightReason(c.kind) }} — {{ c.detail }}
Not blocking. Nothing below was changed automatically.
- {{ c.user_name }} — {{ preflightReason(c.kind) }} — {{ c.detail }}
Manager: {{ payrollSelectedPeriod.verified_by_name }}
Finalized: {{ new Date(payrollSelectedPeriod.finalized_at).toLocaleString() }}
Activity Log
New Pay Period
Creates period for the current week (Monday - Sunday).
Record Payment
{{ payrollPayingItem.nickname || payrollPayingItem.first_name }} — net ${{ Number(payrollPayingItem.net_amount || payrollPayingItem.gross_amount).toFixed(2) }}
Schedule
This week has already been worked
Shifts on days payroll has not closed can still be corrected or removed here. New shifts go on the open rota — {{ scheduleEditableRange.label }}, never on a week already behind us.
Payroll has closed every day of this week
Nothing can be added, changed or removed here while it is closed. Reopen the pay period if this week still needs work on it.
{{ scheduleWeekLock.allLocked ? 'This whole week is locked' : 'Part of this week is locked' }}
Payroll has been closed for {{ scheduleWeekLock.label }}, so {{ scheduleWeekLock.allLocked ? 'every day of this week is read-only' : 'those days are read-only — the rest of the week can still be changed' }}. Reopen that pay period in Payroll if a locked day has to change.
{{ scheduleWeekLabel(scheduleActiveWeek) }}
Showing {{ scheduleVisibleCount }} of {{ scheduleWeekCount }} shift{{ scheduleWeekCount === 1 ? '' : 's' }} · {{ scheduleEmptyDayCount }} day{{ scheduleEmptyDayCount === 1 ? '' : 's' }} with nobody on
{{ day.weekday }} {{ day.pretty }} Today Locked
{{ scheduleStaffFilter ? 'Not scheduled' : 'Nobody was on this day' }}
Nobody on this day
Not scheduled
{{ day.shifts.length }} shift{{ day.shifts.length === 1 ? '' : 's' }} · peak {{ day.peak.count }} of {{ SCHEDULE_MAX_CONCURRENT }} people at the same time
Full from {{ formatTime12h(day.peak.at) }} — {{ day.peak.names.join(', ') }} are all on. A fourth person will be rejected.
{{ s.user_name }}
{{ formatTime12h(s.start_time) }} – {{ formatTime12h(s.end_time) }} · {{ s.location_name }}
{{ s.note }}
Expenses
Total
${{ Number(expenseReport.totals?.total || 0).toFixed(2) }}
Count
{{ expenseReport.totals?.count || 0 }}
{{ cat.category }}
${{ Number(cat.total).toFixed(2) }}
| Date | Vendor | Category | Amount | Method | Actions |
|---|---|---|---|---|---|
| {{ exp.expense_date }} | {{ exp.vendor || '—' }} | {{ exp.category }} | ${{ Number(exp.amount).toFixed(2) }} | {{ exp.payment_method }} |
|
| No expenses found | |||||
{{ editingExpense ? 'Edit' : 'Add' }} Expense
Till Count
{{ tillSelectedSession.session_date }} — {{ tillSelectedSession.shift }}
{{ tillSelectedSession.status }}Starting Cash
${{ Number(tillSelectedSession.starting_cash || 0).toFixed(2) }}
Denomination Count
Paid Outs
Receipt Paid Outs
Notes
{{ tillSelectedSession.notes || 'No notes' }}
Summary
{{ tillSelectedSession.status === 'open' ? 'Employee Signature' : 'Manager Signature' }}
Verified by {{ tillSelectedSession.verified_by_name || 'Manager' }}
{{ tillSelectedSession.finalized_at ? new Date(tillSelectedSession.finalized_at).toLocaleString() : (tillSelectedSession.locked_at ? new Date(tillSelectedSession.locked_at).toLocaleString() : '') }}
Employee
Manager
Awaiting Manager Verification
Submitted {{ tillSelectedSession.submitted_at ? new Date(tillSelectedSession.submitted_at).toLocaleString() : '' }}
Employee Signature
No active till session for today
| Date | Shift | Opened By | Over/Short | Status | Actions |
|---|---|---|---|---|---|
| {{ session.session_date }} | {{ session.shift?.replace('_', ' ') }} | {{ session.nickname || session.first_name || '—' }} | ${{ Number(session.over_short_amount || 0).toFixed(2) }} | {{ session.status }} | |
| No till sessions found | |||||
New Till Session
Console · v{{ APP_VERSION }}
{{ user?.nickname || user?.first_name }} — {{ user?.role }}
Your workspace
{{ selectedLocation ? selectedLocation.name : 'No location selected' }}{{ user?.role }}
Pick a location to see what you can do here.
No apps are enabled for your account at this location.
{{ group.title }}
Stock items
Everything you count: ingredients, supplies, prepared goods and goods you resell. Ingredients stay in the kitchen.
Showing {{ siRows.length }} of {{ si.items.length }}
Ingredients are kept in the kitchen only, so this location holds none.
| Item | Kind | On hand | Par | Status | Actions |
|---|---|---|---|---|---|
| {{ i.name }}inactive | {{ i.kind }} | {{ siOnHand(i) }} {{ i.unit }} | {{ Number(i.par_min) || '' }} | Low |
|
| {{ si.loading ? 'Loading...' : 'Nothing matches.' }} | |||||
{{ tourCurrent.step.title }}
{{ tourCurrent.step.body }}
{{ tourRoundNote }}
Keyboard: right arrow for next, left arrow for back, Escape to stop.
New to this screen?
Take a short tour of {{ (TOUR_TOPICS.find(t => t.id === tour.offer) || {}).title }}. It shows what each part does, one step at a time. You can stop whenever you like.
Learn Taza
Pick a screen to be walked through it, or go around everything. These are the tours for what a {{ user?.role }} can do. You can stop at any point and come back here later.
The loop moves on by itself and starts again when it reaches the end, telling you which time around it is. Pause or stop it any time.
{{ g.label }}
There are no tours for the screens you can open yet.
Move products here
Some things in the register are not for sale: supplies like cups, lids and bar supplies. Moving one makes it a stock item with its count, and archives the product so it leaves the register. Nothing is deleted, and you can undo it.
Showing {{ cvRows.length }} of {{ cv.items.length }}
| Product | Category | Counted | Sold | Action |
|---|---|---|---|---|
| {{ r.name }} Looks like a supplyMoved | {{ r.category }} | {{ r.on_hand }} | {{ r.sold }} | |
| {{ cv.loading ? 'Loading...' : 'No products here.' }} | ||||
Move {{ cv.pick.name }} to stock?
It becomes a stock item with the count it has now ({{ cv.pick.on_hand }}), and the product is archived so it leaves the register. Past sales and reports keep it.
{{ siFormTitle() }}
{{ si.form.error }}
Change stock
{{ si.act.item.name }}
What happened?
{{ SI_VERBS[si.act.how].hint }}.
Where?
Ingredients are kept in the kitchen only, so only kitchens are listed.
in {{ si.act.item.unit }}
{{ si.act.error }}
History: {{ si.hist.item.name }}
| When | Location | What | Change | Who | Note |
|---|---|---|---|---|---|
| {{ siWhen(m.created_at) }} | {{ m.location_name }} | {{ m.reason.replace(/_/g, ' ') }} | {{ siDelta(m.delta) }} | {{ m.user_name || '' }} | {{ m.note || '' }} |
| {{ si.hist.loading ? 'Loading...' : 'No movements yet.' }} | |||||
Stock model (preview)
Phase 1 of the inventory redesign. This copies the old inventory tables into the new model. Staff do not see or use it yet.
Sales and the new stock
{{ ss.status.enabled ? 'Sales take from the new stock' : 'Sales take from the old counts' }}A paid order takes stock from the new stock using each product's list (What one sale uses). It never stops a sale, never goes below zero (it takes what is there and notes the shortfall), and takes stock once per order. A void does not put stock back, same as before.
- Items in the new stock: {{ ss.status.stock_items }} · the stock model has not been applied yet (use Check, then Apply, below)
- Counted products with no list of what one sale uses: {{ ss.status.tracked_without_list }} (the new stock would stop counting them: {{ ss.status.tracked_without_list_names.slice(0, 6).join(', ') }}, ...)
- Orders handled by the new stock so far: {{ ss.status.orders_applied }}, of which came up short: {{ ss.status.orders_short }}
If the last {{ ss.preview.orders_checked }} paid orders were rung up against today's counts: {{ ss.preview.orders_that_would_come_up_short }} would come up short, and {{ ss.preview.orders_with_nothing_to_take }} have nothing to take (no list on their products). Nothing was changed.
| Item | Used by these sales | On hand now | Result |
|---|---|---|---|
| {{ i.name }} ({{ i.kind }}) | {{ i.used }} {{ i.unit }} | {{ i.held_now }} | Not held hereWould run outEnough |
| None of the recent orders used anything on a product list yet. | |||
A check does all the work and then undoes it. Apply is only offered after a check that reconciles.
In the new model now: {{ stockModel.status.stock_items }} items, {{ stockModel.status.stock_levels }} counts, {{ stockModel.status.stock_movements }} ledger rows, {{ stockModel.status.product_components }} product links. Last applied {{ stockModel.status.last_run.ran_at }}. Never applied.
Report
{{ stockModel.report.reconciles ? 'Reconciles' : 'Does not reconcile' }} {{ stockModel.report.mode }}Would create: {{ stockModel.report.created.items }} items, {{ stockModel.report.created.levels }} counts, {{ stockModel.report.created.components }} product links, {{ stockModel.report.created.movements }} ledger rows. Re-synced: {{ stockModel.report.refreshed_levels }}.
- {{ c.check }} ({{ c.detail }})
Problems
- {{ p }}
For a person to look at
{{ key.replace(/_/g, ' ') }} {{ Array.isArray(list) ? list.length : list }}
- {{ typeof line === 'string' ? line : JSON.stringify(line) }}
Nothing to review.
Showing {{ stockModelItems.length }} of {{ stockModel.items.length }}
| Item | Kind | Unit | Total | Counts by location | Used by |
|---|---|---|---|---|---|
| {{ i.name }}inactive | {{ i.kind }} | {{ i.unit }} | {{ i.total }} | {{ stockLevelsText(i) }} | {{ i.used_by_products }} |
| Nothing yet. Run a check, then apply. | |||||
Options
The choices a drink or sandwich offers. Build a group once, then say which categories or products use it.
Showing {{ ogShown.length }} of {{ optionGroups.length }}
| Group | Picks | Options | Used by | Actions |
|---|---|---|---|---|
|
{{ g.name }} Hidden from the register |
{{ ogPicksText(g) }} | {{ o.name }} ({{ o.price_delta > 0 ? '+' : '-' }}${{ Math.abs(o.price_delta).toFixed(2) }}), and {{ g.options.length - 6 }} more None yet | {{ ogUsedByText(g) }} |
|
|
Loading option groups. No option groups yet. Start with Milk or Temperature: press New group. No groups match that search. |
||||
{{ ogEdit.id ? 'Edit group' : 'New group' }}
Options in this group
Save the group first, then add its options.
| Option | Price change | Actions |
|---|---|---|
| {{ o.name }} Default Quick add | {{ o.price_delta ? (o.price_delta > 0 ? '+' : '-') + '$' + Math.abs(o.price_delta).toFixed(2) : 'No change' }} |
|
| No options yet. | ||
Where this group applies
Pick whole categories first. Add single products only for the exceptions.
{{ ogError }}
{{ ogOpt.id ? 'Edit option' : 'Add option' }}
{{ ogError }}
Problem reports
What staff reported from the app, newest and unhandled first.
Leave blank to keep them in this inbox only.
When someone sends a red box that says "Reference: ERR_...", type it here to see what actually went wrong.
{{ errLook.error }}
- What went wrong
- {{ errLook.result.what }}
- When
- {{ errLook.result.when }}
- Request
- {{ errLook.result.request }}
- In the code
- {{ errLook.result.where }}
{{ errLook.result.why }}
Showing {{ bugShown.length }} of {{ bugReports.length }}
| When | Who | Where | What happened | Status | Actions |
|---|---|---|---|---|---|
| {{ bugWhen(r) }} | {{ r.user_name || 'Unknown' }}{{ r.user_role }} | {{ r.location_name || 'No location' }}{{ r.screen }} · v{{ r.app_version }} · {{ r.environment }} | {{ r.message }} | {{ BUG_STATUS[r.status]?.label || r.status }} {{ r.files.length }} | |
| {{ bugReportsLoading ? 'Loading...' : 'Nothing here. No problems reported.' }} | |||||
Report from {{ bugOpen.user_name || 'Unknown' }}
{{ bugWhen(bugOpen) }} · {{ bugOpen.location_name || 'No location' }} · {{ bugOpen.screen }} · v{{ bugOpen.app_version }} · {{ bugOpen.environment }}
{{ bugOpen.message }}
What the app saw
Screen size: {{ bugOpen.context.viewport?.w }} x {{ bugOpen.context.viewport?.h }} · Online: {{ bugOpen.context.online ? 'yes' : 'no' }} · Offline orders waiting: {{ bugOpen.context.offline_queue }}
Device: {{ bugOpen.context.ua }}
Recent errors
- {{ e.at }} · {{ e.kind }} · {{ e.where }} · {{ e.status }} · {{ e.message }}
No errors were recorded just before this report.
No extra details were sent.
Answer {{ bugOpen.user_name || 'them' }}
They see this and the status under Report a problem, My reports.
Staff Management
| Name | Role | PIN | Status | Last Login | Actions |
|---|---|---|---|---|---|
|
{{ (member.nickname || member.first_name || 'U').charAt(0).toUpperCase() }}
{{ member.nickname || (member.first_name + ' ' + member.last_name) }} {{ member.email }} |
{{ member.role }} |
{{ revealedPins[member.id].pin }}
Not viewable
••••
|
Active Inactive | {{ timeSince(member.last_login) }} |
| When | Who | Action | IP |
|---|---|---|---|
| {{ formatFullTimestamp(ev.created_at) }} | {{ ev.user_name || (ev.user_id ? ('User #' + ev.user_id) : '—') }} | {{ loginActionLabel(ev.action) }} {{ activityTarget(ev) }} | {{ ev.ip_address || '—' }} |
Invite New Staff
Generate an invite code that new staff can use to create their account.
Share this code with the new staff member:
{{ generatedInviteCode }}
Valid for 7 days. They can use this on the login page.
Suggestion Box
{{ s.message }}
{{ s.message }}
{{ s.admin_reply }}