diff --git a/addon/models/service-rate-fee.js b/addon/models/service-rate-fee.js index 511eca7..a512e1d 100644 --- a/addon/models/service-rate-fee.js +++ b/addon/models/service-rate-fee.js @@ -7,7 +7,7 @@ export default class ServiceRateFeeModel extends Model { @attr('string') service_rate_uuid; /** @attributes */ - @attr('string') distance; + @attr('number') distance; @attr('string') distance_unit; @attr('string') unit; @attr('string') fee; diff --git a/addon/models/service-rate.js b/addon/models/service-rate.js index 85feb6d..2162703 100644 --- a/addon/models/service-rate.js +++ b/addon/models/service-rate.js @@ -1,5 +1,4 @@ import Model, { attr, hasMany, belongsTo } from '@ember-data/model'; -import { tracked } from '@glimmer/tracking'; import { computed, action } from '@ember/object'; import { getOwner } from '@ember/application'; import { format as formatDate, formatDistanceToNow } from 'date-fns'; @@ -20,9 +19,6 @@ export default class ServiceRate extends Model { @belongsTo('zone') zone; @hasMany('custom-field-value', { async: false }) custom_field_values; - /** @tracked */ - @tracked perDropFees = []; - /** @attributes */ @attr('string') service_area_name; @attr('string') zone_name; @@ -120,46 +116,15 @@ export default class ServiceRate extends Model { return this.cod_calculation_method === 'percentage'; } - @computed('max_distance', 'max_distance_unit', 'currency', 'rate_fees') get rateFees() { - const store = getOwner(this).lookup('service:store'); - const unit = this.max_distance_unit; - const currency = this.currency; + @computed('rate_fees.@each.distance', 'max_distance') get rateFees() { const n = Math.max(0, Number(this.max_distance) || 0); - const existing = (this.rate_fees?.toArray?.() ?? []).slice(); - const byDistance = new Map(existing.map((r) => [r.distance, r])); - - const rows = []; - for (let d = 0; d < n; d++) { - let rec = byDistance.get(d); - if (!rec) { - rec = store.createRecord('service-rate-fee', { - distance: d, - distance_unit: unit, - fee: 0, - currency, - }); - this.rate_fees.addObject(rec); - } else { - rec.setProperties({ distance_unit: unit, currency }); - } - rows.push(rec); - } + const existing = (this.rate_fees?.toArray?.() ?? []).filter((r) => r.distance !== null && r.distance !== undefined && !r.isDeleted); - // note: do NOT prune here in a getter; do it via an explicit action - return rows; + // Return existing fees sorted by distance, filtered by max_distance + return existing.filter((r) => r.distance >= 0 && r.distance < n).sort((a, b) => a.distance - b.distance); } /** @methods */ - @action syncServiceRateFees() { - if (!this.isFixedRate) return; - this.set('rate_fees', this.rateFees); - } - - @action syncPerDropFees() { - if (!this.isPerDrop) return; - this.set('rate_fees', this.perDropFees); - } - @action createDefaultPerDropFee(attributes = {}) { const store = getOwner(this).lookup('service:store'); return store.createRecord('service-rate-fee', { @@ -173,28 +138,41 @@ export default class ServiceRate extends Model { } @action addPerDropRateFee() { - const last = this.perDropFees[this.perDropFees.length - 1]; + const store = getOwner(this).lookup('service:store'); + const existingFees = this.rate_fees?.toArray?.() ?? []; + const last = existingFees[existingFees.length - 1]; const min = last ? last.max + 1 : 1; const max = min + 5; - this.perDropFees = [ - ...this.perDropFees, - { - min: min, - max: max, - unit: 'waypoint', - fee: 0, - currency: this.currency, - }, - ]; + const newFee = store.createRecord('service-rate-fee', { + min: min, + max: max, + unit: 'waypoint', + fee: 0, + currency: this.currency, + }); + + this.rate_fees.addObject(newFee); } - @action removePerDropFee(index) { - if (index === 0) return; - this.perDropFees = [...this.perDropFees.filter((_, i) => i !== index)]; + @action removePerDropFee(fee) { + if (!fee || !fee.destroyRecord) return; + this.rate_fees.removeObject(fee); + fee.destroyRecord(); } @action resetPerDropFees() { - this.perDropFees = [this.createDefaultPerDropFee()]; + // 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); } } diff --git a/addon/models/vehicle.js b/addon/models/vehicle.js index 7e3754d..90f1117 100644 --- a/addon/models/vehicle.js +++ b/addon/models/vehicle.js @@ -272,4 +272,4 @@ export default class VehicleModel extends Model { return devices; } -} \ No newline at end of file +} diff --git a/addon/serializers/service-rate.js b/addon/serializers/service-rate.js index c8c09a7..7f1ebeb 100644 --- a/addon/serializers/service-rate.js +++ b/addon/serializers/service-rate.js @@ -1,5 +1,6 @@ import ApplicationSerializer from '@fleetbase/ember-core/serializers/application'; import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest'; +import { next } from '@ember/runloop'; export default class ServiceRateSerializer extends ApplicationSerializer.extend(EmbeddedRecordsMixin) { /** @@ -16,4 +17,64 @@ export default class ServiceRateSerializer extends ApplicationSerializer.extend( rate_fees: { embedded: 'always' }, }; } + + /** + * Normalize single response - called after save/create + * Directly set rate_fees relationship to only saved records from backend + */ + normalizeSaveResponse(store, primaryModelClass, payload, id, requestType) { + const normalized = super.normalizeSaveResponse(store, primaryModelClass, payload, id, requestType); + + // After normalization, replace rate_fees with only the saved records from backend + if (normalized.data && normalized.data.type === 'service-rate') { + const serviceRateId = normalized.data.id; + + // Schedule after store update using Ember run loop + next(() => { + const serviceRate = store.peekRecord('service-rate', serviceRateId); + if (serviceRate) { + // Cleanup rate_fees duplicates (for Fixed Rate and Per-Drop) + const allRateFees = serviceRate.get('rate_fees').toArray(); + const savedRateFees = allRateFees.filter((f) => !f.isNew); + const unsavedRateFees = allRateFees.filter((f) => f.isNew); + + // Create a map of saved fees by distance + const savedByDistance = new Map(savedRateFees.map((f) => [f.distance, f])); + + // Only remove unsaved fees that duplicate saved fees + unsavedRateFees.forEach((fee) => { + if (savedByDistance.has(fee.distance)) { + serviceRate.get('rate_fees').removeObject(fee); + fee.unloadRecord(); + } + }); + + // Cleanup parcel_fees duplicates + const allParcelFees = serviceRate.get('parcel_fees').toArray(); + const savedParcelFees = allParcelFees.filter((f) => !f.isNew); + const unsavedParcelFees = allParcelFees.filter((f) => f.isNew); + + // // Create a map of saved parcel fees by uuid (or another unique key) + // const savedParcelByUuid = new Map(savedParcelFees.map((f) => [f.get('id'), f])); + + // Remove unsaved parcel fees that have a saved version + unsavedParcelFees.forEach((fee) => { + // For parcel fees, we need to check if there's a duplicate based on attributes + // Since they don't have a simple key like distance, we'll just remove all unsaved ones + // that don't have unique identifying attributes + const hasDuplicate = savedParcelFees.some( + (saved) => saved.size === fee.size && saved.length === fee.length && saved.width === fee.width && saved.height === fee.height + ); + + if (hasDuplicate) { + serviceRate.get('parcel_fees').removeObject(fee); + fee.unloadRecord(); + } + }); + } + }); + } + + return normalized; + } } diff --git a/package.json b/package.json index e754800..f9673a5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fleetbase/fleetops-data", - "version": "0.1.23", + "version": "0.1.24", "description": "Fleetbase Fleet-Ops based models, serializers, transforms, adapters and GeoJson utility functions.", "keywords": [ "fleetbase-data", @@ -36,7 +36,7 @@ "publish:github": "npm config set '@fleetbase:registry' https://npm.pkg.github.com/ && npm publish" }, "dependencies": { - "@fleetbase/ember-core": "latest", + "@fleetbase/ember-core": "^0.3.9", "@babel/core": "^7.23.2", "date-fns": "^2.29.3", "ember-cli-babel": "^8.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ac3104..09b03df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^7.23.2 version: 7.27.1 '@fleetbase/ember-core': - specifier: latest - version: 0.3.3(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.27.1)(ember-source@5.4.1(@babel/core@7.27.1)(@glimmer/component@1.1.2(@babel/core@7.27.1))(rsvp@4.8.5)(webpack@5.99.8))(webpack@5.99.8))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.27.1)(@glimmer/component@1.1.2(@babel/core@7.27.1))(rsvp@4.8.5)(webpack@5.99.8)))(ember-source@5.4.1(@babel/core@7.27.1)(@glimmer/component@1.1.2(@babel/core@7.27.1))(rsvp@4.8.5)(webpack@5.99.8))(eslint@8.57.1)(webpack@5.99.8) + specifier: ^0.3.9 + version: 0.3.9(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.27.1)(ember-source@5.4.1(@babel/core@7.27.1)(@glimmer/component@1.1.2(@babel/core@7.27.1))(rsvp@4.8.5)(webpack@5.99.8))(webpack@5.99.8))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.27.1)(@glimmer/component@1.1.2(@babel/core@7.27.1))(rsvp@4.8.5)(webpack@5.99.8)))(ember-source@5.4.1(@babel/core@7.27.1)(@glimmer/component@1.1.2(@babel/core@7.27.1))(rsvp@4.8.5)(webpack@5.99.8))(eslint@8.57.1)(webpack@5.99.8) date-fns: specifier: ^2.29.3 version: 2.30.0 @@ -938,8 +938,8 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@fleetbase/ember-core@0.3.3': - resolution: {integrity: sha512-1KVYnSiA6TuYIh00IHy6z4ndbhZ1yazXb+jPt4qG+kVabIZh1salFFvWSwex9GbM3Aw31PdptKS845yV4uUbmQ==} + '@fleetbase/ember-core@0.3.9': + resolution: {integrity: sha512-CxMEyNGhSk0u8SkI6GZiKY2W/246PyBpY6clZahoxtyokL76kWx2KjEk4iXqKdhKKU5a5OKlS8Kw9wb0peZZzw==} engines: {node: '>= 18'} '@formatjs/ecma402-abstract@2.2.4': @@ -7267,7 +7267,7 @@ snapshots: '@eslint/js@8.57.1': {} - '@fleetbase/ember-core@0.3.3(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.27.1)(ember-source@5.4.1(@babel/core@7.27.1)(@glimmer/component@1.1.2(@babel/core@7.27.1))(rsvp@4.8.5)(webpack@5.99.8))(webpack@5.99.8))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.27.1)(@glimmer/component@1.1.2(@babel/core@7.27.1))(rsvp@4.8.5)(webpack@5.99.8)))(ember-source@5.4.1(@babel/core@7.27.1)(@glimmer/component@1.1.2(@babel/core@7.27.1))(rsvp@4.8.5)(webpack@5.99.8))(eslint@8.57.1)(webpack@5.99.8)': + '@fleetbase/ember-core@0.3.9(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.27.1)(ember-source@5.4.1(@babel/core@7.27.1)(@glimmer/component@1.1.2(@babel/core@7.27.1))(rsvp@4.8.5)(webpack@5.99.8))(webpack@5.99.8))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.27.1)(@glimmer/component@1.1.2(@babel/core@7.27.1))(rsvp@4.8.5)(webpack@5.99.8)))(ember-source@5.4.1(@babel/core@7.27.1)(@glimmer/component@1.1.2(@babel/core@7.27.1))(rsvp@4.8.5)(webpack@5.99.8))(eslint@8.57.1)(webpack@5.99.8)': dependencies: '@babel/core': 7.27.1 compress-json: 3.4.0