@fleetbase/fleetops-engine 0.6.44 → 0.6.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/addon/components/entity/form.hbs +24 -0
- package/addon/components/entity/form.js +30 -0
- package/addon/components/leaflet-draw-control.js +1 -1
- package/addon/components/map/drawer/geofence-event-listing.js +5 -2
- package/addon/components/order/form/service-rate.hbs +1 -1
- package/addon/components/service-rate/details.hbs +6 -6
- package/addon/components/service-rate/form.hbs +28 -15
- package/addon/controllers/operations/orders/index/details/virtual.js +5 -0
- package/addon/controllers/operations/orders/index/details.js +27 -2
- package/addon/controllers/operations/service-rates/index/edit.js +1 -0
- package/addon/controllers/operations/service-rates/index/new.js +1 -0
- package/addon/helpers/f-to-int.js +6 -0
- package/addon/routes/application.js +3 -3
- package/addon/routes/operations/orders/index/details/virtual.js +1 -0
- package/addon/routes/operations/orders/index/details.js +19 -3
- package/addon/services/equipment-actions.js +4 -6
- package/addon/services/leaflet-map-manager.js +4 -0
- package/addon/services/maintenance-actions.js +4 -6
- package/addon/services/part-actions.js +4 -6
- package/addon/services/service-rate-actions.js +8 -3
- package/addon/templates/operations/orders/index/details/virtual.hbs +7 -1
- package/addon/utils/fleet-ops-options.js +33 -0
- package/addon/utils/serialize-payload.js +1 -1
- package/app/controllers/operations/orders/index/details/virtual.js +1 -0
- package/app/helpers/f-to-int.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +2 -2
- package/server/src/Http/Controllers/Api/v1/OrderController.php +5 -5
- package/server/src/Http/Controllers/Api/v1/PurchaseRateController.php +11 -3
- package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +4 -0
- package/server/src/Http/Controllers/Api/v1/ServiceRateController.php +6 -1
- package/server/src/Http/Requests/CreateServiceRateRequest.php +2 -2
- package/server/src/Http/Resources/v1/Order.php +0 -1
- package/server/src/Http/Resources/v1/ServiceQuote.php +2 -2
- package/server/src/Models/Entity.php +7 -0
- package/server/src/Models/Order.php +39 -7
- package/server/src/Models/ServiceRate.php +122 -30
- package/server/src/Observers/PurchaseRateObserver.php +3 -5
- package/server/src/Rules/ComputableAlgo.php +1 -3
- package/server/src/Support/Algo.php +87 -1
- package/server/src/Support/Utils.php +35 -0
|
@@ -321,7 +321,7 @@ class OrderController extends Controller
|
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
// load required relations
|
|
324
|
-
$order->load(['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate', 'customer', 'facilitator']);
|
|
324
|
+
$order->load(['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate.serviceQuote.items', 'customer', 'facilitator']);
|
|
325
325
|
|
|
326
326
|
// Determine if order should be dispatched on creation
|
|
327
327
|
$shouldDispatch = $request->boolean('dispatch') && $integratedVendorOrder === null;
|
|
@@ -364,7 +364,7 @@ class OrderController extends Controller
|
|
|
364
364
|
|
|
365
365
|
// find for the order
|
|
366
366
|
try {
|
|
367
|
-
$order = Order::findRecordOrFail($id, ['trackingNumber', 'driverAssigned', 'purchaseRate', 'customer', 'facilitator']);
|
|
367
|
+
$order = Order::findRecordOrFail($id, ['trackingNumber', 'driverAssigned', 'purchaseRate.serviceQuote.items', 'customer', 'facilitator']);
|
|
368
368
|
} catch (ModelNotFoundException $exception) {
|
|
369
369
|
return response()->json(
|
|
370
370
|
[
|
|
@@ -549,7 +549,7 @@ class OrderController extends Controller
|
|
|
549
549
|
$order->flushAttributesCache();
|
|
550
550
|
|
|
551
551
|
// load required relations
|
|
552
|
-
$order->load(['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate', 'customer', 'facilitator']);
|
|
552
|
+
$order->load(['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate.serviceQuote.items', 'customer', 'facilitator']);
|
|
553
553
|
|
|
554
554
|
// response the order resource
|
|
555
555
|
return new OrderResource($order);
|
|
@@ -759,7 +759,7 @@ class OrderController extends Controller
|
|
|
759
759
|
{
|
|
760
760
|
// find for the order
|
|
761
761
|
try {
|
|
762
|
-
$order = Order::findRecordOrFail($id, ['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate', 'customer', 'facilitator']);
|
|
762
|
+
$order = Order::findRecordOrFail($id, ['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate.serviceQuote.items', 'customer', 'facilitator']);
|
|
763
763
|
} catch (ModelNotFoundException $exception) {
|
|
764
764
|
return response()->json(
|
|
765
765
|
[
|
|
@@ -839,7 +839,7 @@ class OrderController extends Controller
|
|
|
839
839
|
public function dispatchOrder(string $id)
|
|
840
840
|
{
|
|
841
841
|
try {
|
|
842
|
-
$order = Order::findRecordOrFail($id, ['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate', 'customer', 'facilitator']);
|
|
842
|
+
$order = Order::findRecordOrFail($id, ['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate.serviceQuote.items', 'customer', 'facilitator']);
|
|
843
843
|
} catch (ModelNotFoundException $exception) {
|
|
844
844
|
return response()->json(
|
|
845
845
|
[
|
|
@@ -41,10 +41,19 @@ class PurchaseRateController extends Controller
|
|
|
41
41
|
|
|
42
42
|
// order assignment
|
|
43
43
|
if ($request->has('order')) {
|
|
44
|
-
$
|
|
44
|
+
$orderUuid = Utils::getUuid('orders', [
|
|
45
45
|
'public_id' => $request->input('order'),
|
|
46
46
|
'company_uuid' => session('company'),
|
|
47
47
|
]);
|
|
48
|
+
|
|
49
|
+
$order = Order::where('uuid', $orderUuid)->first();
|
|
50
|
+
|
|
51
|
+
if ($order instanceof Order) {
|
|
52
|
+
$input['payload_uuid'] = $order->payload_uuid;
|
|
53
|
+
$input['customer_uuid'] = $order->customer_uuid;
|
|
54
|
+
$input['customer_type'] = $order->customer_type;
|
|
55
|
+
$input['company_uuid'] = $order->company_uuid ?? $input['company_uuid'];
|
|
56
|
+
}
|
|
48
57
|
} elseif ($createOrder) {
|
|
49
58
|
// create order from service quote
|
|
50
59
|
$serviceQuote = ServiceQuote::where('uuid', $input['service_quote_uuid'])->orWhere('public_id', $request->input('service_quote'))->first();
|
|
@@ -70,9 +79,8 @@ class PurchaseRateController extends Controller
|
|
|
70
79
|
// create the purchaseRate
|
|
71
80
|
$purchaseRate = PurchaseRate::create($input);
|
|
72
81
|
|
|
73
|
-
// set purchase rate to order
|
|
74
82
|
if ($order instanceof Order) {
|
|
75
|
-
$order->
|
|
83
|
+
$order->attachPurchaseRate($purchaseRate);
|
|
76
84
|
}
|
|
77
85
|
|
|
78
86
|
// response the driver resource
|
|
@@ -98,6 +98,7 @@ class ServiceQuoteController extends Controller
|
|
|
98
98
|
'amount' => $subTotal,
|
|
99
99
|
'currency' => $serviceRate->currency,
|
|
100
100
|
]);
|
|
101
|
+
$quote->setRelation('serviceRate', $serviceRate);
|
|
101
102
|
|
|
102
103
|
$items = $lines->map(function ($line) use ($quote) {
|
|
103
104
|
return ServiceQuoteItem::create([
|
|
@@ -143,6 +144,7 @@ class ServiceQuoteController extends Controller
|
|
|
143
144
|
'amount' => $subTotal,
|
|
144
145
|
'currency' => $serviceRate->currency,
|
|
145
146
|
]);
|
|
147
|
+
$quote->setRelation('serviceRate', $serviceRate);
|
|
146
148
|
|
|
147
149
|
$items = $lines->map(function ($line) use ($quote) {
|
|
148
150
|
return ServiceQuoteItem::create([
|
|
@@ -288,6 +290,7 @@ class ServiceQuoteController extends Controller
|
|
|
288
290
|
'amount' => $subTotal,
|
|
289
291
|
'currency' => $serviceRate->currency,
|
|
290
292
|
]);
|
|
293
|
+
$quote->setRelation('serviceRate', $serviceRate);
|
|
291
294
|
|
|
292
295
|
// set preliminary data to meta
|
|
293
296
|
$quote->updateMeta('preliminary_data', $preliminaryData);
|
|
@@ -335,6 +338,7 @@ class ServiceQuoteController extends Controller
|
|
|
335
338
|
'amount' => $subTotal,
|
|
336
339
|
'currency' => $serviceRate->currency,
|
|
337
340
|
]);
|
|
341
|
+
$quote->setRelation('serviceRate', $serviceRate);
|
|
338
342
|
|
|
339
343
|
// set preliminary data to meta
|
|
340
344
|
$quote->updateMeta('preliminary_data', $preliminaryData);
|
|
@@ -30,6 +30,8 @@ class ServiceRateController extends Controller
|
|
|
30
30
|
'rate_calculation_method',
|
|
31
31
|
'currency',
|
|
32
32
|
'base_fee',
|
|
33
|
+
'max_distance_unit',
|
|
34
|
+
'max_distance',
|
|
33
35
|
'per_meter_unit',
|
|
34
36
|
'per_meter_flat_rate_fee',
|
|
35
37
|
'meter_fees',
|
|
@@ -119,7 +121,10 @@ class ServiceRateController extends Controller
|
|
|
119
121
|
'rate_calculation_method',
|
|
120
122
|
'currency',
|
|
121
123
|
'base_fee',
|
|
122
|
-
'
|
|
124
|
+
'max_distance_unit',
|
|
125
|
+
'max_distance',
|
|
126
|
+
'per_meter_unit',
|
|
127
|
+
'per_meter_flat_rate_fee',
|
|
123
128
|
'meter_fees',
|
|
124
129
|
'meter_fees.*.distance',
|
|
125
130
|
'meter_fees.*.fee',
|
|
@@ -31,10 +31,10 @@ class CreateServiceRateRequest extends FleetbaseRequest
|
|
|
31
31
|
'service_type' => [Rule::requiredIf($this->isMethod('POST')), 'string'],
|
|
32
32
|
'service_area' => [Rule::exists('service_areas', 'public_id')->whereNull('deleted_at')],
|
|
33
33
|
'zone' => [Rule::exists('zones', 'public_id')->whereNull('deleted_at')],
|
|
34
|
-
'rate_calculation_method' => [Rule::requiredIf($this->isMethod('POST')), 'string', 'in:fixed_meter,fixed_rate,per_meter,per_drop,algo'],
|
|
34
|
+
'rate_calculation_method' => [Rule::requiredIf($this->isMethod('POST')), 'string', 'in:fixed_meter,fixed_rate,per_meter,per_drop,algo,parcel'],
|
|
35
35
|
'currency' => ['required', 'size:3'],
|
|
36
36
|
'base_fee' => ['numeric'],
|
|
37
|
-
'per_meter_unit' => ['required_if:rate_calculation_method,per_meter', 'string', 'in:km,m'],
|
|
37
|
+
'per_meter_unit' => ['required_if:rate_calculation_method,per_meter', 'string', 'in:km,m,ft,yd,mi'],
|
|
38
38
|
'per_meter_flat_rate_fee' => ['required_if:rate_calculation_method,per_meter', 'numeric'],
|
|
39
39
|
'meter_fees' => [Rule::requiredIf(function () {
|
|
40
40
|
return in_array($this->input('rate_calculation_method'), ['fixed_meter', 'fixed_rate']);
|
|
@@ -45,7 +45,6 @@ class Order extends FleetbaseResource
|
|
|
45
45
|
'tracking_number_uuid' => $this->when($isInternal, $this->tracking_number_uuid),
|
|
46
46
|
'driver_assigned_uuid' => $this->when($isInternal, $this->driver_assigned_uuid),
|
|
47
47
|
'vehicle_assigned_uuid'=> $this->when($isInternal, $this->vehicle_assigned_uuid),
|
|
48
|
-
'service_quote_uuid' => $this->when($isInternal, $this->service_quote_uuid),
|
|
49
48
|
'has_driver_assigned' => $this->when($isInternal, $this->has_driver_assigned),
|
|
50
49
|
'is_scheduled' => $this->when($isInternal, $this->is_scheduled),
|
|
51
50
|
'order_config_uuid' => $this->when($isInternal, $this->order_config_uuid),
|
|
@@ -23,8 +23,8 @@ class ServiceQuote extends FleetbaseResource
|
|
|
23
23
|
'public_id' => $this->when(Http::isInternalRequest(), $this->public_id),
|
|
24
24
|
'service_rate_uuid' => $this->when(Http::isInternalRequest(), $this->service_rate_uuid),
|
|
25
25
|
'payload_uuid' => $this->when(Http::isInternalRequest(), $this->payload_uuid),
|
|
26
|
-
'service_rate_name' => data_get($this, 'serviceRate.
|
|
27
|
-
'service_name' => data_get($this, 'serviceRate.
|
|
26
|
+
'service_rate_name' => data_get($this, 'serviceRate.service_name'),
|
|
27
|
+
'service_name' => data_get($this, 'serviceRate.service_name'),
|
|
28
28
|
'service_rate' => $this->when(Http::isPublicRequest(), data_get($this, 'serviceRate.public_id')),
|
|
29
29
|
'facilitator' => $this->when(Http::isPublicRequest(), data_get($this, 'integratedVendor.public_id')),
|
|
30
30
|
'items' => ServiceQuoteItem::collection($this->items),
|
|
@@ -168,6 +168,13 @@ class Entity extends Model
|
|
|
168
168
|
])->render();
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
public function setTypeAttribute(?string $type = null): void
|
|
172
|
+
{
|
|
173
|
+
$type = is_string($type) ? trim($type) : $type;
|
|
174
|
+
|
|
175
|
+
$this->attributes['type'] = filled($type) ? Str::snake($type) : null;
|
|
176
|
+
}
|
|
177
|
+
|
|
171
178
|
/**
|
|
172
179
|
* @var BelongsTo
|
|
173
180
|
*/
|
|
@@ -1055,9 +1055,7 @@ class Order extends Model
|
|
|
1055
1055
|
'meta' => $meta,
|
|
1056
1056
|
]);
|
|
1057
1057
|
|
|
1058
|
-
$this->
|
|
1059
|
-
|
|
1060
|
-
return $this->save();
|
|
1058
|
+
return $this->attachPurchaseRate($purchasedRate);
|
|
1061
1059
|
}
|
|
1062
1060
|
|
|
1063
1061
|
/**
|
|
@@ -1096,14 +1094,48 @@ class Order extends Model
|
|
|
1096
1094
|
'meta' => $meta,
|
|
1097
1095
|
]);
|
|
1098
1096
|
|
|
1099
|
-
return $this->
|
|
1100
|
-
'purchase_rate_uuid' => $purchasedRate->uuid,
|
|
1101
|
-
]);
|
|
1097
|
+
return $this->attachPurchaseRate($purchasedRate);
|
|
1102
1098
|
}
|
|
1103
1099
|
|
|
1104
1100
|
return false;
|
|
1105
1101
|
}
|
|
1106
1102
|
|
|
1103
|
+
/**
|
|
1104
|
+
* Attach a purchase rate to the order and void any superseded transaction.
|
|
1105
|
+
*/
|
|
1106
|
+
public function attachPurchaseRate(PurchaseRate $purchaseRate): bool
|
|
1107
|
+
{
|
|
1108
|
+
$oldTransaction = $this->transaction_uuid
|
|
1109
|
+
? Transaction::where('uuid', $this->transaction_uuid)->first()
|
|
1110
|
+
: null;
|
|
1111
|
+
|
|
1112
|
+
$attached = (bool) $this->updateQuietly([
|
|
1113
|
+
'purchase_rate_uuid' => $purchaseRate->uuid,
|
|
1114
|
+
'transaction_uuid' => $purchaseRate->transaction_uuid,
|
|
1115
|
+
]);
|
|
1116
|
+
|
|
1117
|
+
if ($attached) {
|
|
1118
|
+
$this->setRelation('purchaseRate', $purchaseRate);
|
|
1119
|
+
|
|
1120
|
+
if ($purchaseRate->transaction_uuid) {
|
|
1121
|
+
$this->setRelation('transaction', Transaction::where('uuid', $purchaseRate->transaction_uuid)->first());
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
if (
|
|
1125
|
+
$oldTransaction instanceof Transaction
|
|
1126
|
+
&& $oldTransaction->uuid !== $purchaseRate->transaction_uuid
|
|
1127
|
+
&& !$oldTransaction->trashed()
|
|
1128
|
+
) {
|
|
1129
|
+
$oldTransaction->update([
|
|
1130
|
+
'status' => Transaction::STATUS_VOIDED,
|
|
1131
|
+
'voided_at' => now(),
|
|
1132
|
+
]);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
return $attached;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1107
1139
|
/**
|
|
1108
1140
|
* Creates a transaction for the order without a service quote.
|
|
1109
1141
|
* This method is used when an order is made without selecting a specific service quote.
|
|
@@ -1122,7 +1154,7 @@ class Order extends Model
|
|
|
1122
1154
|
'gateway_transaction_id' => Transaction::generateNumber(),
|
|
1123
1155
|
'gateway' => 'internal',
|
|
1124
1156
|
'amount' => 0,
|
|
1125
|
-
'currency' =>
|
|
1157
|
+
'currency' => Utils::getCompanyTransactionCurrency($this->company ?? $this->company_uuid),
|
|
1126
1158
|
'description' => 'Dispatch order',
|
|
1127
1159
|
'type' => 'dispatch',
|
|
1128
1160
|
'status' => 'success',
|
|
@@ -369,11 +369,37 @@ class ServiceRate extends Model
|
|
|
369
369
|
return $this;
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
+
// Normalize duplicate parcel rows by fee shape and keep the latest submitted value.
|
|
373
|
+
$serviceRateParcelFees = collect($serviceRateParcelFees)
|
|
374
|
+
->filter(fn ($fee) => is_array($fee))
|
|
375
|
+
->map(function ($fee) {
|
|
376
|
+
return collect($fee)->except(['created_at', 'updated_at'])->toArray();
|
|
377
|
+
})
|
|
378
|
+
->keyBy(function ($fee) {
|
|
379
|
+
return implode(':', [
|
|
380
|
+
data_get($fee, 'size'),
|
|
381
|
+
data_get($fee, 'length'),
|
|
382
|
+
data_get($fee, 'width'),
|
|
383
|
+
data_get($fee, 'height'),
|
|
384
|
+
data_get($fee, 'dimensions_unit'),
|
|
385
|
+
data_get($fee, 'weight'),
|
|
386
|
+
data_get($fee, 'weight_unit'),
|
|
387
|
+
]);
|
|
388
|
+
})
|
|
389
|
+
->values()
|
|
390
|
+
->toArray();
|
|
391
|
+
|
|
392
|
+
$submittedUuids = collect($serviceRateParcelFees)
|
|
393
|
+
->pluck('uuid')
|
|
394
|
+
->filter()
|
|
395
|
+
->values()
|
|
396
|
+
->all();
|
|
397
|
+
|
|
372
398
|
$iterate = count($serviceRateParcelFees);
|
|
373
399
|
|
|
374
400
|
for ($i = 0; $i < $iterate; $i++) {
|
|
375
401
|
// if already has uuid then we just update the record and remove from insert array
|
|
376
|
-
if (
|
|
402
|
+
if (!empty($serviceRateParcelFees[$i]['uuid'])) {
|
|
377
403
|
$id = $serviceRateParcelFees[$i]['uuid'];
|
|
378
404
|
$updateableAttributes = collect($serviceRateParcelFees[$i])->except(['uuid', 'created_at', 'updated_at'])->toArray();
|
|
379
405
|
|
|
@@ -391,7 +417,18 @@ class ServiceRate extends Model
|
|
|
391
417
|
}
|
|
392
418
|
|
|
393
419
|
$serviceRateParcelFees = collect($serviceRateParcelFees)->filter()->values()->toArray();
|
|
394
|
-
|
|
420
|
+
|
|
421
|
+
$existingParcelFeesQuery = ServiceRateParcelFee::where('service_rate_uuid', $this->uuid);
|
|
422
|
+
|
|
423
|
+
if (!empty($submittedUuids)) {
|
|
424
|
+
$existingParcelFeesQuery->whereNotIn('uuid', $submittedUuids)->delete();
|
|
425
|
+
} else {
|
|
426
|
+
$existingParcelFeesQuery->delete();
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (!empty($serviceRateParcelFees)) {
|
|
430
|
+
ServiceRateParcelFee::bulkInsert($serviceRateParcelFees);
|
|
431
|
+
}
|
|
395
432
|
|
|
396
433
|
return $this;
|
|
397
434
|
}
|
|
@@ -688,13 +725,13 @@ class ServiceRate extends Model
|
|
|
688
725
|
}
|
|
689
726
|
|
|
690
727
|
if ($this->isPerMeter()) {
|
|
691
|
-
$perMeterDistance = $this->
|
|
692
|
-
$rateFee = $perMeterDistance * $this->per_meter_flat_rate_fee;
|
|
728
|
+
$perMeterDistance = $this->normalizeDistanceForUnit($totalDistance, $this->per_meter_unit);
|
|
729
|
+
$rateFee = $this->normalizeCalculatedMoney($perMeterDistance * $this->per_meter_flat_rate_fee);
|
|
693
730
|
$subTotal += $rateFee;
|
|
694
731
|
|
|
695
732
|
$lines->push([
|
|
696
733
|
'details' => 'Service Fee',
|
|
697
|
-
'amount' =>
|
|
734
|
+
'amount' => $rateFee,
|
|
698
735
|
'formatted_amount' => Utils::moneyFormat($rateFee, $this->currency),
|
|
699
736
|
'currency' => $this->currency,
|
|
700
737
|
'code' => 'BASE_FEE',
|
|
@@ -702,20 +739,23 @@ class ServiceRate extends Model
|
|
|
702
739
|
}
|
|
703
740
|
|
|
704
741
|
if ($this->isAlgorithm()) {
|
|
705
|
-
$rateFee = Algo::exec(
|
|
742
|
+
$rateFee = $this->normalizeCalculatedMoney(Algo::exec(
|
|
706
743
|
$this->algorithm,
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
744
|
+
$this->buildAlgorithmVariables(
|
|
745
|
+
$entities,
|
|
746
|
+
$waypoints,
|
|
747
|
+
$totalDistance,
|
|
748
|
+
$totalTime,
|
|
749
|
+
$this->inferEndpointCountFromStops($waypoints)
|
|
750
|
+
),
|
|
711
751
|
true
|
|
712
|
-
);
|
|
752
|
+
));
|
|
713
753
|
|
|
714
|
-
$subTotal +=
|
|
754
|
+
$subTotal += $rateFee;
|
|
715
755
|
|
|
716
756
|
$lines->push([
|
|
717
757
|
'details' => 'Service Fee',
|
|
718
|
-
'amount' =>
|
|
758
|
+
'amount' => $rateFee,
|
|
719
759
|
'formatted_amount' => Utils::moneyFormat($rateFee, $this->currency),
|
|
720
760
|
'currency' => $this->currency,
|
|
721
761
|
'code' => 'BASE_FEE',
|
|
@@ -758,9 +798,10 @@ class ServiceRate extends Model
|
|
|
758
798
|
}
|
|
759
799
|
|
|
760
800
|
$subTotal += $serviceParcelFee->fee;
|
|
801
|
+
$parcelFeeName = ucwords(str_replace(['_', '-'], ' ', data_get($serviceParcelFee, 'size', 'parcel')));
|
|
761
802
|
|
|
762
803
|
$lines->push([
|
|
763
|
-
'details' => $
|
|
804
|
+
'details' => $parcelFeeName . ' parcel fee',
|
|
764
805
|
'amount' => Utils::numbersOnly($serviceParcelFee->fee),
|
|
765
806
|
'formatted_amount' => Utils::moneyFormat($serviceParcelFee->fee, $this->currency),
|
|
766
807
|
'currency' => $this->currency,
|
|
@@ -777,7 +818,7 @@ class ServiceRate extends Model
|
|
|
777
818
|
if ($this->hasCodFlatFee()) {
|
|
778
819
|
$subTotal += $codFee = $this->cod_flat_fee;
|
|
779
820
|
} elseif ($this->hasCodPercentageFee()) {
|
|
780
|
-
$subTotal += $codFee = Utils::calculatePercentage($this->cod_percent, $baseRate);
|
|
821
|
+
$subTotal += $codFee = $this->normalizeCalculatedMoney(Utils::calculatePercentage($this->cod_percent, $baseRate));
|
|
781
822
|
}
|
|
782
823
|
|
|
783
824
|
$lines->push([
|
|
@@ -794,7 +835,7 @@ class ServiceRate extends Model
|
|
|
794
835
|
if ($this->hasPeakHoursFlatFee()) {
|
|
795
836
|
$subTotal += $peakHoursFee = $this->peak_hours_flat_fee;
|
|
796
837
|
} elseif ($this->hasPeakHoursPercentageFee()) {
|
|
797
|
-
$subTotal += $peakHoursFee = Utils::calculatePercentage($this->peak_hours_percent, $baseRate);
|
|
838
|
+
$subTotal += $peakHoursFee = $this->normalizeCalculatedMoney(Utils::calculatePercentage($this->peak_hours_percent, $baseRate));
|
|
798
839
|
}
|
|
799
840
|
|
|
800
841
|
$lines->push([
|
|
@@ -872,13 +913,13 @@ class ServiceRate extends Model
|
|
|
872
913
|
}
|
|
873
914
|
|
|
874
915
|
if ($this->isPerMeter()) {
|
|
875
|
-
$perMeterDistance = $this->
|
|
876
|
-
$rateFee = $perMeterDistance * $this->per_meter_flat_rate_fee;
|
|
916
|
+
$perMeterDistance = $this->normalizeDistanceForUnit($totalDistance, $this->per_meter_unit);
|
|
917
|
+
$rateFee = $this->normalizeCalculatedMoney($perMeterDistance * $this->per_meter_flat_rate_fee);
|
|
877
918
|
$subTotal += $rateFee;
|
|
878
919
|
|
|
879
920
|
$lines->push([
|
|
880
921
|
'details' => 'Service Fee',
|
|
881
|
-
'amount' =>
|
|
922
|
+
'amount' => $rateFee,
|
|
882
923
|
'formatted_amount' => Utils::moneyFormat($rateFee, $this->currency),
|
|
883
924
|
'currency' => $this->currency,
|
|
884
925
|
'code' => 'BASE_FEE',
|
|
@@ -886,20 +927,23 @@ class ServiceRate extends Model
|
|
|
886
927
|
}
|
|
887
928
|
|
|
888
929
|
if ($this->isAlgorithm()) {
|
|
889
|
-
$rateFee = Algo::exec(
|
|
930
|
+
$rateFee = $this->normalizeCalculatedMoney(Algo::exec(
|
|
890
931
|
$this->algorithm,
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
932
|
+
$this->buildAlgorithmVariables(
|
|
933
|
+
$payload->entities->all(),
|
|
934
|
+
$waypoints->all(),
|
|
935
|
+
$totalDistance,
|
|
936
|
+
$totalTime,
|
|
937
|
+
(int) ($payload->pickup ? 1 : 0) + (int) ($payload->dropoff ? 1 : 0)
|
|
938
|
+
),
|
|
895
939
|
true
|
|
896
|
-
);
|
|
940
|
+
));
|
|
897
941
|
|
|
898
|
-
$subTotal +=
|
|
942
|
+
$subTotal += $rateFee;
|
|
899
943
|
|
|
900
944
|
$lines->push([
|
|
901
945
|
'details' => 'Service Fee',
|
|
902
|
-
'amount' =>
|
|
946
|
+
'amount' => $rateFee,
|
|
903
947
|
'formatted_amount' => Utils::moneyFormat($rateFee, $this->currency),
|
|
904
948
|
'currency' => $this->currency,
|
|
905
949
|
'code' => 'BASE_FEE',
|
|
@@ -942,9 +986,10 @@ class ServiceRate extends Model
|
|
|
942
986
|
}
|
|
943
987
|
|
|
944
988
|
$subTotal += $serviceParcelFee->fee;
|
|
989
|
+
$parcelFeeName = ucwords(str_replace(['_', '-'], ' ', data_get($serviceParcelFee, 'size', 'parcel')));
|
|
945
990
|
|
|
946
991
|
$lines->push([
|
|
947
|
-
'details' => $
|
|
992
|
+
'details' => $parcelFeeName . ' parcel fee',
|
|
948
993
|
'amount' => Utils::numbersOnly($serviceParcelFee->fee),
|
|
949
994
|
'formatted_amount' => Utils::moneyFormat($serviceParcelFee->fee, $this->currency),
|
|
950
995
|
'currency' => $this->currency,
|
|
@@ -961,7 +1006,7 @@ class ServiceRate extends Model
|
|
|
961
1006
|
if ($this->hasCodFlatFee()) {
|
|
962
1007
|
$subTotal += $codFee = $this->cod_flat_fee;
|
|
963
1008
|
} elseif ($this->hasCodPercentageFee()) {
|
|
964
|
-
$subTotal += $codFee = Utils::calculatePercentage($this->cod_percent, $baseRate);
|
|
1009
|
+
$subTotal += $codFee = $this->normalizeCalculatedMoney(Utils::calculatePercentage($this->cod_percent, $baseRate));
|
|
965
1010
|
}
|
|
966
1011
|
|
|
967
1012
|
$lines->push([
|
|
@@ -978,7 +1023,7 @@ class ServiceRate extends Model
|
|
|
978
1023
|
if ($this->hasPeakHoursFlatFee()) {
|
|
979
1024
|
$subTotal += $peakHoursFee = $this->peak_hours_flat_fee;
|
|
980
1025
|
} elseif ($this->hasPeakHoursPercentageFee()) {
|
|
981
|
-
$subTotal += $peakHoursFee = Utils::calculatePercentage($this->peak_hours_percent, $baseRate);
|
|
1026
|
+
$subTotal += $peakHoursFee = $this->normalizeCalculatedMoney(Utils::calculatePercentage($this->peak_hours_percent, $baseRate));
|
|
982
1027
|
}
|
|
983
1028
|
|
|
984
1029
|
$lines->push([
|
|
@@ -993,6 +1038,53 @@ class ServiceRate extends Model
|
|
|
993
1038
|
return [$subTotal, $lines];
|
|
994
1039
|
}
|
|
995
1040
|
|
|
1041
|
+
protected function normalizeDistanceForUnit(?int $distanceInMeters = 0, ?string $unit = 'm'): float|int
|
|
1042
|
+
{
|
|
1043
|
+
$distanceInMeters = (float) ($distanceInMeters ?? 0);
|
|
1044
|
+
|
|
1045
|
+
return match ($unit) {
|
|
1046
|
+
'km' => $distanceInMeters / 1000,
|
|
1047
|
+
'ft' => $distanceInMeters / 0.3048,
|
|
1048
|
+
'yd' => $distanceInMeters / 0.9144,
|
|
1049
|
+
'mi' => $distanceInMeters / 1609.344,
|
|
1050
|
+
default => $distanceInMeters,
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
protected function normalizeCalculatedMoney($amount = 0): int
|
|
1055
|
+
{
|
|
1056
|
+
return (int) round((float) ($amount ?? 0));
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
protected function buildAlgorithmVariables($entities = [], $stops = [], ?int $totalDistance = 0, ?int $totalTime = 0, int $endpointCount = 0): array
|
|
1060
|
+
{
|
|
1061
|
+
$entityCollection = collect($entities)->filter();
|
|
1062
|
+
$stopCount = collect($stops)->filter()->count();
|
|
1063
|
+
$endpointCount = max(0, min($endpointCount, $stopCount));
|
|
1064
|
+
$waypointCount = max($stopCount - $endpointCount, 0);
|
|
1065
|
+
|
|
1066
|
+
return Algo::normalizeVariables([
|
|
1067
|
+
'distance_m' => $totalDistance ?? 0,
|
|
1068
|
+
'time_s' => $totalTime ?? 0,
|
|
1069
|
+
'stops' => $stopCount,
|
|
1070
|
+
'waypoints' => $waypointCount,
|
|
1071
|
+
'parcels' => $entityCollection->where('type', 'parcel')->count(),
|
|
1072
|
+
'entities' => $entityCollection->count(),
|
|
1073
|
+
'base_fee' => Utils::numbersOnly($this->base_fee ?? 0),
|
|
1074
|
+
]);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
protected function inferEndpointCountFromStops($stops = []): int
|
|
1078
|
+
{
|
|
1079
|
+
$stopCount = collect($stops)->filter()->count();
|
|
1080
|
+
|
|
1081
|
+
if ($stopCount <= 1) {
|
|
1082
|
+
return $stopCount;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
return 2;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
996
1088
|
/**
|
|
997
1089
|
* Find the ServiceRateFee based on the total distance.
|
|
998
1090
|
*
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Observers;
|
|
4
4
|
|
|
5
|
-
use Fleetbase\FleetOps\Models\Order;
|
|
6
5
|
use Fleetbase\FleetOps\Models\PurchaseRate;
|
|
7
6
|
use Fleetbase\FleetOps\Support\Utils;
|
|
8
7
|
use Fleetbase\Models\Company;
|
|
@@ -25,7 +24,8 @@ class PurchaseRateObserver
|
|
|
25
24
|
$company = Company::where('uuid', session('company', $purchaseRate->company_uuid))->first();
|
|
26
25
|
|
|
27
26
|
// get currency to use
|
|
28
|
-
$currency = data_get($purchaseRate, 'serviceQuote.currency'
|
|
27
|
+
$currency = data_get($purchaseRate, 'serviceQuote.currency')
|
|
28
|
+
?: Utils::getCompanyTransactionCurrency($company ?? $purchaseRate->company_uuid);
|
|
29
29
|
|
|
30
30
|
// create transaction and transaction items
|
|
31
31
|
$transaction = Transaction::create([
|
|
@@ -41,9 +41,6 @@ class PurchaseRateObserver
|
|
|
41
41
|
'status' => 'success',
|
|
42
42
|
]);
|
|
43
43
|
|
|
44
|
-
// Update order with transaction id
|
|
45
|
-
Order::where('payload_uuid', $purchaseRate->payload_uuid)->update(['transaction_uuid' => $transaction->uuid]);
|
|
46
|
-
|
|
47
44
|
if (isset($purchaseRate->serviceQuote)) {
|
|
48
45
|
$purchaseRate->serviceQuote->items->each(function ($serviceQuoteItem) use ($transaction, $currency) {
|
|
49
46
|
TransactionItem::create([
|
|
@@ -57,5 +54,6 @@ class PurchaseRateObserver
|
|
|
57
54
|
}
|
|
58
55
|
|
|
59
56
|
$purchaseRate->transaction_uuid = $transaction->uuid;
|
|
57
|
+
$purchaseRate->status = $purchaseRate->status ?: Transaction::STATUS_SUCCESS;
|
|
60
58
|
}
|
|
61
59
|
}
|
|
@@ -16,9 +16,7 @@ class ComputableAlgo implements Rule
|
|
|
16
16
|
*/
|
|
17
17
|
public function passes($attribute, $value)
|
|
18
18
|
{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return Algo::exec($value, $distanceAndTime) > 0;
|
|
19
|
+
return Algo::isComputable($value);
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
/**
|