PHP code example of hasyirin / laravel-kpi

1. Go to this page and download the library: Download hasyirin/laravel-kpi library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

hasyirin / laravel-kpi example snippets


use Hasyirin\KPI\Enums\Day;
use Hasyirin\KPI\Models\Holiday;
use Hasyirin\KPI\Models\Movement;
use Hasyirin\KPI\Models\RecurringHoliday;

return [
    'formats' => [
        'datetime' => 'd/m/Y H:i A',
    ],

    'tables' => [
        'movements'          => 'movements',
        'holidays'           => 'holidays',
        'recurring_holidays' => 'recurring_holidays',
    ],

    'models' => [
        'movement'          => Movement::class,
        'holiday'           => Holiday::class,
        'recurring_holiday' => RecurringHoliday::class,
    ],

    // Weekly work schedule — keyed by Day enum value (Sunday = 0 … Saturday = 6).
    // Days omitted are treated as non-working days.
    'schedule' => [
        Day::MONDAY->value    => ['8:00', '17:00'],
        Day::TUESDAY->value   => ['8:00', '17:00'],
        Day::WEDNESDAY->value => ['8:00', '17:00'],
        Day::THURSDAY->value  => ['8:00', '17:00'],
        Day::FRIDAY->value    => ['8:00', '15:30'],
    ],

    // Day-of-week values whose holidays substitute forward to the next working day
    // when observes_substitute = true on the row. Default empty = no substitution.
    // Malaysian state examples:
    //   Sat-Sun states + post-2025 Johor → [Day::SUNDAY->value]
    //   Kelantan, Terengganu             → [Day::SATURDAY->value]
    //   Kedah                            → [Day::FRIDAY->value]
    'substitute' => [],

    // Status values to exclude from KPI calculation, keyed by movable morph type.
    // e.g. 'App\Models\Task' => ['except' => ['on_hold']].
    'status' => [
        // 'App\Models\Task' => ['except' => ['on_hold']],
    ],
];

use Hasyirin\KPI\Facades\KPI;
use Illuminate\Support\Carbon;

$kpi = KPI::calculate(
    start: Carbon::parse('2025-01-01 08:00'),
    end:   Carbon::parse('2025-01-03 15:30'),
);

$kpi->minutes;  // 990.0          — effective working minutes in range
$kpi->hours;    // 16.5           — minutes / 60
$kpi->period;   // 2.0            — sum of (worked / scheduled) per day
$kpi->metadata; // KPIMetadata — counts of scheduled / unscheduled / excluded days

$kpi = KPI::calculate(
    start: Carbon::parse('2025-01-01 08:00'),
    end:   Carbon::parse('2025-01-03 15:30'),
    excludeDates: [Carbon::parse('2025-01-02')],
);

use Hasyirin\KPI\Data\WorkSchedule;
use Hasyirin\KPI\Enums\Day;

$kpi = KPI::calculate(
    start: Carbon::parse('2025-01-06 09:00'),
    end:   Carbon::parse('2025-01-06 15:00'),
    schedules: collect([
        Day::MONDAY->value => WorkSchedule::parse(['9:00', '15:00']),
    ]),
);

use Hasyirin\KPI\Models\Holiday;

Holiday::create(['name' => 'New Year', 'date' => '2025-01-01']);

Holiday::query()->range('2025-01-01', '2025-12-31')->get();

use Hasyirin\KPI\Models\RecurringHoliday;

RecurringHoliday::create([
    'name' => 'Labour Day',
    'month' => 5,
    'day' => 1,
]);

RecurringHoliday::create([
    'name' => 'Old Festival',
    'month' => 6,
    'day' => 15,
    'effective_from' => '2010-01-01',
    'effective_until' => '2024-12-31',
]);

// config/kpi.php

// Most of Malaysia (Mon-Fri working, Sat-Sun off):
'substitute' => [Day::SUNDAY->value],

// Kelantan, Terengganu (Sun-Thu working, Fri-Sat off):
'substitute' => [Day::SATURDAY->value],

// Kedah (Sun-Thu working, Fri-Sat off):
'substitute' => [Day::FRIDAY->value],

RecurringHoliday::create([
    'name' => 'Labour Day',
    'month' => 5,
    'day' => 1,
    'observes_substitute' => true,
]);

use Hasyirin\KPI\Concerns\InteractsWithMovement;
use Hasyirin\KPI\Contracts\HasMovement;
use Illuminate\Database\Eloquent\Model;

class Task extends Model implements HasMovement
{
    use InteractsWithMovement;
}

$task = Task::create([...]);

$movement = $task->pass(
    status:           'open',
    sender:           $system,        // who triggered the transition (optional)
    actor:            $user,          // who is now responsible (optional)
    receivedAt:       now(),          // defaults to now()
    notes:            'Created via API',
    properties:       ['source' => 'web'],
    supersede:        null,           // see "supersede" below
    expectsChildren:  false,          // see "expectsChildren" below
);

enum TaskStatus: string {
    case Open       = 'open';
    case InProgress = 'in_progress';
    case Closed     = 'closed';
}

$task->pass(TaskStatus::InProgress, actor: $user);

// $root is a Movement returned by $task->pass(...)
$child = $root->pass('with_bob', actor: $bob);
// $child has parent_id = $root->id, inherits movable_id/movable_type from $root.
// $root is NOT auto-completed by this call.

$dave = $root->pass('with_dave', actor: $dave_user, supersede: false);
// $dave is a child of $root, sibling of $child.
// supersede: false keeps $child open alongside $dave.

$deep_child->movable->pass('audit', actor: $auditor);

$movement->complete();             // closed at now()
$movement->complete($at);          // closed at a specific Carbon instance

$agency = $file->pass('at_agency', actor: $agency_user, expectsChildren: true);
// Later, even before $agency has any children:
$file->pass('review', actor: $reviewer);
// $agency stays open because expects_children = true.

$movement = $task->passIfNotCurrent(TaskStatus::Open, actor: $user);
// Movement instance on change, false on no-op.

$task->movement;                              // latest open movement of any depth (root or child), or null
$task->movements;                             // MorphMany — full history, all depths
$task->movements()->roots()->open()->get();   // open root tracks
$task->movements()->roots()->closed()->get(); // historical roots

$movement->parent;          // BelongsTo Movement (null for roots)
$movement->children;        // HasMany Movement
$movement->children()->open()->get();
$movement->children()->closed()->get();
$movement->previous;        // BelongsTo Movement — same-level chain pointer
$movement->movable;         // MorphTo — back to the resource

use Hasyirin\KPI\Events\Passed;

Event::listen(function (Passed $event) {
    $event->current;   // Movement that was just created
    $event->previous;  // Movement that was just CLOSED by this pass via supersede, or null
});

use Hasyirin\KPI\Events\Completed;

Event::listen(function (Completed $event) {
    $event->movement;  // the movement that just closed
});
bash
php artisan vendor:publish --tag="laravel-kpi-migrations"
php artisan migrate
bash
php artisan vendor:publish --tag="laravel-kpi-config"