<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Chris Lloyd A Fallaria]]></title><description><![CDATA[Chris Lloyd A Fallaria]]></description><link>https://chrislloydafallaria.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6a09375f5b726333fdc80299/d9aa4784-d434-438a-aefa-3f0b37a8f397.png</url><title>Chris Lloyd A Fallaria</title><link>https://chrislloydafallaria.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 24 Oct 2026 17:20:24 GMT</lastBuildDate><atom:link href="https://chrislloydafallaria.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How I Built Holiday-Aware Deadline Calculations in Laravel]]></title><description><![CDATA[TL;DR: How to calculate business deadlines in Laravel that automatically skip weekends and holidays — using a database-driven holidays table and Carbon…]]></description><link>https://chrislloydafallaria.hashnode.dev/how-i-built-holiday-aware-deadline-calculations-in-laravel</link><guid isPermaLink="true">https://chrislloydafallaria.hashnode.dev/how-i-built-holiday-aware-deadline-calculations-in-laravel</guid><category><![CDATA[Laravel]]></category><category><![CDATA[PHP]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Chris Lloyd Fallaria]]></dc:creator><pubDate>Sun, 31 May 2026 14:37:22 GMT</pubDate><content:encoded><![CDATA[<p>TL;DR: How to calculate business deadlines in Laravel that automatically skip weekends and holidays — using a database-driven holidays table and Carbon. Code examples included.</p>
<p>The Problem<br />When I was building VMMS — a voucher management system for government offices — I needed to calculate processing deadlines for each department.<br />Simple enough, right? Just add X days to the current date.<br />Wrong.</p>
<p>Government offices don't work on weekends. They don't work on holidays. A voucher submitted on Friday shouldn't have a Monday deadline if Monday is a holiday.<br />I needed a deadline calculator that was aware of all of this.</p>
<p>The Approach<br />Instead of hardcoding holidays, I stored them in a database table. This means adding new holidays requires no code changes — just a new database record.</p>
<pre><code class="language-plaintext">Schema::create('holidays', function (Blueprint $table) {
    $table-&gt;id();
    $table-&gt;string('name');
    $table-&gt;date('date');
    $table-&gt;timestamps();
});
</code></pre>
<p>Building the DateHelpers Service<br />I created a dedicated service class to handle all date calculations:</p>
<pre><code class="language-plaintext">&lt;?php

namespace App\Services;

use Carbon\Carbon;
use App\Models\Holiday;

class DateHelpers
{
    protected array $holidays;

    public function __construct()
    {
        $this-&gt;holidays = Holiday::pluck('date')
            -&gt;map(fn(\(d) =&gt; Carbon::parse(\)d)-&gt;toDateString())
            -&gt;toArray();
    }

    public function isNonWorkingDay(Carbon $date): bool
    {
        return $date-&gt;isWeekend()
            || in_array(\(date-&gt;toDateString(), \)this-&gt;holidays);
    }

    public function addBusinessDays(Carbon \(date, int \)days): Carbon
    {
        \(result = \)date-&gt;copy();
        $added = 0;

        while (\(added &lt; \)days) {
            $result-&gt;addDay();
            if (!\(this-&gt;isNonWorkingDay(\)result)) {
                $added++;
            }
        }

        return $result;
    }
}
</code></pre>
<p>How It Works<br />The addBusinessDays method loops day by day — skipping weekends and holidays — until it has counted the required number of business days.<br />For example — if today is Friday and you need 3 business days:</p>
<p>Saturday → skip (weekend)<br />Sunday → skip (weekend)<br />Monday → count (1)<br />Tuesday → count (2)<br />Wednesday → count (3) ← deadline</p>
<p>If Monday is a holiday:</p>
<p>Saturday → skip (weekend)<br />Sunday → skip (weekend)<br />Monday → skip (holiday)<br />Tuesday → count (1)<br />Wednesday → count (2)<br />Thursday → count (3) ← deadline</p>
<p>Using It in the Application<br />php\(dateHelpers = new DateHelpers();<br />\)deadline = $dateHelpers-&gt;addBusinessDays(Carbon::now(), 3);<br />When a department receives a voucher — the deadline is calculated automatically and stored in the audit trail:</p>
<pre><code class="language-plaintext">AuditTrail::create([
    'transaction_id' =&gt; $transaction-&gt;id,
    'processing_offices' =&gt; $department-&gt;name,
    'process_initiate' =&gt; now(),
    'deadline' =&gt; $dateHelpers-&gt;addBusinessDays(Carbon::now(), 3),
]);
</code></pre>
<p>Caching for Performance<br />Since holidays don't change often — I cached the holidays list to avoid hitting the database on every request:</p>
<pre><code class="language-plaintext">public function __construct()
{
    $this-&gt;holidays = cache()-&gt;remember('holidays', now()-&gt;addDay(), function () {
        return Holiday::pluck('date')
            -&gt;map(fn(\(d) =&gt; Carbon::parse(\)d)-&gt;toDateString())
            -&gt;toArray();
    });
}
</code></pre>
<p>This caches the holidays list for 24 hours — refreshing automatically every day.</p>
<p>Checking for Overdue Deadlines<br />With Carbon's diffInDays you can easily check if a deadline has passed:</p>
<pre><code class="language-plaintext">\(deadline = Carbon::parse(\)auditTrail-&gt;deadline)-&gt;startOfDay();
\(daysLeft = (int) Carbon::now()-&gt;startOfDay()-&gt;diffInDays(\)deadline, false);

