Compare commits

..

3 Commits

Author SHA1 Message Date
Ronald A Richardson
f4715d137f fix: update schedule-item model default status from 'pending' to 'scheduled' 2026-04-05 04:29:00 -04:00
Ronald A Richardson
a83f1f7079 fix: use start_at/end_at in schedule-exception model to match backend schema
The backend schedule_exceptions table uses start_at/end_at timestamp columns,
not start_date/end_date. Update the Ember model to match.
2026-04-05 03:51:46 -04:00
Ronald A Richardson
7f5242fff4 feat: update scheduling Ember Data models for robust recurring schedule framework
- schedule-item: add schedule_template_uuid, title, notes, is_exception,
  exception_for_date fields; async relationships to schedule and scheduleTemplate
- schedule: add last_materialized_at, materialization_horizon_days fields;
  add exceptions hasMany relationship; make relationships async
- schedule-template: add break_start_time, break_end_time, color fields;
  add recurrenceSummary computed getter for human-readable RRULE display;
  make company relationship async
- schedule-exception: new model for time-off requests, sick leave, holidays,
  shift swaps and other deviations from recurring schedule patterns;
  includes isPending/isApproved/isRejected and typeLabel getters

These changes align the Ember Data layer with the core-api scheduling
framework refactoring (dev-v1.6.39) that introduces RRULE-based
materialization and exception handling.
2026-04-04 23:46:06 -04:00
21 changed files with 660 additions and 2894 deletions

View File

@@ -240,6 +240,10 @@ jobs:
set -u
DEPLOY_BUCKET=${STATIC_DEPLOY_BUCKET:-${{ env.PROJECT }}-${{ env.STACK }}}
NEW_BUCKET="${PROJECT}-${STACK}-console"
if aws s3api head-bucket --bucket "$NEW_BUCKET" 2>/dev/null; then
DEPLOY_BUCKET="$NEW_BUCKET"
fi
# this value will come from the dotenv above
echo "Deploying to $DEPLOY_BUCKET"

View File

