Joe&dave / Overview
API STATUS · CHECKING
Business command centre

Run the business.
See what needs action.

A standalone business operations engine. Manage customers, issue invoices, record receipts and import inventory using your own records—not invented figures.

Quick access

Workspace tools

Upload your logo, set business details and choose invoice/receipt designs.

Create invoices, then export a selected invoice or receipt to PDF or JPG.

Review 12-month invoicing, collections, expenses, margins and customer trends.

Business health

Sign in to load real data
Invoices—
Invoiced—
Collected—
Low stock items—
◌Your real business workspaceCreate an account or sign in to load actual records. No fabricated financial figures are shown.

System status

Honest status
Web previewAvailable
APIChecking
Private databaseChecking
SessionSigned out
₦

Finance control

Customer invoices, supplier obligations, receipts, payments and aging analysis from submitted records.

▤

Stock intelligence

Import inventory, reconcile physical counts and flag stock items that need replenishment.

✳

Action management

Turn overdue balances and operational exceptions into assigned follow-up work.

Built for independent operation

Independent architecture
Own applicationJoe&dave-managed
Own databaseCloudflare D1
CloudflarePages + Workers
Finance desk

Know what is owed.
Know what needs follow-up.

Live invoices and collections from your Joe&dave workspace. Amounts are stored as integer minor currency units and derived from your actual records.

Receivables

Sign in to calculate customer balances from saved invoices and receipts.

Payables

Saved supplier bills
Sign in to calculate supplier balances from saved bills and payments.

Recent transaction activity

Live business records
Sign in to load actual invoices, receipts, supplier bills and purchase orders.
Inventory tools

Validate your inventory file.

Validate CSV locally, then import inventory into your authenticated Joe&dave workspace. Imports are validated again by the server before any database writes.

CSV structure validator

Local processing only
⇧
Select an inventory CSV

Required columns: item_code, warehouse, qty. Optional: valuation_rate.

No file selected. Nothing has been uploaded.

Import rules

Item codeRequired
WarehouseRequired
QuantityFinite and non-negative
Duplicate pairsRejected
Opening stockNeeds valuation rate

The server revalidates each row. Inventory quantities are saved to your private workspace.

Action centre

Turn exceptions into work.

Joe&dave is designed to identify issues from actual company records and help authorized staff follow them through to resolution.

◷

Overdue invoices

Calculated from saved invoice due dates and outstanding balances.

Sign in to check overdue balances.
⚑

Reorder alerts

Calculated from saved stock quantities and reorder levels.

Sign in to check stock levels.

Action data

No actions are fabricated. Alerts appear only when saved business records meet the stated conditions.

Customer records

One source of truth.

Customer records are saved to your private workspace and isolated by authenticated account.

Add customer

Customers

Sign in to view your customers
Invoice desk

Create an invoice.

Create a real invoice with one line item, then record receipts against its outstanding balance. Amounts are entered in your selected currency and stored as minor units.

New invoice

Recent invoices

Sign in to view invoices

Record a receipt

Professional document export

Your brand · Your data

Choose an invoice or receipt and export a branded PDF (via print dialog) or high-resolution JPG.

Brand identity

Make every document yours.

Upload your business logo, business contact details and select the invoice and receipt designs. These preferences are saved to your workspace.

Business identity

Invoice and receipt design

Choose a template for each document. A sample preview below is illustrative layout only; it contains no financial values.

Your BusinessINVOICE

Customer details · line items · subtotal · tax · total · payment status

Professional. Clear. Ready to send.
Decision support

Read the business.

12-month analysis of saved invoices, receipts, recorded expenses and customer activity. Empty periods remain zero; no sample data is added.

Invoiced (12 months)

—

Receipts collected (12 months)

—

Recorded expenses (12 months)

—

Monthly cash and sales trend

Actual records

All chart values are calculated from your own saved records.

Recorded-cost gross margin

—

Calculated only from posted invoices and recorded item costs. Service costs not entered are not inferred.

Top customers by invoiced value

What the data says

Supplier directory

Manage your suppliers.

Keep vendor contacts in the same workspace as purchasing and expenses.

Add supplier

Supplier records

Sign in to load suppliers.
Expense control

Know where money goes.

