diff --git a/db/changes/10140-kings/00-waste_getDetail.sql b/db/changes/10140-kings/00-waste_getDetail.sql deleted file mode 100644 index 9bf6cf049..000000000 --- a/db/changes/10140-kings/00-waste_getDetail.sql +++ /dev/null @@ -1,30 +0,0 @@ -USE `bs`; -DROP procedure IF EXISTS `waste_getDetail`; - -DELIMITER $$ -USE `bs`$$ -CREATE DEFINER=`root`@`%`PROCEDURE `waste_getDetail` () -BEGIN - DECLARE vWeek INT; - DECLARE vYear INT; - - SELECT week, year - INTO vWeek, vYear - FROM vn.time - WHERE dated = CURDATE(); - - SELECT *, 100 * dwindle / total AS percentage - FROM ( - SELECT buyer, - ws.family, - sum(ws.saleTotal) AS total, - sum(ws.saleWaste) AS dwindle - FROM bs.waste ws - WHERE `year` = vYear AND `week` = vWeek - GROUP BY buyer, family - ) sub - ORDER BY percentage DESC; -END$$ - -DELIMITER ; - diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index d54b28bb7..c27d8a445 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -1119,6 +1119,19 @@ INSERT INTO `bi`.`claims_ratio`(`id_Cliente`, `Consumo`, `Reclamaciones`, `Ratio (103, 2000, 0.00, 0.00, 0.02, 1.00), (104, 2500, 150.00, 0.02, 0.10, 1.00); +INSERT INTO `bs`.`waste`(`buyer`, `year`, `week`, `family`, `saleTotal`, `saleWaste`, `rate`) + VALUES + ('CharlesXavier', YEAR(CURDATE()), WEEK(CURDATE(), 1), 'Clavel', '1062', '51', '4.8'), + ('CharlesXavier', YEAR(CURDATE()), WEEK(CURDATE(), 1), 'Clavel Colombia', '35074', '687', '2.0'), + ('CharlesXavier', YEAR(CURDATE()), WEEK(CURDATE(), 1), 'Clavel Mini', '1777', '13', '0.7'), + ('CharlesXavier', YEAR(CURDATE()), WEEK(CURDATE(), 1), 'Clavel Short', '9182', '59', '0.6'), + ('DavidCharlesHaller', YEAR(CURDATE()), WEEK(CURDATE(), 1), 'Contenedores', '-74', '0', '0.0'), + ('DavidCharlesHaller', YEAR(CURDATE()), WEEK(CURDATE(), 1), 'Embalajes', '-7', '0', '0.0'), + ('DavidCharlesHaller', YEAR(CURDATE()), WEEK(CURDATE(), 1), 'Portes', '1100', '0', '0.0'), + ('HankPym', YEAR(CURDATE()), WEEK(CURDATE(), 1), 'Accesorios Funerarios', '848', '-187', '-22.1'), + ('HankPym', YEAR(CURDATE()), WEEK(CURDATE(), 1), 'Accesorios Varios', '186', '0', '0.0'), + ('HankPym', YEAR(CURDATE()), WEEK(CURDATE(), 1), 'Adhesivos', '277', '0', '0.0'); + INSERT INTO `vn`.`buy`(`id`,`entryFk`,`itemFk`,`buyingValue`,`quantity`,`packageFk`,`stickers`,`freightValue`,`packageValue`,`comissionValue`,`packing`,`grouping`,`groupingMode`,`location`,`price1`,`price2`,`price3`,`minPrice`,`producer`,`printedStickers`,`isChecked`,`isIgnored`, `created`) VALUES (1, 1, 1, 50, 5000, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, DATE_ADD(CURDATE(), INTERVAL -2 MONTH)), diff --git a/e2e/paths/05-ticket-module/01-sale/01_list_sales.spec.js b/e2e/paths/05-ticket-module/01-sale/01_list_sales.spec.js index a5f7aa423..a755cd940 100644 --- a/e2e/paths/05-ticket-module/01-sale/01_list_sales.spec.js +++ b/e2e/paths/05-ticket-module/01-sale/01_list_sales.spec.js @@ -29,7 +29,7 @@ describe('Ticket List sale path', () => { const value = await nightmare .waitToGetProperty(selectors.ticketSales.firstSaleDiscount, 'innerText'); - expect(value).toContain('0 %'); + expect(value).toContain('0.00%'); }); it('should confirm the first sale contains the total import', async() => { diff --git a/e2e/paths/05-ticket-module/01-sale/02_edit_sale.spec.js b/e2e/paths/05-ticket-module/01-sale/02_edit_sale.spec.js index 5a6d6cd62..898a1fe53 100644 --- a/e2e/paths/05-ticket-module/01-sale/02_edit_sale.spec.js +++ b/e2e/paths/05-ticket-module/01-sale/02_edit_sale.spec.js @@ -173,10 +173,10 @@ xdescribe('Ticket Edit sale path', () => { it('should confirm the discount have been updated', async() => { const result = await nightmare - .waitForTextInElement(`${selectors.ticketSales.firstSaleDiscount} > span`, '50 %') + .waitForTextInElement(`${selectors.ticketSales.firstSaleDiscount} > span`, '50.00%') .waitToGetProperty(`${selectors.ticketSales.firstSaleDiscount} > span`, 'innerText'); - expect(result).toContain('50 %'); + expect(result).toContain('50.00%'); }); it('should confirm the total import for that item have been updated', async() => { diff --git a/front/core/filters/percentage.js b/front/core/filters/percentage.js index 58e67ffa6..41e3f8288 100644 --- a/front/core/filters/percentage.js +++ b/front/core/filters/percentage.js @@ -1,15 +1,22 @@ import ngModule from '../module'; -/** - * Formats a number multiplying by 100 and adding character %. - * - * @return {String} The formated number - */ -export default function percentage() { - return function(input) { +export default function percentage($translate) { + function percentage(input, fractionSize = 2) { if (input == null || input === '') return null; - return `${input} %`; - }; + + return new Intl.NumberFormat($translate.use(), { + style: 'percent', + minimumFractionDigits: fractionSize, + maximumFractionDigits: fractionSize + }).format(parseFloat(input)); + } + + percentage.$stateful = true; + + return percentage; } + +percentage.$inject = ['$translate']; + ngModule.filter('percentage', percentage); diff --git a/modules/item/back/methods/item/getWasteDetail.js b/modules/item/back/methods/item/getWasteDetail.js new file mode 100644 index 000000000..edaebf2f2 --- /dev/null +++ b/modules/item/back/methods/item/getWasteDetail.js @@ -0,0 +1,52 @@ +module.exports = Self => { + Self.remoteMethod('getWasteDetail', { + description: 'Returns the ', + accessType: 'READ', + accepts: [], + returns: { + type: ['Object'], + root: true + }, + http: { + path: `/getWasteDetail`, + verb: 'GET' + } + }); + + Self.getWasteDetail = async() => { + const wastes = await Self.rawSql(` + SELECT *, 100 * dwindle / total AS percentage + FROM ( + SELECT buyer, + ws.family, + sum(ws.saleTotal) AS total, + sum(ws.saleWaste) AS dwindle + FROM bs.waste ws + WHERE year = YEAR(CURDATE()) AND week = WEEK(CURDATE(), 1) + GROUP BY buyer, family + ) sub + ORDER BY percentage DESC;`); + + const details = []; + + for (let waste of wastes) { + const buyerName = waste.buyer; + + let buyerDetail = details.find(waste => { + return waste.buyer == buyerName; + }); + + if (!buyerDetail) { + buyerDetail = { + buyer: buyerName, + lines: [] + }; + details.push(buyerDetail); + } + + buyerDetail.lines.push(waste); + } + + return details; + }; +}; diff --git a/modules/item/back/methods/item/specs/getWasteDetail.spec.js b/modules/item/back/methods/item/specs/getWasteDetail.spec.js new file mode 100644 index 000000000..1874371a0 --- /dev/null +++ b/modules/item/back/methods/item/specs/getWasteDetail.spec.js @@ -0,0 +1,23 @@ +const app = require('vn-loopback/server/server'); + +describe('item getWasteDetail()', () => { + it('should check for the waste breakdown for every worker', async() => { + let result = await app.models.Item.getWasteDetail(); + + const firstBuyer = result[0].buyer; + const firstBuyerLines = result[0].lines; + const secondBuyer = result[1].buyer; + const secondBuyerLines = result[1].lines; + const thirdBuyer = result[2].buyer; + const thirdBuyerLines = result[2].lines; + + expect(result.length).toEqual(3); + expect(firstBuyer).toEqual('CharlesXavier'); + expect(firstBuyerLines.length).toEqual(4); + expect(secondBuyer).toEqual('DavidCharlesHaller'); + expect(secondBuyerLines.length).toEqual(3); + + expect(thirdBuyer).toEqual('HankPym'); + expect(thirdBuyerLines.length).toEqual(3); + }); +}); diff --git a/modules/item/back/models/item.js b/modules/item/back/models/item.js index 628bd5a03..6c221e94d 100644 --- a/modules/item/back/models/item.js +++ b/modules/item/back/models/item.js @@ -11,6 +11,7 @@ module.exports = Self => { require('../methods/item/regularize')(Self); require('../methods/item/getVisibleAvailable')(Self); require('../methods/item/new')(Self); + require('../methods/item/getWasteDetail')(Self); Self.validatesPresenceOf('originFk', {message: 'Cannot be blank'}); diff --git a/modules/item/front/locale/es.yml b/modules/item/front/locale/es.yml index c071d2c69..9580fd156 100644 --- a/modules/item/front/locale/es.yml +++ b/modules/item/front/locale/es.yml @@ -60,4 +60,5 @@ Barcodes: Códigos de barras Diary: Histórico Item diary: Registro de compra-venta Last entries: Últimas entradas -Tags: Etiquetas \ No newline at end of file +Tags: Etiquetas +Waste breakdown: Desglose de mermas \ No newline at end of file diff --git a/modules/item/front/routes.json b/modules/item/front/routes.json index 611bba785..a3cf0bee6 100644 --- a/modules/item/front/routes.json +++ b/modules/item/front/routes.json @@ -7,7 +7,8 @@ "menus": { "main": [ {"state": "item.index", "icon": "icon-item"}, - {"state": "item.request", "icon": "pan_tool"} + {"state": "item.request", "icon": "pan_tool"}, + {"state": "item.waste", "icon": "icon-claims"} ], "card": [ {"state": "item.card.basicData", "icon": "settings"}, @@ -141,11 +142,8 @@ "url" : "/waste", "state": "item.waste", "component": "vn-item-waste", - "description": "Waste", - "params": { - "item": "$ctrl.item" - }, - "acl": ["employee"] + "description": "Waste breakdown", + "acl": ["buyer"] } ] } \ No newline at end of file diff --git a/modules/item/front/waste/index.html b/modules/item/front/waste/index.html index e7ddb25fd..059f82db8 100644 --- a/modules/item/front/waste/index.html +++ b/modules/item/front/waste/index.html @@ -1,120 +1,32 @@ + url="Items/getWasteDetail" + data="details"> - - - - Buyer - Family - Percentage - Mermas - Total - - - - - - - {{request.ticketFk}} - - - - - {{::request.shipped | date: 'dd/MM/yyyy'}} - - - {{::request.warehouse}} - - - {{::request.salesPersonNickname}} - - - {{::request.description}} - {{::request.quantity}} - {{::request.price | currency: 'EUR':2}} - - - {{::request.atenderNickname}} - - - - {{request.itemFk}} - - - - - - - {{request.saleQuantity}} - - - - - - - - {{request.itemDescription}} - - - {{$ctrl.getState(request.isOk)}} - - - - - - - - - +
+ +
{{detail.buyer}}
+
+ + + + Family + Percentage + Dwindle + Total + + + + + {{::waste.family}} + {{::(waste.percentage / 100) | percentage: 2}} + {{::waste.dwindle | currency: 'EUR'}} + {{::waste.total | currency: 'EUR'}} + + + +
- - - - - - - - -
Specify the reasons to deny this request
- - - - -
- - - - -
\ No newline at end of file diff --git a/modules/item/front/waste/index.js b/modules/item/front/waste/index.js index f64cf3e1d..9344c2222 100644 --- a/modules/item/front/waste/index.js +++ b/modules/item/front/waste/index.js @@ -2,11 +2,7 @@ import ngModule from '../module'; import Component from 'core/lib/component'; import './style.scss'; -export default class Controller extends Component { - -} - ngModule.component('vnItemWaste', { template: require('./index.html'), - controller: Controller + controller: Component }); diff --git a/modules/item/front/waste/index.spec.js b/modules/item/front/waste/index.spec.js deleted file mode 100644 index 4c1e31634..000000000 --- a/modules/item/front/waste/index.spec.js +++ /dev/null @@ -1,133 +0,0 @@ -import './index.js'; -import crudModel from 'core/mocks/crud-model'; - -describe('Item', () => { - describe('Component vnItemRequest', () => { - let $scope; - let $element; - let controller; - let $httpBackend; - - beforeEach(ngModule('item')); - - beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => { - $httpBackend = _$httpBackend_; - $scope = $rootScope.$new(); - $scope.model = crudModel; - $scope.denyReason = {hide: () => {}}; - $element = angular.element(''); - controller = $componentController('vnItemRequest', {$element, $scope}); - })); - - afterAll(() => { - $scope.$destroy(); - $element.remove(); - }); - - describe('getState()', () => { - it(`should return an string depending to the isOK value`, () => { - let isOk = null; - let result = controller.getState(isOk); - - expect(result).toEqual('Nueva'); - - isOk = 1; - result = controller.getState(isOk); - - expect(result).toEqual('Aceptada'); - - isOk = 0; - result = controller.getState(isOk); - - expect(result).toEqual('Denegada'); - }); - }); - - describe('confirmRequest()', () => { - it(`should do nothing if the request does't have itemFk or saleQuantity`, () => { - let request = {}; - spyOn(controller.vnApp, 'showSuccess'); - - controller.confirmRequest(request); - - expect(controller.vnApp.showSuccess).not.toHaveBeenCalledWith(); - }); - - it('should perform a query and call vnApp.showSuccess() and refresh if the conditions are met', () => { - spyOn(controller.vnApp, 'showSuccess'); - let model = controller.$.model; - spyOn(model, 'refresh'); - - const expectedResult = {concept: 'Melee Weapon'}; - let request = {itemFk: 1, saleQuantity: 1, id: 1}; - - $httpBackend.when('POST', `TicketRequests/${request.id}/confirm`).respond(expectedResult); - $httpBackend.expect('POST', `TicketRequests/${request.id}/confirm`).respond(expectedResult); - controller.confirmRequest(request); - $httpBackend.flush(); - - expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); - }); - }); - - describe('changeQuantity()', () => { - it(`should call confirmRequest() if there's no sale id in the request`, () => { - let request = {}; - spyOn(controller, 'confirmRequest'); - - controller.changeQuantity(request); - - expect(controller.confirmRequest).toHaveBeenCalledWith(jasmine.any(Object)); - }); - - it(`should perform a query and call vnApp.showSuccess() if the conditions are met`, () => { - let request = {saleFk: 1, saleQuantity: 1}; - spyOn(controller.vnApp, 'showSuccess'); - - - $httpBackend.when('PATCH', `Sales/${request.saleFk}/`).respond(); - $httpBackend.expect('PATCH', `Sales/${request.saleFk}/`).respond(); - controller.changeQuantity(request); - $httpBackend.flush(); - - expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); - }); - }); - - describe('compareDate()', () => { - it(`should return "success" if receives a future date`, () => { - let date = '3019-02-18T11:00:00.000Z'; - - let result = controller.compareDate(date); - - expect(result).toEqual('success'); - }); - - it(`should return "warning" if date is today`, () => { - let date = new Date(); - - let result = controller.compareDate(date); - - expect(result).toEqual('warning'); - }); - }); - - describe('denyRequest()', () => { - it(`should perform a query and call vnApp.showSuccess(), refresh(), hide() and set denyObservation to null in the controller`, () => { - spyOn(controller.vnApp, 'showSuccess'); - - const request = {id: 1}; - const expectedResult = {isOk: false, attenderFk: 106, response: 'Denied!'}; - controller.selectedRequest = request; - - $httpBackend.when('POST', `TicketRequests/${request.id}/deny`).respond(expectedResult); - $httpBackend.expect('POST', `TicketRequests/${request.id}/deny`).respond(expectedResult); - controller.denyRequest('accept'); - $httpBackend.flush(); - - expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); - }); - }); - }); -}); - diff --git a/modules/item/front/waste/locale/es.yml b/modules/item/front/waste/locale/es.yml index 33710a327..9f08e3a72 100644 --- a/modules/item/front/waste/locale/es.yml +++ b/modules/item/front/waste/locale/es.yml @@ -1,6 +1,3 @@ -Discard: Descartar -Specify the reasons to deny this request: Especifica las razones para descartar la petición -Buy requests: Peticiones de compra -Search request by id or alias: Buscar peticiones por identificador o alias -Requested: Solicitado -Achieved: Conseguido \ No newline at end of file +Family: Familia +Percentage: Porcentaje +Dwindle: Mermas \ No newline at end of file diff --git a/modules/item/front/waste/style.scss b/modules/item/front/waste/style.scss index 0af32c309..ed8d10369 100644 --- a/modules/item/front/waste/style.scss +++ b/modules/item/front/waste/style.scss @@ -1,18 +1,19 @@ @import "variables"; -vn-item-request { - vn-dialog[vn-id="denyReason"] { - button.close { - display: none - } - vn-button { - margin: 0 auto - } - vn-textarea { - width: 100% - } - } - vn-icon[icon=insert_drive_file]{ - color: $color-font-secondary; +vn-item-waste { + .header { + margin-bottom: 16px; + text-transform: uppercase; + font-size: 15pt; + line-height: 1; + padding: 7px; + padding-bottom: 7px; + padding-bottom: 4px; + font-weight: lighter; + background-color: #fde6ca; + border-bottom: 0.1em solid #f7931e; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } } \ No newline at end of file diff --git a/print/templates/email/buyer-week-waste/assets/css/import.js b/print/templates/email/buyer-week-waste/assets/css/import.js index b44d6bd37..c742fdf90 100644 --- a/print/templates/email/buyer-week-waste/assets/css/import.js +++ b/print/templates/email/buyer-week-waste/assets/css/import.js @@ -4,5 +4,6 @@ module.exports = new Stylesheet([ `${appPath}/common/css/spacing.css`, `${appPath}/common/css/misc.css`, `${appPath}/common/css/layout.css`, - `${appPath}/common/css/email.css`]) + `${appPath}/common/css/email.css`, + `${__dirname}/style.css`]) .mergeStyles(); diff --git a/print/templates/email/buyer-week-waste/assets/css/style.css b/print/templates/email/buyer-week-waste/assets/css/style.css new file mode 100644 index 000000000..5db85befa --- /dev/null +++ b/print/templates/email/buyer-week-waste/assets/css/style.css @@ -0,0 +1,5 @@ +.external-link { + border: 2px dashed #8dba25; + border-radius: 3px; + text-align: center +} \ No newline at end of file diff --git a/print/templates/email/buyer-week-waste/buyer-week-waste.html b/print/templates/email/buyer-week-waste/buyer-week-waste.html index 47d162a6f..56d0a7e0d 100644 --- a/print/templates/email/buyer-week-waste/buyer-week-waste.html +++ b/print/templates/email/buyer-week-waste/buyer-week-waste.html @@ -49,8 +49,15 @@ +

+ +
diff --git a/print/templates/email/buyer-week-waste/locale/es.yml b/print/templates/email/buyer-week-waste/locale/es.yml index 96e49d9eb..fca8c3a2b 100644 --- a/print/templates/email/buyer-week-waste/locale/es.yml +++ b/print/templates/email/buyer-week-waste/locale/es.yml @@ -5,4 +5,5 @@ description: A continuación se muestra la merma semanal a fecha de {0}< buyer: Comprador percentage: Porcentaje weakening: Mermas -total: Total \ No newline at end of file +total: Total +wasteDetailLink: 'Para ver el desglose de mermas haz clic en el siguiente enlace:' \ No newline at end of file