// Negative = overdue, Positive = days remaining, Zero = due today
if ($daysLeft &lt; 0) {
    // Flag as overdue
}
</code></pre>
<p>The false parameter makes diffInDays return negative numbers for past dates — useful for overdue detection.</p>
<p>Managing Holidays<br />Since holidays are stored in the database — admins can manage them through a simple CRUD interface:</p>
<pre><code class="language-plaintext">// Add a holiday
Holiday::create([
    'name' =&gt; 'Independence Day',
    'date' =&gt; '2026-06-12',
]);

// Get all holidays for the year

Holiday::whereYear('date', now()-&gt;year)-&gt;get();
</code></pre>
<p>No code deployments needed when a new holiday is declared — just add it to the database.</p>
<p>What I Learned</p>
<p>Keep business logic out of the database — date calculations belong in PHP, not SQL<br />Cache holiday lookups — they rarely change but get queried often<br />Use Carbon consistently — mixing Carbon with raw date strings causes subtle bugs<br />Test edge cases — Friday submissions, holiday Mondays, and long weekends all need testing</p>
<p>Conclusion<br />Holiday-aware deadline calculations sound simple but have more edge cases than you'd expect. A dedicated service class with a database-driven holidays table keeps the logic clean, flexible, and easy to maintain.<br />This pattern works for any system that needs business day calculations — leave requests, SLA tracking, invoice due dates, and more.</p>
<p>Want to see this in action?<br />I built VMMS — a complete Voucher Management &amp; Monitoring System using everything covered in this article.<br />🔴 Live demo: <a href="https://vmms-app-production.up.railway.app/login">https://vmms-app-production.up.railway.app/login</a><br />💰 Get the full source code: <a href="https://getvmms.gumroad.com/l/zeroqz">https://getvmms.gumroad.com/l/zeroqz</a><br />Happy to answer any questions in the comments! 🚀</p>
]]></content:encoded></item><item><title><![CDATA[I finally recorded a full demo of VMMS — 3 minutes, all 3 roles]]></title><description><![CDATA[I've written a couple of posts here about how I built VMMS — the workflow routing, the Laravel architecture, the Carbon date stuff. But I realized I never…]]></description><link>https://chrislloydafallaria.hashnode.dev/i-finally-recorded-a-full-demo-of-vmms-3-minutes-all-3-roles</link><guid isPermaLink="true">https://chrislloydafallaria.hashnode.dev/i-finally-recorded-a-full-demo-of-vmms-3-minutes-all-3-roles</guid><category><![CDATA[Laravel]]></category><category><![CDATA[Vue.js]]></category><category><![CDATA[Inertia.js]]></category><category><![CDATA[showdev]]></category><dc:creator><![CDATA[Chris Lloyd Fallaria]]></dc:creator><pubDate>Sat, 23 May 2026 22:02:00 GMT</pubDate><content:encoded><![CDATA[<p>I've written a couple of posts here about how I built VMMS — the workflow routing, the Laravel architecture, the Carbon date stuff. But I realized I never actually just <em>showed</em> the thing.</p>
<p>So here it is.</p>
<p><a href="https://www.youtube.com/watch?v=6LBbOwvri3I&amp;t=2s">https://www.youtube.com/watch?v=6LBbOwvri3I&amp;t=2s</a></p>
<p>It's about 3 minutes. Walks through the Admin dashboard, how Staff process and forward requests between departments, and the Client-side pipeline tracker that shows you exactly where your request is in real time.</p>
<p>The pipeline stepper was honestly the part that took the longest — getting the green/active/waiting states to feel right without being annoying. Worth it though.</p>
<p>If you've been following the other posts, this is what all of that code actually looks like in action. And if you're new here — the live demo is open, no signup needed:</p>
<p><a href="https://vmms-app-production.up.railway.app">https://vmms-app-production.up.railway.app</a></p>
<p>Feedback always welcome. 🙏</p>
]]></content:encoded></item><item><title><![CDATA[Why I Stopped Using Raw SQL Date Functions and Switched to Carbon in Laravel]]></title><description><![CDATA[When I was building VMMS — a voucher management systemfor government offices — everything worked fine locally. MySQL. Clean queries. Fast results. Then I…]]></description><link>https://chrislloydafallaria.hashnode.dev/why-i-stopped-using-raw-sql-date-functions-and-switched-to-carbon-in-laravel</link><guid isPermaLink="true">https://chrislloydafallaria.hashnode.dev/why-i-stopped-using-raw-sql-date-functions-and-switched-to-carbon-in-laravel</guid><category><![CDATA[Laravel]]></category><category><![CDATA[PHP]]></category><dc:creator><![CDATA[Chris Lloyd Fallaria]]></dc:creator><pubDate>Fri, 22 May 2026 06:27:18 GMT</pubDate><content:encoded><![CDATA[<h2><strong>It Started With a Bug</strong></h2>
<p>When I was building VMMS — a voucher management system<br />for government offices — everything worked fine locally.</p>
<p>MySQL. Clean queries. Fast results.</p>
<p>Then I deployed to a server running MariaDB.</p>
<p>Half my charts broke.</p>
<hr />
<h2><strong>The Problem</strong></h2>
<p>I had written date queries like this all over the codebase:</p>
<pre><code class="language-plaintext">// This breaks on MariaDB
DB::table('voucher_transactions')
    -&gt;selectRaw('MONTHNAME(created_at) as month, COUNT(*) as total')
    -&gt;groupByRaw('MONTH(created_at), MONTHNAME(created_at)')
    -&gt;get();
