diff --git a/db/changes/10121-zoneClosure/00-zoneClosure.sql b/db/changes/10121-zoneClosure/00-zoneClosure.sql new file mode 100644 index 0000000000..07db5b1678 --- /dev/null +++ b/db/changes/10121-zoneClosure/00-zoneClosure.sql @@ -0,0 +1,5 @@ +CREATE TABLE `vn`.`zoneClosure` ( + `zoneFk` INT NOT NULL, + `dated` DATE NOT NULL, + `hour` TIME NOT NULL, + PRIMARY KEY (`zoneFk`, `dated`)); diff --git a/db/changes/10121-zoneClosure/00-zoneClosure_recalc.sql b/db/changes/10121-zoneClosure/00-zoneClosure_recalc.sql new file mode 100644 index 0000000000..cb313cdec7 --- /dev/null +++ b/db/changes/10121-zoneClosure/00-zoneClosure_recalc.sql @@ -0,0 +1,50 @@ + +DROP procedure IF EXISTS vn.`zoneClosure_recalc`; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE vn.`zoneClosure_recalc`() +proc: BEGIN +/** + * Recalculates the delivery time (hour) for every zone in days + scope in future + */ + DECLARE vScope INT; + DECLARE vCounter INT DEFAULT 0; + DECLARE vShipped DATE DEFAULT CURDATE(); + + DECLARE CONTINUE HANDLER FOR SQLEXCEPTION + BEGIN + DO RELEASE_LOCK('vn.zoneClosure_recalc'); + RESIGNAL; + END; + + IF NOT GET_LOCK('vn.zoneClosure_recalc', 0) THEN + LEAVE proc; + END IF; + + SELECT scope INTO vScope + FROM zoneConfig; + + DROP TEMPORARY TABLE IF EXISTS tmp.zone; + CREATE TEMPORARY TABLE tmp.zone + (INDEX (id)) + ENGINE = MEMORY + SELECT id FROM zone; + + TRUNCATE TABLE zoneClosure; + + REPEAT + CALL zone_getOptionsForShipment(vShipped); + INSERT INTO zoneClosure(zoneFk, dated, `hour`) + SELECT zoneFk, vShipped, `hour` FROM tmp.zoneOption; + + SET vCounter = vCounter + 1; + SET vShipped = TIMESTAMPADD(DAY, 1, vShipped); + UNTIL vCounter > vScope + END REPEAT; + + DROP TEMPORARY TABLE tmp.zone; + DO RELEASE_LOCK('vn.zoneClosure_recalc'); +END$$ + +DELIMITER ; + diff --git a/db/changes/10121-zoneClosure/00-zoneConfig.sql b/db/changes/10121-zoneClosure/00-zoneConfig.sql new file mode 100644 index 0000000000..915fb8894c --- /dev/null +++ b/db/changes/10121-zoneClosure/00-zoneConfig.sql @@ -0,0 +1,11 @@ +CREATE TABLE `vn`.`zoneConfig` ( + `id` INT UNSIGNED NOT NULL, + `scope` INT UNSIGNED NOT NULL, + PRIMARY KEY (`id`)); + +ALTER TABLE `vn`.`zoneConfig` +CHANGE COLUMN `id` `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT ; + +INSERT INTO `vn`.`zoneConfig` (`scope`) VALUES ('1'); + +INSERT INTO `bs`.`nightTask` (`order`, `schema`, `procedure`) VALUES ('100', 'vn', 'zoneClosure_recalc'); diff --git a/db/changes/10140-kings/01-order_confirmWithUser.sql b/db/changes/10140-kings/01-order_confirmWithUser.sql new file mode 100644 index 0000000000..c398edc4c0 --- /dev/null +++ b/db/changes/10140-kings/01-order_confirmWithUser.sql @@ -0,0 +1,240 @@ + +DROP procedure IF EXISTS `hedera`.`order_confirmWithUser`; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `hedera`.`order_confirmWithUser`(IN `vOrder` INT, IN `vUserId` INT) +BEGIN +/** + * Confirms an order, creating each of its tickets on the corresponding + * date, store and user. + * + * @param vOrder The order identifier + * @param vUser The user identifier + */ + DECLARE vOk BOOL; + DECLARE vDone BOOL DEFAULT FALSE; + DECLARE vWarehouse INT; + DECLARE vShipment DATETIME; + DECLARE vTicket INT; + DECLARE vNotes VARCHAR(255); + DECLARE vItem INT; + DECLARE vConcept VARCHAR(30); + DECLARE vAmount INT; + DECLARE vPrice DECIMAL(10,2); + DECLARE vSale INT; + DECLARE vRate INT; + DECLARE vRowId INT; + DECLARE vDelivery DATE; + DECLARE vAddress INT; + DECLARE vIsConfirmed BOOL; + DECLARE vClientId INT; + DECLARE vCompanyId INT; + DECLARE vAgencyModeId INT; + + DECLARE TICKET_FREE INT DEFAULT 2; + + DECLARE cDates CURSOR FOR + SELECT zgs.shipped, r.warehouse_id + FROM `order` o + JOIN order_row r ON r.order_id = o.id + LEFT JOIN tmp.zoneGetShipped zgs ON zgs.warehouseFk = r.warehouse_id + WHERE o.id = vOrder AND r.amount != 0 + GROUP BY r.warehouse_id; + + DECLARE cRows CURSOR FOR + SELECT r.id, r.item_id, i.name, r.amount, r.price, r.rate + FROM order_row r + JOIN vn.item i ON i.id = r.item_id + WHERE r.amount != 0 + AND r.warehouse_id = vWarehouse + AND r.order_id = vOrder + ORDER BY r.rate DESC; + + DECLARE CONTINUE HANDLER FOR NOT FOUND + SET vDone = TRUE; + + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + RESIGNAL; + END; + + -- Carga los datos del pedido + + SELECT o.date_send, o.address_id, o.note, + o.confirmed, a.clientFk, o.company_id, o.agency_id + INTO vDelivery, vAddress, vNotes, + vIsConfirmed, vClientId, vCompanyId, vAgencyModeId + FROM hedera.`order` o + JOIN vn.address a ON a.id = o.address_id + WHERE o.id = vOrder; + + -- Comprueba que el pedido no está confirmado + + IF vIsConfirmed THEN + CALL util.throw ('ORDER_ALREADY_CONFIRMED'); + END IF; + + -- Comprueba que el pedido no está vacío + + SELECT COUNT(*) > 0 INTO vOk + FROM order_row WHERE order_id = vOrder AND amount > 0; + + IF !vOk THEN + CALL util.throw ('ORDER_EMPTY'); + END IF; + + -- Carga las fechas de salida de cada almacén + + CALL vn.zone_getShippedWarehouse (vDelivery, vAddress, vAgencyModeId); + + -- Trabajador que realiza la acción + + IF vUserId IS NULL THEN + SELECT employeeFk INTO vUserId FROM orderConfig; + END IF; + + -- Crea los tickets del pedido + + START TRANSACTION; + + OPEN cDates; + + lDates: + LOOP + SET vTicket = NULL; + SET vDone = FALSE; + FETCH cDates INTO vShipment, vWarehouse; + + IF vDone THEN + LEAVE lDates; + END IF; + + -- Busca un ticket existente que coincida con los parametros + + SELECT t.id INTO vTicket + FROM vn.ticket t + LEFT JOIN vn.ticketState tls on tls.ticket = t.id + JOIN `order` o + ON o.address_id = t.addressFk + AND vWarehouse = t.warehouseFk + AND o.agency_id = t.agencyModeFk + AND o.date_send = t.landed + AND vShipment = DATE(t.shipped) + WHERE o.id = vOrder + AND t.invoiceOutFk IS NULL + AND IFNULL(tls.alertLevel,0) = 0 + AND t.clientFk <> 1118 + LIMIT 1; + + -- Crea el ticket en el caso de no existir uno adecuado + + IF vTicket IS NULL + THEN + CALL vn.ticketCreateWithUser( + vClientId, + IFNULL(vShipment, CURDATE()), + vWarehouse, + vCompanyId, + vAddress, + vAgencyModeId, + NULL, + vDelivery, + vUserId, + vTicket + ); + ELSE + INSERT INTO vncontrol.inter + SET Id_Ticket = vTicket, + Id_Trabajador = vUserId, + state_id = TICKET_FREE; + END IF; + + INSERT IGNORE INTO vn.orderTicket + SET orderFk = vOrder, + ticketFk = vTicket; + + -- Añade las notas + + IF vNotes IS NOT NULL AND vNotes != '' + THEN + INSERT INTO vn.ticketObservation SET + ticketFk = vTicket, + observationTypeFk = 4 /* salesperson */ , + `description` = vNotes + ON DUPLICATE KEY UPDATE + `description` = CONCAT(VALUES(`description`),'. ', `description`); + END IF; + + -- Añade los movimientos y sus componentes + + OPEN cRows; + + lRows: + LOOP + SET vDone = FALSE; + FETCH cRows INTO vRowId, vItem, vConcept, vAmount, vPrice, vRate; + + IF vDone THEN + LEAVE lRows; + END IF; + + INSERT INTO vn.sale + SET + itemFk = vItem, + ticketFk = vTicket, + concept = vConcept, + quantity = vAmount, + price = vPrice, + priceFixed = 0, + isPriceFixed = TRUE; + + SET vSale = LAST_INSERT_ID(); + + INSERT INTO vn.saleComponent + (saleFk, componentFk, `value`) + SELECT vSale, cm.component_id, cm.price + FROM order_component cm + JOIN vn.component c ON c.id = cm.component_id + WHERE cm.order_row_id = vRowId + GROUP BY vSale, cm.component_id; + + UPDATE order_row SET Id_Movimiento = vSale + WHERE id = vRowId; + + END LOOP; + + CLOSE cRows; + + -- Fija el coste + + DROP TEMPORARY TABLE IF EXISTS tComponents; + CREATE TEMPORARY TABLE tComponents + (INDEX (saleFk)) + ENGINE = MEMORY + SELECT SUM(sc.`value`) valueSum, sc.saleFk + FROM vn.saleComponent sc + JOIN vn.component c ON c.id = sc.componentFk + JOIN vn.componentType ct ON ct.id = c.typeFk AND ct.isBase + JOIN vn.sale s ON s.id = sc.saleFk + WHERE s.ticketFk = vTicket + GROUP BY sc.saleFk; + + UPDATE vn.sale s + JOIN tComponents mc ON mc.saleFk = s.id + SET s.priceFixed = valueSum; + + DROP TEMPORARY TABLE tComponents; + END LOOP; + + CLOSE cDates; + + DELETE FROM basketOrder WHERE orderFk = vOrder; + UPDATE `order` SET confirmed = TRUE, confirm_date = NOW() + WHERE id = vOrder; + + COMMIT; +END$$ + +DELIMITER ; + diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 282fb8bab8..019a3a2529 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -489,7 +489,7 @@ export default { priceInput: 'vn-ticket-request-create [ng-model="$ctrl.ticketRequest.price"]', firstRemoveRequestButton: 'vn-ticket-request-index vn-icon[icon="delete"]:nth-child(1)', saveButton: 'vn-ticket-request-create button[type=submit]', - firstDescription: 'vn-ticket-request-index vn-table vn-tr:nth-child(1) > vn-td:nth-child(2)', + firstDescription: 'vn-ticket-request-index vn-table vn-tr:nth-child(1) > vn-td:nth-child(2) vn-textfield', }, ticketLog: { diff --git a/e2e/paths/05-ticket-module/10_request.spec.js b/e2e/paths/05-ticket-module/10_request.spec.js index d1ed7a9779..f4fea9c2be 100644 --- a/e2e/paths/05-ticket-module/10_request.spec.js +++ b/e2e/paths/05-ticket-module/10_request.spec.js @@ -38,7 +38,7 @@ describe('Ticket purchase request path', () => { it(`should confirm the new request was added`, async() => { await page.reloadSection('ticket.card.request.index'); - const result = await page.waitToGetProperty(selectors.ticketRequests.firstDescription, 'innerText'); + const result = await page.waitToGetProperty(`${selectors.ticketRequests.firstDescription} input`, 'value'); expect(result).toEqual('New stuff'); }); diff --git a/e2e/paths/07-order-module/03_lines.spec.js b/e2e/paths/07-order-module/03_lines.spec.js index 50570b4e81..99c8a221b8 100644 --- a/e2e/paths/07-order-module/03_lines.spec.js +++ b/e2e/paths/07-order-module/03_lines.spec.js @@ -1,8 +1,7 @@ import selectors from '../../helpers/selectors.js'; import getBrowser from '../../helpers/puppeteer'; -// #2050 Order.lines confirm error -xdescribe('Order lines', () => { +describe('Order lines', () => { let browser; let page; beforeAll(async() => { diff --git a/modules/agency/back/model-config.json b/modules/agency/back/model-config.json index cfdbb83d31..7638e3f6c5 100644 --- a/modules/agency/back/model-config.json +++ b/modules/agency/back/model-config.json @@ -11,7 +11,7 @@ "Zone": { "dataSource": "vn" }, - "ZoneGeo": { + "ZoneClosure": { "dataSource": "vn" }, "ZoneEvent": { @@ -20,6 +20,9 @@ "ZoneExclusion": { "dataSource": "vn" }, + "ZoneGeo": { + "dataSource": "vn" + }, "ZoneIncluded": { "dataSource": "vn" }, diff --git a/modules/agency/back/models/zone-closure.js b/modules/agency/back/models/zone-closure.js new file mode 100644 index 0000000000..8b66e31ae7 --- /dev/null +++ b/modules/agency/back/models/zone-closure.js @@ -0,0 +1,13 @@ +module.exports = Self => { + Self.doRecalc = async function() { + try { + await Self.rawSql(` + CREATE EVENT zoneClosure_doRecalc + ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 15 SECOND + DO CALL zoneClosure_recalc; + `); + } catch (err) { + if (err.code != 'ER_EVENT_ALREADY_EXISTS') throw err; + } + }; +}; diff --git a/modules/agency/back/models/zone-closure.json b/modules/agency/back/models/zone-closure.json new file mode 100644 index 0000000000..8953748389 --- /dev/null +++ b/modules/agency/back/models/zone-closure.json @@ -0,0 +1,30 @@ +{ + "name": "ZoneClosure", + "base": "VnModel", + "options": { + "mysql": { + "table": "zoneClosure" + } + }, + "properties": { + "zoneFk": { + "id": true, + "type": "Number" + }, + "dated": { + "type": "Date", + "required": true + }, + "hour": { + "type": "date", + "required": true + } + }, + "relations": { + "zone": { + "type": "belongsTo", + "model": "Zone", + "foreignKey": "zoneFk" + } + } +} \ No newline at end of file diff --git a/modules/agency/back/models/zone-event.js b/modules/agency/back/models/zone-event.js index 6af031a239..46b2df2029 100644 --- a/modules/agency/back/models/zone-event.js +++ b/modules/agency/back/models/zone-event.js @@ -1,3 +1,5 @@ +const app = require('vn-loopback/server/server'); + module.exports = Self => { Self.validate('range', function(err) { if (this.type == 'range' @@ -32,4 +34,12 @@ module.exports = Self => { }, { message: `You should mark at least one week day` }); + + Self.observe('after save', async function() { + await app.models.ZoneClosure.doRecalc(); + }); + + Self.observe('after delete', async function() { + await app.models.ZoneClosure.doRecalc(); + }); }; diff --git a/modules/agency/back/models/zone-exclusion.js b/modules/agency/back/models/zone-exclusion.js new file mode 100644 index 0000000000..51998aab87 --- /dev/null +++ b/modules/agency/back/models/zone-exclusion.js @@ -0,0 +1,11 @@ +const app = require('vn-loopback/server/server'); + +module.exports = Self => { + Self.observe('after save', async function() { + await app.models.ZoneClosure.doRecalc(); + }); + + Self.observe('after delete', async function() { + await app.models.ZoneClosure.doRecalc(); + }); +}; diff --git a/modules/agency/back/models/zone.js b/modules/agency/back/models/zone.js index 0c3ac24f66..9d715a8d88 100644 --- a/modules/agency/back/models/zone.js +++ b/modules/agency/back/models/zone.js @@ -1,3 +1,5 @@ +const app = require('vn-loopback/server/server'); + module.exports = Self => { require('../methods/zone/clone')(Self); require('../methods/zone/getLeaves')(Self); @@ -7,4 +9,12 @@ module.exports = Self => { Self.validatesPresenceOf('agencyModeFk', { message: `Agency cannot be blank` }); + + Self.observe('after save', async function() { + await app.models.ZoneClosure.doRecalc(); + }); + + Self.observe('after delete', async function() { + await app.models.ZoneClosure.doRecalc(); + }); }; diff --git a/modules/agency/front/warehouses/index.html b/modules/agency/front/warehouses/index.html index 1a9fee32e4..72d88fa0a1 100644 --- a/modules/agency/front/warehouses/index.html +++ b/modules/agency/front/warehouses/index.html @@ -11,7 +11,7 @@ + ng-click="$ctrl.onDelete(row)"> @@ -49,5 +49,5 @@ vn-id="confirm" message="This item will be deleted" question="Are you sure you want to continue?" - on-response="$ctrl.delete($response)"> + on-accept="$ctrl.delete()"> diff --git a/modules/agency/front/warehouses/index.js b/modules/agency/front/warehouses/index.js index fa99c505a1..328f3a1b49 100644 --- a/modules/agency/front/warehouses/index.js +++ b/modules/agency/front/warehouses/index.js @@ -34,19 +34,19 @@ class Controller extends Component { return false; } - onDelete(index) { + onDelete(row) { this.$.confirm.show(); - this.deleteIndex = index; + this.deleteRow = row; } - delete(response) { - if (response != 'accept') return; - let id = this.$.data[this.deleteIndex].id; - if (!id) return; - this.$http.delete(`${this.path}/${id}`) + delete() { + let row = this.deleteRow; + if (!row) return; + return this.$http.delete(`${this.path}/${row.id}`) .then(() => { - this.$.data.splice(this.deleteIndex, 1); - this.deleteIndex = null; + let index = this.$.data.indexOf(row); + if (index !== -1) this.$.data.splice(index, 1); + this.deleteRow = null; }); } } diff --git a/modules/client/front/summary/index.html b/modules/client/front/summary/index.html index 6678eaa65f..eccf458734 100644 --- a/modules/client/front/summary/index.html +++ b/modules/client/front/summary/index.html @@ -17,6 +17,7 @@ + diff --git a/modules/ticket/front/package/index.html b/modules/ticket/front/package/index.html index 87c424ff11..12ac138621 100644 --- a/modules/ticket/front/package/index.html +++ b/modules/ticket/front/package/index.html @@ -3,7 +3,7 @@ url="TicketPackagings" fields="['id', 'ticketFk', 'packagingFk', 'quantity', 'created']" link="{ticketFk: $ctrl.$stateParams.id}" - data="packages" on-data-change="$ctrl.onDataChange()" + data="packages" auto-load="true"> {{::request.id}} - {{::request.description}} - {{::request.created | date: 'dd/MM/yyyy'}} + + + + {{::request.created | date: 'dd/MM/yyyy'}} + {{::request.requester.user.nickname | dashIfEmpty}} - + {{::request.atender.user.nickname | dashIfEmpty}} - {{::request.quantity}} - {{::request.price | currency: 'EUR': 2}} + + + + + + + + - {{request.saleFk | zeroFill:6}} + {{::request.saleFk | zeroFill:6}} diff --git a/modules/ticket/front/request/index/index.js b/modules/ticket/front/request/index/index.js index 2c89967604..38d800aebe 100644 --- a/modules/ticket/front/request/index/index.js +++ b/modules/ticket/front/request/index/index.js @@ -78,6 +78,18 @@ class Controller { return 'Acepted'; } } + + updateData() { + this.$.model.save().then(() => { + this.$.watcher.notifySaved(); + this.$.watcher.updateOriginalData(); + }); + } + + isEditable(isOk) { + if (isOk != null) + return true; + } } Controller.$inject = ['$scope', '$stateParams']; diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index bd9b390b57..998f481ead 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -168,7 +168,7 @@ - {{sale.discount | percentage}} + {{(sale.discount / 100) | percentage}}