Record actual business expenses. Posted expenses are journalled for accounting reports.

Record expense

Expense history

Sign in to load expenses.
Management accounts

Every posted figure must balance.

Trial balance and profit-and-loss figures are derived from posted journal entries, not sample data.

Trial balance

Not loaded
Sign in to calculate the trial balance.

Profit & loss

Actual journal entries
Select a period and refresh reports.

Posted journal

Read-only ledger
Sign in to view journal entries.

Inventory valuation

Current cost basis
Sign in to calculate stock valuation.
Purchasing & receiving

Control what comes into stock.

Create purchase orders against your supplier directory and receive stock into the inventory movement ledger.

New purchase order

Purchase orders

Sign in to load purchase orders.

Receiving a stock-linked line updates on-hand quantity through the stock movement ledger. Supplier bills and payable settlement are not yet included in this purchasing workflow.

Supplier bills & liabilities

Keep payables under control.

Record vendor bills, track outstanding amounts and post payments against real balances.

Enter supplier bill

Supplier bills

Sign in to load supplier bills.

Pay a bill

Cash & bank control

Reconcile your bank activity.

Add a bank account, import statement lines and match them to posted cash/bank journal entries.

Bank account

Add statement line

Statement lines

Sign in to load statement lines.

Reconcile selected line

Period close

Protect closed accounting periods.

Closing a period requires a balanced trial balance. New journal postings dated in a closed period are rejected.

Create accounting period

Accounting periods

Sign in to load periods.
Connected business operations

One workspace.
Seven operating modules.

Module foundations for inventory, selling, procurement, people, manufacturing, projects and customer support. These screens describe the workflows planned for the platform; live write operations must be enabled and verified module by module before production use.

▦

Inventory & Stock

Serial and batch/lot tracking, multiple warehouses, transfers, stock counts and reconciliation, FIFO or moving-average valuation, expiry awareness and reorder suggestions.

Core recordsItems · Warehouses · Lots · Movements · Counts
Control ruleAdjustments require a reason and audit event
Schema foundation
♙

Selling & CRM

Lead capture, pipeline stages, next follow-ups, quotations, sales orders and customer relationship history.

Core recordsLeads · Quotes · Quote lines · Sales orders
Control ruleQuote acceptance and order confirmation are explicit
Schema foundation
▣

Buying & Procurement

Material requests, supplier records, purchase orders, goods receipts, supplier invoices and three-way matching of PO, receipt and invoice.

Core recordsRequests · Suppliers · POs · Receipts · Bills
Control ruleInvoice mismatches go to exception review
Schema foundation
♧

Human Resources & Payroll

Employee records, attendance, leave, expense claims, controlled payroll runs and payslip records.

Core recordsEmployees · Attendance · Leave · Claims · Payroll
Control rulePayroll approval is separate from payroll preparation
Schema foundation · calculations require validation
⚙

Manufacturing (MRP)

Versioned bills of materials, nested components, work orders, job cards, production scheduling and work-centre capacity planning.

Core recordsBOMs · Components · Work orders · Job cards
Control ruleMaterial issue and finished-goods receipt must reconcile
Schema foundation · planning engine pending
◷

Projects & Tasks

Projects, task ownership, dependency links, timesheets, approved hours, project cost and time-based billing.

Core recordsProjects · Tasks · Dependencies · Timesheets
Control ruleOnly approved billable time can be invoiced
Schema foundation
✉

Support & Helpdesk

Ticket lifecycle, assignment, priority, response/resolution targets, SLA due dates, internal notes and a publishable knowledge base.

Core recordsTickets · Messages · SLA fields · Knowledge articles
Control ruleInternal notes stay separate from customer-visible replies
Schema foundation

Implementation status

Transparent readiness

Database structures are being added first. This does not yet mean each module has complete forms, authenticated API endpoints, approval workflows, reporting, or end-to-end tests. Existing finance, invoices and customer workflows remain separate and are not replaced by this extension.

SaaS workspace

Team, workspace & plan.

Manage access to this business, switch between workspaces you belong to, and review current plan status. Billing is not enabled, so no subscription charges are being taken.

Plan and usage