</code></pre>
<p><code>MONTH()</code> and <code>MONTHNAME()</code> are MySQL functions. They work<br />fine on MySQL but behave differently on MariaDB — especially<br />when combined with <code>GROUP BY</code>.</p>
<p>The result? Months showing up in the wrong order.<br />Duplicate entries. Missing data.</p>
<p><strong>Why This Happens</strong></p>
<p>MySQL and MariaDB have diverged over the years. They share<br />a lot of syntax but handle certain functions differently —<br />especially around date grouping and ordering.</p>
<p>The safe rule is: <strong>if you want your app to work on both,<br />don't rely on database-specific date functions.</strong></p>
<hr />
<h2><strong>The Fix — Pull the Data and Use Carbon</strong></h2>
<p>Instead of doing date calculations in SQL, I pulled the<br />raw data and used Carbon to handle everything in PHP:</p>
<pre><code class="language-plaintext">// DB-agnostic approach
\(rows = VoucherTransaction::where('user_id', \)userId)
    -&gt;whereYear('created_at', $year)
    -&gt;whereNull('deleted_at')
    -&gt;get(['created_at', 'status']);

$byMonth = [];
foreach (\(rows as \)row) {
    \(m = (int) \)row-&gt;created_at-&gt;format('n');
    \(status = strtolower(\)row-&gt;status);
    \(byMonth[\)m] = \(byMonth[\)m] ?? ['accomplished' =&gt; 0, 'rejected' =&gt; 0];
    if (\(status === 'accomplished') \)byMonth[$m]['accomplished']++;
    if (\(status === 'rejected')     \)byMonth[$m]['rejected']++;
}

ksort($byMonth);
</code></pre>
<p>Then to get the month name I used Carbon instead of<br />MONTHNAME():</p>
<pre><code class="language-plaintext">\(monthName = Carbon::create(\)year, $m, 1)-&gt;format('F');
// Returns "January", "February", etc.
// Works the same on MySQL and MariaDB
</code></pre>
<h2><strong>Building the Full Year Chart</strong></h2>
<p>For charts that need all 12 months — even empty ones —<br />I use <code>range(1, 12)</code> and fill in zeros for months with<br />no data:</p>
<pre><code class="language-plaintext">return collect(range(1, 12))-&gt;map(function (\(m) use (\)byMonth, $year) {
    \(d = \)byMonth[$m] ?? ['accomplished' =&gt; 0, 'rejected' =&gt; 0];
    return [
        'month'        =&gt; Carbon::create(\(year, \)m, 1)-&gt;format('F'),
        'accomplished' =&gt; $d['accomplished'],
        'rejected'     =&gt; $d['rejected'],
    ];
});
</code></pre>
<p>This guarantees all 12 months always appear in the chart<br />— even if there's no data for some months. Clean and<br />predictable.</p>
<hr />
<h2><strong>What About Performance?</strong></h2>
<p>You might be thinking — isn't pulling all rows and<br />processing in PHP slower than doing it in SQL?</p>
<p>For most applications — yes, SQL aggregation is faster.</p>
<p>But in practice for this use case:</p>
<ul>
<li><p>The data is filtered by user and year first</p>
</li>
<li><p>The result set is small (max 365 rows per year per user)</p>
</li>
<li><p>The PHP processing is negligible</p>
</li>
</ul>
<p>If you're dealing with millions of rows this approach<br />needs rethinking. But for typical business applications<br />it's fast enough and the portability is worth it.</p>
<hr />
<h2><strong>Other Carbon Tricks I Use in VMMS</strong></h2>
<p><strong>Comparing dates without time:</strong></p>
<pre><code class="language-plaintext">// Use DATE() in SQL to avoid time comparison issues
-&gt;whereRaw('DATE(deadline) &gt;= ?', [$now-&gt;toDateString()])
-&gt;whereRaw('DATE(deadline) &lt;= ?', [$now-&gt;copy()-&gt;addDays(7)-&gt;toDateString()])
</code></pre>
<p><strong>Calculating days left:</strong></p>
<pre><code class="language-plaintext">\(deadline = Carbon::parse(\)t-&gt;deadline)-&gt;startOfDay();
\(daysLeft = (int) \)now-&gt;copy()-&gt;startOfDay()-&gt;diffInDays($deadline, false);
</code></pre>
<p>The <code>false</code> parameter makes <code>diffInDays</code> return negative<br />numbers for past dates — useful for overdue detection.</p>
<p><strong>Calculating processing time:</strong></p>
<pre><code class="language-plaintext">\(minutes = Carbon::parse(\)row-&gt;process_initiate)
    -&gt;diffInMinutes(Carbon::parse($row-&gt;process_accomplished));
