01 / PROJECT

ECOBANK026 / WASTE BANK

A waste-bank ledger with guarded Excel imports and protected stock.

SYSTEMFull Stack Engineer2026
ECOBANK026 / WASTE BANK - cover visual
PROJECT ARTIFACTPRIMARY VISUAL

A Laravel operations system for a community waste bank that unites deposit, withdrawal, and collector-sale records under one ledger of record. It handles dual pricing, bulk Excel import, cumulative stock control, and a multi-role audit trail.

02 / THE CHALLENGE

Orders were simple on paper.
Fulfillment was fragmented.

Waste-bank operations ran on dozens of date-separated Excel spreadsheets, so every deposit had to be reconciled by hand and was prone to duplication or mistyping. Field material labels were inconsistent: Paper, Used Paper, and Crumpled Paper referred to the same item, which risked duplicate categories and inconsistent reports. Customer savings balances and the waste bank's cash were tracked separately from the transaction data, leaving no single source of truth when figures diverged. Aggregate sales to collectors could also exceed the stock that actually existed, particularly when entries were backdated. Without a reliable audit trail, investigating a discrepancy or a suspected misappropriation was extremely difficult.

KEY FAILURE MODES ADDRESSED
01

Deposit records were scattered across many date-based Excel sheets, and material labels were inconsistent (Paper, Used Paper, and Crumpled Paper all meant the same item), inviting duplicate categories and reports that contradicted each other.

02

Customer savings balances and the waste bank's cash were calculated by hand outside the transaction data, so there was no single source of truth when the two disagreed.

03

Aggregate sales to collectors could exceed the stock that actually existed, especially when entries were backdated, and there was no audit trail to trace the amounts or the people behind them.

03 / SYSTEM FLOW & DECISIONS

One order. Multiple fulfillment paths.

Explore how the system splits each line item into independent operational branches while enforcing strict transactional convergence before dispatch.

Direct Processing: Daily Deposits

The primary path that runs every day when an operator records a waste deposit directly at the waste bank.

Operational Transaction
STEP 01Waste Bank Admin

Select Customer & Material

The deposit form loads active customers and waste categories; the customer buy price (member_price) is filled automatically and can be overridden manually.

STEP 02BankSampahService

Validate & Save Atomically

DB::transaction writes the Deposit and each DepositDetail with its subtotal, rejecting any row without a waste_category_id or material_label.

STEP 03System (Ledger)

Record Credit Ledger

Writes a savings_ledgers entry of type credit with reference_type/reference_id to the Deposit as the source of truth for the customer balance.

STEP 04ActivityLogService

Audit Trail After Commit

A deposit.create activity log is written outside the transaction, carrying customer_name and total_amount so it is still recorded even if an exception occurs after commit.

KEY ARCHITECTURAL DECISIONS & TRADE-OFFS
01

LEDGER AS SINGLE SOURCE OF TRUTH

Do not store a directly mutable balance column; customer savings and cash are derived from the append-only savings_ledgers and waste_bank_cash_ledgers.

RATIONALE

A mutable balance column opens a path to desynchronization when transactions are edited or deleted, and it makes reconciliation hard. With a ledger, every change is explained as a new entry and the balance can be recomputed at any time. The trade-off is that every write must consistently create both the primary transaction and the ledger entry within one DB::transaction.

02

DUAL PRICING WITH HISTORICAL SNAPSHOTS

Store member_price and margin per sale_details row, separate from the WastePrice master, so the master price is never reused to reconstruct past data.

RATIONALE

Storing only a reference to the master price lets a retroactive price change rewrite historical financial reports. A snapshot keeps history stable, while WastePriceHistory tracks price changes. The trade-off is duplicated values on detail rows and the need to keep them consistent during explicit overrides.

03

IDEMPOTENT EXCEL IMPORT WITH CROSS-VALIDATION

Bulk import never inserts directly: rows are parsed, normalized, mapped, recomputed, cross-checked through a recap, and only then committed atomically with an anti-reimport fingerprint.

RATIONALE

Raw spreadsheets are full of inconsistent labels, empty prices, and duplicate sheets. Without a validation stage, a single import could permanently pollute the master data and the financial history. The staged approach allows human review of ambiguous rows, at the cost of a longer, stateful import flow.

04

ATOMIC TRANSACTIONS WITH A FULL-TIMELINE STOCK GUARD

A sale locks the stock row with lockForUpdate, aggregates weight per category, and validates remaining stock across the entire timeline through assertCumulativeStockNotNegative before writing.

RATIONALE

Two concurrent sales, or a single backdated sale, can push cumulative stock negative without being detected. Scanning the timeline ensures no date point drops below zero, not just the ending balance. The cost is a heavier query and a longer lock on the sold category.

04 / ARCHITECTURE & STACK

Engineered for relational integrity.

At the data layer, deposit, deposit_details, savings_ledgers, and waste_bank_cash_ledgers record every value movement explicitly; all writes are wrapped in DB::transaction, and balances are restored through deterministic recalculation. The business layer enforces validation boundaries: a withdrawal is allowed only after two deposits and never exceeds the balance, while a sale may not exceed cumulative stock up to the transaction date. Concurrency is protected with lockForUpdate on the stock row, while assertCumulativeStockNotNegative scans the whole timeline to prevent negative stock on backdated transactions. The activity log is append-only and immutable, bound with a correlation_id per operation sequence. A Blade UI with Alpine.js and Tailwind presents the result without leaking transactional complexity to operators.

ARCHITECTURE TIERS
01|

CLIENT LAYER

  • Blade Templates (server-rendered)
  • Alpine.js for interactive state
  • Tailwind CSS + custom UI components
  • ApexCharts for dashboard and report visualization
02|

BUSINESS & DOMAIN

  • Service layer: BankSampahService, DepositImportService, MaterialMappingService
  • State transitions and validation: dual pricing, margin, balance, and cumulative stock
  • Authorization: Spatie Laravel Permission + admin device binding
  • Logging: append-only ActivityLogService with correlation_id
03|

DATA & STORAGE

  • MySQL as the production database
  • Eloquent ORM with ledger and transaction-detail relations
  • Local storage for expense proofs (proof_path)
  • DB::transaction + lockForUpdate + balance recalculation
CORE PRODUCTION STACK
Laravel 13PHP 8.3MySQLBlade + Tailwind CSS + Alpine.jsSpatie Laravel PermissionMaatwebsite Excel / PhpSpreadsheet
OPERATIONAL OUTCOME & RESULT

The system turns manual spreadsheet reconciliation into a deterministic operation: every rupiah that enters or leaves can be traced from the transaction to the ledger entry. Layered validation on balances, cumulative stock, and recap consistency closes the gaps that allowed over-selling and hard-to-detect data discrepancies. Historical price snapshots keep old reports stable even when master prices change. With an append-only audit trail and strict role separation, the system can carry a growing volume of waste-bank transactions without losing accountability.

✓A full-timeline stock guard (assertCumulativeStockNotNegative) rejects any sale that would make stock negative on any date, including backdated transactions.
✓Atomic writes with DB::transaction and lockForUpdate prevent race conditions and double-selling between concurrent sales.
✓Multi-role authorization (Spatie) is reinforced with admin device binding using revocable httpOnly tokens, so operational traces stay tied to a device.
✓An immutable, append-only audit trail with correlation_id and sanitized payloads; balances can be reconciled automatically through an anomaly-based health score.
NEXT WORK

More systems worth exploring.

Explore another project where software, business processes, and practical engineering meet.

BACK TO WORKS