Plan—
Status—
Team members—
Billing—
Customers—
Invoices—
Inventory items—
Period startsNot configured
Period endsNot configured

Subscription dates are administrative records only. Billing and automatic renewals remain disabled.

Switch workspace

Only businesses that have granted your account access will appear here.

Add a team member

Add a person who already has a Joe&dave account. Email invitations are not yet enabled, so this does not send an email.

Workspace access

Sign in to load workspace members.
Joe&dave Operations Intelligence · Independent product · Independent custom application · Cloudflare Workers + D1 · No standalone application architecture.
A · EVIDENCE, NOT GUESSWORK

Operations adviser.

Review current workspace records, surface operational risks and keep lightweight notes when connectivity drops. Recommendations are rule-based and trace back to available records; no invented business figures.

Live findings

Awaiting review

Sign in, then run a review to analyse records available to your account.

Offline workspace notes

This device only

Notes are saved locally on this device and are not synced to the server. Do not put passwords, payroll details or confidential customer information here. Financial transactions are never queued for automatic replay.

Connectivity & safe sync

Checking

The app checks connectivity continuously. Offline notes stay on this device; live records and transactions require a server connection.

Never silently replay financial writes

Invoices, receipts, supplier bills and journals require a live response to avoid duplicate postings.

Read-only advice

The adviser does not post, approve, pay or alter records.

Human approval

Recommendations are prompts for review, not accounting, tax, HR or legal decisions.

Operating specification

A · Guardrails
Metrics

Receivables = invoice balances still unpaid. Payables = supplier bills still outstanding. Stock alerts use recorded reorder levels. Profitability uses posted revenue and recorded costs only. Missing data is shown as unavailable, never zero by assumption.

Permissions

The API is the authority for workspace access. The interface must not treat hidden buttons as security. Sensitive actions must be checked server-side; advice is read-only.

Alert rules

Prioritise overdue balances, low-stock exceptions, unreconciled bank lines and accounting-period risks. Each finding should point back to the source record and state the evidence used.

Auditability

Posted financial changes should retain actor, time, source record and status. Never fabricate audit entries or replace the accounting ledger with dashboard totals.

Operations · 01

Inventory & Stock

Manage items, warehouses, serial/batch lots, stock movements and stock counts. Stock issues are blocked if they would create negative stock.

Live API
Operations · 02

Selling & CRM

Track leads through a pipeline, quotations and sales orders against real workspace records.

Live API
Operations · 03

Buying & Procurement

Manage suppliers, material requests, purchase orders, receipts and supplier invoice matching.

Live API
Operations · 04

HR & Payroll

Manage employee records, attendance, leave, expense claims and payroll runs. Payroll totals currently use base salary only; statutory deductions must be validated before live payroll.

Restricted access
Operations · 05

Manufacturing / MRP

Create bills of materials, work orders and job cards to plan and track production.

Live API
Operations · 06

Projects & Tasks

Track projects, task status, dependencies and logged time. Completion can be blocked until prerequisites are done.

Live API
Operations · 07

Support / Helpdesk

Track customer issues, ticket status, SLA deadlines and knowledge-base articles.

