← Heerax OS case study

Heerax OS / interactive product lab / synthetic data

Make the day
add up.

A matching drawer is not the whole truth. Experience the close. Inspect the system that decides when it is safe to confirm.

01 / Editable scenario02 / Tested rules03 / Inspectable evidence

Product lab / two ways in

Try the workflow. Inspect the reasoning. Your edits stay with you.

A short scenario.

  1. Start with the unresolved handover. The initial drawer count is ₹10,800. Matching it does not resolve Rahul’s cash.
  2. Confirm the handover and recount. Mark the ₹2,100 as returned. Try a wrong count, then enter ₹12,900.
  3. Confirm, then challenge the result. Close the day. Change an expense afterwards and see why it reopens.

This demonstrates software rules, not customer validation. Nothing is sent or saved. Reset restores the example.

Why does the product work this way?

Credit sales are not cash in the drawer. Staff-held collections are counted once, after handover. An unexplained difference blocks the close, and changing a record invalidates a previous confirmation.

Read the alternatives and trade-off ↗
Static starting state: Daily Close is blocked by the unresolved staff handover

Load the existing interactive prototype when you’re ready.

The static example is available above.

Heerax OS / implementation lens

Trust the rule.
Inspect the boundary.

A browser prototype of one workflow. The architecture below describes this repository, not the production beta.

Your current run / in-memory state

Not loaded

Load Daily Close in Experience to inspect your own inputs and result. No run is implied by this diagram.

No calculation yet.

From entry to confirmation.

  1. 01 / INPUTEditable rupee amounts

    Native number fields: required, non-negative, two decimal places, maximum ₹99,999,999.

  2. 02 / BOUNDARYValidate → integer paise

    The existing form checks browser validity, then converts to paise. The engine independently rejects negative, unsafe or non-integer inputs.

  3. 03 / RULEcalculateClose(values)

    A pure function returns expected cash, difference, pending handover and eligibility. Credit sales stay outside the drawer calculation.

  4. 04 / STATEReview → confirm → reopen

    Eligibility is not confirmation. The visitor confirms explicitly. An edited input clears that confirmation. Reloading discards everything.

Product decision / Inferred

Should Daily Close become the primary workflow?

Daily-close-first, with owner confirmation and visible missing information.

Alternatives: Dashboard-first · Ledger-first · Daily-close-first.

Trade-off: Less breadth in the opening interface; an additional confirmation step.

Open the decision record ↗

Implemented ≠ proven

A deliberate boundary.

  • Implemented: local validation, reconciliation, handover gate, explicit confirmation, reopening and reset.
  • Synthetic: all scenarios, transaction values and the named staff handover.
  • Not implemented here: database, authentication, bank feeds, multi-user sync, audit storage or a real cash verification service.
  • Not proven: customer comprehension, adoption, retention or financial accuracy of supplied records.

The engine accepts a negative expected balance if expenses exceed inflows. There is no policy for that case beyond mismatch/confirmation rules. Its numeric guard checks individual amounts, not aggregate overflow; the UI's amount cap keeps this demonstration bounded. It is not a production accounting service.

Repository artifacts / included at build time

Read the mechanism.

The source and test names below come directly from repository files. These are declared automated checks, not a claim that tests execute in your browser.

Core rule · lib/daily-truth.js
// All amounts are integer paise. Collections are prior-credit repayments,
// separate from cash sales; staff receipts enter the drawer only on handover.
export function calculateClose({ opening, cashSales, creditSales, collections, expenses, staffCash, staffReturned, counted }) {
  const values = [opening, cashSales, creditSales, collections, expenses, staffCash, counted];
  if (values.some(value => !Number.isSafeInteger(value) || value < 0)) throw new RangeError('Enter non-negative amounts with at most two decimal places.');
  const expected = opening + cashSales + collections - expenses + (staffReturned ? staffCash : 0);
  return { sales: cashSales + creditSales, expected, difference: counted - expected, pendingStaff: staffReturned ? 0 : staffCash, canClose: staffReturned && counted === expected };
}
Four domain checks · lib/daily-truth.test.js
  • an exact drawer count is insufficient when staff cash is missing
  • handed-over cash is counted once; credit sales never enter the drawer
  • a changed expense exposes the discrepancy
  • invalid amounts are rejected
import test from 'node:test';
import assert from 'node:assert/strict';
import { calculateClose } from './daily-truth.js';
const day = { opening:200000, cashSales:850000, creditSales:430000, collections:120000, expenses:90000, staffCash:210000, staffReturned:false, counted:1080000 };
test('an exact drawer count is insufficient when staff cash is missing',()=>{ const result=calculateClose(day); assert.equal(result.difference,0); assert.equal(result.pendingStaff,210000); assert.equal(result.canClose,false); });
test('handed-over cash is counted once; credit sales never enter the drawer',()=>{ const result=calculateClose({...day,staffReturned:true,counted:1290000}); assert.equal(result.expected,1290000); assert.equal(result.sales,1280000); assert.equal(result.canClose,true); assert.equal(calculateClose({...day,creditSales:99999999}).expected,1080000); });
test('a changed expense exposes the discrepancy',()=>{ const result=calculateClose({...day,staffReturned:true,counted:1290000,expenses:100000}); assert.equal(result.difference,10000); assert.equal(result.canClose,false); });
test('invalid amounts are rejected',()=>{ for(const value of [-1,NaN,Infinity,1.5]) assert.throws(()=>calculateClose({...day,expenses:value}),RangeError); });

UI and confirmation state: decision-mind.js → bindPrototype. Loading and teardown: proof-demo.js. Scenario adapter: app/close-lab-adapter.js. No parallel reconciliation engine.