Akshar KaPatel logo
ERP & Logistics2024

Frosty — Cold Storage ERP

A specialized enterprise resource planning system built for cold storage operations, supporting allocation, billing, stock tracking, and controls.

Technologies Stack:
LaravelPHPMySQLJavaScriptTailwind CSSReports API
Architect role

Lead System Developer

Deployment Location

Anand, Gujarat, India

Timeframe

2024 Release

Frosty — Cold Storage ERP detailed dashboard showcase

90%

Manual billing overhead cut

0%

Allocation discrepancy rate

Daily

Rent generation automation

Executive Summary

Perishable logistics operations represent a high-stakes segment of supply chain management. In major agricultural and exporting hubs across Gujarat, warehouses require precise space mapping, temperature audits, and variable billing rules to handle bulk goods. When managing tons of agricultural yields, simple inventory spreadsheets fail to coordinate spatial availability, leading to rotting stock or wasted space.

Akshar KaPatel designed the Frosty Cold Storage ERP to replace paper registers with a dynamic digital grid. The system maps warehouse capacity across chambers, rows, and bays in real time, automatically compiles daily storage-rent accruals, and coordinates batch allocations. Deployed in cold storage environments, it reduced manual billing compile times by 90% and reduced inventory allocation errors to zero.

The Challenge

Cold storage warehousing carries specific physical constraints:

  • Spatial Dynamics: Crops are deposited, merged, split, and released in small batches. Tracking the specific physical location (Chamber to Row to Bay) of each client lot requires a hierarchical data structure that calculates capacity constraints in real time.
  • Temperature Compliance: Different items (e.g. potatoes, onions, spices, pulp) require different temperature zones. Storing a lot in a room with incompatible temperatures will ruin the stock, causing huge business losses.
  • High-Density Billing Calculations: Client billing is dynamic: clients accrue rent daily based on weight, bag count, or pallet volume. Generating monthly billing ledgers across millions of historic transactions often chokes memory limits and blocks web servers.

Architectural Solutions & Code Mappings

1. Grid Mapping Database Schema

To map a warehouse into a queryable structure, we configured relational coordinates mapping chambers down to individual storage bays:

CREATE TABLE chambers (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL, -- e.g., 'Chamber A'
    temperature_min DECIMAL(4,2) NOT NULL,
    temperature_max DECIMAL(4,2) NOT NULL,
    created_at TIMESTAMP NULL,
    updated_at TIMESTAMP NULL
);

CREATE TABLE rows_sectors (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    chamber_id BIGINT UNSIGNED NOT NULL,
    name VARCHAR(50) NOT NULL, -- e.g., 'Row 12'
    FOREIGN KEY (chamber_id) REFERENCES chambers(id) ON DELETE CASCADE
);

CREATE TABLE bays (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    sector_id BIGINT UNSIGNED NOT NULL,
    name VARCHAR(50) NOT NULL, -- e.g., 'Bay B-10'
    max_pallet_capacity INT UNSIGNED DEFAULT 10,
    current_pallet_count INT UNSIGNED DEFAULT 0,
    status ENUM('active', 'maintenance', 'full') DEFAULT 'active',
    FOREIGN KEY (sector_id) REFERENCES rows_sectors(id) ON DELETE CASCADE
);

2. Concurrency-Safe Bay Allocation

When multiple operators assign incoming pallets at the same time, we must prevent double-booking. We write Laravel transaction handlers using explicit lockForUpdate() blocks to isolate the bay query during placement checks:

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Bay;
use App\Models\Lot;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class BayAllocationController extends Controller
{
    public function allocatePallets(Request $request)
    {
        $validated = $request->validate([
            'bay_id' => 'required|exists:bays,id',
            'lot_id' => 'required|exists:lots,id',
            'pallet_count' => 'required|integer|min:1',
        ]);

        return DB::transaction(function () use ($validated) {
            // Apply row lock to prevent simultaneous updates
            $bay = Bay::where('id', $validated['bay_id'])
                ->lockForUpdate()
                ->firstOrFail();

            $lot = Lot::findOrFail($validated['lot_id']);

            // Validate temperature zones matching
            $chamber = $bay->sector->chamber;
            if ($lot->required_temp < $chamber->temperature_min || 
                $lot->required_temp > $chamber->temperature_max) {
                return response()->json(['error' => 'Chamber temperature invalid for crop type.'], 422);
            }

            // Capacity validation
            if (($bay->current_pallet_count + $validated['pallet_count']) > $bay->max_pallet_capacity) {
                return response()->json(['error' => 'Insufficient space in selected bay.'], 422);
            }

            // Assign stock and increment counter
            $bay->increment('current_pallet_count', $validated['pallet_count']);
            $lot->update([
                'bay_id' => $bay->id,
                'allocated_at' => now(),
            ]);

            return response()->json([
                'success' => true,
                'allocated_bay' => $bay->name,
                'occupancy' => "{$bay->current_pallet_count}/{$bay->max_pallet_capacity}"
            ]);
        });
    }
}

3. Memory-Safe Storage Rent Calculations

Rent compiles daily. To avoid memory depletion, calculations run via queued background processes. This decouples daily logs compiling from active user requests:

namespace App\Jobs;

use App\Models\Lot;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessLotRent implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $lot;

    public function __construct(Lot $lot)
    {
        $this->lot = $lot;
    }

    public function handle()
    {
        $activeDays = now()->diffInDays($this->lot->created_at);
        $dailyRate = $this->lot->client->billingRules->rate_per_day;
        
        $totalRentAccrued = $activeDays * $dailyRate;
        
        $this->lot->update([
            'rent_accrued' => $totalRentAccrued,
            'last_calculated_at' => now(),
        ]);
    }
}

Logistics Allocation Flow

1. Stock Scanned2. Bay ScopedFOR UPDATE Lock3. Occupancy Update4. Daily Rent Cron

Results & Metrics

Performance analysis comparison showing manual spreadsheets audits vs the Frosty ERP platform:

Process VariableExcel-Based AuditsFrosty ERP Platform
Daily Invoicing Compilation6-8 hours / cycleInstant generation (Queued Job)
Pallet Allocation Discrepancy3.5% / month0.00% (Real-time locked coordinates)
Room Temp Audit ComplianceManual ChecklistAutomated logs & alarms
Client Inventory AccessEmail requestsReal-Time Client Dashboard

Billing Autopilot: Running daily rent calculations via background queue workers removes administrative delay, converting hours of spreadsheet auditing into a dynamic background operation.

Bay Allocation Precision: Using SELECT FOR UPDATE locks on specific coordinate records prevents double-pallet allocations, resulting in 0.00% discrepancies in warehouse planning.

Environmental Security: Digital temperature checks replace manual compliance sheets, logging hourly temperature changes and firing automatic alerts if chambers exceed configured limits.

Client Transparency: Merchants and clients view active holdings directly via secure web portals, eliminating telephone checking cycles and shipping delays.

Interested in launching similar digital systems?

Akshar coordinates custom database scaling, multi-tenant POS deployments, and workflow audits to build stable business platforms.

Discuss your project