Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2266_refactor_gulpfile_docker

This commit is contained in:
Carlos Jimenez Ruiz 2020-06-11 10:13:54 +02:00
commit 477b1c8e1e
50 changed files with 2652 additions and 589 deletions

View File

@ -12,11 +12,23 @@ module.exports = Self => {
message: `A client with that Web User name already exists`
});
Self.observe('before save', (ctx, next) => {
Self.observe('before save', async function(ctx) {
if (ctx.currentInstance && ctx.currentInstance.id && ctx.data && ctx.data.password)
ctx.data.password = md5(ctx.data.password);
next();
if (!ctx.isNewInstance && ctx.data && (ctx.data.name || ctx.data.active)) {
let instance = JSON.parse(JSON.stringify(ctx.currentInstance));
let userId = ctx.options.accessToken.userId;
let logRecord = {
originFk: ctx.currentInstance.id,
userFk: userId,
action: 'update',
changedModel: 'Account',
oldInstance: {name: instance.name, active: instance.active},
newInstance: ctx.data
};
await Self.app.models.ClientLog.create(logRecord);
}
});
Self.remoteMethod('getCurrentUserData', {

View File

@ -45,7 +45,7 @@ proc: BEGIN
IF vLanded IS NULL OR vZoneFk IS NULL THEN
CALL zone_getLanded(vShipped, vAddressFk, vAgencyModeFk, vWarehouseFk);
CALL zone_getLanded(vShipped, vAddressFk, vAgencyModeFk, vWarehouseFk, TRUE);
IF (SELECT COUNT(*) FROM tmp.zoneGetLanded LIMIT 1) = 0 THEN
CALL util.throw('There is no zone for these parameters');

View File

@ -0,0 +1,93 @@
USE `vn`;
DROP procedure IF EXISTS `ticketCalculateClon`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `ticketCalculateClon`(IN vTicketNew INT, vTicketOld INT)
BEGIN
/*
* Recalcula los componentes un ticket clonado,
* las lineas a precio cero fuerza para que tengan precio, el resto lo respeta
* @param vTicketNew nuevo ticket clonado
* @param vTicketOld icket original, a partir del qual se clonara el nuevo
*/
DECLARE vShipped DATE;
DECLARE vClient INT;
DECLARE vWarehouse SMALLINT;
DECLARE vAgencyMode INT;
DECLARE vAddress INT;
DECLARE vLanded DATE;
DECLARE vAgency INT;
DECLARE vZoneFk INT;
REPLACE INTO orderTicket(orderFk,ticketFk)
SELECT orderFk, vTicketNew
FROM orderTicket
WHERE ticketFk = vTicketOld;
SELECT t.clientFk, t.warehouseFk, date(t.shipped), t.addressFk, t.agencyModeFk, t.landed, a.agencyFk, t.zoneFk
INTO vClient, vWarehouse, vShipped, vAddress, vAgencyMode, vLanded, vAgency, vZoneFk
FROM agencyMode a
JOIN ticket t ON t.agencyModeFk = a.id
WHERE t.id = vTicketNew;
IF vLanded IS NULL THEN
CALL zone_getLanded(vShipped, vAddress, vAgency, vWarehouse, TRUE);
UPDATE ticket t
JOIN tmp.zoneGetLanded zgl ON t.warehouseFk = zgl.warehouseFk
SET t.landed = zgl.landed,
t.zone = zgl.zoneFk
WHERE t.id = vTicketNew;
SELECT zoneFk INTO vZoneFk FROM tmp.zoneGetLanded LIMIT 1;
DROP TEMPORARY TABLE IF EXISTS tmp.zoneGetLanded;
END IF;
-- rellena la tabla tmp.buyUltimate con la ultima compra
CALL buyUltimate(vWarehouse, vShipped);
DROP TEMPORARY TABLE IF EXISTS tmp.ticketLot;
CREATE TEMPORARY TABLE tmp.ticketLot
SELECT vWarehouse warehouseFk, NULL available, s.itemFk, bu.buyFk, vZoneFk zoneFk
FROM sale s
LEFT JOIN tmp.buyUltimate bu ON bu.itemFk = s.itemFk
WHERE s.ticketFk = vTicketOld GROUP BY s.itemFk;
CALL catalog_componentPrepare();
CALL catalog_componentCalculate(vZoneFk, vAddress, vAgencyMode, vWarehouse);
-- Bionizamos lineas con Preu = 0
DROP TEMPORARY TABLE IF EXISTS tmp.sale;
CREATE TEMPORARY TABLE tmp.sale
(PRIMARY KEY (saleFk)) ENGINE = MEMORY
SELECT s.id saleFk, vWarehouse warehouseFk
FROM sale s
JOIN ticket t on t.id = s.ticketFk WHERE s.ticketFk = vTicketNew AND s.price = 0;
CALL ticketComponentUpdateSale(1);
-- Bionizamos lineas con Preu > 0
DROP TEMPORARY TABLE IF EXISTS tmp.sale;
CREATE TEMPORARY TABLE tmp.sale
(PRIMARY KEY (saleFk)) ENGINE = MEMORY
SELECT s.id saleFk, vWarehouse warehouseFk
FROM sale s
JOIN ticket t on t.id = s.ticketFk WHERE s.ticketFk = vTicketNew
AND s.price > 0;
CALL ticketComponentUpdateSale(6);
-- Log
CALL `logAdd`(vTicketNew, 'update', ' ticket' , 'Bioniza Ticket');
-- Limpieza
CALL catalog_componentPurge();
DROP TEMPORARY TABLE IF EXISTS
tmp.buyUltimate,
tmp.sale,
tmp.zoneGetLanded;
END$$
DELIMITER ;

View File

@ -0,0 +1,132 @@
USE `vn`;
DROP procedure IF EXISTS `ticket_cloneWeekly`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `ticket_cloneWeekly`(IN vWeek INT)
BEGIN
DECLARE vIsDone BOOL;
DECLARE vLanding DATE;
DECLARE vShipment DATE;
DECLARE vWarehouse INT;
DECLARE vTicket INT;
DECLARE vWeekDay INT;
DECLARE vClient INT;
DECLARE vEmpresa INT;
DECLARE vAddressFk INT;
DECLARE vAgencyModeFk INT;
DECLARE vNewTicket INT;
DECLARE vYear INT;
DECLARE rsTicket CURSOR FOR
SELECT tw.ticketFk, weekDay, t.clientFk, t.warehouseFk, t.companyFk, t.addressFk, tw.agencyModeFk
FROM ticketWeekly tw
JOIN ticket t ON tt.ticketFk = t.id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vIsDone = TRUE;
SET vYear = YEAR(CURDATE()) + IF(vWeek < WEEK(CURDATE()),1, 0);
OPEN rsTicket;
myLoop: LOOP
BEGIN
DECLARE vError TEXT;
DECLARE vSalesPersonEmail VARCHAR(150);
DECLARE vMailSent BOOL;
DECLARE vSubject VARCHAR(150);
DECLARE vMessage TEXT;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1
vError = MESSAGE_TEXT;
END;
SET vIsDone = FALSE;
FETCH rsTicket INTO vTicket, vWeekDay, vClient, vWarehouse, vEmpresa, vAddressFk, vAgencyModeFk;
IF vIsDone THEN
LEAVE myLoop;
END IF;
SELECT date INTO vShipment
FROM `time`
WHERE `year` = vYear AND `week` = vWeek
AND WEEKDAY(date) = vWeekDay;
-- busca si el ticket ya ha sido clonado
IF (SELECT COUNT(*) FROM vn.ticket tOrig
JOIN vn.sale saleOrig ON tOrig.id = saleOrig.ticketFk
JOIN vn.saleCloned sc ON sc.saleOriginalFk = saleOrig.id
JOIN vn.sale saleClon ON saleClon.id = sc.saleClonedFk
JOIN vn.ticket tClon ON tClon.id = saleClon.ticketFk
WHERE tOrig.id = vTicket AND DATE(tClon.shipped) = vShipment) > 0
THEN
ITERATE myLoop;
END IF;
CALL vn.zone_getLanded(vShipment, vAddressFk, vAgencyModeFk, vWarehouse, TRUE);
SELECT landed INTO vLanding from tmp.zoneGetLanded LIMIT 1;
CALL vn.ticketCreateWithoutZone(vClient, vShipment, vWarehouse, vEmpresa, vAddressFk, vAgencyModeFk, NULL, vLanding, account.userGetId(), vNewTicket);
IF (vLanding IS NULL) THEN
SELECT e.email INTO vSalesPersonEmail
FROM vn.client c
JOIN vn.worker sp ON sp.id = c.salesPersonFk
JOIN account.emailUser e ON e.userFk = sp.userFk
WHERE c.id = vClient;
SET vSubject = CONCAT('Turnos - No se ha podido clonar correctamente el ticket ', vTicket,
' para el dia: ', vShipment);
SET vMessage = CONCAT('No se ha podido clonar el ticket ', vTicket,
' para el dia: ', vShipment,
' porque no hay una zona de envío disponible. Se ha creado el ticket: ', vNewTicket,
' pero ha que revisar las fechas y la agencia');
SELECT COUNT(*) INTO vMailSent
FROM vn.mail
WHERE sender = vSalesPersonEmail
AND subject = vSubject;
IF NOT vMailSent THEN
INSERT INTO vn.mail (sender,`subject`,body)
VALUES (vSalesPersonEmail, vSubject, vMessage);
END IF;
CALL vn.ticketStateUpdate (vNewTicket, 'FIXING');
END IF;
INSERT INTO vn.sale (ticketFk, itemFk, concept, quantity, price, discount, priceFixed, isPriceFixed)
SELECT vNewTicket, saleOrig.itemFk , saleOrig.concept , saleOrig.quantity, saleOrig.price , saleOrig.discount, saleOrig.priceFixed, saleOrig.isPriceFixed
FROM vn.ticket tOrig
JOIN vn.sale saleOrig ON tOrig.id = saleOrig.ticketFk
LEFT JOIN vn.saleCloned sc ON sc.saleOriginalFk = saleOrig.id
LEFT JOIN vn.sale saleClon ON saleClon.id = sc.saleClonedFk
LEFT JOIN vn.ticket tClon ON tClon.id = saleClon.ticketFk AND DATE(tClon.shipped) = vShipment
WHERE tOrig.id = vTicket AND saleClon.id IS NULL;
INSERT IGNORE INTO vn.saleCloned(saleOriginalFk, saleClonedFk)
SELECT saleOriginal.id, saleClon.id
FROM vn.sale saleOriginal
JOIN vn.sale saleClon ON saleOriginal.itemFk = saleClon.itemFk AND saleOriginal.quantity = saleClon.quantity
WHERE saleOriginal.ticketFk = vTicket AND saleClon.ticketFk = vNewTicket;
INSERT INTO ticketRequest (description, ordered, shipped, salesPersonCode, buyerCode, quantity, price,
itemFk ,clientFk, response, total, buyed, saleFk)
SELECT tr.description, tr.ordered, tr.shipped, tr.salesPersonCode, tr.buyerCode, tr.quantity, tr.price,
tr.itemFk, tr.clientFk, tr.response, tr.total, tr.buyed, tr.saleFk
FROM sale s JOIN ticketRequest tr ON tr.saleFk = s.id
JOIN sale s2 ON s.concept = s2.concept AND s.quantity = s2.quantity AND m.Id_Article = m2.Id_Article
WHERE s.ticketFk = vTicket AND s2.ticketFk = vNewTicket;
CALL vn.ticketCalculateClon(vNewTicket, vTicket);
END;
END LOOP;
CLOSE rsTicket;
END$$
DELIMITER ;

View File

@ -0,0 +1,93 @@
USE `vn`;
DROP procedure IF EXISTS `ticket_recalcComponents`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `ticket_recalcComponents`(IN vTicketFk BIGINT, vIsTicketEditable BOOLEAN)
proc: BEGIN
/**
* Este procedimiento recalcula los componentes de un ticket,
* eliminando los componentes existentes e insertandolos de nuevo
*
* @param vTicketFk Id del ticket
* @param vIsTicketEditable si no se quiere forzar llamar con NULL
*/
DECLARE vShipped DATE;
DECLARE vWarehouseFk SMALLINT;
DECLARE vAgencyModeFk INT;
DECLARE vAddressFk INT;
DECLARE vLanded DATE;
DECLARE vZoneFk INTEGER;
IF vIsTicketEditable IS NULL THEN
SELECT IFNULL(ts.alertLevel,0) = 0 AND IFNULL(t.refFk,'') = ''
INTO vIsTicketEditable
FROM ticket t LEFT JOIN ticketState ts ON t.id = ts.ticket
WHERE id = vTicketFk;
END IF;
SELECT t.warehouseFk,
t.shipped,
t.addressFk,
t.agencyModeFk,
t.landed,
t.zoneFk
INTO vWarehouseFk, vShipped, vAddressFk, vAgencyModeFk, vLanded, vZoneFk
FROM ticket t LEFT JOIN ticketState ts ON t.id = ts.ticket
WHERE t.id = vTicketFk;
IF vLanded IS NULL OR vZoneFk IS NULL THEN
CALL zone_getLanded(vShipped, vAddressFk, vAgencyModeFk, vWarehouseFk, TRUE);
IF (SELECT COUNT(*) FROM tmp.zoneGetLanded LIMIT 1) = 0 THEN
CALL util.throw('There is no zone for these parameters');
END IF;
UPDATE ticket t
SET t.landed = (SELECT landed FROM tmp.zoneGetLanded LIMIT 1)
WHERE t.id = vTicketFk AND t.landed IS NULL;
IF vZoneFk IS NULL THEN
SELECT zoneFk INTO vZoneFk FROM tmp.zoneGetLanded LIMIT 1;
UPDATE ticket t
SET t.zoneFk = vZoneFk
WHERE t.id = vTicketFk AND t.zoneFk IS NULL;
END IF;
DROP TEMPORARY TABLE tmp.zoneGetLanded;
END IF;
-- rellena la tabla buyUltimate con la ultima compra
CALL buyUltimate (vWarehouseFk, vShipped);
DROP TEMPORARY TABLE IF EXISTS tmp.ticketLot;
CREATE TEMPORARY TABLE tmp.ticketLot
SELECT vWarehouseFk warehouseFk, NULL available,
s.itemFk, bu.buyFk, vZoneFk zoneFk
FROM sale s
LEFT JOIN tmp.buyUltimate bu ON bu.itemFk = s.itemFk
WHERE s.ticketFk = vTicketFk
GROUP BY s.itemFk;
CALL catalog_componentPrepare();
CALL catalog_componentCalculate(vZoneFk, vAddressFk, vShipped, vWarehouseFk);
DROP TEMPORARY TABLE IF EXISTS tmp.sale;
CREATE TEMPORARY TABLE tmp.sale
(PRIMARY KEY (saleFk)) ENGINE = MEMORY
SELECT id saleFk, vWarehouseFk warehouseFk
FROM sale s
WHERE s.ticketFk = vTicketFk;
-- si el ticket esta facturado, respeta los precios
CALL ticketComponentUpdateSale(IF(vIsTicketEditable, 1, 6));
CALL catalog_componentPurge();
DROP TEMPORARY TABLE
tmp.buyUltimate,
tmp.sale;
END$$
DELIMITER ;

View File

@ -0,0 +1,40 @@
USE `vn`;
DROP procedure IF EXISTS `zone_getLanded`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `zone_getLanded`(vShipped DATE, vAddressFk INT, vAgencyModeFk INT, vWarehouseFk INT, vShowExpiredZones BOOLEAN)
BEGIN
/**
* Devuelve una tabla temporal con el dia de recepcion para vShipped.
*
* @param vShipped Fecha de preparacion de mercancia
* @param vAddressFk Id de consignatario, %NULL para recogida
* @param vAgencyModeFk Id agencia
* @param vWarehouseFk vWarehouseFk
* @table tmp.zoneGetLanded Datos de recepción
*/
CALL zone_getFromGeo(address_getGeo(vAddressFk));
CALL zone_getOptionsForShipment(vShipped, vShowExpiredZones);
DROP TEMPORARY TABLE IF EXISTS tmp.zoneGetLanded;
CREATE TEMPORARY TABLE tmp.zoneGetLanded
ENGINE = MEMORY
SELECT vWarehouseFk warehouseFk,
TIMESTAMPADD(DAY,zo.travelingDays, vShipped) landed,
zo.zoneFk
FROM tmp.zoneOption zo
JOIN zone z ON z.id = zo.zoneFk
JOIN zoneWarehouse zw ON zw.zoneFk = z.id
WHERE agencyModeFk = vAgencyModeFk
AND zw.warehouseFk = vWarehouseFk;
DROP TEMPORARY TABLE
tmp.zone,
tmp.zoneOption;
END$$
DELIMITER ;

View File

@ -11,12 +11,14 @@ describe('zone zone_getLanded()', () => {
let params = {
addressFk: 121,
agencyModeFk: 7,
warehouseFk: 1};
warehouseFk: 1,
showExpiredZones: true};
stmt = new ParameterizedSQL('CALL zone_getLanded(DATE_ADD(CURDATE(), INTERVAL -1 DAY), ?, ?, ?)', [
stmt = new ParameterizedSQL('CALL zone_getLanded(DATE_ADD(CURDATE(), INTERVAL -1 DAY), ?, ?, ?, ?)', [
params.addressFk,
params.agencyModeFk,
params.warehouseFk
params.warehouseFk,
params.showExpiredZones
]);
stmts.push(stmt);
@ -42,12 +44,14 @@ describe('zone zone_getLanded()', () => {
let params = {
addressFk: 121,
agencyModeFk: 7,
warehouseFk: 1};
warehouseFk: 1,
showExpiredZones: false};
stmt = new ParameterizedSQL('CALL zone_getLanded(DATE_ADD(CURDATE(), INTERVAL +2 DAY), ?, ?, ?)', [
stmt = new ParameterizedSQL('CALL zone_getLanded(DATE_ADD(CURDATE(), INTERVAL +2 DAY), ?, ?, ?, ?)', [
params.addressFk,
params.agencyModeFk,
params.warehouseFk
params.warehouseFk,
params.showExpiredZones
]);
stmts.push(stmt);

View File

@ -10,7 +10,7 @@ export default {
ticketsButton: '.modules-menu [ui-sref="ticket.index"]',
invoiceOutButton: '.modules-menu [ui-sref="invoiceOut.index"]',
claimsButton: '.modules-menu [ui-sref="claim.index"]',
returnToModuleIndexButton: 'a[ui-sref="order.index"]',
returnToModuleIndexButton: 'a[name="goToModuleIndex"]',
homeButton: 'vn-topbar > div.side.start > a',
userLocalWarehouse: '.user-popover vn-autocomplete[ng-model="$ctrl.localWarehouseFk"]',
userLocalBank: '.user-popover vn-autocomplete[ng-model="$ctrl.localBankFk"]',
@ -365,7 +365,8 @@ export default {
firstSaleQuantity: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3)',
firstSaleDiscount: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6)',
invoiceOutRef: 'vn-ticket-summary > vn-card > vn-horizontal > vn-one:nth-child(1) > vn-label-value:nth-child(7) > section > span',
setOk: 'vn-ticket-summary vn-button[label="SET OK"] > button'
setOk: 'vn-ticket-summary vn-button[label="SET OK"] > button',
descriptorTicketId: 'vn-ticket-descriptor > vn-descriptor-content > div > div.body > div.top > div'
},
ticketsIndex: {
openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]',

View File

@ -4,6 +4,9 @@ import getBrowser from '../../helpers/puppeteer';
describe('Ticket create path', () => {
let browser;
let page;
let nextMonth = new Date();
nextMonth.setMonth(nextMonth.getMonth() + 1);
let stowawayTicketId;
beforeAll(async() => {
browser = await getBrowser();
@ -21,13 +24,9 @@ describe('Ticket create path', () => {
});
it('should succeed to create a ticket', async() => {
const nextMonth = new Date();
nextMonth.setMonth(nextMonth.getMonth() + 1);
await page.autocompleteSearch(selectors.createTicketView.client, 'Tony Stark');
await page.autocompleteSearch(selectors.createTicketView.address, 'Tony Stark');
await page.autocompleteSearch(selectors.createTicketView.client, 'Clark Kent');
await page.pickDate(selectors.createTicketView.deliveryDate, nextMonth);
await page.autocompleteSearch(selectors.createTicketView.warehouse, 'Warehouse One');
await page.autocompleteSearch(selectors.createTicketView.warehouse, 'Warehouse Two');
await page.autocompleteSearch(selectors.createTicketView.agency, 'Silla247');
await page.waitToClick(selectors.createTicketView.createButton);
const message = await page.waitForSnackbar();
@ -37,5 +36,53 @@ describe('Ticket create path', () => {
it('should check the url is now the summary of the ticket', async() => {
await page.waitForState('ticket.card.summary');
stowawayTicketId = await page.waitToGetProperty(selectors.ticketSummary.descriptorTicketId, 'innerText');
stowawayTicketId = stowawayTicketId.substring(1);
});
it('should again open the new ticket form', async() => {
await page.waitToClick(selectors.globalItems.returnToModuleIndexButton);
await page.waitToClick(selectors.ticketsIndex.newTicketButton);
await page.waitForState('ticket.create');
});
it('should succeed to create another ticket for the same client', async() => {
await page.autocompleteSearch(selectors.createTicketView.client, 'Clark Kent');
await page.pickDate(selectors.createTicketView.deliveryDate, nextMonth);
await page.autocompleteSearch(selectors.createTicketView.warehouse, 'Warehouse One');
await page.autocompleteSearch(selectors.createTicketView.agency, 'Silla247');
await page.waitToClick(selectors.createTicketView.createButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
});
it('should check the url is now the summary of the created ticket', async() => {
await page.waitForState('ticket.card.summary');
});
it('should make the previously created ticket the stowaway of the current ticket', async() => {
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuAddStowaway);
await page.waitToClick(selectors.ticketDescriptor.addStowawayDialogFirstTicket);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
});
it('should delete the current ticket', async() => {
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteTicket);
await page.waitToClick(selectors.ticketDescriptor.acceptDeleteButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
});
it('should search for the stowaway ticket of the previously deleted ticket', async() => {
await page.accessToSearchResult(stowawayTicketId);
const result = await page.countElement(selectors.ticketDescriptor.shipButton);
expect(result).toBe(0);
});
});

View File

@ -0,0 +1,5 @@
<section vn-id="contextmenu">
<vn-menu vn-id="menu">
<div ng-transclude="menu"></div>
</vn-menu>
</section>

View File

@ -0,0 +1,224 @@
import ngModule from '../../module';
import {buildFilter} from 'vn-loopback/util/filter';
import './style.scss';
export default class Contextmenu {
constructor($element, $, $transclude) {
this.$element = $element;
this.element = $element[0];
this.$ = $;
this.$transclude = $transclude;
}
get targets() {
return this._targets;
}
set targets(value) {
this._targets = value;
if (!value) return;
for (let selector of value) {
const target = document.querySelector(selector);
if (!target) continue;
target.addEventListener('contextmenu', event => {
this.target = event.target;
if (!event.defaultPrevented)
event.preventDefault();
if (!this.isFilterEnabled()) return;
const parent = this.$.contextmenu;
parent.style.top = event.pageY + 'px';
parent.style.left = event.pageX + 'px';
this.$.menu.show(parent);
});
}
}
get row() {
if (!this.target) return null;
return this.target.closest('vn-tr, .vn-tr');
}
get rowIndex() {
if (!this.row) return null;
const table = this.row.closest('vn-table, .vn-table');
const tBody = table.querySelector('vn-tbody, .vn-tbody');
const rows = tBody.querySelectorAll('vn-tr, .vn-tr');
return Array.from(rows).findIndex(
rowItem => rowItem == this.row
);
}
get rowData() {
const model = this.model;
const rowData = model.data[this.rowIndex];
return rowData;
}
get cell() {
if (!this.target) return null;
return this.target.closest('vn-td, .vn-td');
}
get cellIndex() {
if (!this.row) return null;
const cells = this.row.querySelectorAll('vn-td, .vn-td');
return Array.from(cells).findIndex(
cellItem => cellItem == this.cell
);
}
get rowHeader() {
if (!this.row) return null;
const table = this.row.closest('vn-table, .vn-table');
const headerCells = table && table.querySelectorAll('vn-thead vn-th');
const headerCell = headerCells && headerCells[this.cellIndex];
return headerCell;
}
/**
* Selected model field name
*
* @return {string}
*/
get fieldName() {
if (!this.rowHeader) return null;
return this.rowHeader.getAttribute('field');
}
/**
* Selected field value
*
* @return {any}
*/
get fieldValue() {
return this.rowData[this.fieldName];
}
/**
* Returns true if filter is not disabled
*
* @return {Boolean}
*/
isFilterEnabled() {
if (!this.rowHeader) return true;
const isEnabled = this.rowHeader.getAttribute('filter-enabled');
return isEnabled != 'false';
}
/**
* Returns true if filter
* by selection is allowed
*
* @return {Boolean}
*/
isFilterAllowed() {
if (!this.target) return false;
const isTableCell = this.target.closest('vn-td, .vn-td');
return isTableCell && this.fieldName;
}
/**
* Filter by current field selection
*/
filterBySelection() {
let where = {[this.fieldName]: this.fieldValue};
if (this.exprBuilder) {
where = buildFilter(where, (param, value) =>
this.exprBuilder({param, value})
);
}
this.model.addFilter({where});
}
/**
* Exclude by current field selection
*/
excludeSelection() {
let where = {[this.fieldName]: {neq: this.fieldValue}};
if (this.exprBuilder) {
where = buildFilter(where, (param, value) =>
this.exprBuilder({param, value})
);
}
this.model.addFilter({where});
}
removeFilter() {
const userFilter = this.model.userFilter;
const userParams = this.model.userParams;
const where = userFilter.where;
let filterKey = this.fieldName;
if (this.exprBuilder) {
const param = this.exprBuilder({
param: filterKey,
value: null
});
[filterKey] = Object.keys(param);
}
const whereKeys = Object.keys(where);
for (let key of whereKeys)
removeProp(where, filterKey, key);
function removeProp(instance, findProp, prop) {
if (prop == findProp)
delete instance[prop];
if (prop === 'and') {
for (let [index, param] of instance[prop].entries()) {
const [key] = Object.keys(param);
if (key == findProp)
instance[prop].splice(index, 1);
if (param[key] instanceof Array)
removeProp(param, filterKey, key);
}
}
}
this.model.applyFilter(userFilter, userParams);
}
/**
* Removes all applied filters
*/
removeAllFilters() {
const userParams = this.model.userParams;
this.model.applyFilter(null, userParams);
}
}
Contextmenu.$inject = ['$element', '$scope', '$transclude'];
ngModule.vnComponent('vnContextmenu', {
controller: Contextmenu,
template: require('./index.html'),
bindings: {
targets: '<?',
model: '<?',
exprBuilder: '&?'
},
transclude: {
menu: '?slotMenu'
}
});

View File

@ -0,0 +1,3 @@
section[vn-id="contextmenu"] {
position: absolute
}

View File

@ -13,7 +13,7 @@
</label>
</div>
<div class="icons pre">
<vn-icon
<vn-icon ng-show="::$ctrl.clearDisabled != true"
icon="clear"
translate-attr="{title: 'Clear'}"
ng-click="$ctrl.onClear($event)">

View File

@ -203,6 +203,7 @@ ngModule.vnComponent('vnField', {
type: '@?',
autocomplete: '@?',
placeholder: '@?',
clearDisabled: '<?',
value: '=?',
info: '@?',
required: '<?',

View File

@ -0,0 +1,24 @@
describe('Component vnField', () => {
let $element;
let controller;
beforeEach(ngModule('vnCore'));
beforeEach(inject(($compile, $rootScope) => {
$element = $compile(`<vn-textfield></vn-textfield>`)($rootScope);
controller = $element.controller('vnTextfield');
}));
afterEach(() => {
$element.remove();
});
// Remove this block
describe('clearDisabled binding', () => {
it(`should enable the show property`, () => {
controller.clearDisabled = true;
expect(controller.clearDisabled).toEqual(true);
});
});
});

View File

@ -50,3 +50,4 @@ import './th';
import './treeview';
import './wday-picker';
import './datalist';
import './contextmenu';

View File

@ -1,5 +1,6 @@
import ngModule from '../../module';
import Field from '../field';
import './style.scss';
export default class Textarea extends Field {
constructor($element, $scope, $compile) {

View File

@ -0,0 +1,8 @@
.vn-textarea {
& > .container {
& > .icons {
display: flex;
align-items: flex-start;
}
}
}

View File

@ -259,6 +259,10 @@ module.exports = function(Self) {
removeUnloggable(definition, oldInstance);
removeUnloggable(definition, newInstance);
// Prevent log with no new changes
const hasNewChanges = Object.keys(newInstance).length;
if (!hasNewChanges) return;
let logRecord = {
originFk: originId,
userFk: userFk,

View File

@ -1,6 +1,7 @@
const app = require('vn-loopback/server/server');
describe('regularizeClaim()', () => {
// #2304
xdescribe('regularizeClaim()', () => {
const claimFk = 1;
const pendentState = 1;
const resolvedState = 3;

View File

@ -76,8 +76,8 @@
<span
ng-if="balance.isInvoice"
title="{{'BILL' | translate: {ref: balance.ref} }}"
vn-click-stop="invoiceOutDescriptor.show($event, balance)"
ng-class="link">
vn-click-stop="$ctrl.showInvoiceOutDescriptor($event, balance)"
class="link">
{{'BILL' | translate: {ref: balance.ref} }}
</span>
<span

View File

@ -72,6 +72,13 @@ class Controller extends Section {
}
});
}
showInvoiceOutDescriptor(event, balance) {
if (!balance.isInvoice) return;
if (event.defaultPrevented) return;
this.$.invoiceOutDescriptor.show(event.target, balance.id);
}
}
Controller.$inject = ['$element', '$scope'];

View File

@ -16,7 +16,7 @@
<vn-item
ng-click="consumerReportDialog.show()"
translate>
Send consumer report
View consumer report
</vn-item>
</slot-menu>
<slot-body>

View File

@ -1,4 +1,4 @@
Simple ticket: Ticket simple
Send consumer report: Enviar informe de consumo
View consumer report: Ver informe de consumo
From date: Fecha desde
To date: Fecha hasta

View File

@ -56,7 +56,7 @@
<div ng-transclude="btnTwo">
<vn-quick-link
tooltip="Invoice ticket list"
state="['ticket.card.summary', {id: $ctrl.invoiceOut.ref}]"
state="['ticket.index', {q: $ctrl.filter}]"
icon="icon-ticket">
</vn-quick-link>
</div>

View File

@ -22,6 +22,13 @@ class Controller extends Descriptor {
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut booked')));
}
get filter() {
if (this.invoiceOut)
return JSON.stringify({refFk: this.invoiceOut.ref});
return null;
}
loadData() {
const filter = {
include: [

View File

@ -1,5 +1,3 @@
let UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('createIntrastat', {
description: 'Creates a new item intrastat',

View File

@ -100,34 +100,27 @@ module.exports = Self => {
});
Self.filter = async(ctx, filter) => {
const userId = ctx.req.accessToken.userId;
const conn = Self.dataSource.connector;
const models = Self.app.models;
const args = ctx.args;
let worker = await Self.app.models.Worker.findOne({
where: {userFk: ctx.req.accessToken.userId},
include: [
{relation: 'collegues'}
]
});
let teamIds = [];
if (worker.collegues().length && args.myTeam) {
worker.collegues().forEach(collegue => {
teamIds.push(collegue.collegueFk);
// Apply filter by team
const teamMembersId = [];
if (args.myTeam != null) {
const worker = await models.Worker.findById(userId, {
include: {
relation: 'collegues'
}
});
}
if (args.mine || (worker.collegues().length === 0 && args.myTeam)) {
worker = await Self.app.models.Worker.findOne({
fields: ['id'],
where: {userFk: ctx.req.accessToken.userId}
const collegues = worker.collegues() || [];
collegues.forEach(collegue => {
teamMembersId.push(collegue.collegueFk);
});
teamIds = [worker && worker.id];
}
if (ctx.args && (args.mine || args.myTeam))
args.teamIds = teamIds;
if (teamMembersId.length == 0)
teamMembersId.push(userId);
}
if (ctx.args && args.to) {
const dateTo = args.to;
@ -156,7 +149,11 @@ module.exports = Self => {
return {'ts.stateFk': value};
case 'mine':
case 'myTeam':
return {'c.salesPersonFk': {inq: teamIds}};
if (value)
return {'c.salesPersonFk': {inq: teamMembersId}};
else
return {'c.salesPersonFk': {nin: teamMembersId}};
case 'alertLevel':
return {'ts.alertLevel': value};
case 'pending':
@ -186,43 +183,46 @@ module.exports = Self => {
let stmt;
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.filter');
stmt = new ParameterizedSQL(
`CREATE TEMPORARY TABLE tmp.filter
(INDEX (id))
ENGINE = MEMORY
SELECT
t.id,
t.shipped,
t.nickname,
t.refFk,
t.routeFk,
t.warehouseFk,
t.clientFk,
p.name AS province,
w.name AS warehouse,
am.name AS agencyMode,
st.name AS state,
wk.lastName AS salesPerson,
ts.stateFk as stateFk,
ts.alertLevel as alertLevel,
ts.code as alertLevelCode,
u.nickname userNickname,
c.salesPersonFk,
z.hour zoneLanding,
HOUR(z.hour) zoneHour,
MINUTE(z.hour) zoneMinute
FROM ticket t
LEFT JOIN zone z ON z.id = t.zoneFk
LEFT JOIN address a ON a.id = t.addressFk
LEFT JOIN province p ON p.id = a.provinceFk
LEFT JOIN warehouse w ON w.id = t.warehouseFk
LEFT JOIN agencyMode am ON am.id = t.agencyModeFk
LEFT JOIN ticketState ts ON ts.ticketFk = t.id
LEFT JOIN state st ON st.id = ts.stateFk
LEFT JOIN client c ON c.id = t.clientFk
LEFT JOIN worker wk ON wk.id = c.salesPersonFk
LEFT JOIN account.user u ON u.id = wk.userFk`);
SELECT
t.id,
t.shipped,
CAST(DATE(t.shipped) AS CHAR) AS shippedDate,
t.nickname,
t.refFk,
t.routeFk,
t.warehouseFk,
t.clientFk,
a.provinceFk,
p.name AS province,
w.name AS warehouse,
am.name AS agencyMode,
am.id AS agencyModeFk,
st.name AS state,
wk.lastName AS salesPerson,
ts.stateFk as stateFk,
ts.alertLevel as alertLevel,
ts.code as alertLevelCode,
u.nickname userNickname,
c.salesPersonFk,
z.hour zoneLanding,
HOUR(z.hour) zoneHour,
MINUTE(z.hour) zoneMinute,
CAST(z.hour AS CHAR) AS hour
FROM ticket t
LEFT JOIN zone z ON z.id = t.zoneFk
LEFT JOIN address a ON a.id = t.addressFk
LEFT JOIN province p ON p.id = a.provinceFk
LEFT JOIN warehouse w ON w.id = t.warehouseFk
LEFT JOIN agencyMode am ON am.id = t.agencyModeFk
LEFT JOIN ticketState ts ON ts.ticketFk = t.id
LEFT JOIN state st ON st.id = ts.stateFk
LEFT JOIN client c ON c.id = t.clientFk
LEFT JOIN worker wk ON wk.id = c.salesPersonFk
LEFT JOIN account.user u ON u.id = wk.userFk`);
if (args.orderFk) {
stmt.merge({

View File

@ -105,7 +105,7 @@ module.exports = Self => {
if (shipped && !landed) {
const landedResult = await models.Agency.getLanded(shipped,
address.id, agencyModeId, warehouseId);
address.id, agencyModeId, warehouseId, false);
landed = landedResult && landedResult.landed;
}

View File

@ -43,18 +43,6 @@ module.exports = Self => {
if (hasItemShelvingSales && !isSalesAssistant)
throw new UserError(`You cannot delete a ticket that part of it is being prepared`);
if (hasItemShelvingSales && isSalesAssistant) {
const promises = [];
for (let sale of sales) {
if (sale.itemShelvingSale()) {
const itemShelvingSale = sale.itemShelvingSale();
const destroyedShelving = models.ItemShelvingSale.destroyById(itemShelvingSale.id);
promises.push(destroyedShelving);
}
}
await Promise.all(promises);
}
// Check for existing claim
const claimOfATicket = await models.Claim.findOne({where: {ticketFk: id}});
if (claimOfATicket)
@ -69,10 +57,23 @@ module.exports = Self => {
if (hasPurchaseRequests)
throw new UserError('You must delete all the buy requests first');
// removes item shelvings
if (hasItemShelvingSales && isSalesAssistant) {
const promises = [];
for (let sale of sales) {
if (sale.itemShelvingSale()) {
const itemShelvingSale = sale.itemShelvingSale();
const destroyedShelving = models.ItemShelvingSale.destroyById(itemShelvingSale.id);
promises.push(destroyedShelving);
}
}
await Promise.all(promises);
}
// Remove ticket greuges
const ticketGreuges = await models.Greuge.find({where: {ticketFk: id}});
const ownGreuges = ticketGreuges.every(greuge => {
return greuge.ticketFk = id;
return greuge.ticketFk == id;
});
if (ownGreuges) {
for (const greuge of ticketGreuges) {
@ -104,7 +105,7 @@ module.exports = Self => {
}]
});
// Change state to "fixing" if contains an stowaway
// Change state to "fixing" if contains an stowaway and removed the link between them
let otherTicketId;
if (ticket.stowaway())
otherTicketId = ticket.stowaway().shipFk;
@ -112,6 +113,7 @@ module.exports = Self => {
otherTicketId = ticket.ship().id;
if (otherTicketId) {
await models.Ticket.deleteStowaway(ctx, otherTicketId);
await models.TicketTracking.changeState(ctx, {
ticketFk: otherTicketId,
code: 'FIXING'

View File

@ -71,4 +71,20 @@ describe('ticket filter()', () => {
expect(secondRow.state).toEqual('Entregado');
expect(thirdRow.state).toEqual('Entregado');
});
it('should return the tickets from the worker team', async() => {
const ctx = {req: {accessToken: {userId: 9}}, args: {myTeam: true}};
const filter = {};
const result = await app.models.Ticket.filter(ctx, filter);
expect(result.length).toEqual(17);
});
it('should return the tickets that are not from the worker team', async() => {
const ctx = {req: {accessToken: {userId: 9}}, args: {myTeam: false}};
const filter = {};
const result = await app.models.Ticket.filter(ctx, filter);
expect(result.length).toEqual(7);
});
});

View File

@ -1,7 +1,6 @@
const app = require('vn-loopback/server/server');
let UserError = require('vn-loopback/util/user-error');
describe('ticket new()', () => {
let ticket;
let today = new Date();
@ -69,7 +68,7 @@ describe('ticket new()', () => {
clientId: 104,
shipped: today,
landed: today,
warehouseId: 1,
warehouseId: 2,
companyId: 442,
addressId: 4,
agencyModeId: 1

View File

@ -1,116 +1,7 @@
const app = require('vn-loopback/server/server');
const models = app.models;
// 2296 Failing tests
xdescribe('ticket deleted()', () => {
let ticket;
let sale;
let deletedClaim;
beforeAll(async done => {
let originalTicket = await models.Ticket.findOne({where: {id: 16}});
originalTicket.id = null;
ticket = await models.Ticket.create(originalTicket);
sale = await models.Sale.create({
ticketFk: ticket.id,
itemFk: 4,
concept: 'Melee weapon',
quantity: 10
});
await models.ItemShelvingSale.create({
itemShelvingFk: 1,
saleFk: sale.id,
quantity: 10,
userFk: 106
});
done();
});
afterAll(async done => {
const ticketId = 16;
const stowawayTicketId = 17;
const ctx = {
req: {
accessToken: {userId: 106},
headers: {
origin: 'http://localhost:5000'
},
__: () => {}
}
};
await models.Ticket.destroyById(ticket.id);
const stowaway = await models.Stowaway.findOne({
where: {
id: stowawayTicketId,
shipFk: ticketId
}
});
await stowaway.destroy();
await models.Claim.create(deletedClaim);
await models.TicketTracking.changeState(ctx, {
ticketFk: ticketId,
code: 'OK'
});
await models.TicketTracking.changeState(ctx, {
ticketFk: stowawayTicketId,
code: 'OK'
});
const orgTicket = await models.Ticket.findById(ticketId);
await orgTicket.updateAttribute('isDeleted', false);
done();
});
it('should make sure the ticket is not deleted yet', async() => {
expect(ticket.isDeleted).toEqual(false);
});
it('should make sure the ticket sale has an item shelving', async() => {
const sales = await models.Sale.find({
include: {relation: 'itemShelvingSale'},
where: {ticketFk: ticket.id}
});
const hasItemShelvingSales = sales.some(sale => {
return sale.itemShelvingSale();
});
expect(hasItemShelvingSales).toEqual(true);
});
it('should set a ticket to deleted and remove all item shelvings', async() => {
const salesAssistantId = 21;
const ctx = {
req: {
accessToken: {userId: salesAssistantId},
headers: {
origin: 'http://localhost:5000'
},
__: () => {}
}
};
await app.models.Ticket.setDeleted(ctx, ticket.id);
let deletedTicket = await app.models.Ticket.findOne({
where: {id: ticket.id},
fields: ['isDeleted']
});
expect(deletedTicket.isDeleted).toEqual(true);
});
it('should not have any item shelving', async() => {
const sales = await models.Sale.find({
include: {relation: 'itemShelvingSale'},
where: {ticketFk: ticket.id}
});
const hasItemShelvingSales = sales.some(sale => {
return sale.itemShelvingSale();
});
expect(hasItemShelvingSales).toEqual(false);
});
describe('ticket setDeleted()', () => {
it('should throw an error if the given ticket has a claim', async() => {
const ticketId = 16;
const ctx = {
@ -134,13 +25,11 @@ xdescribe('ticket deleted()', () => {
expect(error.message).toEqual('You must delete the claim id %d first');
});
it('should delete the ticket and change the state to "FIXING" to the stowaway ticket', async() => {
const ticketId = 16;
const claimIdToRemove = 2;
const stowawayTicketId = 17;
it('should delete the ticket, remove the stowaway link and change the stowaway ticket state to "FIXING" and get ride of the itemshelving', async() => {
const employeeUser = 110;
const ctx = {
req: {
accessToken: {userId: 106},
accessToken: {userId: employeeUser},
headers: {
origin: 'http://localhost:5000'
},
@ -148,20 +37,66 @@ xdescribe('ticket deleted()', () => {
}
};
await app.models.Stowaway.rawSql(`
INSERT INTO vn.stowaway(id, shipFk)
VALUES (?, ?)`, [stowawayTicketId, ticketId]);
let sampleTicket = await models.Ticket.findById(12);
let sampleStowaway = await models.Ticket.findById(13);
deletedClaim = await app.models.Claim.findById(claimIdToRemove);
await app.models.Claim.destroyById(claimIdToRemove);
await app.models.Ticket.setDeleted(ctx, ticketId);
sampleTicket.id = undefined;
let shipTicket = await models.Ticket.create(sampleTicket);
const stowawayTicket = await app.models.TicketState.findOne({
sampleStowaway.id = undefined;
let stowawayTicket = await models.Ticket.create(sampleStowaway);
await models.Stowaway.rawSql(`
INSERT INTO vn.stowaway(id, shipFk)
VALUES (?, ?)`, [stowawayTicket.id, shipTicket.id]);
const boardingState = await models.State.findOne({
where: {
ticketFk: stowawayTicketId
code: 'BOARDING'
}
});
await models.TicketTracking.create({
ticketFk: stowawayTicket.id,
stateFk: boardingState.id,
workerFk: ctx.req.accessToken.userId
});
const okState = await models.State.findOne({
where: {
code: 'OK'
}
});
await models.TicketTracking.create({
ticketFk: shipTicket.id,
stateFk: okState.id,
workerFk: ctx.req.accessToken.userId
});
let stowawayTicketState = await models.TicketState.findOne({
where: {
ticketFk: stowawayTicket.id
}
});
expect(stowawayTicket.code).toEqual('FIXING');
let stowaway = await models.Stowaway.findById(shipTicket.id);
expect(stowaway).toBeDefined();
expect(stowawayTicketState.code).toEqual('BOARDING');
await models.Ticket.setDeleted(ctx, shipTicket.id);
stowawayTicketState = await models.TicketState.findOne({
where: {
ticketFk: stowawayTicket.id
}
});
stowaway = await models.Stowaway.findById(shipTicket.id);
expect(stowaway).toBeNull();
expect(stowawayTicketState.code).toEqual('FIXING');
await shipTicket.destroy();
await stowawayTicket.destroy();
});
});

View File

@ -75,7 +75,8 @@ class Controller extends Component {
shipped: value,
addressFk: this.ticket.addressFk,
agencyModeFk: this.ticket.agencyModeFk,
warehouseFk: this.ticket.warehouseFk
warehouseFk: this.ticket.warehouseFk,
showExpiredZones: false
});
}
@ -104,7 +105,8 @@ class Controller extends Component {
shipped: this.ticket.shipped,
addressFk: this.ticket.addressFk,
agencyModeFk: value,
warehouseFk: this.ticket.warehouseFk
warehouseFk: this.ticket.warehouseFk,
showExpiredZones: false
});
}
}

View File

@ -2,15 +2,13 @@ import './index.js';
describe('Ticket', () => {
describe('Component vnTicketBasicDataStepOne', () => {
let $state;
let controller;
let $httpBackend;
let $httpParamSerializer;
beforeEach(ngModule('ticket'));
beforeEach(angular.mock.inject(($componentController, _$state_, _$httpBackend_, _$httpParamSerializer_) => {
$state = _$state_;
beforeEach(angular.mock.inject(($componentController, _$httpBackend_, _$httpParamSerializer_) => {
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
const $element = angular.element('<vn-ticket-basic-data-step-one></vn-ticket-basic-data-step-one>');
@ -127,6 +125,7 @@ describe('Ticket', () => {
shipped: shipped,
addressFk: 121,
agencyModeFk: 7,
showExpiredZones: false,
warehouseFk: 1
};
controller.shipped = shipped;
@ -177,7 +176,8 @@ describe('Ticket', () => {
shipped: shipped,
addressFk: 121,
agencyModeFk: agencyModeId,
warehouseFk: 1
warehouseFk: 1,
showExpiredZones: false,
};
controller.ticket.shipped = shipped;
controller.agencyModeId = 8;

View File

@ -3,7 +3,7 @@
data="ticketUpdateActions"
auto-load="true">
</vn-crud-model>
<vn-card class="vn-w-lg vn-pa-md vn-mb-md">
<vn-card ng-show="::$ctrl.totalPriceDifference" class="vn-w-lg vn-pa-md vn-mb-md">
<h6
class="text-secondary"
style="font-weight: normal;"

View File

@ -15,16 +15,16 @@
</vn-th>
<vn-th></vn-th>
<vn-th field="id" number>Id</vn-th>
<vn-th field="salesPerson" class="expendable">Salesperson</vn-th>
<vn-th field="salesPersonFk" class="expendable">Salesperson</vn-th>
<vn-th field="shipped">Date</vn-th>
<vn-th>Hour</vn-th>
<vn-th field="nickname">Alias</vn-th>
<vn-th field="province" class="expendable">Province</vn-th>
<vn-th field="state" >State</vn-th>
<vn-th field="agencyMode">Agency</vn-th>
<vn-th field="warehouse">Warehouse</vn-th>
<vn-th field="provinceFk" class="expendable">Province</vn-th>
<vn-th field="stateFk" >State</vn-th>
<vn-th field="agencyModeFk">Agency</vn-th>
<vn-th field="warehouseFk">Warehouse</vn-th>
<vn-th field="refFk" class="expendable">Invoice</vn-th>
<vn-th field="zoneHour" shrink>Closure</vn-th>
<vn-th field="hour" shrink>Closure</vn-th>
<vn-th number>Total</vn-th>
<vn-th></vn-th>
</vn-tr>
@ -151,3 +151,27 @@
<vn-client-balance-create
vn-id="balanceCreateDialog">
</vn-client-balance-create>
<vn-contextmenu vn-id="contextmenu" targets="['vn-data-viewer']" model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-menu>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.filterBySelection()">
Filter by selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.excludeSelection()">
Exclude selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.removeFilter()" >
Remove filter
</vn-item>
<vn-item translate
ng-click="contextmenu.removeAllFilters()" >
Remove all filters
</vn-item>
</slot-menu>
</vn-contextmenu>

View File

@ -92,6 +92,38 @@ export default class Controller extends Section {
this.selectedTicket = ticket;
this.$.summary.show();
}
exprBuilder(param, value) {
switch (param) {
case 'stateFk':
return {'ts.stateFk': value};
case 'salesPersonFk':
return {'c.salesPersonFk': value};
case 'provinceFk':
return {'a.provinceFk': value};
case 'hour':
return {'z.hour': value};
case 'shipped':
return {'t.shipped': {
between: this.dateRange(value)}
};
case 'id':
case 'refFk':
case 'nickname':
case 'agencyModeFk':
case 'warehouseFk':
return {[`t.${param}`]: value};
}
}
dateRange(value) {
const minHour = new Date(value);
minHour.setHours(0, 0, 0, 0);
const maxHour = new Date(value);
maxHour.setHours(23, 59, 59, 59);
return [minHour, maxHour];
}
}
ngModule.component('vnTicketIndex', {

View File

@ -4,3 +4,7 @@ Not available: No disponible
Payment on account...: Pago a cuenta...
Closure: Cierre
You cannot make a payment on account from multiple clients: No puedes realizar un pago a cuenta de clientes diferentes
Filter by selection: Filtro por selección
Exclude selection: Excluir selección
Remove filter: Quitar filtro por selección
Remove all filters: Eliminar todos los filtros

View File

@ -17,6 +17,20 @@ class Controller extends Section {
]
};
}
showDescriptor(event, sale) {
this.quicklinks = {
btnThree: {
icon: 'icon-transaction',
state: `item.card.diary({
id: ${sale.itemFk},
warehouseFk: ${this.ticket.warehouseFk},
lineFk: ${sale.id}
})`,
tooltip: 'Item diary'
}
};
this.$.descriptor.show(event.target, sale.itemFk);
}
}
ngModule.component('vnTicketSaleChecked', {

View File

@ -5,11 +5,11 @@
<div class="attributes">
<vn-label-value
label="Wh. In"
value="{{$ctrl.travel.warehouseOut.name}}">
value="{{$ctrl.travel.warehouseIn.name}}">
</vn-label-value>
<vn-label-value
label="Wh. Out"
value="{{$ctrl.travel.warehouseIn.name}}">
value="{{$ctrl.travel.warehouseOut.name}}">
</vn-label-value>
<vn-label-value
label="Shipped"

View File

@ -32,7 +32,7 @@
<span translate class="label">Changed by</span><span class="label">: </span>
<span
ng-class="{'link': log.user.worker.id, 'value': !log.user.worker.id}"
ng-click="workerDescriptor.show($event, log.user.id)"
ng-click="$ctrl.showWorkerDescriptor($event, log.user.worker.id)"
translate>{{::log.user.name | dashIfEmpty}}
</span>
</div>
@ -53,7 +53,7 @@
<vn-td class="expendable">
<span
ng-class="{'link': log.user.worker.id, 'value': !log.user.worker.id}"
ng-click="workerDescriptor.show($event, log.user.id)"
ng-click="$ctrl.showWorkerDescriptor($event, log.user.worker.id)"
translate>{{::log.user.name | dashIfEmpty}}
</span>
</vn-td>

View File

@ -57,6 +57,11 @@ export default class Controller extends Section {
}
return null;
}
showWorkerDescriptor(event, workerId) {
if (!workerId) return;
this.$.workerDescriptor.show(event.target, workerId);
}
}
ngModule.component('vnLog', {

View File

@ -23,6 +23,11 @@ module.exports = Self => {
arg: 'warehouseFk',
type: 'number',
required: true
},
{
arg: 'showExpiredZones',
type: 'boolean',
required: true
}],
returns: {
type: 'object',
@ -34,14 +39,15 @@ module.exports = Self => {
}
});
Self.getLanded = async(shipped, addressFk, agencyModeFk, warehouseFk) => {
Self.getLanded = async(shipped, addressFk, agencyModeFk, warehouseFk, showExpiredZones) => {
let stmts = [];
stmts.push(new ParameterizedSQL(
`CALL vn.zone_getLanded(?, ?, ?, ?)`, [
`CALL vn.zone_getLanded(?, ?, ?, ?, ?)`, [
shipped,
addressFk,
agencyModeFk,
warehouseFk
warehouseFk,
showExpiredZones
]
));

View File

@ -7,7 +7,8 @@ describe('agency getLanded()', () => {
const addressFk = 121;
const agencyModeFk = 7;
const warehouseFk = 1;
let result = await app.models.Agency.getLanded(shipped, addressFk, agencyModeFk, warehouseFk);
const showExpiredZones = true;
let result = await app.models.Agency.getLanded(shipped, addressFk, agencyModeFk, warehouseFk, showExpiredZones);
expect(result.landed).toBeDefined();
});

View File

@ -1,13 +1,16 @@
const app = require('vn-loopback/server/server');
// 2302
describe('zone deletezone()', () => {
let zoneId = 1;
let zoneId = 9;
let originalZoneTickets;
let originalZone;
let originalZoneIncluded;
beforeAll(async done => {
originalZone = await app.models.Zone.findById(zoneId);
originalZoneTickets = await app.models.Ticket.find({where: {zoneFk: zoneId}});
originalZoneIncluded = await app.models.ZoneIncluded.find({where: {zoneFk: zoneId}});
done();
});
@ -17,6 +20,9 @@ describe('zone deletezone()', () => {
originalZoneTickets.forEach(async ticket => {
await ticket.updateAttributes({zoneFk: zoneId});
});
originalZoneIncluded.forEach(async zoneIncluded => {
await zoneIncluded.save();
});
done();
});

1981
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -56,9 +56,9 @@
"gulp-install": "^1.1.0",
"gulp-jasmine": "^4.0.0",
"gulp-merge-json": "^1.3.1",
"gulp-nodemon": "^2.4.2",
"gulp-nodemon": "^2.5.0",
"gulp-print": "^2.0.1",
"gulp-wrap": "^0.13.0",
"gulp-wrap": "^0.15.0",
"gulp-yaml": "^1.0.1",
"html-loader": "^0.4.5",
"html-loader-jest": "^0.2.1",
@ -71,9 +71,9 @@
"jest-junit": "^8.0.0",
"json-loader": "^0.5.7",
"merge-stream": "^1.0.1",
"minimist": "^1.2.0",
"minimist": "^1.2.5",
"mysql2": "^1.7.0",
"node-sass": "^4.13.0",
"node-sass": "^4.14.1",
"nodemon": "^1.19.4",
"plugin-error": "^1.0.1",
"puppeteer": "^2.0.0",
@ -82,7 +82,7 @@
"style-loader": "^0.23.1",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.1",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^4.2.2",
"yaml-loader": "^0.5.0"
},

View File

@ -72,9 +72,10 @@ class Email extends Component {
await getAttachments(this.path, this.attachments);
const localeSubject = await this.getSubject();
const replyTo = this.args.replyTo || this.args.auth.email;
const options = {
to: this.args.recipient,
replyTo: this.args.auth.email,
replyTo: replyTo,
subject: localeSubject,
html: rendered,
attachments: attachments

View File

@ -58,7 +58,8 @@ module.exports = app => {
const args = Object.assign({
ticketId: ticket.id,
recipientId: ticket.clientFk,
recipient: ticket.recipient
recipient: ticket.recipient,
replyTo: ticket.salesPersonEmail
}, reqArgs);
const email = new Email('delivery-note-link', args);