kennofizet/workpoint-backend

Workpoint Record backend for Laravel — record workpoint events with check rules, zone/server scoped via packages-core

Maintainers

Package info

github.com/kennofizet/point-backend

pkg:composer/kennofizet/workpoint-backend

Statistics

Installs: 53

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main 2026-05-07 05:56 UTC

This package is auto-updated.

Last update: 2026-05-07 06:00:01 UTC


README

Laravel package to record workpoints (points) for users with configurable rules. Zone-scoped via packages-core. Use the HasWorkpointRecords trait to record points and expose top ranking + rules APIs.

Requirements

  • PHP 8.2+, Laravel 12.x
  • kennofizet/packages-core-backend (zone scoping + API auth)

Install

composer require kennofizet/workpoint-backend
php artisan vendor:publish --tag=workpoint-config
php artisan vendor:publish --tag=workpoint-cases-config
php artisan vendor:publish --tag=workpoint-migrations
php artisan migrate

Note: workpoint-config publishes config/workpoint.php (and packages-core.php) only. config/workpoint_cases.php is on a separate tag (workpoint-cases-config) so your existing rules file is not overwritten when you run --tag=workpoint-config --force.

Optional .env (defaults are fine for most):

WORKPOINT_TABLE=workpoint_records
WORKPOINT_PERIOD_TOTALS_TABLE=workpoint_period_totals
WORKPOINT_ZONE_CASES_TABLE=workpoint_zone_cases
WORKPOINT_API_PREFIX=workpoint
WORKPOINT_USE_PERIOD_TOTALS_TABLE=true
WORKPOINT_HISTORY_PER_PAGE=30
WORKPOINT_ADMIN_MEMBERS_PER_PAGE=30

User display column comes from packages-core config:

  • packages-core.user_col_name (default: name)
  • packages-core.table_user (user table used by core)

Season context also comes from packages-core:

  • Active season in current zone is used by default.
  • workpoint_records + workpoint_period_totals are season-scoped via season_id.

Config

  • config/workpoint.php — Tables, API prefix, period totals, event/listeners, rules map.
  • config/workpoint_cases.php — Action keys (e.g. task_completed_on_time) with points, check, period, cap, optional limit_period (day|week|month|year) and limit_period_time (max recordings per user per zone in that window, enforced after the rule check; omit or 0 for no extra limit), and descriptions (vi/en). Per-zone overrides stored in DB; defaults from this file.

Usage in code

1. Add trait to the model that earns points (e.g. User):

use Kennofizet\Workpoint\Traits\HasWorkpointRecords;

class User extends Authenticatable
{
    use HasWorkpointRecords;
}

2. Record workpoints (action key must exist in workpoint_cases.php):

$user->recordWorkpoint('app_first_visit_day');                    // no target
$user->recordWorkpoint('task_completed_on_time', $task);          // with target
$user->recordWorkpoint('task_accepted_sla', $task, ['sla' => 24]); // with payload

// Optional zone list (multi-zone record). Invalid/non-member zones are skipped silently.
$user->recordWorkpoint('task_completed_on_time', $task, [], [1, 2, 3]);

// Null zone list (or [null]) means: record across all zones that belong to this user.
$user->recordWorkpoint('app_first_visit_day', null, [], null);

Returns WorkpointRecord or null if skipped/disallowed.

workpoint_records and workpoint_period_totals now store user_id and all checks/queries use user_id as the primary key. subject_type/subject_id stay for polymorphic relation metadata only.

When request context has currentUserId, backend always uses that user first. If currentUserZoneId exists, it records only in that zone; otherwise it uses all zones of that current user. Only when currentUserId is null will backend use passed userId + zoneIds.

3. Check if a record already exists (by user_id; same action_key / target shape as when recording — does not re-run rule logic, only looks for a row):

use App\Constants\ProjectConstant;

if ($user->hasWorkpointRecord(ProjectConstant::$workpointCase['project_confirm_assign'], $project)) {
    // already has a workpoint row for this case + project in this zone
}

if ($user->hasWorkpointRecord('app_first_visit_day')) {
    // no-target case: matches rows with null target
}

// Optional: explicit zoneIds and/or userId
$user->hasWorkpointRecord('task_completed_on_time', $task, [1, 2], 123);

4. Get zone IDs that can record this action now (after membership filter + rule check):

// Returns only zone IDs where this action is currently allowed for this user.
$allowedZoneIds = $user->canWorkpointRecordZoneIds('task_completed_on_time', $task);

// Optional: limit candidate zones and/or force user id.
$allowedZoneIds = $user->canWorkpointRecordZoneIds(
    'task_completed_on_time',
    $task,
    [1, 2, 3], // optional candidate zones
    123        // optional user id override
);

canWorkpointRecordZoneIds() returns array<int, int>:

  • First filters zones to zones the user is a member of.
  • Then checks each zone with the configured rule (check) for that action key.
  • Returns only zones that pass the rule at call time.

API

All under {packages-core.api_prefix}/{workpoint.api_prefix}/ (e.g. api/knf/workpoint). Requires X-Knf-Token; zone from X-Knf-Zone-Id.

Method Endpoint Description
GET top?period=day|week|month|year&limit=10 Top users by points in period (zone + season scoped).
GET rules?language=vi|en Merged rules (default + zone overrides). Returns rules, language, isManager.
POST rules/save Save one zone case override (manager). Zone from X-Knf-Zone-Id. Body: case_key, points, check, period?, cap?, descriptions?.
POST rules/reset Reset zone rules to default for the current zone (manager). Zone from X-Knf-Zone-Id; no body required.
GET history/me/summary?language=vi|en Current user summary: totals, ranks, today_by_rule, isManager.
GET history/me/logs?period=day|week|month|year&cursor=&language=vi|en Current user logs only (cursor pagination).
GET history/user/{userId}/summary?language= Summary for one user (manager).
GET history/user/{userId}/logs?period=&cursor=&language= Logs for one user (manager).
GET admin/members?cursor= Manager only. Cursor-paginated users who have workpoints in the zone (next_cursor is base64 JSON).
GET seasons Manager only. Seasons list of current zone.
POST seasons Manager only. Create a new season (name, optional starts_at, ends_at) and make it active.
POST seasons/{seasonId}/activate Manager only. Set existing season as active for current zone.

History summary responses include today_by_rule: per rule, earned is points earned today; max_points is the max total points for that rule when the check is capped — for count_cap_per_period it is points × cap (cap = max awards per period); for first_time / first_time_per_period / first_time_per_target it is points (one award). If limit_period_time is set, an additional ceiling points × limit_period_time applies; when both a count rule and a limit apply, the smaller ceiling is shown. max_points is null when unlimited (none or no cap on count rules and no limit period cap).

Backend always resolves season from active season of current zone.

Summary

Step Action
Install composer require kennofizet/workpoint-backend
Config php artisan vendor:publish --tag=workpoint-config
Cases config (optional first publish) php artisan vendor:publish --tag=workpoint-cases-config
Migrations php artisan vendor:publish --tag=workpoint-migrations then php artisan migrate
Model use HasWorkpointRecords;$model->recordWorkpoint(...), $model->hasWorkpointRecord($key, $target?), $model->canWorkpointRecordZoneIds($key, $target?)

More in config file comments (events, listeners, rules).