@@ -22,18 +22,9 @@
"appstract/laravel-opcache": "^4.0",
"fleetbase/core-api": "^1.6.38",
"fleetbase/fleetops-api": "^0.6.38",
"fleetbase/registry-bridge": "^0.1.9",
"fleetbase/registry-bridge": "^0.1.8",
"fleetbase/storefront-api": "^0.4.14",
"fleetbase/ledger-api": "^0.0.1",
"fleetbase/aws-marketplace": "^0.0.8",
"fleetbase/billing-api": "^0.1.21",
"fleetbase/customer-portal-api": "^0.0.10",
"fleetbase/flespi-integration": "^0.1.16",
"fleetbase/internals-api": "^0.0.29",
"fleetbase/samsara-api": "^0.0.3",
"fleetbase/solid-api": "^0.0.7",
"fleetbase/valhalla-api": "^0.0.3",
"fleetbase/vroom-api": "^0.0.3",
"guzzlehttp/guzzle": "^7.0.1",
"laravel/framework": "^10.0",
"laravel/octane": "^2.3",
@@ -59,18 +50,6 @@
"phpunit/phpunit": "^10.0"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/fleetbase/aws-marketplace"
},
{
"type": "vcs",
"url": "https://github.com/fleetbase/internals"
},
{
"type": "vcs",
"url": "https://github.com/fleetbase/billing"
},
{
"type": "composer",
"url": "https://registry.fleetbase.io"

1562
api/composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,69 @@
import Model, { attr, belongsTo } from '@ember-data/model';
/**
* ScheduleException
*
* Represents a deviation from a driver's recurring schedule — time off requests,
* sick leave, holidays, shift swaps, training days, or other exceptions.
*
* An exception is submitted by a driver or dispatcher, then approved or rejected
* by a manager. When approved, all ScheduleItem records that fall within the
* exception's date range are automatically cancelled.
*
* @see core-api ScheduleException model
*/
export default class ScheduleExceptionModel extends Model {
/** @ids */
@attr('string') public_id;
@attr('string') schedule_uuid;
@attr('string') subject_uuid;
@attr('string') subject_type;
@attr('string') requested_by_uuid;
/** @attributes */
@attr('string', { defaultValue: 'time_off' }) type;
@attr('string', { defaultValue: 'pending' }) status;
@attr('date') start_at;
@attr('date') end_at;
@attr('string') reason;
@attr('string') notes;
@attr('string') rejection_reason;
@attr('date') reviewed_at;
@attr('string') reviewed_by_uuid;
/** @meta */
@attr('object') meta;
/** @relationships */
@belongsTo('schedule', { async: true }) schedule;
/** @dates */
@attr('date') created_at;
@attr('date') updated_at;
@attr('date') deleted_at;
/** @computed */
get isPending() {
return this.status === 'pending';
}
get isApproved() {
return this.status === 'approved';
}
get isRejected() {
return this.status === 'rejected';
}
get typeLabel() {
const labels = {
time_off: 'Time Off',
sick_leave: 'Sick Leave',
holiday: 'Holiday',
swap: 'Shift Swap',
training: 'Training',
other: 'Other',
};
return labels[this.type] || this.type;
}
}

View File

@@ -1,8 +1,10 @@
import Model, { attr, belongsTo } from '@ember-data/model';
export default class ScheduleItemModel extends Model {
/** @ids */
@attr('string') public_id;
@attr('string') schedule_uuid;
@attr('string') schedule_template_uuid;
@attr('string') assignee_uuid;
@attr('string') assignee_type;
@attr('string') resource_uuid;
@@ -12,10 +14,20 @@ export default class ScheduleItemModel extends Model {
@attr('number') duration;
@attr('date') break_start_at;
@attr('date') break_end_at;
@attr('string', { defaultValue: 'pending' }) status;
@attr('string') title;
@attr('string', { defaultValue: 'scheduled' }) status;
@attr('string') notes;
/** @exception fields */
@attr('boolean', { defaultValue: false }) is_exception;
@attr('date') exception_for_date;
/** @meta */
@attr('object') meta;
@belongsTo('schedule') schedule;
/** @relationships */
@belongsTo('schedule', { async: true }) schedule;
@belongsTo('schedule-template', { async: true }) scheduleTemplate;
@attr('date') created_at;
@attr('date') updated_at;

View File

@@ -1,22 +1,51 @@
import Model, { attr, belongsTo } from '@ember-data/model';
/**
* ScheduleTemplate
*
* A reusable recurring shift pattern that a manager can apply to one or many
* drivers. Stores the RRULE (e.g. FREQ=WEEKLY;BYDAY=MO,TU,TH) plus the
* daily start/end times. When applied to a driver's Schedule, the
* ScheduleService materialises ScheduleItem records for the rolling horizon.
*/
export default class ScheduleTemplateModel extends Model {
/** @ids */
@attr('string') public_id;
@attr('string') company_uuid;
@attr('string') subject_uuid;
@attr('string') subject_type;
/** @attributes */
@attr('string') name;
@attr('string') description;
@attr('string') start_time;
@attr('string') end_time;
@attr('number') duration;
@attr('number') break_duration;
@attr('string') rrule;
@attr('string') start_time; // HH:mm — e.g. "08:00"
@attr('string') end_time; // HH:mm — e.g. "16:00"
@attr('string') break_start_time; // HH:mm — e.g. "12:00" (optional)
@attr('string') break_end_time; // HH:mm — e.g. "13:00" (optional)
@attr('number') duration; // computed minutes (end - start)
@attr('number') break_duration; // minutes
@attr('string') rrule; // RFC 5545 RRULE string
@attr('string') color; // hex color for calendar display
@attr('object') meta;
@belongsTo('company') company;
/** @relationships */
@belongsTo('company', { async: true }) company;
/** @dates */
@attr('date') created_at;
@attr('date') updated_at;
@attr('date') deleted_at;
/**
* Parse the RRULE string and return a human-readable summary.
* e.g. "Weekly on Mon, Tue, Thu · 08:0016:00"
*/
get recurrenceSummary() {
if (!this.rrule) return null;
const dayMap = { MO: 'Mon', TU: 'Tue', WE: 'Wed', TH: 'Thu', FR: 'Fri', SA: 'Sat', SU: 'Sun' };
const byday = this.rrule.match(/BYDAY=([^;]+)/);
const days = byday ? byday[1].split(',').map((d) => dayMap[d] || d).join(', ') : '';
const freq = this.rrule.match(/FREQ=(\w+)/);
const freqLabel = freq ? freq[1].charAt(0) + freq[1].slice(1).toLowerCase() : '';
const times = this.start_time && this.end_time ? ` · ${this.start_time}${this.end_time}` : '';
return `${freqLabel} on ${days}${times}`;
}
}

View File

@@ -16,9 +16,14 @@ export default class ScheduleModel extends Model {
@attr('string', { defaultValue: 'draft' }) status;
@attr('object') meta;
/** @materialization tracking */
@attr('date') last_materialized_at;
@attr('number', { defaultValue: 60 }) materialization_horizon_days;
/** @relationships */
@hasMany('schedule-item') items;
@belongsTo('company') company;
@hasMany('schedule-item', { async: true }) items;
@hasMany('schedule-exception', { async: true }) exceptions;
@belongsTo('company', { async: true }) company;
/** @dates */
@attr('date') created_at;

View File

@@ -1,9 +1,8 @@
API_HOST=https://api.fleetbase.io
API_HOST=
API_NAMESPACE=int/v1
API_SECURE=true
SOCKETCLUSTER_PATH=/socketcluster/
SOCKETCLUSTER_HOST=socket.fleetbase.io
SOCKETCLUSTER_HOST=
SOCKETCLUSTER_SECURE=true
SOCKETCLUSTER_PORT=8000
OSRM_HOST=https://router.project-osrm.org
DISABLE_RUNTIME_CONFIG=true
SOCKETCLUSTER_PORT=38000
OSRM_HOST=https://router.project-osrm.org

View File

@@ -1,9 +0,0 @@
API_HOST=https://api.qa.fleetbase.io
API_NAMESPACE=int/v1
API_SECURE=true
SOCKETCLUSTER_PATH=/socketcluster/
SOCKETCLUSTER_HOST=socket.qa.fleetbase.io
SOCKETCLUSTER_SECURE=true
SOCKETCLUSTER_PORT=8000
OSRM_HOST=https://router.project-osrm.org
DISABLE_RUNTIME_CONFIG=true

View File

@@ -33,25 +33,16 @@
},
"dependencies": {
"@ember/legacy-built-in-components": "^0.4.2",
"@fleetbase/aws-marketplace": "^0.0.8",
"@fleetbase/billing-engine": "^0.1.21",
"@fleetbase/customer-portal-engine": "^0.0.10",
"@fleetbase/dev-engine": "^0.2.13",
"@fleetbase/ember-core": "^0.3.18",
"@fleetbase/ember-ui": "^0.3.25",
"@fleetbase/fleetops-data": "^0.1.25",
"@fleetbase/fleetops-engine": "^0.6.38",
"@fleetbase/iam-engine": "^0.1.8",
"@fleetbase/flespi-engine": "^0.1.16",
"@fleetbase/internals-engine": "^0.0.29",
"@fleetbase/leaflet-routing-machine": "^3.2.17",
"@fleetbase/registry-bridge-engine": "^0.1.8",
"@fleetbase/samsara-engine": "^0.0.3",
"@fleetbase/solid-engine": "^0.0.7",
"@fleetbase/storefront-engine": "^0.4.14",
"@fleetbase/valhalla-engine": "^0.0.3",
"@fleetbase/vroom-engine": "^0.0.3",
"@fleetbase/ledger-engine": "^0.0.1",
"@fleetbase/registry-bridge-engine": "^0.1.8",
"@fleetbase/storefront-engine": "^0.4.14",
"@formatjs/intl-datetimeformat": "^6.18.2",
"@formatjs/intl-numberformat": "^8.15.6",
"@formatjs/intl-pluralrules": "^5.4.6",

1775
console/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff