diff --git a/CHANGELOG.md b/CHANGELOG.md index 984f8fffd7..b8d3a97c9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - (Trabajadores -> Nuevo trabajador) Los clientes se crean sin 'TR' pero se añade tipo de negocio 'Trabajador' ### Fixed -- +- (Tickets -> Líneas) Se permite hacer split de líneas al mismo ticket diff --git a/db/changes/232001/00-procedurecanAdvance.sql b/db/changes/232001/00-procedurecanAdvance.sql new file mode 100644 index 0000000000..9d5c242968 --- /dev/null +++ b/db/changes/232001/00-procedurecanAdvance.sql @@ -0,0 +1,127 @@ +DELIMITER $$ +$$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_canAdvance`(vDateFuture DATE, vDateToAdvance DATE, vWarehouseFk INT) +BEGIN +/** + * Devuelve los tickets y la cantidad de lineas de venta que se pueden adelantar. + * + * @param vDateFuture Fecha de los tickets que se quieren adelantar. + * @param vDateToAdvance Fecha a cuando se quiere adelantar. + * @param vWarehouseFk Almacén + */ + + DECLARE vDateInventory DATE; + + SELECT inventoried INTO vDateInventory FROM config; + + DROP TEMPORARY TABLE IF EXISTS tmp.stock; + CREATE TEMPORARY TABLE tmp.stock + (itemFk INT PRIMARY KEY, + amount INT) + ENGINE = MEMORY; + + INSERT INTO tmp.stock(itemFk, amount) + SELECT itemFk, SUM(quantity) amount FROM + ( + SELECT itemFk, quantity + FROM itemTicketOut + WHERE shipped >= vDateInventory + AND shipped < vDateFuture + AND warehouseFk = vWarehouseFk + UNION ALL + SELECT itemFk, quantity + FROM itemEntryIn + WHERE landed >= vDateInventory + AND landed < vDateFuture + AND isVirtualStock = FALSE + AND warehouseInFk = vWarehouseFk + UNION ALL + SELECT itemFk, quantity + FROM itemEntryOut + WHERE shipped >= vDateInventory + AND shipped < vDateFuture + AND warehouseOutFk = vWarehouseFk + ) t + GROUP BY itemFk HAVING amount != 0; + + DROP TEMPORARY TABLE IF EXISTS tmp.filter; + CREATE TEMPORARY TABLE tmp.filter + (INDEX (id)) +SELECT + origin.ticketFk futureId, + dest.ticketFk id, + dest.state, + origin.futureState, + origin.futureIpt, + dest.ipt, + origin.workerFk, + origin.futureLiters, + origin.futureLines, + dest.shipped, + origin.shipped futureShipped, + dest.totalWithVat, + origin.totalWithVat futureTotalWithVat, + dest.agency, + origin.futureAgency, + dest.lines, + dest.liters, + origin.futureLines - origin.hasStock AS notMovableLines, + (origin.futureLines = origin.hasStock) AS isFullMovable, + origin.classColor, + dest.classColor futureClassColor + FROM ( + SELECT + s.ticketFk, + t.workerFk, + t.shipped, + t.totalWithVat, + st.name futureState, + t.addressFk, + am.name futureAgency, + count(s.id) futureLines, + GROUP_CONCAT(DISTINCT ipt.code ORDER BY ipt.code) futureIpt, + CAST(SUM(litros) AS DECIMAL(10,0)) futureLiters, + SUM((s.quantity <= IFNULL(st.amount,0))) hasStock, + st.classColor + FROM ticket t + JOIN sale s ON s.ticketFk = t.id + JOIN saleVolume sv ON sv.saleFk = s.id + JOIN item i ON i.id = s.itemFk + JOIN ticketState ts ON ts.ticketFk = t.id + JOIN state st ON st.id = ts.stateFk + JOIN agencyMode am ON t.agencyModeFk = am.id + LEFT JOIN itemPackingType ipt ON ipt.code = i.itemPackingTypeFk + LEFT JOIN tmp.stock st ON st.itemFk = i.id + WHERE t.shipped BETWEEN vDateFuture AND util.dayend(vDateFuture) + AND t.warehouseFk = vWarehouseFk + GROUP BY t.id + ) origin + JOIN ( + SELECT + t.id ticketFk, + t.addressFk, + st.name state, + GROUP_CONCAT(DISTINCT ipt.code ORDER BY ipt.code) ipt, + t.shipped, + t.totalWithVat, + am.name agency, + CAST(SUM(litros) AS DECIMAL(10,0)) liters, + CAST(COUNT(*) AS DECIMAL(10,0)) `lines`, + st.classColor + FROM ticket t + JOIN sale s ON s.ticketFk = t.id + JOIN saleVolume sv ON sv.saleFk = s.id + JOIN item i ON i.id = s.itemFk + JOIN ticketState ts ON ts.ticketFk = t.id + JOIN state st ON st.id = ts.stateFk + JOIN agencyMode am ON t.agencyModeFk = am.id + LEFT JOIN itemPackingType ipt ON ipt.code = i.itemPackingTypeFk + WHERE t.shipped BETWEEN vDateToAdvance AND util.dayend(vDateToAdvance) + AND t.warehouseFk = vWarehouseFk + AND st.order <= 5 + GROUP BY t.id + ) dest ON dest.addressFk = origin.addressFk + WHERE origin.hasStock != 0; + DROP TEMPORARY TABLE tmp.stock; +END$$ +DELIMITER ; \ No newline at end of file diff --git a/db/changes/232001/00-procedurecanbePostponed.sql b/db/changes/232001/00-procedurecanbePostponed.sql new file mode 100644 index 0000000000..39aa35cd5c --- /dev/null +++ b/db/changes/232001/00-procedurecanbePostponed.sql @@ -0,0 +1,72 @@ +DELIMITER $$ +$$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_canbePostponed`(vOriginDated DATE, vFutureDated DATE, vWarehouseFk INT) +BEGIN +/** + * Devuelve un listado de tickets susceptibles de fusionarse con otros tickets en el futuro + * + * @param vOriginDated Fecha en cuestión + * @param vFutureDated Fecha en el futuro a sondear + * @param vWarehouseFk Identificador de vn.warehouse + */ + DROP TEMPORARY TABLE IF EXISTS tmp.filter; + CREATE TEMPORARY TABLE tmp.filter + (INDEX (id)) + SELECT sv.ticketFk id, + sub2.id futureId, + GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) ipt, + CAST(sum(litros) AS DECIMAL(10,0)) liters, + CAST(count(*) AS DECIMAL(10,0)) `lines`, + st.name state, + sub2.iptd futureIpt, + sub2.state futureState, + t.clientFk, + t.warehouseFk, + ts.alertLevel, + t.shipped, + sub2.shipped futureShipped, + t.workerFk, + st.code stateCode, + sub2.code futureStateCode, + st.classColor + FROM vn.saleVolume sv + JOIN vn.sale s ON s.id = sv.saleFk + JOIN vn.item i ON i.id = s.itemFk + JOIN vn.ticket t ON t.id = sv.ticketFk + JOIN vn.address a ON a.id = t.addressFk + JOIN vn.province p ON p.id = a.provinceFk + JOIN vn.country c ON c.id = p.countryFk + JOIN vn.ticketState ts ON ts.ticketFk = t.id + JOIN vn.state st ON st.id = ts.stateFk + JOIN vn.alertLevel al ON al.id = ts.alertLevel + LEFT JOIN vn.ticketParking tp ON tp.ticketFk = t.id + LEFT JOIN ( + SELECT * + FROM ( + SELECT + t.addressFk, + t.id, + t.shipped, + st.name state, + st.code code, + GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) iptd + FROM vn.ticket t + JOIN vn.ticketState ts ON ts.ticketFk = t.id + JOIN vn.state st ON st.id = ts.stateFk + JOIN vn.sale s ON s.ticketFk = t.id + JOIN vn.item i ON i.id = s.itemFk + WHERE t.shipped BETWEEN vFutureDated + AND util.dayend(vFutureDated) + AND t.warehouseFk = vWarehouseFk + GROUP BY t.id + ) sub + GROUP BY sub.addressFk + ) sub2 ON sub2.addressFk = t.addressFk AND t.id != sub2.id + WHERE t.shipped BETWEEN vOriginDated AND util.dayend(vOriginDated) + AND t.warehouseFk = vWarehouseFk + AND al.code = 'FREE' + AND tp.ticketFk IS NULL + GROUP BY sv.ticketFk + HAVING futureId; +END$$ +DELIMITER ; \ No newline at end of file diff --git a/db/changes/232201/00-defaulterView.sql b/db/changes/232201/00-defaulterView.sql new file mode 100644 index 0000000000..8b56e59452 --- /dev/null +++ b/db/changes/232201/00-defaulterView.sql @@ -0,0 +1,20 @@ +-- vn.defaulter source + +CREATE OR REPLACE +ALGORITHM = UNDEFINED VIEW `vn`.`defaulter` AS +select + `d`.`clientFk` AS `clientFk`, + `d`.`created` AS `created`, + `d`.`amount` AS `amount`, + `d`.`defaulterSinced` AS `defaulterSinced`, + `d`.`hasChanged` AS `hasChanged`, + `c`.`countryFk` AS `country`, + `c`.`payMethodFk` AS `payMethod` +from + (((`bs`.`defaulter` `d` +join `vn`.`client` `c` on + (`c`.`id` = `d`.`clientFk`)) +join `vn`.`country` `co` on + (`co`.`id` = `c`.`countryFk`)) +join `vn`.`payMethod` `pm` on + (`pm`.`id` = `c`.`payMethodFk`)); \ No newline at end of file diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index b19db24d77..086909ebfe 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -313,7 +313,7 @@ export default { anyClient: 'vn-client-defaulter tbody > tr', firstClientName: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(2) > span', firstSalesPersonName: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(3) > span', - firstObservation: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(6) > vn-textarea[ng-model="defaulter.observation"]', + firstObservation: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(8) > vn-textarea[ng-model="defaulter.observation"]', allDefaulterCheckbox: 'vn-client-defaulter thead vn-multi-check', addObservationButton: 'vn-client-defaulter vn-button[icon="icon-notes"]', observation: '.vn-dialog.shown vn-textarea[ng-model="$ctrl.defaulter.observation"]', diff --git a/front/core/components/searchbar/searchbar.js b/front/core/components/searchbar/searchbar.js index aefa89b5ba..c741468178 100644 --- a/front/core/components/searchbar/searchbar.js +++ b/front/core/components/searchbar/searchbar.js @@ -203,7 +203,7 @@ export default class Searchbar extends Component { doSearch(filter, source) { if (filter === this.filter && !this.isIndex) return; - let promise = this.onSearch({$params: filter}); + let promise = this.onSearch({$params: filter}, source); promise = promise || this.$q.resolve(); promise.then(data => this.onFilter(filter, source, data)); this.toBar(filter); @@ -259,12 +259,6 @@ export default class Searchbar extends Component { this.filter = filter; - if (source == 'removeBar') { - delete params[this.toRemove]; - delete this.model.userParams[this.toRemove]; - this.model.refresh(); - } - if (!filter && this.model) this.model.clear(); if (source != 'state') @@ -279,7 +273,7 @@ export default class Searchbar extends Component { return {id: params.$row.id}; } - onSearch(args) { + onSearch(args, source) { if (!this.model) return; let filter = args.$params; @@ -325,6 +319,12 @@ export default class Searchbar extends Component { for (let param in stateFilter.tableQ) params[param] = stateFilter.tableQ[param]; + if (source == 'removeBar') { + delete params[this.toRemove]; + delete this.model.userParams[this.toRemove]; + delete stateFilter[this.toRemove]; + } + const newParams = Object.assign(stateFilter, params); return this.model.applyParams(newParams) .then(() => this.model.data); diff --git a/front/core/components/searchbar/searchbar.spec.js b/front/core/components/searchbar/searchbar.spec.js index 9998e7a7c9..2e00c2905e 100644 --- a/front/core/components/searchbar/searchbar.spec.js +++ b/front/core/components/searchbar/searchbar.spec.js @@ -197,7 +197,7 @@ describe('Component vnSearchbar', () => { controller.doSearch(filter, 'any'); $scope.$apply(); - expect(controller.onSearch).toHaveBeenCalledWith({$params: filter}); + expect(controller.onSearch).toHaveBeenCalledWith({$params: filter}, 'any'); expect(controller.onFilter).toHaveBeenCalledWith(filter, 'any', undefined); }); }); diff --git a/modules/client/back/methods/defaulter/filter.js b/modules/client/back/methods/defaulter/filter.js index 748581913a..736c29f9ca 100644 --- a/modules/client/back/methods/defaulter/filter.js +++ b/modules/client/back/methods/defaulter/filter.js @@ -67,9 +67,13 @@ module.exports = Self => { uw.id workerFk, uw.name workerName, c.creditInsurance, - d.defaulterSinced + d.defaulterSinced, + cn.country, + pm.name payMethod FROM vn.defaulter d JOIN vn.client c ON c.id = d.clientFk + JOIN vn.country cn ON cn.id = c.countryFk + JOIN vn.payMethod pm ON pm.id = c.payMethodFk LEFT JOIN vn.clientObservation co ON co.clientFk = c.id LEFT JOIN account.user u ON u.id = c.salesPersonFk LEFT JOIN account.user uw ON uw.id = co.workerFk diff --git a/modules/client/back/models/defaulter.json b/modules/client/back/models/defaulter.json index ddff1d2143..03d68ea71a 100644 --- a/modules/client/back/models/defaulter.json +++ b/modules/client/back/models/defaulter.json @@ -29,6 +29,16 @@ "type": "belongsTo", "model": "Client", "foreignKey": "clientFk" + }, + "country": { + "type": "belongsTo", + "model": "Country", + "foreignKey": "country" + }, + "payMethod": { + "type": "belongsTo", + "model": "PayMethod", + "foreignKey": "payMethod" } } } \ No newline at end of file diff --git a/modules/client/front/defaulter/index.html b/modules/client/front/defaulter/index.html index 8f22629a95..4f662b62bd 100644 --- a/modules/client/front/defaulter/index.html +++ b/modules/client/front/defaulter/index.html @@ -57,6 +57,13 @@ Comercial + + Country + + + P.Method + @@ -111,6 +118,12 @@ {{::defaulter.salesPersonName | dashIfEmpty}} + + {{::defaulter.country}} + + + {{::defaulter.payMethod}} + {{::defaulter.amount | currency: 'EUR': 2}} { const saleIds = sales.map(sale => sale.id); const hasClaimedSales = await models.ClaimBeginning.findOne({where: {saleFk: {inq: saleIds}}}); - if (hasClaimedSales) + if (ticketId != id && hasClaimedSales) throw new UserError(`Can't transfer claimed sales`); for (const sale of sales) { diff --git a/modules/ticket/front/advance/index.html b/modules/ticket/front/advance/index.html index 007f80fe66..c937fe2ac7 100644 --- a/modules/ticket/front/advance/index.html +++ b/modules/ticket/front/advance/index.html @@ -122,7 +122,7 @@ {{::ticket.ipt | dashIfEmpty}} + class="chip {{ticket.classColor}}"> {{::ticket.state | dashIfEmpty}} @@ -150,7 +150,7 @@ {{::ticket.futureIpt | dashIfEmpty}} + class="chip {{ticket.classColor}}"> {{::ticket.futureState | dashIfEmpty}} diff --git a/modules/ticket/front/future/index.html b/modules/ticket/front/future/index.html index 8628bb34cd..2f0290c27d 100644 --- a/modules/ticket/front/future/index.html +++ b/modules/ticket/front/future/index.html @@ -137,7 +137,7 @@ {{::ticket.ipt | dashIfEmpty}} + class="chip {{ticket.classColor}}"> {{::ticket.state}} @@ -158,7 +158,7 @@ {{::ticket.futureIpt | dashIfEmpty}} + class="chip {{ticket.classColor}}"> {{::ticket.futureState}} diff --git a/modules/ticket/front/future/index.js b/modules/ticket/front/future/index.js index 1732c562c7..66d6269936 100644 --- a/modules/ticket/front/future/index.js +++ b/modules/ticket/front/future/index.js @@ -95,13 +95,6 @@ export default class Controller extends Section { return checkedLines; } - stateColor(state) { - if (state === 'OK') - return 'success'; - else if (state === 'Libre') - return 'notice'; - } - dateRange(value) { const minHour = new Date(value); minHour.setHours(0, 0, 0, 0); diff --git a/modules/ticket/front/future/index.spec.js b/modules/ticket/front/future/index.spec.js index 1884212984..ca29c6df7a 100644 --- a/modules/ticket/front/future/index.spec.js +++ b/modules/ticket/front/future/index.spec.js @@ -60,24 +60,6 @@ describe('Component vnTicketFuture', () => { }); }); - describe('stateColor()', () => { - it('should return success to the OK tickets', () => { - const ok = controller.stateColor(controller.$.model.data[0].state); - const notOk = controller.stateColor(controller.$.model.data[1].state); - - expect(ok).toEqual('success'); - expect(notOk).not.toEqual('success'); - }); - - it('should return success to the FREE tickets', () => { - const notFree = controller.stateColor(controller.$.model.data[0].state); - const free = controller.stateColor(controller.$.model.data[1].state); - - expect(free).toEqual('notice'); - expect(notFree).not.toEqual('notice'); - }); - }); - describe('dateRange()', () => { it('should return two dates with the hours at the start and end of the given date', () => { const dateRange = controller.dateRange(today); diff --git a/modules/ticket/front/main/index.html b/modules/ticket/front/main/index.html index 82b5e58cd2..e1e6c4e470 100644 --- a/modules/ticket/front/main/index.html +++ b/modules/ticket/front/main/index.html @@ -16,4 +16,4 @@ - \ No newline at end of file + diff --git a/modules/ticket/front/main/index.js b/modules/ticket/front/main/index.js index 3f9482fc4e..c49418cfc6 100644 --- a/modules/ticket/front/main/index.js +++ b/modules/ticket/front/main/index.js @@ -1,5 +1,6 @@ import ngModule from '../module'; import ModuleMain from 'salix/components/module-main'; +const UserError = require('vn-loopback/util/user-error'); export default class Ticket extends ModuleMain { fetchParams($params) { @@ -14,10 +15,19 @@ export default class Ticket extends ModuleMain { 'scopeDays' ]; + const seachPanelParams = Object.entries($params); + const hasFromParam = seachPanelParams.some(subarray => subarray.length > 0 && subarray[0] === 'from'); + const hasToParam = seachPanelParams.some(subarray => subarray.length > 0 && subarray[0] === 'to'); + + if ((hasFromParam && !hasToParam) || (!hasFromParam && hasToParam)) + throw new UserError(`Date range must have 'from' and 'to'`); + const hasExcludedParams = excludedParams.some(param => { return $params && $params[param] != undefined; }); + const hasParams = Object.entries($params).length; + if (!hasParams || !hasExcludedParams) $params.scopeDays = 1; @@ -28,7 +38,6 @@ export default class Ticket extends ModuleMain { const to = new Date(from.getTime()); to.setDate(to.getDate() + $params.scopeDays); to.setHours(23, 59, 59, 999); - Object.assign($params, {from, to}); } diff --git a/modules/ticket/front/main/locale/es.yml b/modules/ticket/front/main/locale/es.yml new file mode 100644 index 0000000000..8db264e459 --- /dev/null +++ b/modules/ticket/front/main/locale/es.yml @@ -0,0 +1 @@ +Date range must have 'from' and 'to': El rango de fechas debe tener 'desde' y 'hasta' diff --git a/modules/ticket/front/sale/index.js b/modules/ticket/front/sale/index.js index b7cdc22b9f..be5f22154f 100644 --- a/modules/ticket/front/sale/index.js +++ b/modules/ticket/front/sale/index.js @@ -244,7 +244,12 @@ class Controller extends Section { const query = `tickets/${this.ticket.id}/transferSales`; this.$http.post(query, params) - .then(res => this.$state.go('ticket.card.sale', {id: res.data.id})); + .then(res => { + if (res.data && res.data.id === this.ticket.id) { + this.$.transfer.hide(); + this.$.model.refresh(); + } else this.$state.go('ticket.card.sale', {id: res.data.id}); + }); } showEditPricePopover(event, sale) {