@fleetbase/fleetops-data
Version:
Fleetbase Fleet-Ops based models, serializers, transforms, adapters and GeoJson utility functions.
335 lines (273 loc) • 11.3 kB
JavaScript
import Model, { attr, hasMany, belongsTo } from '@ember-data/model';
import { computed, action } from '@ember/object';
import { getOwner } from '@ember/application';
import { format as formatDate, formatDistanceToNow } from 'date-fns';
export default class ServiceRate extends Model {
/** @ids */
public_id;
company_uuid;
service_area_uuid;
zone_uuid;
order_config_uuid;
/** @relationships */
rate_fees;
parcel_fees;
service_area;
order_config;
zone;
custom_field_values;
/** @attributes */
service_area_name;
zone_name;
service_name;
service_type;
base_fee;
per_meter_flat_rate_fee;
per_meter_unit;
max_distance_unit;
max_distance;
algorithm;
rate_calculation_method;
cod_calculation_method;
cod_flat_fee;
cod_percent;
peak_hours_calculation_method;
peak_hours_flat_fee;
peak_hours_percent;
peak_hours_start;
peak_hours_end;
currency;
duration_terms;
estimated_days;
has_cod_fee;
has_peak_hours_fee;
meta;
/** @dates */
deleted_at;
created_at;
updated_at;
/** @computed */
get updatedAgo() {
return formatDistanceToNow(this.updated_at);
}
get updatedAt() {
return formatDate(this.updated_at, 'yyyy-MM-dd HH:mm');
}
get updatedAtShort() {
return formatDate(this.updated_at, 'dd, MMM');
}
get createdAgo() {
return formatDistanceToNow(this.created_at);
}
get createdAt() {
return this.created_at ? formatDate(this.created_at, 'yyyy-MM-dd HH:mm') : null;
}
get createdAtShort() {
return this.created_at ? formatDate(this.created_at, 'dd, MMM') : null;
}
get isFixedMeter() {
return this.rate_calculation_method === 'fixed_meter' || this.rate_calculation_method === 'fixed_rate';
}
get isFixedRate() {
return this.rate_calculation_method === 'fixed_meter' || this.rate_calculation_method === 'fixed_rate';
}
get isPerMeter() {
return this.rate_calculation_method === 'per_meter';
}
get isMultiZoneDistance() {
return this.rate_calculation_method === 'multi_zone_distance';
}
get isPerDrop() {
return this.rate_calculation_method === 'per_drop';
}
get isAlgorithm() {
return this.rate_calculation_method === 'algo';
}
get isParcelService() {
return this.rate_calculation_method === 'parcel';
}
get hasPeakHoursFlatFee() {
return this.peak_hours_calculation_method === 'flat';
}
get hasPeakHoursPercentageFee() {
return this.peak_hours_calculation_method === 'percentage';
}
get hasCodFlatFee() {
return this.cod_calculation_method === 'flat';
}
get hasCodPercentageFee() {
return this.cod_calculation_method === 'percentage';
}
get rateFees() {
const existing = (this.rate_fees?.toArray?.() ?? []).filter((r) => !r.isDeleted);
if (this.isMultiZoneDistance) {
const deduped = new Map();
const rankFee = (fee) => {
if (fee.id && !fee.isNew) {
return 3;
}
if (!fee.isNew) {
return 2;
}
return 1;
};
const updatedAtMs = (fee) => {
const value = fee.updated_at instanceof Date ? fee.updated_at : new Date(fee.updated_at);
const timestamp = value.getTime();
return Number.isNaN(timestamp) ? 0 : timestamp;
};
const geographyId = (fee) => {
if (fee.is_fallback) {
return 'fallback';
}
return fee.zone_uuid || fee.zone?.id || fee.service_area_uuid || fee.service_area?.id || 'unassigned';
};
existing
.filter((r) => r.unit === 'multi_zone_distance')
.forEach((fee) => {
const key = `multi-zone:${fee.is_fallback}:${geographyId(fee)}:${fee.priority}:${fee.label}`;
const current = deduped.get(key);
const feeRank = rankFee(fee);
const currentRank = current ? rankFee(current) : 0;
if (!current || feeRank > currentRank || (feeRank === currentRank && updatedAtMs(fee) >= updatedAtMs(current))) {
deduped.set(key, fee);
}
});
return Array.from(deduped.values()).sort((a, b) => (Number(b.priority) || 0) - (Number(a.priority) || 0));
}
if (this.isPerDrop) {
const deduped = new Map();
const rankFee = (fee) => {
if (fee.id && !fee.isNew) {
return 3;
}
if (!fee.isNew) {
return 2;
}
return 1;
};
existing
.filter((r) => r.unit === 'waypoint')
.forEach((fee) => {
const key = `drop:${fee.min}:${fee.max}:${fee.unit}`;
const current = deduped.get(key);
if (!current || rankFee(fee) >= rankFee(current)) {
deduped.set(key, fee);
}
});
return Array.from(deduped.values()).sort((a, b) => (a.min ?? 0) - (b.min ?? 0));
}
const n = Math.max(0, Number(this.max_distance) || 0);
return existing.filter((r) => r.distance !== null && r.distance !== undefined && r.distance >= 0 && r.distance < n).sort((a, b) => a.distance - b.distance);
}
get parcelFees() {
const existing = (this.parcel_fees?.toArray?.() ?? []).filter((fee) => !fee.isDeleted);
const deduped = new Map();
const feeKey = (fee) => {
return [fee.size, fee.length, fee.width, fee.height, fee.dimensions_unit, fee.weight, fee.weight_unit].join(':');
};
const rankFee = (fee) => {
if (fee.id && !fee.isNew) {
return 3;
}
if (!fee.isNew) {
return 2;
}
return 1;
};
existing.forEach((fee) => {
const key = feeKey(fee);
const current = deduped.get(key);
if (!current || rankFee(fee) >= rankFee(current)) {
deduped.set(key, fee);
}
});
return Array.from(deduped.values());
}
/** @methods */
createDefaultPerDropFee(attributes = {}) {
const store = getOwner(this).lookup('service:store');
return store.createRecord('service-rate-fee', {
min: 1,
max: 5,
fee: 0,
unit: 'waypoint',
currency: this.currency,
...attributes,
});
}
addPerDropRateFee() {
const store = getOwner(this).lookup('service:store');
const existingFees = this.rate_fees?.toArray?.() ?? [];
const last = existingFees[existingFees.length - 1];
const lastMax = Number(last?.max) || 0;
const min = last ? lastMax + 1 : 1;
const max = min + 5;
const newFee = store.createRecord('service-rate-fee', {
min: min,
max: max,
unit: 'waypoint',
fee: 0,
currency: this.currency,
});
this.rate_fees.addObject(newFee);
return newFee;
}
removePerDropFee(fee) {
if (!fee || !fee.destroyRecord) return;
this.rate_fees.removeObject(fee);
fee.destroyRecord();
}
resetPerDropFees() {
// Remove all existing per-drop fees
const existingFees = this.rate_fees?.toArray?.() ?? [];
existingFees.forEach((fee) => {
if (fee.unit === 'waypoint') {
this.rate_fees.removeObject(fee);
fee.destroyRecord();
}
});
// Add a new default per-drop fee
const defaultFee = this.createDefaultPerDropFee();
this.rate_fees.addObject(defaultFee);
}
addMultiZoneDistanceRule(attributes = {}) {
const store = getOwner(this).lookup('service:store');
const existingFees = this.rate_fees?.toArray?.() ?? [];
const nextPriority = existingFees.filter((fee) => fee.unit === 'multi_zone_distance').reduce((highest, fee) => Math.max(highest, Number(fee.priority) || 0), 0) + 10;
const newFee = store.createRecord('service-rate-fee', {
label: 'Distance rule',
priority: nextPriority,
is_fallback: false,
distance_unit: 'km',
unit: 'multi_zone_distance',
fee: 0,
currency: this.currency,
...attributes,
});
this.rate_fees.addObject(newFee);
}
addMultiZoneDistanceFallbackRule() {
const existingFallback = (this.rate_fees?.toArray?.() ?? []).find((fee) => fee.unit === 'multi_zone_distance' && fee.is_fallback && !fee.isDeleted);
if (existingFallback) {
return existingFallback;
}
return this.addMultiZoneDistanceRule({
label: 'Fallback distance',
priority: 0,
is_fallback: true,
});
}
removeMultiZoneDistanceRule(fee) {
if (!fee || !fee.destroyRecord) return;
this.rate_fees.removeObject(fee);
fee.destroyRecord();
}
}