From cbf3e27c0f171499ca6ac405b9e2f707a3727778 Mon Sep 17 00:00:00 2001 From: Mohau Nkepane Date: Mon, 15 Jun 2026 16:21:47 +0200 Subject: [PATCH] Improve Manage POD discrepancy visibility --- .../add-discrepancy-modal.controller.js | 77 ++++----- .../add-discrepancy-modal.controller.spec.js | 116 +++++++++++++ src/point-of-delivery-manage/messages_en.json | 1 + .../point-of-delivery-manage.controller.js | 57 +++++-- ...oint-of-delivery-manage.controller.spec.js | 158 ++++++++++++++++++ .../point-of-delivery-manage.html | 36 +++- .../point-of-delivery.service.js | 19 +-- 7 files changed, 386 insertions(+), 78 deletions(-) create mode 100644 src/pod-add-discrepancy-modal/add-discrepancy-modal.controller.spec.js create mode 100644 src/point-of-delivery-manage/point-of-delivery-manage.controller.spec.js diff --git a/src/pod-add-discrepancy-modal/add-discrepancy-modal.controller.js b/src/pod-add-discrepancy-modal/add-discrepancy-modal.controller.js index 8d7e649..2b59707 100755 --- a/src/pod-add-discrepancy-modal/add-discrepancy-modal.controller.js +++ b/src/pod-add-discrepancy-modal/add-discrepancy-modal.controller.js @@ -28,10 +28,10 @@ .module('pod-add-discrepancy-modal') .controller('podAddDiscrepancyModalController', controller); - controller.$inject = ['pointOfDeliveryService', 'rejectionReasons','$filter', 'shipmentType', 'notificationService', 'modalDeferred','discrepancies']; - - function controller( pointOfDeliveryService, rejectionReasons, $filter, shipmentType, notificationService, modalDeferred, discrepancies) { - var vm = this; + controller.$inject = ['rejectionReasons', '$filter', 'shipmentType', 'notificationService', 'modalDeferred', 'discrepancies']; + + function controller(rejectionReasons, $filter, shipmentType, notificationService, modalDeferred, discrepancies) { + var vm = this; vm.$onInit = onInit; vm.currentShipmentType = shipmentType; //Storing Selected ShipmentType @@ -85,42 +85,39 @@ } function confirm (){ - if(vm.discrepancies.length!=0){ - var rejection = {}; - angular.forEach(vm.discrepancies, function(reason){ - // Use $filter to find the matching object in rejectionReasons - var reasonDetails = $filter('filter')(vm.rejectionReasons, { name: reason.name }, true); - // If a match is found, build the rejection object - if (reasonDetails.length > 0) { - rejection = { - rejectionReason: angular.copy(reasonDetails[0]), - quantityAffected: reason.quantity, - shipmentType: reason.shipmentType, - comments: reason.comments - } - pointOfDeliveryService.addDiscrepancies(rejection); - vm.discrepancies = []; - rejection = {}; - } - }); - modalDeferred.resolve(); - } - else{ - notificationService.error('Add discrepancies before saving them.'); - } - } - - function populateModalWithCurrentDiscrepancies (currentDiscrepancies){ - if(currentDiscrepancies.length!=0){ - angular.forEach(currentDiscrepancies, function(reason){ - reason.quantity = reason.quantityAffected; - reason.name = reason.rejectionReason.name - - }); - return currentDiscrepancies; - } - else{ - return []; + var resolvedDiscrepancies = []; + + angular.forEach(vm.discrepancies, function(reason) { + var reasonDetails = $filter('filter')(vm.rejectionReasons, { + name: reason.name + }, true); + + if (reasonDetails.length > 0) { + resolvedDiscrepancies.push({ + rejectionReason: angular.copy(reasonDetails[0]), + quantityAffected: reason.quantity, + shipmentType: reason.shipmentType, + comments: reason.comments + }); + } + }); + + modalDeferred.resolve(resolvedDiscrepancies); + } + + function populateModalWithCurrentDiscrepancies (currentDiscrepancies){ + if(currentDiscrepancies && currentDiscrepancies.length!=0){ + return currentDiscrepancies.map(function(reason) { + return { + shipmentType: reason.shipmentType, + name: reason.name || (reason.rejectionReason ? reason.rejectionReason.name : ''), + quantity: angular.isDefined(reason.quantity) ? reason.quantity : reason.quantityAffected, + comments: reason.comments + }; + }); + } + else{ + return []; } } diff --git a/src/pod-add-discrepancy-modal/add-discrepancy-modal.controller.spec.js b/src/pod-add-discrepancy-modal/add-discrepancy-modal.controller.spec.js new file mode 100644 index 0000000..4cf5da1 --- /dev/null +++ b/src/pod-add-discrepancy-modal/add-discrepancy-modal.controller.spec.js @@ -0,0 +1,116 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + *   + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  + */ + +describe('podAddDiscrepancyModalController', function() { + + beforeEach(function() { + module('pod-add-discrepancy-modal'); + + inject(function($injector) { + this.$controller = $injector.get('$controller'); + }); + + this.damagedReason = { + name: 'Damaged', + rejectionReasonCategory: { + code: 'POD' + } + }; + this.missingReason = { + name: 'Missing', + rejectionReasonCategory: { + code: 'POD' + } + }; + this.nonPODReason = { + name: 'Other', + rejectionReasonCategory: { + code: 'OTHER' + } + }; + + this.rejectionReasons = { + content: [this.damagedReason, this.missingReason, this.nonPODReason] + }; + this.modalDeferred = jasmine.createSpyObj('modalDeferred', ['resolve']); + this.notificationService = jasmine.createSpyObj('notificationService', ['error']); + + this.createController = function(discrepancies) { + this.vm = this.$controller('podAddDiscrepancyModalController', { + rejectionReasons: this.rejectionReasons, + shipmentType: 'carton', + notificationService: this.notificationService, + modalDeferred: this.modalDeferred, + discrepancies: discrepancies || [] + }); + this.vm.$onInit(); + }; + }); + + it('should prepopulate existing discrepancies for editing', function() { + this.createController([{ + rejectionReason: this.damagedReason, + quantityAffected: 4, + shipmentType: 'carton', + comments: 'Crushed' + }]); + + expect(this.vm.discrepancies).toEqual([{ + name: 'Damaged', + quantity: 4, + shipmentType: 'carton', + comments: 'Crushed' + }]); + }); + + it('should add and remove discrepancy rows', function() { + this.createController(); + this.vm.selectedDiscrepancy = 'Damaged'; + + this.vm.addDiscrepancy(); + expect(this.vm.discrepancies.length).toEqual(1); + expect(this.vm.discrepancies[0].name).toEqual('Damaged'); + + this.vm.removeDispency(0); + expect(this.vm.discrepancies).toEqual([]); + }); + + it('should resolve saved discrepancies in POD backend shape', function() { + this.createController(); + this.vm.discrepancies = [{ + name: 'Damaged', + quantity: 4, + shipmentType: 'carton', + comments: 'Crushed' + }]; + + this.vm.confirm(); + + expect(this.modalDeferred.resolve).toHaveBeenCalledWith([{ + rejectionReason: this.damagedReason, + quantityAffected: 4, + shipmentType: 'carton', + comments: 'Crushed' + }]); + }); + + it('should allow saving an empty discrepancy list', function() { + this.createController(); + + this.vm.confirm(); + + expect(this.modalDeferred.resolve).toHaveBeenCalledWith([]); + }); +}); diff --git a/src/point-of-delivery-manage/messages_en.json b/src/point-of-delivery-manage/messages_en.json index 18b3bec..07ca316 100755 --- a/src/point-of-delivery-manage/messages_en.json +++ b/src/point-of-delivery-manage/messages_en.json @@ -24,6 +24,7 @@ "proofOfDeliveryManage.quantityRejected":"Rejected Quantity", "proofOfDeliveryManage.action":"Discrepancy", "proofOfDeliveryManage.addDiscrepancy":"Add", + "pointOfDeliveryManage.discrepancies": "Discrepancies", "proofOfDeliveryManage.cartons": "Cartons", "pointOfDeliveryManage.containers": "Containers", "pointOfDeliveryManage.emptyConsignment": "Waybill Quantity and Accepted Quantity cannot be zero. Please enter correct values" diff --git a/src/point-of-delivery-manage/point-of-delivery-manage.controller.js b/src/point-of-delivery-manage/point-of-delivery-manage.controller.js index ee25c39..b039f38 100755 --- a/src/point-of-delivery-manage/point-of-delivery-manage.controller.js +++ b/src/point-of-delivery-manage/point-of-delivery-manage.controller.js @@ -52,6 +52,10 @@ vm.homeFacilities = [facility]; vm.validateConsignment = validateConsignment; + vm.addDiscrepancyOnModal = addDiscrepancyOnModal; + vm.getShipmentDiscrepancies = getShipmentDiscrepancies; + vm.hasShipmentDiscrepancies = hasShipmentDiscrepancies; + vm.getDiscrepancySummary = getDiscrepancySummary; /** * @ngdoc method @@ -68,6 +72,8 @@ vm.supplyingFacilities = facilities; vm.offline = $stateParams.offline === 'true' || offlineService.isOffline(); vm.POD.referenceNo = $rootScope.referenceNoPOD; // Getting Ref Number from Quality Checks + vm.POD.receivingFacility = facility; + vm.POD.discrepancies = []; $rootScope.referenceNoPOD = undefined; // Clear Var on Root Scope if ($stateParams.podId) { vm.tempPOD = filterShipmentById(podEvents, $stateParams.podId); @@ -84,18 +90,44 @@ } } - vm.addDiscrepancyOnModal = function (shipmentType, currentDiscrepancies) { - pointOfDeliveryService.show(shipmentType, currentDiscrepancies).then(function () { - $stateParams.noReload = true; - draft.$modified = true; - vm.cacheDraft(); - //Only reload current state and avoid reloading parent state - $state.go($state.current.name, $stateParams, { - reload: $state.current.name - }); + function addDiscrepancyOnModal(shipmentType) { + pointOfDeliveryService.show(shipmentType, vm.POD.discrepancies).then(function (discrepancies) { + vm.POD.discrepancies = discrepancies || []; }); } + function getShipmentDiscrepancies(shipmentType) { + return (vm.POD.discrepancies || []).filter(function(discrepancy) { + return discrepancy.shipmentType === shipmentType; + }); + } + + function hasShipmentDiscrepancies(shipmentType) { + return getShipmentDiscrepancies(shipmentType).length > 0; + } + + function getDiscrepancySummary(shipmentType) { + var discrepancies = getShipmentDiscrepancies(shipmentType); + + if (!discrepancies.length) { + return ''; + } + + if (discrepancies.length === 1) { + return getDiscrepancyName(discrepancies[0]); + } + + return discrepancies.length + ' Discrepancies'; + } + + function getDiscrepancyName(discrepancy) { + if (discrepancy.name) { + return discrepancy.name; + } + + return discrepancy.rejectionReason ? discrepancy.rejectionReason.name : ''; + } + /** * @ngdoc method * @methodOf point-of-delivery-manage.controller:pointOfDeliveryManageController @@ -111,6 +143,7 @@ vm.POD.cartonsQuantityOnWaybill = podObject.cartonsQuantityOnWaybill; vm.POD.cartonsQuantityAccepted = podObject.cartonsQuantityAccepted; vm.POD.cartonsQuantityRejected = podObject.cartonsQuantityRejected; + vm.POD.discrepancies = angular.copy(podObject.discrepancies || []); // vm.POD.containersQuantityOnWayBill = podObject.containersQuantityOnWaybill; // vm.POD.containersQuantityAccepted = podObject.containersQuantityAccepted; // vm.POD.containersQuantityRejected = podObject.containersQuantityRejected; @@ -128,8 +161,6 @@ */ vm.buildPayload = function () { - var discrepancyList = pointOfDeliveryService.getDiscrepancies(); - var payloadData = { sourceId: vm.POD.supplyingFacility.id, destinationId: vm.POD.receivingFacility.id, @@ -142,7 +173,7 @@ // containersQuantityOnWaybill: vm.POD ? vm.POD.containersQuantityOnWayBill : null, // containersQuantityShipped: vm.POD ? (vm.POD.containersQuantityAccepted + vm.POD.containersQuantityRejected) : null, // containersQuantityAccepted: vm.POD ? vm.POD.containersQuantityAccepted : null, - discrepancies: discrepancyList + discrepancies: vm.POD.discrepancies || [] }; const inputsValid = vm.validatePODinputs(payloadData); const consignmentValid = validateConsignment(payloadData); @@ -261,9 +292,9 @@ */ vm.clearForm = function () { vm.POD = {}; + vm.POD.discrepancies = []; vm.discrepancy = []; vm.proofOfDelivery = {}; - pointOfDeliveryService.clearDiscrepancies(); $scope.podManageForm.$setPristine(); $scope.podManageForm.$setUntouched(); }; diff --git a/src/point-of-delivery-manage/point-of-delivery-manage.controller.spec.js b/src/point-of-delivery-manage/point-of-delivery-manage.controller.spec.js new file mode 100644 index 0000000..0d8a934 --- /dev/null +++ b/src/point-of-delivery-manage/point-of-delivery-manage.controller.spec.js @@ -0,0 +1,158 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + *   + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  + */ + +describe('pointOfDeliveryManageController', function() { + + beforeEach(function() { + module('point-of-delivery-manage'); + + inject(function($injector) { + this.$controller = $injector.get('$controller'); + this.$q = $injector.get('$q'); + this.$rootScope = $injector.get('$rootScope'); + }); + + this.facility = { + id: 'destination-id', + name: 'Receiving Facility' + }; + this.facilities = [{ + id: 'source-id', + name: 'Supplying Facility' + }]; + this.pointOfDeliveryService = jasmine.createSpyObj('pointOfDeliveryService', ['show']); + this.offlineService = jasmine.createSpyObj('offlineService', ['isOffline']); + this.facilityService = jasmine.createSpyObj('facilityService', ['get']); + this.notificationService = jasmine.createSpyObj('notificationService', ['success', 'error']); + this.confirmService = jasmine.createSpyObj('confirmService', ['confirm']); + this.alertService = jasmine.createSpyObj('alertService', ['error']); + this.$state = jasmine.createSpyObj('$state', ['go']); + this.$stateParams = {}; + this.$scope = this.$rootScope.$new(); + this.$scope.podManageForm = jasmine.createSpyObj('podManageForm', ['$setPristine', '$setUntouched']); + + this.createController = function() { + this.vm = this.$controller('pointOfDeliveryManageController', { + $rootScope: this.$rootScope, + $state: this.$state, + $stateParams: this.$stateParams, + facility: this.facility, + facilities: this.facilities, + facilityService: this.facilityService, + offlineService: this.offlineService, + pointOfDeliveryService: this.pointOfDeliveryService, + $scope: this.$scope, + notificationService: this.notificationService, + podEvents: {}, + confirmService: this.confirmService, + alertService: this.alertService + }); + this.vm.$onInit(); + }; + }); + + it('should initialize new POD discrepancies on the page model', function() { + this.createController(); + + expect(this.vm.POD.receivingFacility).toEqual(this.facility); + expect(this.vm.POD.discrepancies).toEqual([]); + }); + + it('should summarize shipment discrepancies like physical inventory reasons', function() { + this.createController(); + + expect(this.vm.hasShipmentDiscrepancies('carton')).toBe(false); + expect(this.vm.getDiscrepancySummary('carton')).toEqual(''); + + this.vm.POD.discrepancies = [{ + rejectionReason: { + name: 'Damaged' + }, + shipmentType: 'carton' + }]; + expect(this.vm.hasShipmentDiscrepancies('carton')).toBe(true); + expect(this.vm.getDiscrepancySummary('carton')).toEqual('Damaged'); + + this.vm.POD.discrepancies.push({ + rejectionReason: { + name: 'Missing' + }, + shipmentType: 'carton' + }); + this.vm.POD.discrepancies.push({ + rejectionReason: { + name: 'Wrong Item' + }, + shipmentType: 'container' + }); + + expect(this.vm.getShipmentDiscrepancies('carton').length).toEqual(2); + expect(this.vm.getDiscrepancySummary('carton')).toEqual('2 Discrepancies'); + }); + + it('should update page discrepancies from the modal result', function() { + var deferred = this.$q.defer(), + discrepancies = [{ + rejectionReason: { + name: 'Damaged' + }, + shipmentType: 'carton' + }]; + + this.pointOfDeliveryService.show.andReturn(deferred.promise); + this.createController(); + + this.vm.addDiscrepancyOnModal('carton'); + expect(this.pointOfDeliveryService.show).toHaveBeenCalledWith('carton', []); + + deferred.resolve(discrepancies); + this.$rootScope.$apply(); + + expect(this.vm.POD.discrepancies).toEqual(discrepancies); + }); + + it('should submit payload with current page discrepancies', function() { + var discrepancy = { + rejectionReason: { + name: 'Damaged' + }, + shipmentType: 'carton' + }; + + this.createController(); + this.vm.POD = { + supplyingFacility: { + id: 'source-id' + }, + receivingFacility: { + id: 'destination-id' + }, + referenceNo: 'REF-1', + receivedDate: '2026-06-15', + packedBy: 'Packer', + cartonsQuantityOnWaybill: 5, + cartonsQuantityAccepted: 4, + cartonsQuantityRejected: 1, + discrepancies: [discrepancy] + }; + spyOn(this.vm, 'submitPOD'); + + this.vm.buildPayload(); + + expect(this.vm.submitPOD.calls[0].args[0].sourceId).toEqual('source-id'); + expect(this.vm.submitPOD.calls[0].args[0].destinationId).toEqual('destination-id'); + expect(this.vm.submitPOD.calls[0].args[0].discrepancies).toEqual([discrepancy]); + }); +}); diff --git a/src/point-of-delivery-manage/point-of-delivery-manage.html b/src/point-of-delivery-manage/point-of-delivery-manage.html index c5b8013..2da021d 100755 --- a/src/point-of-delivery-manage/point-of-delivery-manage.html +++ b/src/point-of-delivery-manage/point-of-delivery-manage.html @@ -55,9 +55,20 @@

{{ 'pointOfDeliveryManage.title' | message }}

+ ng-click="vm.addDiscrepancyOnModal('carton')" + class="reasons" + ng-class="{edit: vm.hasShipmentDiscrepancies('carton'), add: !vm.hasShipmentDiscrepancies('carton')}"> + + {{'proofOfDeliveryManage.addDiscrepancy' | message}} + + + {{vm.getDiscrepancySummary('carton')}} + + + {{vm.getShipmentDiscrepancies('carton').length}} + {{'pointOfDeliveryManage.discrepancies' | message}} + + @@ -81,4 +103,4 @@

{{ 'pointOfDeliveryManage.title' | message }}

- \ No newline at end of file + diff --git a/src/point-of-delivery/point-of-delivery.service.js b/src/point-of-delivery/point-of-delivery.service.js index 502deea..c7be7cb 100755 --- a/src/point-of-delivery/point-of-delivery.service.js +++ b/src/point-of-delivery/point-of-delivery.service.js @@ -55,26 +55,9 @@ this.submitPodManage = submitPodManage; // To post data POD Manage payload this.getPODs = getPODs; //To retrieve PODs from the database this.show = show; - this.getDiscrepancies = getDiscrepancies; // To retrieve the compiled discrepancies - this.addDiscrepancies = addDiscrepancies; // To compile the list of discrepancies - this.clearDiscrepancies = clearDiscrepancies; this.showViewModal = showViewModal; this.editPOD = editPOD; - var discrepancyList = []; - - function getDiscrepancies() { - return discrepancyList; - } - - function addDiscrepancies (discrepancies) { - discrepancyList.push(discrepancies); - } - function clearDiscrepancies() { - discrepancyList = []; - } - - /** * @ngdoc method * @methodOf point-of-delivery.pointOfDeliveryService @@ -175,4 +158,4 @@ } } -})(); \ No newline at end of file +})();