Download the PHP package hasyirin/laravel-kpi without Composer

On this page you can find all versions of the php package hasyirin/laravel-kpi. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package laravel-kpi

Laravel KPI

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

Measure turnaround time against a working schedule, and track status transitions (movements) on any Eloquent model.

Given a start and end timestamp, laravel-kpi computes the effective working duration — skipping weekends, holidays, and any custom exclude dates — expressed as minutes, hours, and a period ratio against scheduled working minutes. It also ships a lightweight workflow layer: attach the InteractsWithMovement trait to a model and you get a chain of status transitions (pass, passIfNotCurrent), each stamped with the KPI duration it took.

Requirements

Installation

Publish and run the migrations:

Publish the config file:

Configuration

config/kpi.php:

Calculating KPI

Excluding dates

Holidays in the holidays table within the range are always excluded. You can also pass ad-hoc dates:

Overriding the schedule per call

Holidays

The package recognizes two kinds of holiday rows, both contributing to exclusion in KPI::calculate().

One-off holidays

Holiday is a regular Eloquent model:

Recurring (fixed annual) holidays

For holidays that fall on the same gregorian month/day every year (Labour Day = May 1, Malaysian National Day = Aug 31), use RecurringHoliday:

The calculator expands these to a concrete date for each year intersecting the calc range. Feb 29 is silently skipped in non-leap years.

Validity windows

effective_from and effective_until bound when the rule applies. Use them when a holiday was established or retired mid-stream — a 2015 calc should still exclude a holiday that ran 2010–2024, but a 2026 calc should not.

Both bounds are nullable; null = unbounded.

Substitute day (next-working-day observance)

Both Holiday and RecurringHoliday carry an observes_substitute boolean. When true AND the row's day-of-week is listed in config('kpi.substitute') AND that day is non-working in the active schedule, the calculator observes the holiday on the next working day instead.

Substitute eligibility is configured at the package level because the same weekly schedule can have different substitute policies (Kelantan/Terengganu and Kedah both work Sun-Thu, but Kelantan substitutes a Saturday holiday while Kedah substitutes a Friday one):

Multi-day skip and collisions

If the day immediately after the holiday is also non-working, the calculator keeps advancing until it finds a working day (Kedah's Fri → Sat (off) → Sun case is the canonical example).

The package does NOT chain substitutes through other holidays — if a substituted holiday lands on a day that is itself a holiday, both observe that same day (the calc loop dedups). If you want explicit chained observance (e.g., the federal Malaysian "next working day if Monday is also a public holiday" rule), add the chained day as its own one-off row.

Note on historical calculations

Movement::saving stores period and hours at completion time. Past completed movements are frozen — adding a new holiday row does NOT retroactively change them. This is intentional: a holiday introduced in 2026 should not be assumed to have existed in 2020.

Tracking movements on a model

A resource (any Eloquent model) can hold a forest of movements: multiple concurrent open root tracks, each of which can have a tree of child movements. Receiver-based dispatch decides whether pass() creates a root or a child.

Implement HasMovement and apply the trait to any model you want to track:

$model->pass() — create a root

BackedEnum statuses are accepted:

$movement->pass() — create a child of the receiver

To create a sibling of an existing child, call pass() on the parent:

To start a new concurrent root from a deeply nested context, go through movable:

Refreshing relations: $movement->pass(...) does not auto-reload the receiver's children relation cache. If you need fresh state after appending children, call $root->load('children') or $root->refresh(). The trait's $model->pass() does reload $model->movement for backwards compatibility with v1 single-chain callers.

$movement->complete() — close a movement

pass() no longer auto-completes prior movements by default (see supersede below). Use complete() when you're done with a movement:

If the movement has open descendants, they are cascade-closed with the same completed_at timestamp. Each level computes its own period/hours via the saving hook.

complete() is wrapped in a database transaction with lockForUpdate() on the open-children query, making concurrent pass()/complete() interleaving safe.

Concurrency note: pass() and complete() use lockForUpdate() to serialize operations on the same "previous" row, but a concurrent INSERT of a new sibling/root by a third transaction is not blocked. Two simultaneous pass() calls at the same level may both pick a "previous" that's about to be invalidated by the other, producing a stale previous_id chain. If you require strict ordering, serialize at the application layer (queue worker, lock-based job, etc.).

supersede semantics

When you call $model->pass() or $movement->pass(), the same-level previous movement (most recent open root for $model->pass(), most recent open sibling for $movement->pass()) may be auto-closed. The supersede parameter controls this:

Value Behavior
null (default) Close previous iff it has no open children and expects_children == false.
true Always close previous — cascades through any open descendants.
false Never close previous — the new movement runs concurrently.

The default is convenient for sequential single-track workflows (a leaf root naturally yields to its successor) while staying safe for trees (a parent with open children, or a planned branch point with expects_children, is preserved).

expectsChildren

Marks a movement as a planned branch point. While expects_children = true, the movement is protected from auto-supersession even when childless. The flag is sticky — set once at creation, persists for the row's lifetime.

passIfNotCurrent()

Only creates a new movement if the current one (most recent open root for $model, most recent open child for $movement) doesn't already match the given status and actor:

Reading movements and the tree

On a Movement:

Query scopes on Movement:

Scope Filter
roots() whereNull('parent_id')
open() whereNull('completed_at')
closed() whereNotNull('completed_at')

Computed attributes on Movement

period and hours are stored on save only when completed_at is set. Incomplete movements have null for both; the on-the-fly accessors below fall back to live calculation.

Attribute Description
period Stored on save. Ratio of worked time to scheduled time on a completed movement.
hours Stored on save. Worked time in hours.
interval Accessor. hours * 3600 in seconds.
formatted_period Accessor. Falls back to an on-the-fly calculation for incomplete movements.
formatted_interval Accessor. Human-readable duration (e.g. 2 hours 15 minutes).
formatted_received_at Accessor. received_at formatted via config('kpi.formats.datetime').

For trees: a parent's hours is inclusive — it covers the full duration the parent was open, including time its open children were active. So parent.hours ≥ Σ children.hours.

Events

Passed

Fires after every successful pass():

$previous is null when no supersession actually fired — i.e., when the previous candidate had open children, was marked expects_children = true, or when supersede: false was passed. Consumers wanting the chain pointer regardless of closure should read $current->previous (the existing belongsTo).

Completed

Fires once per movement closure, regardless of trigger:

Triggers:

Cascaded closures fire Completed for each descendant — useful for syncing per-movement state to external systems.

Testing

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.


All versions of laravel-kpi with dependencies

PHP Build Version
Package Version
Requires php Version ^8.4
spatie/laravel-package-tools Version ^1.16
illuminate/contracts Version ^12.0||^13.0
ext-bcmath Version *
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package hasyirin/laravel-kpi contains the following files

Loading the files please wait ...