From cd145523c30d2c53b64af8659cc6831624fb306e Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Fri, 1 May 2026 16:43:44 +0800 Subject: [PATCH 1/3] Initialize recurring order schedules feature branch From 253fa94137d744bb8802fa254834a6be8e839728 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Fri, 1 May 2026 17:22:47 +0800 Subject: [PATCH 2/3] Add recurring scheduled order infrastructure --- addon/components/layout/fleet-ops-sidebar.js | 11 +- addon/components/order/details/detail.hbs | 9 + addon/components/order/panel-header.hbs | 3 + .../recurring-order-schedule/details.hbs | 82 +++++ .../recurring-order-schedule/details.js | 24 ++ .../recurring-order-schedule/form.hbs | 136 +++++++++ .../recurring-order-schedule/form.js | 160 ++++++++++ .../operations/orders/index/details.js | 8 + .../operations/recurring-orders/index.js | 106 +++++++ .../recurring-orders/index/details.js | 39 +++ .../operations/recurring-orders/index/edit.js | 39 +++ .../operations/recurring-orders/index/new.js | 36 +++ addon/models/recurring-order-schedule.js | 50 +++ addon/routes.js | 9 + .../operations/recurring-orders/index.js | 21 ++ .../recurring-orders/index/details.js | 29 ++ .../recurring-orders/index/details/index.js | 3 + .../operations/recurring-orders/index/edit.js | 29 ++ .../operations/recurring-orders/index/new.js | 21 ++ .../recurring-order-schedule-actions.js | 113 +++++++ .../templates/operations/recurring-orders.hbs | 1 + .../operations/recurring-orders/index.hbs | 13 + .../recurring-orders/index/details.hbs | 10 + .../recurring-orders/index/details/index.hbs | 1 + .../recurring-orders/index/edit.hbs | 11 + .../operations/recurring-orders/index/new.hbs | 10 + addon/utils/recurring-order-blueprint.js | 145 +++++++++ addon/utils/recurring-rrule.js | 57 ++++ server/config/fleetops.php | 9 + ...create_recurring_order_schedules_table.php | 57 ++++ ...rring_order_schedule_occurrences_table.php | 38 +++ ...ecurring_order_columns_to_orders_table.php | 24 ++ .../Commands/MaterializeRecurringOrders.php | 26 ++ .../v1/RecurringOrderScheduleController.php | 251 +++++++++++++++ server/src/Http/Resources/v1/Index/Order.php | 3 + .../v1/Index/RecurringOrderSchedule.php | 38 +++ server/src/Http/Resources/v1/Order.php | 6 + .../Resources/v1/RecurringOrderSchedule.php | 52 ++++ server/src/Models/Order.php | 14 + server/src/Models/RecurringOrderSchedule.php | 234 ++++++++++++++ .../RecurringOrderScheduleOccurrence.php | 58 ++++ .../src/Providers/FleetOpsServiceProvider.php | 2 + .../RecurringOrderMaterializationService.php | 287 ++++++++++++++++++ server/src/routes.php | 8 + server/tests/RecurringOrderScheduleTest.php | 48 +++ tests/unit/utils/recurring-rrule-test.js | 24 ++ translations/en-us.yaml | 3 + 47 files changed, 2357 insertions(+), 1 deletion(-) create mode 100644 addon/components/recurring-order-schedule/details.hbs create mode 100644 addon/components/recurring-order-schedule/details.js create mode 100644 addon/components/recurring-order-schedule/form.hbs create mode 100644 addon/components/recurring-order-schedule/form.js create mode 100644 addon/controllers/operations/recurring-orders/index.js create mode 100644 addon/controllers/operations/recurring-orders/index/details.js create mode 100644 addon/controllers/operations/recurring-orders/index/edit.js create mode 100644 addon/controllers/operations/recurring-orders/index/new.js create mode 100644 addon/models/recurring-order-schedule.js create mode 100644 addon/routes/operations/recurring-orders/index.js create mode 100644 addon/routes/operations/recurring-orders/index/details.js create mode 100644 addon/routes/operations/recurring-orders/index/details/index.js create mode 100644 addon/routes/operations/recurring-orders/index/edit.js create mode 100644 addon/routes/operations/recurring-orders/index/new.js create mode 100644 addon/services/recurring-order-schedule-actions.js create mode 100644 addon/templates/operations/recurring-orders.hbs create mode 100644 addon/templates/operations/recurring-orders/index.hbs create mode 100644 addon/templates/operations/recurring-orders/index/details.hbs create mode 100644 addon/templates/operations/recurring-orders/index/details/index.hbs create mode 100644 addon/templates/operations/recurring-orders/index/edit.hbs create mode 100644 addon/templates/operations/recurring-orders/index/new.hbs create mode 100644 addon/utils/recurring-order-blueprint.js create mode 100644 addon/utils/recurring-rrule.js create mode 100644 server/migrations/2026_05_01_000001_create_recurring_order_schedules_table.php create mode 100644 server/migrations/2026_05_01_000002_create_recurring_order_schedule_occurrences_table.php create mode 100644 server/migrations/2026_05_01_000003_add_recurring_order_columns_to_orders_table.php create mode 100644 server/src/Console/Commands/MaterializeRecurringOrders.php create mode 100644 server/src/Http/Controllers/Internal/v1/RecurringOrderScheduleController.php create mode 100644 server/src/Http/Resources/v1/Index/RecurringOrderSchedule.php create mode 100644 server/src/Http/Resources/v1/RecurringOrderSchedule.php create mode 100644 server/src/Models/RecurringOrderSchedule.php create mode 100644 server/src/Models/RecurringOrderScheduleOccurrence.php create mode 100644 server/src/Support/RecurringOrderMaterializationService.php create mode 100644 server/tests/RecurringOrderScheduleTest.php create mode 100644 tests/unit/utils/recurring-rrule-test.js diff --git a/addon/components/layout/fleet-ops-sidebar.js b/addon/components/layout/fleet-ops-sidebar.js index 841108489..660f1af3f 100644 --- a/addon/components/layout/fleet-ops-sidebar.js +++ b/addon/components/layout/fleet-ops-sidebar.js @@ -73,6 +73,15 @@ export default class LayoutFleetOpsSidebarComponent extends Component { }, { priority: 3, + intl: 'menu.recurring-orders', + title: this.intl.t('menu.recurring-orders'), + icon: 'arrows-rotate', + route: 'operations.recurring-orders', + permission: 'fleet-ops list recurring-order-schedule', + visible: this.abilities.can('fleet-ops see recurring-order-schedule'), + }, + { + priority: 4, intl: 'menu.order-config', title: this.intl.t('menu.order-config'), icon: 'diagram-project', @@ -81,7 +90,7 @@ export default class LayoutFleetOpsSidebarComponent extends Component { visible: this.abilities.can('fleet-ops see order-config'), }, { - priority: 4, + priority: 5, intl: 'menu.service-rates', title: this.intl.t('menu.service-rates'), icon: 'file-invoice-dollar', diff --git a/addon/components/order/details/detail.hbs b/addon/components/order/details/detail.hbs index 0312d0dc2..def12ee0d 100644 --- a/addon/components/order/details/detail.hbs +++ b/addon/components/order/details/detail.hbs @@ -7,6 +7,9 @@ {{#if @resource.adhoc}} {{t "order.fields.ad-hoc"}} {{/if}} + {{#if @resource.meta.is_recurring_generated}} + Recurring + {{/if}} <:default> @@ -111,6 +114,12 @@
{{t "common.type"}}
{{n-a (humanize @resource.type)}}
+ {{#if @resource.meta.is_recurring_generated}} +
+
Recurring Schedule
+
{{n-a @resource.meta.recurring_order_schedule_public_id @resource.meta.recurring_order_schedule_uuid}}
+
+ {{/if}} {{#if @resource.pod_required}}
{{t "order.fields.proof-of-delivery"}}
diff --git a/addon/components/order/panel-header.hbs b/addon/components/order/panel-header.hbs index 21940ee15..44ae0d1df 100644 --- a/addon/components/order/panel-header.hbs +++ b/addon/components/order/panel-header.hbs @@ -10,6 +10,9 @@
{{@resource.tracking}}
+ {{#if @resource.meta.is_recurring_generated}} + Recurring + {{/if}} {{#if @resource.dispatched_at}} {{concat "Dispatched at " @resource.dispatchedAt}} {{/if}} diff --git a/addon/components/recurring-order-schedule/details.hbs b/addon/components/recurring-order-schedule/details.hbs new file mode 100644 index 000000000..484177fa7 --- /dev/null +++ b/addon/components/recurring-order-schedule/details.hbs @@ -0,0 +1,82 @@ +
+ +
+
+
ID
+
{{n-a @resource.public_id}}
+
+
+
Status
+
{{smart-humanize @resource.status}}
+
+
+
Timezone
+
{{n-a @resource.timezone}}
+
+
+
Next Occurrence
+
{{n-a @resource.next_occurrence_at}}
+
+
+
Customer
+
{{n-a @resource.customer.name}}
+
+
+
Order Type
+
{{n-a @resource.order_config.name}}
+
+
+
Service Rate
+
{{n-a @resource.service_rate.service_name "No default service rate"}}
+
+
+
Starts At
+
{{n-a @resource.starts_at}}
+
+
+
Ends At
+
{{n-a @resource.ends_at}}
+
+
+
Recurrence Rule
+
{{n-a @resource.rrule}}
+
+
+
+ + + {{#if this.upcomingOccurrences.length}} +
+ {{#each this.upcomingOccurrences as |occurrence|}} +
+
+
{{n-a occurrence.occurrence_at_local occurrence.occurrence_at}}
+
Status: {{smart-humanize occurrence.status}}
+ {{#if occurrence.order}} +
Order: {{occurrence.order.public_id}}
+ {{/if}} +
+
+ {{#if occurrence.order}} +
+
+ {{/each}} +
+ {{else}} +
No upcoming occurrences found for this recurring schedule.
+ {{/if}} +
+ + {{#if @resource.description}} + +
{{@resource.description}}
+
+ {{/if}} + + +
\ No newline at end of file diff --git a/addon/components/recurring-order-schedule/details.js b/addon/components/recurring-order-schedule/details.js new file mode 100644 index 000000000..7e18fa1f1 --- /dev/null +++ b/addon/components/recurring-order-schedule/details.js @@ -0,0 +1,24 @@ +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; + +export default class RecurringOrderScheduleDetailsComponent extends Component { + @service recurringOrderScheduleActions; + @service hostRouter; + + get upcomingOccurrences() { + return this.args.resource?.upcoming_occurrences ?? this.args.resource?.meta?.upcoming_occurrences ?? []; + } + + @action skipOccurrence(occurrence) { + return this.recurringOrderScheduleActions.skipOccurrence(this.args.resource, occurrence.occurrence_at); + } + + @action viewOrder(order) { + if (!order?.public_id) { + return; + } + + return this.hostRouter.transitionTo('console.fleet-ops.operations.orders.index.details', order.public_id); + } +} diff --git a/addon/components/recurring-order-schedule/form.hbs b/addon/components/recurring-order-schedule/form.hbs new file mode 100644 index 000000000..87231bbdf --- /dev/null +++ b/addon/components/recurring-order-schedule/form.hbs @@ -0,0 +1,136 @@ +
+ +
+ + + + + +