Live API
');win.document.close();return;} const b=doc.business;const lines=kind==='receipt'?['Payment received for '+doc.invoice.invoice_number,'Amount received: '+money(doc.receipt.amount_minor,b.currency_code),'Method: '+doc.receipt.payment_method,'Date: '+String(doc.receipt.received_at||'').slice(0,10)]:[...(doc.lines||[]).map(l=>l.description+' | '+Number(l.quantity)+' x '+money(l.unit_price_minor,b.currency_code)+' = '+money(l.line_subtotal_minor,b.currency_code)),'Subtotal: '+money(doc.invoice.subtotal_minor,b.currency_code),'Tax: '+money(doc.invoice.tax_minor,b.currency_code),'Total: '+money(doc.invoice.total_minor,b.currency_code),'Paid: '+money(doc.invoice.paid_minor,b.currency_code),'Balance due: '+money(Math.max(0,doc.invoice.total_minor-doc.invoice.paid_minor),b.currency_code)]; const escXml=xmlEsc,accent=(kind==='receipt'?b.receipt_template:b.invoice_template)==='minimal'?'#202820':(b.brand_color||'#245b3a');let y=300,svg=''; if(b.logo_data_url)svg+=''; svg+=''+escXml(b.display_name||b.legal_name)+''+escXml(b.business_address||'')+''+escXml([b.business_phone,b.business_email,b.tax_id].filter(Boolean).join(' · '))+''+(kind==='receipt'?'RECEIPT':'INVOICE')+''+escXml(doc.number)+'CUSTOMER'+escXml(doc.invoice.customer_name||'Walk-in customer')+''+escXml(doc.invoice.customer_address||'')+'Date: '+escXml(kind==='receipt'?String(doc.receipt.received_at||'').slice(0,10):doc.invoice.issue_date)+'DESCRIPTIONAMOUNT'; if(kind==='receipt'){svg+=''+escXml('Payment for '+doc.invoice.invoice_number)+''+escXml(money(doc.receipt.amount_minor,b.currency_code))+'';y=720;} else{for(const l of doc.lines||[]){if(y>1100)break;svg+=''+escXml(l.description.slice(0,70))+''+escXml(money(l.line_subtotal_minor,b.currency_code))+'';y+=54;}} const totals=kind==='receipt'?[['Amount received',money(doc.receipt.amount_minor,b.currency_code)],['Payment method',doc.receipt.payment_method]]:[['Subtotal',money(doc.invoice.subtotal_minor,b.currency_code)],['Tax',money(doc.invoice.tax_minor,b.currency_code)],['Total',money(doc.invoice.total_minor,b.currency_code)],['Paid',money(doc.invoice.paid_minor,b.currency_code)],['Balance due',money(Math.max(0,doc.invoice.total_minor-doc.invoice.paid_minor),b.currency_code)]]; y=Math.max(y+50,kind==='receipt'?760:700);for(const [label,val] of totals){svg+=''+escXml(label)+''+escXml(val)+'';y+=48;} svg+='Thank you for your business. Keep this document for your records.'; const blob=new Blob([svg],{type:'image/svg+xml;charset=utf-8'}),url=URL.createObjectURL(blob),img=new Image();img.onload=()=>{try{const canvas=document.createElement('canvas');canvas.width=1200;canvas.height=1600;const ctx=canvas.getContext('2d');ctx.fillStyle='#fff';ctx.fillRect(0,0,canvas.width,canvas.height);ctx.drawImage(img,0,0);canvas.toBlob(file=>{if(!file){result('document-msg','JPG rendering failed. Try PDF / Print.',false);return;}const a=document.createElement('a');a.href=URL.createObjectURL(file);a.download=(kind+'-'+doc.number+'.jpg').replace(/[^a-zA-Z0-9._-]/g,'_');document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),1500);result('document-msg','JPG downloaded.');},'image/jpeg',0.94);}catch(err){result('document-msg','JPG rendering failed: '+err.message,false);}finally{URL.revokeObjectURL(url);}};img.onerror=()=>{URL.revokeObjectURL(url);result('document-msg','Could not render the JPG. Try PDF / Print.',false);};img.src=url; }catch(err){if(win)win.close();result('document-msg',err.message,false);} } $('invoice-pdf').addEventListener('click',()=>exportDocument('invoice','pdf'));$('invoice-jpg').addEventListener('click',()=>exportDocument('invoice','jpg'));$('receipt-pdf').addEventListener('click',()=>exportDocument('receipt','pdf'));$('receipt-jpg').addEventListener('click',()=>exportDocument('receipt','jpg')); async function loadAnalytics(){ const d=await api('/analytics'),cur=business.currency_code,inv=d.monthly_invoices||[],rec=d.monthly_receipts||[],exp=d.monthly_expenses||[],sum=a=>a.reduce((n,x)=>n+Number(x.invoiced_minor||x.collected_minor||x.expenses_minor||0),0); const totalInv=inv.reduce((n,x)=>n+Number(x.invoiced_minor||0),0),totalRec=rec.reduce((n,x)=>n+Number(x.collected_minor||0),0),totalExp=exp.reduce((n,x)=>n+Number(x.expenses_minor||0),0); $('analytics-invoiced').textContent=money(totalInv,cur);$('analytics-collected').textContent=money(totalRec,cur);$('analytics-expenses').textContent=money(totalExp,cur);$('analytics-profit').textContent=money(d.gross_profit_minor||0,cur); const months=[];const now=new Date();for(let n=11;n>=0;n--){const dt=new Date(now.getFullYear(),now.getMonth()-n,1);months.push(dt.getFullYear()+'-'+String(dt.getMonth()+1).padStart(2,'0'));} const im=Object.fromEntries(inv.map(x=>[x.month,Number(x.invoiced_minor||0)])),rm=Object.fromEntries(rec.map(x=>[x.month,Number(x.collected_minor||0)])),em=Object.fromEntries(exp.map(x=>[x.month,Number(x.expenses_minor||0)])),max=Math.max(1,...months.flatMap(m=>[im[m]||0,rm[m]||0,em[m]||0])); $('analytics-chart').innerHTML=months.map(m=>'
'+m.slice(5)+'
').join('');$('analytics-monthly-table').innerHTML=months.slice().reverse().map(m=>'
'+m+'Invoiced '+money(im[m]||0,cur)+' · Collected '+money(rm[m]||0,cur)+' · Expenses '+money(em[m]||0,cur)+'
').join(''); $('analytics-categories').innerHTML=(d.expense_categories||[]).length?d.expense_categories.map(x=>'
'+esc(x.category)+' ('+x.count+')'+money(x.amount_minor,cur)+'
').join(''):'
No recorded expenses in this period.
'; $('analytics-customers').innerHTML=(d.top_customers||[]).length?d.top_customers.map(x=>'
'+esc(x.customer_name)+''+x.invoice_count+' invoices · collected '+money(x.collected_minor,cur)+''+money(x.invoiced_minor,cur)+'
').join(''):'
No invoices recorded yet.
'; const insights=[];if(totalInv===0&&totalRec===0&&totalExp===0)insights.push('There is not enough recorded activity to identify a trend yet. Create real invoices and record receipts or expenses.');else{insights.push('Collection ratio: '+(totalInv?Math.round(totalRec/totalInv*100):0)+'% of invoiced value was collected in the period.');const last=months[11],prev=months[10];if((im[prev]||0)>0)insights.push('Latest-month invoicing '+((im[last]||0)>=(im[prev]||0)?'increased':'decreased')+' by '+money(Math.abs((im[last]||0)-(im[prev]||0)),cur)+' compared with the prior month.');if(d.expense_categories?.length)insights.push('Largest recorded expense category: '+d.expense_categories[0].category+' ('+money(d.expense_categories[0].amount_minor,cur)+').');if(d.top_customers?.length)insights.push('Highest invoiced customer: '+d.top_customers[0].customer_name+'.');} $('analytics-insights').innerHTML=insights.map(x=>'
'+esc(x)+'
').join(''); } $('analytics-refresh').addEventListener('click',()=>loadAnalytics().catch(e=>result('analytics-msg',e.message,false))); $('saas-period-form').addEventListener('submit',async e=>{e.preventDefault();const f=new FormData(e.target),start=String(f.get('current_period_start')),end=String(f.get('current_period_end'));if(start>end){result('saas-msg','Period end cannot be before period start.',false);return;}try{await api('/saas/period',{method:'POST',body:JSON.stringify({current_period_start:start,current_period_end:end})});result('saas-msg','Plan period dates saved. Billing remains disabled.');await loadSaaS();}catch(err){result('saas-msg',err.message,false);}}); $('saas-refresh').addEventListener('click',()=>loadSaaS().then(()=>result('saas-msg','Workspace status refreshed.')).catch(e=>result('saas-msg',e.message,false))); $('team-refresh').addEventListener('click',()=>loadSaaS().catch(e=>result('team-member-msg',e.message,false))); $('workspace-switch-form').addEventListener('submit',async e=>{e.preventDefault();try{const d=await api('/workspace/switch',{method:'POST',body:JSON.stringify({business_id:$('workspace-switch-select').value})});business=await api('/business');$('workspace-label').textContent=business.display_name;result('workspace-switch-msg','Switched to '+d.business.display_name+'.');await loadWorkspace();showPage('team');}catch(err){result('workspace-switch-msg',err.message,false);}}); $('team-member-form').addEventListener('submit',async e=>{e.preventDefault();try{const email=$('team-member-email').value,role=$('team-member-role').value;await api('/workspace/members',{method:'POST',body:JSON.stringify({email,role})});$('team-member-form').reset();result('team-member-msg','Team member added with '+role+' access.');await loadSaaS();}catch(err){result('team-member-msg',err.message,false);}}); $('team-members-list').addEventListener('click',async e=>{const b=e.target.closest('[data-remove-member]');if(!b)return;if(!confirm('Remove this person from the current workspace?'))return;try{await api('/workspace/members/'+encodeURIComponent(b.dataset.removeMember),{method:'DELETE'});await loadSaaS();result('team-member-msg','Team member removed.');}catch(err){result('team-member-msg',err.message,false);}}); health();if(token){loadWorkspace().catch(()=>setSignedOut());}else setSignedOut(); })(); const jdOfflineKey='jd_ops_notes_v1'; function jdNotes(){try{return JSON.parse(localStorage.getItem(jdOfflineKey)||'[]').filter(x=>x&&typeof x.text==='string')}catch{return[]}} function jdRenderNotes(){const box=document.getElementById('offline-notes');if(!box)return;const notes=jdNotes();box.innerHTML=notes.length?notes.map((n,i)=>'
'+esc(n.text)+''+esc(n.created||'Saved locally')+'
').join(''):'

