{{ 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
{{ 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 }}
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 }}.
{{ 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 }}
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.
| Item | On Hand | |
|---|---|---|
|
{{ row.name }} {{ row.kind }} |
{{ row.quantity }} {{ row.unit }} {{ siPackText(row, row.quantity) }} | |
| Loading… No items 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 }}
{{ SI_KINDS[si.kind].label }}: {{ SI_KINDS[si.kind].long }} Four kinds of things are counted here. Pick one above to see what it means, or press What is what? for the full picture, including how products fit in.
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 }} {{ siPackText(i, siOnHand(i)) }} | {{ Number(i.par_min) || '' }} | Low |
|
| {{ si.loading ? 'Loading...' : 'Nothing matches.' }} | |||||
Showing {{ siMoveRows.length }} of the latest {{ si.moves.length }}
| When | Item | Location | Change | What happened | By | Note |
|---|---|---|---|---|---|---|
| {{ siWhen(m.created_at) }} | {{ m.item_name }} | {{ m.location_name }} | {{ siDelta(m.delta) }} | {{ SI_REASONS[m.reason] || m.reason }} | {{ m.user_name || '' }} | {{ m.note || '' }} |
| {{ si.movesLoading ? 'Loading...' : 'No changes match.' }} | ||||||
{{ 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 }}
{{ cv.bulkResult.moved }} of {{ cv.bulkResult.total }} products in {{ cv.bulkResult.cat }} are now stock items.
They are archived, so they are off the register and can be put back with the undo arrow. To remove them for good, open Products, tick Show archived, tick them and press Delete; any that were ever sold ({{ cv.bulkResult.sold }} here) cannot be deleted, because that would erase sales history, so leave those archived.
- {{ f }}
| 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 all {{ cv.bulk.rows.length }} products in {{ cv.bulk.cat }} to stock?
Each one becomes a stock item with the count it has now, and the product is archived so it leaves the register. Past sales and reports keep them. Nothing is deleted, and each can be put back with its undo arrow.
{{ cv.bulk.rows.filter(r => Number(r.sold) > 0).length }} of them have been sold before.
You can change the kind of any one later from its Edit button.
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_KINDS[si.form.kind].label }}: {{ SI_KINDS[si.form.kind].long }}
Bought in a bigger pack? (optional)
One {{ si.form.purchase_unit }} is {{ si.form.purchase_unit_qty }} {{ siWord(si.form.unit || 'each', 2) || 'of them' }}. You will be able to receive or count in {{ siWord(si.form.purchase_unit, 2) }} or in {{ siWord(si.form.unit || 'each', 2) || 'single ones' }}, and the screen shows both, like "2 {{ siWord(si.form.purchase_unit, 2) }} + 8". At ${{ si.form.cost_per_unit }} each, one {{ si.form.purchase_unit }} costs ${{ (Number(si.form.cost_per_unit) * Number(si.form.purchase_unit_qty)).toFixed(2) }}.
Examples: a bottle of liquor counted in shots (counted in shot, pack bottle, one holds 17), or Thai tea powder counted in servings (counted in serving, pack bag, one holds 35). Then you can see how many are left.
{{ 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.
Enter it in
in {{ si.act.entry === 'pack' && siPack(si.act.item) ? siWord(siPack(si.act.item).name, 2) : si.act.item.unit }} ({{ siPack(si.act.item).n }} {{ siWord(si.act.item.unit || 'each', 2) || 'each' }} in one)
{{ si.act.error }}
Add this receipt to stock
{{ rc.expense.vendor || 'Receipt' }}, {{ rc.expense.expense_date }}. For each line pick the stock item it is, check how many came in, and press Add to stock. Nothing changes until you press it.
Reading the receipt lines...
| On the receipt | Is really | How many came in | Remember | |||
|---|---|---|---|---|---|---|
| {{ l.raw_text }} {{ l.quantity }} x ${{ Number(l.unit_price).toFixed(2) }} = ${{ Number(l.total).toFixed(2) }} | {{ l.suggestion.source === 'remembered' ? 'Remembered from last time' : 'Matched by name, please check' }} |
{{ rcPackOf(l.pick) ? rcPackOf(l.pick).unit : '' }}
that is {{ siPackText(rcPackOf(l.pick), l.qty) }}
Not counted in stock
|
{{ l.status === 'received' ? 'Added to stock: ' + (rc.items.find(x => x.id === l.item_id)?.name || 'item') + ', ' + l.received_qty : 'Marked not stock' }} | |||
| No lines were read from this receipt. | ||||||
New stock item
{{ SI_KINDS[rc.creating.kind].short }}
{{ rc.creating.error }}
{{ rc.error }}
{{ rcToAdd.length }} to add to stock, {{ rcToSkip.length }} not stock, {{ rcPending.length - rcToAdd.length - rcToSkip.length }} left for later
What is what
Taza keeps two separate lists. Products are what you sell. Stock items are what you count. A product never holds stock itself; it only lists which stock items go when one is sold.
| Kind | What it is | Examples | Where it is kept | Sold at the register? |
|---|---|---|---|---|
| {{ SI_KINDS[k].label }}stock item | {{ SI_KINDS[k].long }} | {{ SI_KINDS[k].examples }} | {{ SI_KINDS[k].held }} | {{ SI_KINDS[k].sold }} |
| Productnot stock | What the register sells: a name, a price, a picture, a place in a category. It has no count of its own. | Latte, tequila shot, banana bread slice, bottled water | Nowhere. It is a menu line. | Yes. This is the only thing that is. |
The same idea with things you know
- A coffee cup is a supply. Every hot drink uses one, nobody eats it, and a stand needs cups as much as Main does.
- Honey and a tomato are ingredients. They go into things the kitchen makes (or a drink), they are counted where the kitchen is, and nobody buys them by themselves.
- Banana bread is prepared stock (the kitchen made it from bananas, flour and so on) and also a product (the slice you sell).
- A bottle of water is resale stock and also a product: bought and sold unchanged.
- A latte is only a product. On its "What one sale uses" tab it lists 1 cup, 1 lid, 18 g of beans and 200 ml of milk. Each sale takes those out of the count where it was sold.
- A shot of tequila is a product that uses 1 shot of the tequila ingredient. Count the tequila in shots, buy it in bottles of 17, and the screen shows "2 bottles + 8 shots".
Which kind do I pick?
- Do you sell it exactly as you bought it? It is resale.
- Does the kitchen make it, then you sell it? What comes out is prepared; what goes in is an ingredient.
- Is it used up with a sale but not eaten (cup, lid, napkin)? It is a supply.
- Is it only ever used to make something else (food or a drink)? It is an ingredient. If a stand has to pour it too, file it as a supply, because ingredients stay at Main.
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)
How sales are using the new stock, and the one-time move from the old inventory tables (already done on live). Admin only.
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 }}