</code></pre>
<h2><strong>The Lesson</strong></h2>
<p>Don't assume your local MySQL behavior will match<br />production. If your app might run on MariaDB, PostgreSQL,<br />or any other database — keep your date logic in PHP<br />with Carbon and use only standard SQL for filtering.</p>
<p>It's a small habit that saves a lot of debugging time.</p>
<hr />
<h2><strong>About VMMS</strong></h2>
<p>This and many other lessons came from building VMMS —<br />a complete voucher management system for government<br />offices, companies, and educational institutions.</p>
<p>🔴 Live demo: <a href="https://vmms-app-production.up.railway.app/login">https://vmms-app-production.up.railway.app/login</a></p>
<p>Available on Gumroad:<br />👉 <a href="https://getvmms.gumroad.com/l/zeroqz">https://getvmms.gumroad.com/l/zeroqz</a></p>
<p>Happy to answer any questions in the comments! 🚀</p>
]]></content:encoded></item><item><title><![CDATA[How to Build a Multi-Department Approval Workflow in Laravel]]></title><description><![CDATA[IntroductionOne of the most common problems I saw while building VMMS was this: documents get created, emailed to the next department, sit in someone's…]]></description><link>https://chrislloydafallaria.hashnode.dev/how-to-build-a-multi-department-approval-workflow-in-laravel</link><guid isPermaLink="true">https://chrislloydafallaria.hashnode.dev/how-to-build-a-multi-department-approval-workflow-in-laravel</guid><category><![CDATA[Laravel]]></category><category><![CDATA[phpt]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Chris Lloyd Fallaria]]></dc:creator><pubDate>Thu, 21 May 2026 14:37:03 GMT</pubDate><content:encoded><![CDATA[<p><strong>Introduction</strong><br />One of the most common problems I saw while building VMMS was this: documents get created, emailed to the next department, sit in someone's inbox for days, and when management asks "where's that voucher?" — nobody knows.<br />In this article I'll walk you through how I built a flexible multi-department approval workflow in Laravel that solves exactly this problem.</p>
<p><strong>The Core Concept</strong><br />Instead of hardcoding approval steps, I built a pipeline where each document has a current stage, and the next stage is determined by a department configuration stored in the database — not in the code.<br />This means you can add, remove, or reorder departments without touching a single line of PHP.</p>
<p><strong>Database Structure</strong><br />Here are the key tables:</p>
<pre><code class="language-plaintext">// vouchers table
Schema::create('vouchers', function (Blueprint $table) {
    $table-&gt;id();
    $table-&gt;string('title');
    $table-&gt;foreignId('current_department_id')-&gt;constrained('departments');
    $table-&gt;enum('status', ['ongoing', 'accomplished', 'rejected']);
    $table-&gt;timestamps();
});

// departments table
Schema::create('departments', function (Blueprint $table) {
    $table-&gt;id();
    $table-&gt;string('name');
    $table-&gt;integer('order'); // determines routing sequence
    $table-&gt;timestamps();
});
</code></pre>
<p><strong>Routing Logic</strong><br />When a staff member approves a voucher, it automatically moves to the next department based on the order column:</p>
<pre><code class="language-plaintext">public function approve(Voucher $voucher)
{
    \(currentOrder = \)voucher-&gt;department-&gt;order;

    \(nextDepartment = Department::where('order', '&gt;', \)currentOrder)
        -&gt;orderBy('order')
        -&gt;first();

    if ($nextDepartment) {
        $voucher-&gt;update([
            'current_department_id' =&gt; $nextDepartment-&gt;id,
            'status' =&gt; 'ongoing'
        ]);
    } else {
        // No more departments — voucher is complete
        $voucher-&gt;update(['status' =&gt; 'accomplished']);
    }

    // Fire event for email notification
    event(new VoucherAdvanced($voucher));
}
</code></pre>
<p><strong>Email Notifications</strong><br />I used Laravel Events and Listeners to trigger email notifications every time a voucher moves to the next stage:</p>
<pre><code class="language-plaintext">// VoucherAdvanced Event
class VoucherAdvanced
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(public Voucher $voucher) {}
}

// Listener
class SendVoucherNotification implements ShouldQueue
{
    public function handle(VoucherAdvanced $event): void
    {
        \(voucher = \)event-&gt;voucher;
        \(staff = \)voucher-&gt;department-&gt;staff;

        foreach (\(staff as \)member) {
            Mail::to(\(member-&gt;email)-&gt;send(new VoucherNotification(\)voucher));
        }
    }
}
</code></pre>
<p><strong>Role-Based Access</strong><br />Each department only sees vouchers assigned to them using Laravel Policies:</p>
<pre><code class="language-plaintext">public function view(User \(user, Voucher \)voucher): bool
{
    return \(user-&gt;department_id === \)voucher-&gt;current_department_id
        || $user-&gt;role === 'admin';
}
</code></pre>
<p><strong>Real-Time Pipeline Tracker</strong><br />To show users where a voucher is in real time, I built a simple status tracker on the Vue 3 frontend that reads the current department and renders it as a progress indicator — no websockets needed, just a clean Inertia.js page refresh on action.</p>
<p><strong>What I Learned</strong></p>
<p>Keep routing logic in the database, not the code — it makes the system far more flexible<br />Laravel Events are perfect for notifications — clean, queueable, and easy to extend<br />Policies over middleware for row-level access — much cleaner when you have complex role rules</p>
<p><strong>Conclusion</strong><br />This pattern works for any multi-step approval system — not just vouchers. You could use it for leave requests, purchase orders, or any document that needs to pass through multiple departments.<br />If you want to see the full system in action, check out the live demo of VMMS: <a href="https://vmms-app-production.up.railway.app/login">https://vmms-app-production.up.railway.app/login</a><br />Happy to answer any questions in the comments!</p>
]]></content:encoded></item><item><title><![CDATA[How I Built a Multi-Department Workflow Routing System in Laravel with Inertia.js]]></title><description><![CDATA[When I started building VMMS — a voucher management system for government offices — one of the hardest parts wasn't the UI or the database schema. It was…]]></description><link>https://chrislloydafallaria.hashnode.dev/how-i-built-a-multi-department-workflow-routing-system-in-laravel-with-inertia-js</link><guid isPermaLink="true">https://chrislloydafallaria.hashnode.dev/how-i-built-a-multi-department-workflow-routing-system-in-laravel-with-inertia-js</guid><category><![CDATA[Laravel]]></category><category><![CDATA[PHP]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Inertia.js]]></category><dc:creator><![CDATA[Chris Lloyd Fallaria]]></dc:creator><pubDate>Wed, 20 May 2026 02:07:11 GMT</pubDate><content:encoded><![CDATA[<h3><strong>The Problem I Was Trying to Solve</strong></h3>
<p>When I started building VMMS — a voucher management system for government offices — one of the hardest parts wasn't the UI or the database schema.</p>
<p>It was the workflow routing.</p>
<p>A voucher request doesn't just go to one office. It goes to multiple departments in a specific order — each one needs to process it before passing it to the next.</p>
<p>And at any point, a department can:</p>
<p>Complete their step and pass it forward Reject the entire request Flag it for missing documents and pause processing I needed a system that could handle all of that cleanly.</p>
<h3><strong>How I Modeled It</strong></h3>
<p>Every voucher type has a configurable processing flow stored in a transaction_flows table:</p>
<p>Schema::create('transaction_flows', function (Blueprint \(table) { \)table-&gt;id(); \(table-&gt;foreignId('voucher_id')-&gt;constrained()-&gt;cascadeOnDelete(); \)table-&gt;string('department'); \(table-&gt;integer('order_number'); \)table-&gt;timestamps(); }); When a client submits a request, the system reads the flow for that voucher type and routes it to the first department automatically.</p>
<h3><strong>Tracking Progress with Audit Trails</strong></h3>
<p>Every time a department processes a request, an audit trail record is created:</p>
<p>Schema::create('audit_trails', function (Blueprint \(table) { \)table-&gt;id(); \(table-&gt;foreignId('transaction_id')-&gt;constrained()-&gt;cascadeOnDelete(); \)table-&gt;string('processing_offices'); \(table-&gt;timestamp('process_initiate')-&gt;nullable(); \)table-&gt;timestamp('process_accomplished')-&gt;nullable(); \(table-&gt;date('deadline')-&gt;nullable(); \)table-&gt;timestamps(); }); process_initiate is set when a department starts processing. process_accomplished is set when they finish. If only process_initiate is set — that's the currently active department.</p>
<h3><strong>Finding the Current Department</strong></h3>
<p>This was trickier than I expected. Here's the logic:</p>
<p>\(activeAudit = \)auditTrails-&gt;first( fn(\(a) =&gt; \)a-&gt;process_initiate &amp;&amp; !$a-&gt;process_accomplished );</p>
<p>if (\(activeAudit) { return \)activeAudit-&gt;processing_offices; }</p>
<p>// If no active audit, find the next step \(doneOrders = \)auditTrails -&gt;filter(fn(\(a) =&gt; \)a-&gt;process_accomplished) -&gt;map(fn(\(a) =&gt; \)flow-&gt;firstWhere('department', $a-&gt;processing_offices)?-&gt;order_number ?? 0);</p>
<p>\(lastDone = \)doneOrders-&gt;max() ?? 0; \(nextStep = \)flow-&gt;firstWhere('order_number', $lastDone + 1);</p>
<p>return $nextStep?-&gt;department ?? 'Completed'; The tricky part was handling edge cases:</p>
<p>What if a department is skipped? What if a request is returned for missing documents? What if the last department just finished? Each of these needed its own handling.</p>
<h3><strong>The Pipeline Stepper on the Frontend</strong></h3>
<p>On the Vue 3 side, I built a stepper component that shows each department as a step — green for done, pulsing for active, grey for pending.</p>
<p>const stepStatus = (req, step, stepIndex) =&gt; { const audits = req.audit_trails ?? []; const accomplishedCount = audits.filter( a =&gt; a.process_accomplished !== null ).length;</p>
<pre><code class="language-plaintext">if (stepIndex &lt; accomplishedCount)  return 'done';
if (stepIndex === accomplishedCount) return 'active';
return 'pending';
</code></pre>
<p>}; Simple logic but it took a few rewrites to handle all the edge cases correctly.</p>
<h3><strong>Missing Documents — Pausing the Flow</strong></h3>
<p>When a staff member flags a request for missing documents, the flow pauses. The client gets notified and needs to upload the corrected files before processing can continue.</p>
<p>I handled this through a separate requirement_validations table that tracks which transactions are waiting for documents from which user.</p>
<p>\(missingDocIds = RequirementValidation::where('receiver_id', \)userId) -&gt;whereNull('resume_transaction') -&gt;pluck('transaction_id') -&gt;toArray(); When resume_transaction is filled — the flow resumes and the request goes back to the staff for continued processing.</p>
<h3><strong>What I Learned</strong></h3>
<ol>
<li><p><strong>Model the flow separately from the transaction</strong> Keeping transaction_flows separate from voucher_transactions made it easy to change the processing order for a voucher type without affecting existing requests.</p>
</li>
<li><p><strong>Audit trails are your best friend</strong> Storing every processing event with timestamps made it easy to calculate processing times, track performance, and build analytics on top.</p>
</li>
<li><p><strong>Edge cases will get you</strong> The "happy path" was easy. Missing documents, skipped departments, and rejected requests all needed separate handling. Budget time for this.</p>
</li>
</ol>
<p><strong>The Result</strong></p>
<p>The pipeline tracker is now one of the most useful features in VMMS. Clients can see exactly where their request is in real time — no more following up in person.</p>
<p>If you want to see it in action:</p>
<p>🔴 Live demo: <a href="https://vmms-app-production.up.railway.app/login">https://vmms-app-production.up.railway.app/login</a></p>
<p>VMMS is available on Gumroad if you want to use it or build on top of it: 👉 <a href="https://getvmms.gumroad.com/l/zeroqz">https://getvmms.gumroad.com/l/zeroqz</a></p>
<p>Happy to answer any questions in the comments!</p>
]]></content:encoded></item><item><title><![CDATA[How I Built a Production-Ready Voucher Management System with Laravel 12 and Vue 3]]></title><description><![CDATA[I'm a BS Information Technology student in the Philippines. During my time observing howoffices work, I noticed something that kept bothering me. Voucher…]]></description><link>https://chrislloydafallaria.hashnode.dev/how-i-built-a-production-ready-voucher-management-system-with-laravel-12-and-vue-3</link><guid isPermaLink="true">https://chrislloydafallaria.hashnode.dev/how-i-built-a-production-ready-voucher-management-system-with-laravel-12-and-vue-3</guid><category><![CDATA[Laravel]]></category><category><![CDATA[PHP]]></category><category><![CDATA[webdev]]></category><category><![CDATA[showdev]]></category><dc:creator><![CDATA[Chris Lloyd Fallaria]]></dc:creator><pubDate>Tue, 19 May 2026 02:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a09375f5b726333fdc80299/493e4754-0e41-452b-8455-a1787981c149.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2><strong>The Problem</strong></h2>
<p>I'm a BS Information Technology student in the Philippines. During my time observing how<br />offices work, I noticed something that kept bothering me.</p>
<p>Voucher processing is still done manually almost everywhere.<br />Someone fills out a paper form, hands it to the next office,<br />that office stamps it and passes it again, and so on — until<br />it either gets done or gets lost on someone's desk.</p>
<p>No tracking. No visibility. The client has no idea where their<br />request is. The admin has no data. Staff have no accountability.</p>
<p>I kept thinking — this is exactly the kind of problem software<br />should solve. So I spent the last few months building it.</p>
<hr />
<h2><strong>What I Built</strong></h2>
<p>VMMS stands for Voucher Management &amp; Monitoring System.</p>
<p>It handles the entire voucher request process — from the moment<br />a client submits a request, all the way through each department<br />that needs to process it, until it's finally released.</p>
<p>No paper. No manual routing. No "what's the status of my request?"</p>
<hr />
<h2><strong>Tech Stack</strong></h2>
<p>I went with what I know best:</p>
<ul>
<li><p><strong>Laravel 12</strong> for the backend — routing, queues, and email jobs</p>
</li>
<li><p><strong>Vue 3 (Composition API)</strong> for the frontend — reactive and clean</p>
</li>
<li><p><strong>Inertia.js v2</strong> — so I don't have to build a separate API</p>
</li>
<li><p><strong>Tailwind CSS + Vuetify 3</strong> — for UI components and styling</p>
</li>
<li><p><strong>MySQL 8.0</strong> — for the relational data and complex queries</p>
</li>
<li><p><strong>Resend</strong> — for transactional emails</p>
</li>
</ul>
<hr />
<h2><strong>What It Can Do</strong></h2>
<p>There are three separate panels depending on your role:</p>
<p><strong>Admin</strong><br />You get full control — manage users, voucher types, and see<br />everything through an analytics dashboard. There's a staff<br />performance leaderboard, rejection rate charts, audit logs,<br />and even custom branding options.</p>
<p><strong>Staff</strong><br />Each department gets their own queue of requests to process.<br />You can complete, reject, or flag a request for missing documents.<br />There's also a personal performance dashboard showing your stats<br />and ratings from clients.</p>
<p><strong>Client</strong><br />You submit requests online, track which department is currently<br />processing your voucher in real time, upload corrected documents<br />if something gets flagged, and rate the staff after it's done.</p>
<p>Some other things worth mentioning:</p>
<ul>
<li><p>Deadlines are holiday-aware — staff don't get penalized for non-working days</p>
</li>
<li><p>Every action is logged in an audit trail</p>
</li>
<li><p>PDF and Excel exports on all reports</p>
</li>
<li><p>Automated email notifications at every stage</p>
</li>
</ul>
<hr />
<h2><strong>The Parts That Were Actually Hard</strong></h2>
<p><strong>Date queries across MySQL and MariaDB</strong><br />I originally wrote date queries using MySQL-specific functions<br />like MONTH() and MONTHNAME(). Broke on MariaDB. Had to rewrite<br />everything to use Carbon instead — which honestly ended up<br />being cleaner anyway.</p>
<p><strong>The pipeline tracker</strong><br />Getting the pipeline stepper to accurately show which department<br />is currently processing a request was trickier than I expected.<br />Especially edge cases — what if a department is skipped? What if<br />a request gets returned for missing documents mid-flow? Took a<br />few rewrites to get right.</p>
<p><strong>Holiday-aware deadlines</strong><br />I built a DateHelpers service that calculates business days<br />while excluding weekends and holidays. Sounds simple, took way<br />longer than expected. Really happy with how it turned out though.</p>
<hr />
<h2><strong>Try It Yourself</strong></h2>
<p>🔴 Live demo: <a href="https://vmms-app-production.up.railway.app/login">https://vmms-app-production.up.railway.app/login</a></p>
<p>Credentials if you want to explore:</p>
<p>Admin: <a href="mailto:admin@vmms.demo">admin@vmms.demo</a> | Password: Admin@Demo123!<br />Staff: <a href="mailto:staff.infra@vmms.demo">staff.infra@vmms.demo</a> | <a href="mailto:staff.budget@vmms.demo">staff.budget@vmms.demo</a> | <a href="mailto:staff.procurement@vmms.demo">staff.procurement@vmms.demo</a> | <a href="http://staff.supply">staff.supply</a><a href="mailto:staff.supply@vmms.demo">@vmms.demo</a> | <a href="mailto:staff.executive@vmms.demo">staff.executive@vmms.demo</a> | Password: Staff@Demo123!<br />Client: <a href="mailto:client@vmms.demo">client@vmms.demo</a> | Password: Client@Demo123!</p>
<hr />
<h2><strong>It's Available on Gumroad</strong></h2>
<p>I'm selling VMMS in two versions:</p>
<p>🔒 <strong>Obfuscated / Protected — $150</strong><br />Deploy it as-is. PHP backend is protected, Vue frontend and<br />config files are fully readable. Good for offices and<br />institutions that just want to run it.</p>
<p>👨‍💻 <strong>Full Source Code — $400</strong><br />Every file is readable and editable. Good for developers who<br />want to customize it for a specific client or use case.</p>
<p>Both versions come with documentation, a quick start guide,<br />and deployment configs for Nginx, Apache, and Railway.</p>
<p>👉 <a href="https://getvmms.gumroad.com/l/zeroqz">https://getvmms.gumroad.com/l/zeroqz</a></p>
<hr />
<p>Happy to answer questions in the comments — whether about<br />the build, the tech stack, or anything else. 🚀</p>
]]></content:encoded></item><item><title><![CDATA[Why I Built VMMS — And How It Replaces Excel-Based Voucher Processing in Government Offices]]></title><description><![CDATA[How I built VMMS — a Laravel 12 + Vue 3 system that replaces Excel-based voucher processing for government offices and companies.]]></description><link>https://chrislloydafallaria.hashnode.dev/why-i-built-vmms-and-how-it-replaces-excel-based-voucher-processing-in-government-offices</link><guid isPermaLink="true">https://chrislloydafallaria.hashnode.dev/why-i-built-vmms-and-how-it-replaces-excel-based-voucher-processing-in-government-offices</guid><category><![CDATA[Laravel]]></category><category><![CDATA[PHP]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Chris Lloyd Fallaria]]></dc:creator><pubDate>Sun, 17 May 2026 04:30:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a09375f5b726333fdc80299/89fce39e-79b6-40d5-8180-f2cfee131e99.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>The Problem I Kept Seeing</h3>
<p>Every government office and company I visited had the same setup — someone creates a voucher, emails it to the next department, it sits in an inbox for days, and when management asks "where's that document?" nobody knows.</p>
<p>Forms getting lost. Staff manually routing papers between departments. No visibility. No accountability. Just Excel sheets and email chains holding everything together.</p>
<p>I'm a BSIT student from the Philippines and I got tired of seeing this. So I spent months building a solution.</p>
<hr />
<h3>What VMMS Does</h3>
<p>VMMS (Voucher Management &amp; Monitoring System) is a complete multi-role web system built with Laravel 12, Vue 3, and Inertia.js designed specifically for government offices, companies, and educational institutions.</p>
<p>Here's what it replaces:</p>
<ul>
<li><p>❌ Excel tracking → ✅ Real-time pipeline tracker</p>
</li>
<li><p>❌ Email routing → ✅ Automated multi-department workflow</p>
</li>
<li><p>❌ No accountability → ✅ Full audit trail per department</p>
</li>
<li><p>❌ No visibility → ✅ Admin, Staff &amp; Client panels</p>
</li>
<li><p>❌ Manual follow-ups → ✅ Automated email notifications at every stage</p>
</li>
</ul>
<hr />
<h3>How the Workflow Routing Works</h3>
<p>The trickiest part of building VMMS was making the multi-department routing flexible. Each department has a different approval flow, and I needed vouchers to move automatically to the next department once approved.</p>
<p>I solved this by storing the workflow sequence in the database — not hardcoded in the application logic. This means you can add, remove, or reorder departments without touching a single line of PHP.</p>
<p>Every stage transition is recorded in a separate audit trail table with who processed it, when it started, when it was accomplished, and the status. The current stage on the voucher is just a cache for quick lookups — the real data lives in the transition history.</p>
<hr />
<h3>Who It's For</h3>
<p>VMMS is designed for:</p>
<ul>
<li><p>Government offices processing disbursement vouchers, purchase orders, and reimbursements</p>
</li>
<li><p>Companies with multi-department approval workflows</p>
</li>
<li><p>Educational institutions managing administrative requests</p>
</li>
</ul>
<p>If your office is still routing documents through email and tracking them in Excel — this was built for you.</p>
<hr />
<h3>Try It Before You Buy It</h3>
<p>I built a fully working live demo with pre-filled credentials for all three roles — Admin, Staff, and Client. You can click through the entire system without signing up for anything.</p>
<p>🔴 <strong>Live demo:</strong> <a href="https://vmms-app-production.up.railway.app/login">https://vmms-app-production.up.railway.app/login</a></p>
<p>▶️ <strong>3-minute video walkthrough:</strong> <a href="https://www.youtube.com/watch?v=6LBbOwvri3I">https://www.youtube.com/watch?v=6LBbOwvri3I</a></p>
<hr />
<h3>Get the Full System</h3>
<p>VMMS is available as a complete ready-to-deploy package with full documentation and free bug fix updates.</p>
<p>💰 <strong>Get it on Gumroad:</strong> <a href="https://getvmms.gumroad.com/l/zeroqz">https://getvmms.gumroad.com/l/zeroqz</a></p>
<p>Happy to answer any questions in the comments!</p>
]]></content:encoded></item></channel></rss>