No local notes saved.

';box.querySelectorAll('[data-note-delete]').forEach(b=>b.addEventListener('click',()=>{const a=jdNotes();a.splice(Number(b.dataset.noteDelete),1);localStorage.setItem(jdOfflineKey,JSON.stringify(a));jdRenderNotes()}))} function jdConnectivity(){const online=navigator.onLine;const tag=document.getElementById('connectivity-tag');const copy=document.getElementById('connectivity-copy');if(tag){tag.textContent=online?'Online':'Offline';tag.className='tag '+(online?'good':'warn')}if(copy)copy.textContent=online?'Live records require sign-in and server confirmation. Local notes remain on this device until you export or clear them.':'Offline: you can continue editing local notes. Live records and financial transactions are unavailable; nothing will be silently synced.';const pill=document.getElementById('api-status');if(pill&&!online){pill.innerHTML=' OFFLINE · LOCAL NOTES ONLY'}} window.addEventListener('online',jdConnectivity);window.addEventListener('offline',jdConnectivity); document.getElementById('offline-save')?.addEventListener('click',()=>{const input=document.getElementById('offline-note'),msg=document.getElementById('offline-msg'),v=input.value.trim();msg.hidden=false;if(!v){msg.textContent='Enter a note first.';return}const notes=jdNotes();notes.unshift({text:v,created:new Date().toLocaleString()});localStorage.setItem(jdOfflineKey,JSON.stringify(notes.slice(0,100)));input.value='';msg.textContent='Saved on this device only. It has not been sent to the server.';jdRenderNotes()}); document.getElementById('offline-clear')?.addEventListener('click',()=>{if(!confirm('Clear all local notes from this device?'))return;localStorage.removeItem(jdOfflineKey);jdRenderNotes();const m=document.getElementById('offline-msg');m.hidden=false;m.textContent='Local notes cleared.'}); document.getElementById('offline-export')?.addEventListener('click',()=>{const notes=jdNotes();if(!notes.length){const m=document.getElementById('offline-msg');m.hidden=false;m.textContent='There are no notes to export.';return}const blob=new Blob([JSON.stringify({exported_at:new Date().toISOString(),scope:'local-only notes; not server-synced',notes},null,2)],{type:'application/json'});const url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download='joe-dave-local-notes.json';a.click();URL.revokeObjectURL(url)}); document.getElementById('adviser-run')?.addEventListener('click',async()=>{const btn=document.getElementById('adviser-run'),state=document.getElementById('adviser-state'),summary=document.getElementById('adviser-summary'),box=document.getElementById('adviser-findings'),msg=document.getElementById('adviser-msg');btn.disabled=true;state.textContent='Reviewing live records…';box.innerHTML='';msg.hidden=true;try{if(!token)throw new Error('Sign in before running a workspace review.');const results=await Promise.allSettled([api('/dashboard'),api('/invoices'),api('/expenses'),api('/inventory'),api('/supplier-bills'),api('/bank-transactions')]);const labels=['dashboard','invoices','expenses','inventory','supplier bills','bank transactions'];const data={};results.forEach((r,i)=>{if(r.status==='fulfilled')data[labels[i]]=r.value});if(!Object.keys(data).length)throw new Error('No live data could be loaded. Check your connection and sign-in.');const findings=[];const invs=data.invoices?.invoices||data.invoices?.results||[];const bills=data['supplier bills']?.bills||data['supplier bills']?.supplier_bills||[];const stock=data.inventory?.items||data.inventory?.inventory||[];const today=new Date().toISOString().slice(0,10);const outstanding=x=>Math.max(0,Number(x.total_minor||0)-Number(x.paid_minor||0));const overdue=invs.filter(x=>x.due_date&&x.due_date0&&x.status!=='void');if(overdue.length){const amount=overdue.reduce((n,x)=>n+outstanding(x),0);findings.push({level:'high',title:overdue.length+' overdue customer invoice(s)',detail:'Recorded outstanding balance: '+money(amount,business?.currency_code||'NGN')+'. Review the source invoices and follow up with customers.',action:'Open invoices',page:'invoices'})}else if(data.invoices)findings.push({level:'ok',title:'No overdue invoices identified in the records loaded',detail:'This is based only on invoices returned to your account; verify date and payment completeness.',page:'invoices'});const low=stock.filter(x=>x.reorder_level!=null&&Number(x.quantity_on_hand??x.quantity??0)<=Number(x.reorder_level));if(low.length)findings.push({level:'high',title:low.length+' stock item(s) at or below reorder level',detail:'Check availability and approved purchase orders before reordering.',action:'Review inventory',page:'inventory'});else if(data.inventory)findings.push({level:'ok',title:'No reorder-level exception identified',detail:'Only items with recorded quantity and reorder level can be assessed.',page:'inventory'});const openBills=bills.filter(x=>outstanding(x)>0&&x.status!=='void');if(openBills.length)findings.push({level:'medium',title:openBills.length+' supplier bill(s) remain outstanding',detail:'Confirm due dates, supplier statements and approvals before payment.',action:'Review payables',page:'payables'});const tx=data['bank transactions']?.transactions||data['bank transactions']?.bank_transactions||[];const unreconciled=tx.filter(x=>!x.reconciled_at&&x.status!=='reconciled');if(unreconciled.length)findings.push({level:'medium',title:unreconciled.length+' bank transaction(s) may need reconciliation',detail:'Match statement lines against recorded receipts and payments before closing the period.',action:'Open banking',page:'banking'});const sum=data.dashboard?.summary||{};if(data.dashboard){const e=Number(sum.month_expenses_minor||0),c=Number(sum.month_collected_minor||sum.collected_minor||0);if(e>0&&c===0)findings.push({level:'medium',title:'Expenses are recorded but collections are zero for this summary period',detail:'Check the selected period and receipt postings; this is a review prompt, not proof of a cash-flow problem.'})}if(!findings.length)findings.push({level:'info',title:'No rule-based exception found in the data returned',detail:'This is not an audit opinion. Review source records, access controls and reconciliations before relying on the figures.'});box.innerHTML=findings.map(f=>'
'+esc(f.level.toUpperCase())+''+esc(f.title)+''+esc(f.detail)+'
'+(f.page?'':'')+'
').join('');box.querySelectorAll('[data-adviser-page]').forEach(b=>b.addEventListener('click',()=>showPage(b.dataset.adviserPage)));state.textContent='Review complete · '+findings.length+' finding(s)';summary.textContent='Reviewed the live records your account could access. '+results.filter(r=>r.status==='fulfilled').length+' of '+results.length+' data sources responded. Missing sources are not treated as zero.'}catch(e){msg.hidden=false;msg.textContent=e.message||'The live review could not complete.';state.textContent='Review unavailable'}finally{btn.disabled=false}}); jdRenderNotes();jdConnectivity(); if('serviceWorker' in navigator){window.addEventListener('load',()=>navigator.serviceWorker.register('/service-worker.js').catch(()=>{}));}