Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2742--invoiceIn-create
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Joan Sanchez 2021-06-21 09:19:00 +00:00
commit 8c0171096f
185 changed files with 3932 additions and 1758 deletions

View File

@ -12,17 +12,23 @@ module.exports = function(Self) {
}
});
Self.getStarredModules = async ctx => {
Self.getStarredModules = async(ctx, options) => {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
let myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const filter = {
where: {
workerFk: userId
},
fields: ['moduleFk']
fields: ['id', 'workerFk', 'moduleFk', 'position'],
order: 'position ASC'
};
const starredModules = await Self.app.models.StarredModule.find(filter);
return starredModules;
return models.StarredModule.find(filter, myOptions);
};
};

View File

@ -0,0 +1,98 @@
module.exports = function(Self) {
Self.remoteMethodCtx('setPosition', {
description: 'sets the position of a given module',
accessType: 'WRITE',
returns: {
type: 'object',
root: true
},
accepts: [
{
arg: 'moduleName',
type: 'string',
required: true,
description: 'The module name'
},
{
arg: 'direction',
type: 'string',
required: true,
description: 'Whether to move left or right the module position'
}
],
http: {
path: `/setPosition`,
verb: 'post'
}
});
Self.setPosition = async(ctx, moduleName, direction, options) => {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
let tx;
let myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const filter = {
where: {
workerFk: userId,
moduleFk: moduleName
},
order: 'position DESC'
};
const [movingModule] = await models.StarredModule.find(filter, myOptions);
let operator;
let order;
switch (direction) {
case 'left':
operator = {lt: movingModule.position};
order = 'position DESC';
break;
case 'right':
operator = {gt: movingModule.position};
order = 'position ASC';
break;
default:
return;
}
const pushedModule = await models.StarredModule.findOne({
where: {
position: operator,
workerFk: userId
},
order: order
}, myOptions);
if (!pushedModule) return;
const movingPosition = pushedModule.position;
const pushingPosition = movingModule.position;
await movingModule.updateAttribute('position', movingPosition, myOptions);
await pushedModule.updateAttribute('position', pushingPosition, myOptions);
if (tx) await tx.commit();
return {
movingModule: movingModule,
pushedModule: pushedModule
};
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -19,7 +19,7 @@ describe('getStarredModules()', () => {
});
it(`should return the starred modules for a given user`, async() => {
const newStarred = await app.models.StarredModule.create({workerFk: 9, moduleFk: 'Clients'});
const newStarred = await app.models.StarredModule.create({workerFk: 9, moduleFk: 'Clients', position: 1});
const starredModules = await app.models.StarredModule.getStarredModules(ctx);
expect(starredModules.length).toEqual(1);

View File

@ -0,0 +1,223 @@
const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('setPosition()', () => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
const ctx = {
req: activeCtx
};
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should increase the orders module position by replacing it with clients and vice versa', async() => {
const tx = await app.models.StarredModule.beginTransaction({});
const filter = {
where: {
workerFk: ctx.req.accessToken.userId,
moduleFk: 'Orders'
}
};
try {
const options = {transaction: tx};
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options);
let orders = await app.models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'Clients';
let clients = await app.models.StarredModule.findOne(filter, options);
expect(orders.position).toEqual(1);
expect(clients.position).toEqual(2);
await app.models.StarredModule.setPosition(ctx, 'Clients', 'left', options);
filter.where.moduleFk = 'Clients';
clients = await app.models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'Orders';
orders = await app.models.StarredModule.findOne(filter, options);
expect(clients.position).toEqual(1);
expect(orders.position).toEqual(2);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should decrease the orders module position by replacing it with clients and vice versa', async() => {
const tx = await app.models.StarredModule.beginTransaction({});
const filter = {
where: {
workerFk: ctx.req.accessToken.userId,
moduleFk: 'Orders'
}
};
try {
const options = {transaction: tx};
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options);
let orders = await app.models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'Clients';
let clients = await app.models.StarredModule.findOne(filter, options);
expect(orders.position).toEqual(1);
expect(clients.position).toEqual(2);
await app.models.StarredModule.setPosition(ctx, 'Orders', 'right', options);
filter.where.moduleFk = 'Orders';
orders = await app.models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'Clients';
clients = await app.models.StarredModule.findOne(filter, options);
expect(orders.position).toEqual(2);
expect(clients.position).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should switch two modules after adding and deleting several modules', async() => {
const tx = await app.models.StarredModule.beginTransaction({});
const filter = {
where: {
workerFk: ctx.req.accessToken.userId,
moduleFk: 'Items'
}
};
try {
const options = {transaction: tx};
await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'Items', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'Claims', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'Zones', options);
const items = await app.models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'Claims';
const claims = await app.models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'Clients';
let clients = await app.models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'Orders';
let orders = await app.models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'Zones';
const zones = await app.models.StarredModule.findOne(filter, options);
expect(items.position).toEqual(1);
expect(claims.position).toEqual(2);
expect(clients.position).toEqual(3);
expect(orders.position).toEqual(4);
expect(zones.position).toEqual(5);
await app.models.StarredModule.setPosition(ctx, 'Clients', 'right', options);
filter.where.moduleFk = 'Orders';
orders = await app.models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'Clients';
clients = await app.models.StarredModule.findOne(filter, options);
expect(orders.position).toEqual(3);
expect(clients.position).toEqual(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should switch two modules after adding and deleting a module between them', async() => {
const tx = await app.models.StarredModule.beginTransaction({});
const filter = {
where: {
workerFk: ctx.req.accessToken.userId,
moduleFk: 'Items'
}
};
try {
const options = {transaction: tx};
await app.models.StarredModule.toggleStarredModule(ctx, 'Items', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'Claims', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'Zones', options);
const items = await app.models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'Clients';
let clients = await app.models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'Claims';
const claims = await app.models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'Orders';
let orders = await app.models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'Zones';
const zones = await app.models.StarredModule.findOne(filter, options);
expect(items.position).toEqual(1);
expect(clients.position).toEqual(2);
expect(claims.position).toEqual(3);
expect(orders.position).toEqual(4);
expect(zones.position).toEqual(5);
await app.models.StarredModule.toggleStarredModule(ctx, 'Claims', options);
await app.models.StarredModule.setPosition(ctx, 'Clients', 'right', options);
filter.where.moduleFk = 'Clients';
clients = await app.models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'Orders';
orders = await app.models.StarredModule.findOne(filter, options);
expect(orders.position).toEqual(2);
expect(clients.position).toEqual(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -27,6 +27,7 @@ describe('toggleStarredModule()', () => {
expect(starredModules.length).toEqual(1);
expect(starredModule.moduleFk).toEqual('Orders');
expect(starredModule.workerFk).toEqual(activeCtx.accessToken.userId);
expect(starredModule.position).toEqual(starredModules.length);
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders');
starredModules = await app.models.StarredModule.getStarredModules(ctx);

View File

@ -18,24 +18,61 @@ module.exports = function(Self) {
}
});
Self.toggleStarredModule = async(ctx, moduleName) => {
Self.toggleStarredModule = async(ctx, moduleName, options) => {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const filter = {
where: {
workerFk: userId,
moduleFk: moduleName
let tx;
let myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const filter = {
where: {
workerFk: userId,
moduleFk: moduleName
}
};
const [starredModule] = await models.StarredModule.find(filter, myOptions);
delete filter.moduleName;
const allStarredModules = await models.StarredModule.getStarredModules(ctx, myOptions);
let addedModule;
if (starredModule)
await starredModule.destroy(myOptions);
else {
let highestPosition;
if (allStarredModules.length) {
allStarredModules.sort((a, b) => {
return a.position - b.position;
});
highestPosition = allStarredModules[allStarredModules.length - 1].position + 1;
} else
highestPosition = 1;
addedModule = await models.StarredModule.create({
workerFk: userId,
moduleFk: moduleName,
position: highestPosition
}, myOptions);
}
};
const [starredModule] = await Self.app.models.StarredModule.find(filter);
if (tx) await tx.commit();
if (starredModule)
await starredModule.destroy();
else {
return Self.app.models.StarredModule.create({
workerFk: userId,
moduleFk: moduleName
});
return addedModule;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -1,4 +1,5 @@
module.exports = Self => {
require('../methods/starred-module/getStarredModules')(Self);
require('../methods/starred-module/toggleStarredModule')(Self);
require('../methods/starred-module/setPosition')(Self);
};

View File

@ -18,6 +18,10 @@
"moduleFk": {
"type": "string",
"required": true
},
"position": {
"type": "number",
"required": true
}
},
"relations": {

View File

@ -0,0 +1 @@
INSERT INTO salix.module (code) VALUES ('Monitors');

View File

@ -120,7 +120,7 @@ BEGIN
GROUP BY tl.ticketFk
ON DUPLICATE KEY UPDATE
isAvailable = 0;
/*
INSERT INTO tmp.ticketProblems(ticketFk, itemShortage)
SELECT ticketFk, problem
FROM (
@ -142,10 +142,10 @@ BEGIN
AND NOT i.generic
AND CURDATE() = vDate
AND t.warehouseFk = vWarehouse
GROUP BY tl.ticketFk LIMIT 1) sub
GROUP BY tl.ticketFk) sub
ON DUPLICATE KEY UPDATE
itemShortage = sub.problem;
*/
INSERT INTO tmp.ticketProblems(ticketFk, itemDelay)
SELECT ticketFk, problem
FROM (
@ -165,7 +165,7 @@ BEGIN
AND NOT i.generic
AND CURDATE() = vDate
AND t.warehouseFk = vWarehouse
GROUP BY tl.ticketFk LIMIT 1) sub
GROUP BY tl.ticketFk) sub
ON DUPLICATE KEY UPDATE
itemDelay = sub.problem;
END WHILE;
@ -182,7 +182,6 @@ BEGIN
DROP TEMPORARY TABLE
tmp.clientGetDebt,
tmp.ticketList;
tmp.ticketList;
END;;$$
DELIMITER ;

View File

@ -0,0 +1,9 @@
ALTER TABLE `vn`.`starredModule`
ADD `position` INT NOT NULL AFTER `moduleFk`;
SET @count:=0;
UPDATE `vn`.`starredModule` sm
JOIN (
SELECT sm.id, IF(@workerFk = sm.workerFk, @count:=@count+1, @count:=1) AS position, @workerFk:=sm.workerFk
FROM `vn`.`starredModule` sm ORDER BY workerFk, moduleFk ASC) AS smt ON smt.id = sm.id
SET sm.position = smt.position;

View File

@ -0,0 +1,18 @@
CREATE TABLE `vn`.`invoiceInLog` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`originFk` MEDIUMINT UNSIGNED NOT NULL,
`userFk` int(10) unsigned DEFAULT NULL,
`action` set('insert','update','delete') COLLATE utf8_unicode_ci NOT NULL,
`creationDate` timestamp NULL DEFAULT current_timestamp(),
`description` text CHARACTER SET utf8 DEFAULT NULL,
`changedModel` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
`oldInstance` text COLLATE utf8_unicode_ci DEFAULT NULL,
`newInstance` text COLLATE utf8_unicode_ci DEFAULT NULL,
`changedModelId` int(11) DEFAULT NULL,
`changedModelValue` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `originFk` (`originFk`),
KEY `userFk` (`userFk`),
CONSTRAINT `invoiceInLog_ibfk_1` FOREIGN KEY (`originFk`) REFERENCES `vn`.`invoiceIn` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `invoiceInLog_ibfk_2` FOREIGN KEY (`userFk`) REFERENCES `account`.`user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

View File

@ -0,0 +1,103 @@
DROP PROCEDURE IF EXISTS `vn`.`ticket_componentMakeUpdate`;
DELIMITER $$
$$
CREATE
DEFINER = root@`%` PROCEDURE `vn`.`ticket_componentMakeUpdate`(IN vTicketFk INT, IN vClientFk INT,
IN vNickname VARCHAR(50), IN vAgencyModeFk INT,
IN vAddressFk INT, IN vZoneFk INT, IN vWarehouseFk TINYINT,
IN vCompanyFk SMALLINT, IN vShipped DATETIME,
IN vLanded DATE, IN vIsDeleted TINYINT(1),
IN vHasToBeUnrouted TINYINT(1), IN vOption INT)
BEGIN
/**
* Modifica en el ticket los campos que se le pasan por parámetro
* y cambia sus componentes
*
* @param vTicketFk Id del ticket a modificar
* @param vClientFk nuevo cliente
* @param vNickname nuevo alias
* @param vAgencyModeFk nueva agencia
* @param vAddressFk nuevo consignatario
* @param vZoneFk nueva zona
* @param vWarehouseFk nuevo almacen
* @param vCompanyFk nueva empresa
* @param vShipped nueva fecha del envio de mercancia
* @param vLanded nueva fecha de recepcion de mercancia
* @param vIsDeleted si se borra el ticket
* @param vHasToBeUnrouted si se le elimina la ruta al ticket
* @param vOption opcion para el case del proc ticketComponentUpdateSale
*/
DECLARE vPrice DECIMAL(10,2);
DECLARE vBonus DECIMAL(10,2);
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
CALL ticket_componentPreview (vTicketFk, vLanded, vAddressFk, vZoneFk, vWarehouseFk);
START TRANSACTION;
IF (SELECT addressFk FROM ticket WHERE id = vTicketFk) <> vAddressFk THEN
UPDATE ticket t
JOIN address a ON a.id = vAddressFk
SET t.nickname = a.nickname
WHERE t.id = vTicketFk;
END IF;
CALL zone_getShippedWarehouse(vlanded, vAddressFk, vAgencyModeFk);
SELECT zoneFk, price, bonus INTO vZoneFk, vPrice, vBonus
FROM tmp.zoneGetShipped
WHERE shipped BETWEEN DATE(vShipped) AND util.dayEnd(vShipped) AND warehouseFk = vWarehouseFk LIMIT 1;
UPDATE ticket t
SET
t.clientFk = vClientFk,
t.nickname = vNickname,
t.agencyModeFk = vAgencyModeFk,
t.addressFk = vAddressFk,
t.zoneFk = vZoneFk,
t.zonePrice = vPrice,
t.zoneBonus = vBonus,
t.warehouseFk = vWarehouseFk,
t.companyFk = vCompanyFk,
t.landed = vLanded,
t.shipped = vShipped,
t.isDeleted = vIsDeleted
WHERE
t.id = vTicketFk;
IF vHasToBeUnrouted THEN
UPDATE ticket t SET t.routeFk = NULL
WHERE t.id = vTicketFk;
END IF;
IF vOption <> 8 THEN
DROP TEMPORARY TABLE IF EXISTS tmp.sale;
CREATE TEMPORARY TABLE tmp.sale
(PRIMARY KEY (saleFk))
ENGINE = MEMORY
SELECT id AS saleFk, vWarehouseFk warehouseFk
FROM sale s WHERE s.ticketFk = vTicketFk;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketComponent;
CREATE TEMPORARY TABLE tmp.ticketComponent
SELECT * FROM tmp.ticketComponentPreview;
CALL ticketComponentUpdateSale (vOption);
DROP TEMPORARY TABLE tmp.sale;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketComponent;
END IF;
COMMIT;
DROP TEMPORARY TABLE tmp.zoneGetShipped, tmp.ticketComponentPreview;
END$$
DELIMITER ;

View File

@ -806,19 +806,6 @@ INSERT INTO `vn`.`priceFixed`(`id`, `itemFk`, `rate0`, `rate1`, `rate2`, `rate3`
(2, 3, 10, 10, 10, 10, CURDATE(), DATE_ADD(CURDATE(), INTERVAL +1 MONTH), 0, 1, CURDATE()),
(3, 5, 8.5, 10, 7.5, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL +1 MONTH), 1, 2, CURDATE());
INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `created`, `itemFk`, `counter`, `checked`, `workerFk`)
VALUES
(1, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1, 1, 18),
(2, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 2, 1, 18),
(3, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 2, 3, 1, 18),
(4, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 4, 4, 1, 18),
(5, 1, 2, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1, 1, 18),
(6, 7, 3, 71, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), 1, 1, 1, 18),
(7, 2, 4, 71, DATE_ADD(CURDATE(), INTERVAL -3 MONTH), 1, 1, 1, 18),
(8, 3, 5, 71, DATE_ADD(CURDATE(), INTERVAL -4 MONTH), 1, 1, 1, 18),
(9, 3, 6, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1, 1, 18),
(10, 7, 7, 71, CURDATE(), 1, 1, 1, 18);
INSERT INTO `vn`.`expeditionBoxVol`(`boxFk`, `m3`, `ratio`)
VALUES
(71,0.141,1);
@ -834,6 +821,19 @@ INSERT INTO `vn`.`packaging`(`id`, `volume`, `width`, `height`, `depth`, `isPack
('cc', 1640038.00, 56.00, 220.00, 128.00, 1, CURDATE(), 15, 90.00),
('pallet 100', 2745600.00, 100.00, 220.00, 120.00, 1, CURDATE(), 16, 0.00);
INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `created`, `itemFk`, `counter`, `checked`, `workerFk`, `externalId`, `packagingFk`)
VALUES
(1, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 15, 1, 1, 18, 'UR9000006041', 94),
(2, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 16, 2, 1, 18, 'UR9000006041', 94),
(3, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), NULL, 3, 1, 18, 'UR9000006041', 94),
(4, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), NULL, 4, 1, 18, 'UR9000006041', 94),
(5, 1, 2, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), NULL, 1, 1, 18, NULL, 94),
(6, 7, 3, 71, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), NULL, 1, 1, 18, NULL, 94),
(7, 2, 4, 71, DATE_ADD(CURDATE(), INTERVAL -3 MONTH), NULL, 1, 1, 18, NULL, 94),
(8, 3, 5, 71, DATE_ADD(CURDATE(), INTERVAL -4 MONTH), NULL, 1, 1, 18, NULL, 94),
(9, 3, 6, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), NULL, 1, 1, 18, NULL, 94),
(10, 7, 7, 71, CURDATE(), NULL, 1, 1, 18, NULL, 94);
INSERT INTO `vn`.`ticketPackaging`(`id`, `ticketFk`, `packagingFk`, `quantity`, `created`, `pvp`)
VALUES
(1, 1, 2, 2, CURDATE(), NULL),
@ -2348,4 +2348,25 @@ INSERT INTO `vn`.`zoneAgencyMode`(`id`, `agencyModeFk`, `zoneFk`)
(1, 1, 1),
(2, 1, 2),
(3, 6, 5),
(4, 7, 1);
(4, 7, 1);
INSERT INTO `vn`.`expeditionTruck` (`id`, `ETD`, `description`)
VALUES
(1, CONCAT(YEAR(DATE_ADD(CURDATE(), INTERVAL +3 YEAR))), 'Best truck in fleet');
INSERT INTO `vn`.`expeditionPallet` (`id`, `truckFk`, `built`, `position`, `isPrint`)
VALUES
(1, 1, CURDATE(), 1, 1);
INSERT INTO `vn`.`expeditionScan` (`id`, `expeditionFk`, `scanned`, `palletFk`)
VALUES
(1, 1, CURDATE(), 1),
(2, 2, CURDATE(), 1),
(3, 3, CURDATE(), 1),
(4, 4, CURDATE(), 1),
(5, 5, CURDATE(), 1),
(6, 6, CURDATE(), 1),
(7, 7, CURDATE(), 1),
(8, 8, CURDATE(), 1),
(9, 9, CURDATE(), 1),
(10, 10, CURDATE(), 1);

View File

@ -151,6 +151,7 @@ export default {
mobile: 'vn-client-basic-data vn-textfield[ng-model="$ctrl.client.mobile"]',
salesPerson: 'vn-client-basic-data vn-autocomplete[ng-model="$ctrl.client.salesPersonFk"]',
channel: 'vn-client-basic-data vn-autocomplete[ng-model="$ctrl.client.contactChannelFk"]',
transferor: 'vn-client-basic-data vn-autocomplete[ng-model="$ctrl.client.transferorFk"]',
saveButton: 'vn-client-basic-data button[type=submit]'
},
clientFiscalData: {
@ -162,7 +163,6 @@ export default {
postcode: 'vn-client-fiscal-data vn-datalist[ng-model="$ctrl.client.postcode"]',
sageTax: 'vn-client-fiscal-data vn-autocomplete[ng-model="$ctrl.client.sageTaxTypeFk"]',
sageTransaction: 'vn-client-fiscal-data vn-autocomplete[ng-model="$ctrl.client.sageTransactionTypeFk"]',
transferor: 'vn-client-fiscal-data vn-autocomplete[ng-model="$ctrl.client.transferorFk"]',
city: 'vn-client-fiscal-data vn-datalist[ng-model="$ctrl.client.city"]',
province: 'vn-client-fiscal-data vn-autocomplete[ng-model="$ctrl.client.provinceFk"]',
country: 'vn-client-fiscal-data vn-autocomplete[ng-model="$ctrl.client.countryFk"]',
@ -474,7 +474,7 @@ export default {
advancedSearchDaysOnward: 'vn-ticket-search-panel vn-input-number[ng-model="filter.scopeDays"]',
advancedSearchClient: 'vn-ticket-search-panel vn-textfield[ng-model="filter.clientFk"]',
advancedSearchButton: 'vn-ticket-search-panel button[type=submit]',
newTicketButton: 'vn-ticket-index a[ui-sref="ticket.create"]',
newTicketButton: 'vn-ticket-index vn-button[icon="add"]',
searchResult: 'vn-ticket-index vn-card > vn-table > div > vn-tbody > a.vn-tr',
firstTicketCheckbox: 'vn-ticket-index vn-tbody > a:nth-child(1) > vn-td:nth-child(1) > vn-check',
secondTicketCheckbox: 'vn-ticket-index vn-tbody > a:nth-child(2) > vn-td:nth-child(1) > vn-check',
@ -572,11 +572,11 @@ export default {
firstSaleZoomedImage: 'body > div > div > img',
firstSaleQuantity: 'vn-ticket-sale [ng-model="sale.quantity"]',
firstSaleQuantityCell: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td-editable:nth-child(6)',
firstSalePrice: 'vn-ticket-sale vn-table vn-tr:nth-child(1) > vn-td:nth-child(8) > span',
firstSalePrice: 'vn-ticket-sale vn-table vn-tr:nth-child(1) > vn-td:nth-child(9) > span',
firstSalePriceInput: '.vn-popover.shown input[ng-model="$ctrl.field"]',
firstSaleDiscount: 'vn-ticket-sale vn-table vn-tr:nth-child(1) > vn-td:nth-child(9) > span',
firstSaleDiscount: 'vn-ticket-sale vn-table vn-tr:nth-child(1) > vn-td:nth-child(10) > span',
firstSaleDiscountInput: '.vn-popover.shown [ng-model="$ctrl.field"]',
firstSaleImport: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(10)',
firstSaleImport: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(11)',
firstSaleReservedIcon: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td:nth-child(2) > vn-icon:nth-child(3)',
firstSaleColour: 'vn-ticket-sale vn-tr:nth-child(1) vn-fetched-tags section',
firstSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(1) vn-check[ng-model="sale.checked"]',
@ -837,7 +837,8 @@ export default {
saveButton: 'vn-worker-pbx button[type=submit]'
},
workerTimeControl: {
timeDialog: '.vn-dialog.shown vn-input-time[ng-model="$ctrl.newTime"]',
dialogTimeInput: '.vn-dialog.shown vn-input-time[ng-model="$ctrl.newTimeEntry.timed"]',
dialogTimeDirection: '.vn-dialog.shown vn-autocomplete[ng-model="$ctrl.newTimeEntry.direction"]',
mondayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(1) > vn-icon-button',
tuesdayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(2) > vn-icon-button',
wednesdayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(3) > vn-icon-button',
@ -845,35 +846,35 @@ export default {
fridayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(5) > vn-icon-button',
saturdayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(6) > vn-icon-button',
sundayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(7) > vn-icon-button',
firstEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(1) > vn-chip > div',
firstEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(1) > vn-chip > div',
firstEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(1) > vn-chip > div',
firstEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(1) > vn-chip > div',
firstEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(1) > vn-chip > div',
firstEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(1) > vn-chip > div',
firstEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(1) > vn-chip > div',
secondEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(2) > vn-chip > div',
secondEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(2) > vn-chip > div',
secondEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(2) > vn-chip > div',
secondEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(2) > vn-chip > div',
secondEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(2) > vn-chip > div',
secondEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(2) > vn-chip > div',
secondEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(2) > vn-chip > div',
thirdEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(3) > vn-chip > div',
firstEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(1) > vn-chip > div:nth-child(2)',
secondEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(2) > vn-chip > div:nth-child(2)',
secondEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(2) > vn-chip > div:nth-child(2)',
secondEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(2) > vn-chip > div:nth-child(2)',
secondEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(2) > vn-chip > div:nth-child(2)',
secondEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(2) > vn-chip > div:nth-child(2)',
secondEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(2) > vn-chip > div:nth-child(2)',
secondEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(2) > vn-chip > div:nth-child(2)',
thirdEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(3) > vn-chip > div:nth-child(2)',
thirdEntryOfMondayDelete: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(3) > vn-chip > vn-icon[icon="cancel"]',
thirdEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(3) > vn-chip > div',
thirdEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(3) > vn-chip > div',
thirdEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(3) > vn-chip > div',
thirdEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(3) > vn-chip > div',
thirdEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(3) > vn-chip > div',
thirdEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(3) > vn-chip > div',
fourthEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(4) > vn-chip > div',
fourthEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(4) > vn-chip > div',
fourthEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(4) > vn-chip > div',
fourthEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(4) > vn-chip > div',
fourthEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(4) > vn-chip > div',
fourthEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(4) > vn-chip > div',
fourthEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(4) > vn-chip > div',
thirdEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(3) > vn-chip > div:nth-child(2)',
thirdEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(3) > vn-chip > div:nth-child(2)',
thirdEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(3) > vn-chip > div:nth-child(2)',
thirdEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(3) > vn-chip > div:nth-child(2)',
thirdEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(3) > vn-chip > div:nth-child(2)',
thirdEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(3) > vn-chip > div:nth-child(2)',
fourthEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(4) > vn-chip > div:nth-child(2)',
fourthEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(4) > vn-chip > div:nth-child(2)',
fourthEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(4) > vn-chip > div:nth-child(2)',
fourthEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(4) > vn-chip > div:nth-child(2)',
fourthEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(4) > vn-chip > div:nth-child(2)',
fourthEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(4) > vn-chip > div:nth-child(2)',
fourthEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(4) > vn-chip > div:nth-child(2)',
mondayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(1)',
tuesdayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(2)',
wednesdayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(3)',
@ -888,7 +889,7 @@ export default {
},
workerCalendar: {
year: 'vn-worker-calendar vn-autocomplete[ng-model="$ctrl.year"]',
totalHolidaysUsed: 'vn-worker-calendar div.totalBox > div',
totalHolidaysUsed: 'vn-worker-calendar div.totalBox:first-child > div',
penultimateMondayOfJanuary: 'vn-worker-calendar vn-calendar:nth-child(2) section:nth-child(22) > div',
lastMondayOfMarch: 'vn-worker-calendar vn-calendar:nth-child(4) section:nth-child(29) > div',
fistMondayOfMay: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(8) > div',
@ -896,11 +897,11 @@ export default {
secondTuesdayOfMay: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(16) > div',
secondWednesdayOfMay: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(17) > div',
secondThursdayOfMay: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(18) > div',
holidays: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(1)',
absence: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(2)',
halfHoliday: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(3)',
furlough: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(4)',
halfFurlough: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(5)',
holidays: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(1)',
absence: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(2)',
halfHoliday: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(3)',
furlough: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(4)',
halfFurlough: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(5)',
},
invoiceOutIndex: {
topbarSearch: 'vn-searchbar',

View File

@ -34,6 +34,7 @@ describe('Client Edit basicData path', () => {
await page.clearInput(selectors.clientBasicData.email);
await page.write(selectors.clientBasicData.email, 'PWallace@verdnatura.es');
await page.autocompleteSearch(selectors.clientBasicData.channel, 'Rumors on the streets');
await page.autocompleteSearch(selectors.clientBasicData.transferor, 'Max Eisenhardt');
await page.waitToClick(selectors.clientBasicData.saveButton);
const message = await page.waitForSnackbar();
@ -67,6 +68,13 @@ describe('Client Edit basicData path', () => {
expect(result).toEqual('Rumors on the streets');
});
it('should confirm the previous client have been selected', async() => {
const result = await page
.waitToGetProperty(selectors.clientBasicData.transferor, 'value');
expect(result).toEqual('Max Eisenhardt');
});
});
describe('as salesAssistant', () => {

View File

@ -68,7 +68,6 @@ describe('Client Edit fiscalData path', () => {
await page.write(selectors.clientFiscalData.city, 'Valencia');
await page.autocompleteSearch(selectors.clientFiscalData.sageTax, 'operaciones no sujetas');
await page.autocompleteSearch(selectors.clientFiscalData.sageTransaction, 'regularización de inversiones');
await page.autocompleteSearch(selectors.clientFiscalData.transferor, 'Max Eisenhardt');
await page.clearInput(selectors.clientFiscalData.postcode);
await page.write(selectors.clientFiscalData.postcode, '46000');
await page.waitToClick(selectors.clientFiscalData.activeCheckbox);
@ -202,12 +201,6 @@ describe('Client Edit fiscalData path', () => {
expect(result).toEqual('36: Regularización de inversiones');
});
it('should confirm the transferor have been edited', async() => {
const result = await page.waitToGetProperty(selectors.clientFiscalData.transferor, 'value');
expect(result).toEqual('Max Eisenhardt');
});
it('should confirm the city have been autocompleted', async() => {
const result = await page.waitToGetProperty(selectors.clientFiscalData.city, 'value');

View File

@ -22,7 +22,8 @@ describe('Worker time control path', () => {
const scanTime = '07:00';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText');
@ -33,7 +34,8 @@ describe('Worker time control path', () => {
const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfMonday, 'innerText');
@ -44,7 +46,8 @@ describe('Worker time control path', () => {
const scanTime = '18:00';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfMonday, 'innerText');
@ -66,7 +69,8 @@ describe('Worker time control path', () => {
const scanTime = '14:00';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfMonday, 'innerText');
@ -77,7 +81,8 @@ describe('Worker time control path', () => {
const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfMonday, 'innerText');
@ -103,7 +108,8 @@ describe('Worker time control path', () => {
const scanTime = '08:00';
await page.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfTuesday, 'innerText');
@ -114,7 +120,8 @@ describe('Worker time control path', () => {
const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfTuesday, 'innerText');
@ -125,7 +132,8 @@ describe('Worker time control path', () => {
const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfTuesday, 'innerText');
@ -136,7 +144,8 @@ describe('Worker time control path', () => {
const scanTime = '16:00';
await page.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfTuesday, 'innerText');
@ -153,7 +162,8 @@ describe('Worker time control path', () => {
const scanTime = '09:00';
await page.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfWednesday, 'innerText');
@ -164,7 +174,8 @@ describe('Worker time control path', () => {
const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfWednesday, 'innerText');
@ -175,7 +186,8 @@ describe('Worker time control path', () => {
const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfWednesday, 'innerText');
@ -186,7 +198,8 @@ describe('Worker time control path', () => {
const scanTime = '17:00';
await page.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfWednesday, 'innerText');
@ -203,7 +216,8 @@ describe('Worker time control path', () => {
const scanTime = '09:59';
await page.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfThursday, 'innerText');
@ -213,7 +227,8 @@ describe('Worker time control path', () => {
it(`should joyfully scan out Hank Pym for break`, async() => {
const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfThursday, 'innerText');
@ -223,7 +238,8 @@ describe('Worker time control path', () => {
it(`should joyfully scan in Hank Pym from the break`, async() => {
const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfThursday, 'innerText');
@ -233,7 +249,8 @@ describe('Worker time control path', () => {
it(`should joyfully scan out Hank Pym for the day`, async() => {
const scanTime = '17:59';
await page.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfThursday, 'innerText');
@ -249,7 +266,8 @@ describe('Worker time control path', () => {
it('should smilingly scan in Hank Pym', async() => {
const scanTime = '07:30';
await page.waitToClick(selectors.workerTimeControl.fridayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfFriday, 'innerText');
@ -259,7 +277,8 @@ describe('Worker time control path', () => {
it(`should smilingly scan out Hank Pym for break`, async() => {
const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.fridayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfFriday, 'innerText');
@ -269,7 +288,8 @@ describe('Worker time control path', () => {
it(`should smilingly scan in Hank Pym from the break`, async() => {
const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.fridayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfFriday, 'innerText');
@ -279,7 +299,8 @@ describe('Worker time control path', () => {
it(`should smilingly scan out Hank Pym for the day`, async() => {
const scanTime = '15:30';
await page.waitToClick(selectors.workerTimeControl.fridayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfFriday, 'innerText');
@ -310,8 +331,10 @@ describe('Worker time control path', () => {
it('should lovingly scan in Hank Pym', async() => {
const scanTime = '06:00';
await page.waitForTimeout(1000); // without this timeout the dialog doesn't pop up
await page.waitToClick(selectors.workerTimeControl.saturdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfSaturday, 'innerText');
@ -321,7 +344,8 @@ describe('Worker time control path', () => {
it(`should lovingly scan out Hank Pym for the day with no break to leave a bit early`, async() => {
const scanTime = '13:40';
await page.waitToClick(selectors.workerTimeControl.saturdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfSaturday, 'innerText');
@ -337,7 +361,8 @@ describe('Worker time control path', () => {
it('should gladly scan in Hank Pym', async() => {
const scanTime = '05:00';
await page.waitToClick(selectors.workerTimeControl.sundayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfSunday, 'innerText');
@ -347,7 +372,8 @@ describe('Worker time control path', () => {
it(`should gladly scan out Hank Pym for the day with no break to leave a bit early`, async() => {
const scanTime = '12:40';
await page.waitToClick(selectors.workerTimeControl.sundayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfSunday, 'innerText');

View File

@ -26,7 +26,8 @@ describe('Travel create path', () => {
it('should fill the reference, agency and ship date then save the form', async() => {
await page.write(selectors.travelIndex.reference, 'Testing reference');
await page.autocompleteSearch(selectors.travelIndex.agency, 'inhouse pickup');
await page.pickDate(selectors.travelIndex.shipDate, date);
await page.pickDate(selectors.travelIndex.shipDate, date); // this line autocompletes another 3 fields
await page.waitForTimeout(1000);
await page.waitToClick(selectors.travelIndex.save);
const message = await page.waitForSnackbar();
@ -35,8 +36,6 @@ describe('Travel create path', () => {
});
it('should check the user was redirected to the travel basic data upon creation', async() => {
// backup code for further intermitences still on track.
// await page.screenshot({path: 'e2e/paths/10-travel/error.jpeg', type: 'jpeg'});
await page.waitForState('travel.card.basicData');
});

View File

@ -136,6 +136,7 @@ describe('Travel descriptor path', () => {
it('should navigate to the summary and then clone the travel and its entries using the descriptor menu to get redirected to the cloned travel basic data', async() => {
await page.waitToClick('vn-icon[icon="preview"]'); // summary icon
await page.waitForState('travel.card.summary');
await page.waitForTimeout(1000);
await page.waitToClick(selectors.travelDescriptor.dotMenu);
await page.waitToClick(selectors.travelDescriptor.dotMenuCloneWithEntries);
await page.waitToClick(selectors.travelDescriptor.acceptClonation);

View File

@ -65,6 +65,7 @@ describe('Account create and basic data path', () => {
describe('Descriptor option', () => {
describe('Edit role', () => {
it('should edit the role using the descriptor menu', async() => {
await page.waitForTimeout(1000); // sometimes descriptor fails to load it's functionalities without this timeout
await page.waitToClick(selectors.accountDescriptor.menuButton);
await page.waitToClick(selectors.accountDescriptor.changeRole);
await page.autocompleteSearch(selectors.accountDescriptor.newRole, 'adminBoss');

View File

@ -11,7 +11,7 @@ import './style.scss';
* @property {String} valueField The data field name that should be used as value
* @property {Array} data Static data for the autocomplete
* @property {Object} intialData An initial data to avoid the server request used to get the selection
* @property {Boolean} multiple Wether to allow multiple selection
* @property {Boolean} multiple Whether to allow multiple selection
* @property {Object} selection Current object selected
*
* @event change Thrown when value is changed

View File

@ -1,6 +1,12 @@
<div
ng-transclude="prepend"
class="prepend"></div>
<div ng-transclude></div>
<div
ng-transclude="append"
class="append"></div>
<vn-icon
ng-click="$ctrl.onRemove()"
ng-click="$ctrl.onRemove($event)"
ng-if="$ctrl.removable"
icon="cancel"
tabindex="0">

View File

@ -3,16 +3,19 @@ import Component from '../../lib/component';
import './style.scss';
export default class Chip extends Component {
onRemove() {
if (!this.disabled) this.emit('remove');
onRemove($event) {
if (!this.disabled) this.emit('remove', {$event});
}
}
Chip.$inject = ['$element', '$scope', '$transclude'];
ngModule.vnComponent('vnChip', {
template: require('./index.html'),
transclude: {
prepend: '?prepend',
append: '?append'
},
controller: Chip,
transclude: true,
bindings: {
disabled: '<?',
removable: '<?'

View File

@ -18,9 +18,14 @@ describe('Component vnChip', () => {
it(`should emit remove event`, () => {
controller.emit = () => {};
jest.spyOn(controller, 'emit');
controller.onRemove();
expect(controller.emit).toHaveBeenCalledWith('remove');
const $event = new Event('click');
const target = document.createElement('div');
target.dispatchEvent($event);
controller.onRemove($event);
expect(controller.emit).toHaveBeenCalledWith('remove', {$event});
});
});
});

View File

@ -1,4 +1,5 @@
@import "variables";
@import "effects";
vn-chip {
border-radius: 16px;
@ -24,25 +25,47 @@ vn-chip {
&.transparent {
background-color: transparent;
}
&.colored {
&.colored,
&.colored.clickable:hover,
&.colored.clickable:focus {
background-color: $color-main;
color: $color-font-bg;
}
&.notice {
background-color: $color-notice-medium
&.notice,
&.notice.clickable:hover,
&.notice.clickable:focus {
background-color: $color-notice-medium;
}
&.success {
&.success,
&.success.clickable:hover,
&.success.clickable:focus {
background-color: $color-success-medium;
}
&.warning {
&.warning,
&.warning.clickable:hover,
&.warning.clickable:focus {
background-color: $color-main-medium;
}
&.alert {
&.alert,
&.alert.clickable:hover,
&.alert.clickable:focus {
background-color: $color-alert-medium;
}
&.message {
&.message,
&.message.clickable:hover,
&.message.clickable:focus {
color: $color-font-dark;
background-color: $color-bg-dark
background-color: $color-bg-dark;
}
&.clickable {
@extend %clickable;
opacity: 0.8;
&:hover,
&:focus {
opacity: 1;
}
}
& > div {
@ -75,6 +98,20 @@ vn-chip {
opacity: 1;
}
}
& > .prepend {
padding: 0 5px;
padding-right: 0;
&:empty {display:none;}
}
& > .append {
padding: 0 5px;
padding-left: 0;
&:empty {display:none;}
}
}
vn-avatar {

View File

@ -43,14 +43,14 @@ export default class Contextmenu {
get row() {
if (!this.target) return null;
return this.target.closest('vn-tr, .vn-tr');
return this.target.closest('[ng-repeat]');
}
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');
const rows = table.querySelectorAll('[ng-repeat]');
return Array.from(rows).findIndex(
rowItem => rowItem == this.row

View File

@ -17,7 +17,7 @@ export default class Popup extends Component {
}
/**
* @type {Boolean} Wether to show or hide the popup.
* @type {Boolean} Whether to show or hide the popup.
*/
get shown() {
return this._shown;

View File

@ -102,7 +102,7 @@ vn-table {
& > vn-one {
overflow: hidden;
text-overflow: ellipsis;
font-size: 0.75rem;
font-size: 1rem;
}
& > vn-one:nth-child(2) h3 {
@ -229,4 +229,8 @@ vn-table.scrollable.sm,
vn-table.scrollable.lg,
.vn-table.scrollable.lg {
max-height: 700px
}
.tableWrapper {
overflow-x: auto;
}

View File

@ -9,18 +9,33 @@
<vn-icon icon="push_pin"></vn-icon>
</div>
<div class="modules">
<a
ng-repeat="mod in ::$ctrl.modules"
<a
ng-repeat="mod in $ctrl.modules | orderBy: '+position'"
ng-animate-ref="{{mod.position}}"
ng-if='mod.starred'
ui-sref="{{::mod.route.state}}"
translate-attr="{title: mod.name}"
class="vn-shadow">
<div
vn-tooltip="Remove from favorites"
class="pin"
ng-click="$ctrl.toggleStarredModule(mod, $event)">
<vn-icon icon="remove_circle"></vn-icon>
</div>
<span>
<vn-icon
vn-tooltip="Move left"
class="small-icon"
ng-click="$ctrl.moveModule(mod, $event, 'left')"
icon="arrow_left">
</vn-icon>
<vn-icon
vn-tooltip="Remove from favorites"
class="small-icon"
ng-click="$ctrl.toggleStarredModule(mod, $event)"
icon="remove_circle">
</vn-icon>
<vn-icon
vn-tooltip="Move right"
class="small-icon"
ng-click="$ctrl.moveModule(mod, $event, 'right')"
icon="arrow_right">
</vn-icon>
</span>
<div>
<vn-icon icon="{{::mod.icon || 'photo'}}"></vn-icon>
</div>
@ -41,17 +56,19 @@
</div>
<div class="modules">
<a
ng-repeat="mod in ::$ctrl.modules"
ng-repeat="mod in $ctrl.modules"
ng-if='!mod.starred'
ui-sref="{{::mod.route.state}}"
translate-attr="{title: mod.name}"
class="vn-shadow">
<div
vn-tooltip="Add to favorites"
class="pin"
ng-click="$ctrl.toggleStarredModule(mod, $event)">
<vn-icon icon="push_pin"></vn-icon>
</div>
<span>
<div
vn-tooltip="Add to favorites"
class="small-icon"
ng-click="$ctrl.toggleStarredModule(mod, $event)">
<vn-icon icon="push_pin"></vn-icon>
</div>
</span>
<div>
<vn-icon icon="{{::mod.icon || 'photo'}}"></vn-icon>
</div>

View File

@ -35,6 +35,7 @@ export default class Controller extends Component {
for (let starredModule of res.data) {
const module = this.modules.find(mod => mod.name === starredModule.moduleFk);
module.starred = true;
module.position = starredModule.position;
}
this.countModules();
});
@ -48,9 +49,10 @@ export default class Controller extends Component {
const params = {moduleName: module.name};
const query = `starredModules/toggleStarredModule`;
this.$http.post(query, params).then(res => {
if (res.data)
if (res.data) {
module.starred = true;
else
module.position = res.data.position;
} else
module.starred = false;
this.vnApp.showSuccess(this.$t('Data saved!'));
@ -74,6 +76,26 @@ export default class Controller extends Component {
return this.$sce.trustAsHtml(getName(mod));
}
moveModule(module, event, direction) {
if (event.defaultPrevented) return;
event.preventDefault();
event.stopPropagation();
const params = {moduleName: module.name, direction: direction};
const query = `starredModules/setPosition`;
this.$http.post(query, params).then(res => {
if (res.data) {
module.position = res.data.movingModule.position;
this.modules.forEach(mod => {
if (mod.name == res.data.pushedModule.moduleFk)
mod.position = res.data.pushedModule.position;
});
this.vnApp.showSuccess(this.$t('Data saved!'));
this.countModules();
}
});
}
}
Controller.$inject = ['$element', '$scope', 'vnModules', '$sce'];

View File

@ -59,7 +59,7 @@ describe('Salix Component vnHome', () => {
expect(controller._modules[0].starred).toBe(true);
});
it(`should set the received module as regular if it was starred`, () => {
it('should set the received module as regular if it was starred', () => {
const event = new Event('target');
controller._modules = [{module: 'client', name: 'Clients', starred: true}];
@ -72,4 +72,34 @@ describe('Salix Component vnHome', () => {
expect(controller._modules[0].starred).toBe(false);
});
});
describe('moveModule()', () => {
it('should perform a query to setPosition and the apply the position to the moved and pushed modules', () => {
const starredModules = [
{id: 1, moduleFk: 'Clients', workerFk: 9},
{id: 2, moduleFk: 'Orders', workerFk: 9}
];
const movedModules = {
movingModule: {position: 2, moduleFk: 'Clients'},
pushedModule: {position: 1, moduleFk: 'Orders'}
};
const event = new Event('target');
controller._modules = [
{module: 'client', name: 'Clients', position: 1},
{module: 'orders', name: 'Orders', position: 2}
];
$httpBackend.whenRoute('GET', 'starredModules/getStarredModules').respond(starredModules);
$httpBackend.expectPOST('starredModules/setPosition').respond(movedModules);
expect(controller._modules[0].position).toEqual(1);
expect(controller._modules[1].position).toEqual(2);
controller.moveModule(controller._modules[0], event, 'right');
$httpBackend.flush();
expect(controller._modules[0].position).toEqual(2);
expect(controller._modules[1].position).toEqual(1);
});
});
});

View File

@ -1,4 +1,6 @@
Favorites: Favoritos
You can set modules as favorites by clicking their icon: Puedes establecer módulos como favoritos haciendo clic en el icono
Add to favorites: Añadir a favoritos.
Remove from favorites: Quitar de favoritos.
Add to favorites: Añadir a favoritos
Remove from favorites: Quitar de favoritos
Move left: Mover a la izquierda
Move right: Mover a la derecha

View File

@ -52,6 +52,11 @@ vn-home {
max-width: 704px;
margin: 0 auto;
& > a:first-child span vn-icon[icon="arrow_left"],
& > a:last-child span vn-icon[icon="arrow_right"] {
visibility: hidden;
}
& > a {
@extend %clickable-light;
display: flex;
@ -66,19 +71,18 @@ vn-home {
background-color: $color-button;
color: $color-font-dark;
& .pin {
& .small-icon {
display: inline-flex;
opacity: 0;
flex-direction: row;
justify-content: left;
height: 20px;
width: 20px;
vn-icon {
margin: auto;
font-size: 1.5rem;
}
}
&:hover .pin {
&:hover .small-icon {
flex-direction: row;
opacity: 1;
}
& > div {

View File

@ -97,5 +97,6 @@
"Role name must be written in camelCase": "Role name must be written in camelCase",
"Client assignment has changed": "I did change the salesperson ~*\"<{{previousWorkerName}}>\"*~ by *\"<{{currentWorkerName}}>\"* from the client [{{clientName}} ({{clientId}})]({{{url}}})",
"None": "None",
"error densidad = 0": "error densidad = 0"
"error densidad = 0": "error densidad = 0",
"nickname": "nickname"
}

View File

@ -180,5 +180,6 @@
"This genus already exist": "Este genus ya existe",
"This specie already exist": "Esta especie ya existe",
"Client assignment has changed": "He cambiado el comercial ~*\"<{{previousWorkerName}}>\"*~ por *\"<{{currentWorkerName}}>\"* del cliente [{{clientName}} ({{clientId}})]({{{url}}})",
"None": "Ninguno"
"None": "Ninguno",
"The contract was not active during the selected date": "El contrato no estaba activo durante la fecha seleccionada"
}

View File

@ -93,7 +93,7 @@ module.exports = function(Self) {
clientOriginal.accountingAccount
],
options);
} else {
} else if (accountingType.isAutoConciliated == true) {
const description = `${clientOriginal.id} : ${clientOriginal.socialName} - ${accountingType.receiptDescription}`;
const [xdiarioNew] = await Self.rawSql(
`SELECT xdiario_new(?, CURDATE(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ledger;`,

View File

@ -16,9 +16,6 @@
"type": "number",
"required": true
},
"amountUnpaid": {
"type": "number"
},
"payed": {
"type": "date",
"required": true

View File

@ -69,6 +69,19 @@
label="Channel">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
ng-model="$ctrl.client.transferorFk"
url="Clients/isActive"
search-function="$ctrl.transferorSearchFunction($search)"
where="{id: {neq: $ctrl.client.id}}"
show-field="name"
value-field="id"
label="Previous client"
info="In case of a company succession, specify the grantor company"
rule>
</vn-autocomplete>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit

View File

@ -1,9 +1,17 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
transferorSearchFunction($search) {
return /^\d+$/.test($search)
? {id: $search}
: {name: {like: '%' + $search + '%'}};
}
}
ngModule.vnComponent('vnClientBasicData', {
template: require('./index.html'),
controller: Section,
controller: Controller,
bindings: {
client: '<'
}

View File

@ -51,6 +51,8 @@
</vn-data-viewer>
<vn-float-button
ng-if="$ctrl.canCreateNew()"
vn-acl="insurance"
vn-acl-action="remove"
vn-tooltip="New contract"
fixed-bottom-right
ui-sref="client.card.creditInsurance.create"

View File

@ -30,6 +30,8 @@
</div>
<a
ng-if="!$ctrl.isClosed"
vn-acl="insurance"
vn-acl-action="remove"
ui-sref="client.card.creditInsurance.insurance.create({classificationId: {{$ctrl.$params.classificationId}}})"
fixed-bottom-right
vn-tooltip="New credit"

View File

@ -73,17 +73,6 @@
rule>
<tpl-item>{{id}}: {{transaction}}</tpl-item>
</vn-autocomplete>
<vn-autocomplete vn-one
ng-model="$ctrl.client.transferorFk"
url="Clients/isActive"
search-function="$ctrl.transferorSearchFunction($search)"
where="{id: {neq: $ctrl.client.id}}"
show-field="name"
value-field="id"
label="Previous client"
info="In case of a company succession, specify the grantor company"
rule>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-datalist vn-one

View File

@ -173,12 +173,6 @@ export default class Controller extends Section {
this.client.provinceFk = response.provinceFk;
this.client.countryFk = response.countryFk;
}
transferorSearchFunction($search) {
return /^\d+$/.test($search)
? {id: $search}
: {name: {like: '%' + $search + '%'}};
}
}
ngModule.vnComponent('vnClientFiscalData', {

View File

@ -240,14 +240,12 @@
"url": "/credit-insurance",
"abstract": true,
"state": "client.card.creditInsurance",
"component": "ui-view",
"acl": ["insurance"]
"component": "ui-view"
}, {
"url": "/index",
"state": "client.card.creditInsurance.index",
"component": "vn-client-credit-insurance-index",
"description": "Credit contracts",
"acl": ["insurance"],
"params": {
"client": "$ctrl.client"
}
@ -256,6 +254,7 @@
"state": "client.card.creditInsurance.create",
"component": "vn-client-credit-insurance-create",
"description": "New insurance",
"acl": ["insurance"],
"params": {
"client": "$ctrl.client"
}
@ -279,6 +278,7 @@
"state": "client.card.creditInsurance.insurance.create",
"component": "vn-client-credit-insurance-insurance-create",
"description": "New credit",
"acl": ["insurance"],
"params": {
"client": "$ctrl.client"
}

View File

@ -1,3 +1,13 @@
<vn-crud-model
vn-id="ticketsModel"
auto-load="true"
url="Tickets"
link="{clientFk: $ctrl.$params.id}"
filter="::$ctrl.ticketFilter"
limit="5"
data="tickets"
order="shipped DESC">
</vn-crud-model>
<vn-card class="summary">
<h5>
<a ng-if="::$ctrl.summary.id"
@ -269,14 +279,107 @@
ng-class="{alert: $ctrl.summary.defaulters[0].amount}"
info="Deviated invoices minus payments">
</vn-label-value>
<vn-vertical ng-if="$ctrl.summary.recovery.started">
<vn-label-value label="Recovery since"
value="{{$ctrl.summary.recovery.started | date:'dd/MM/yyyy'}}">
<vn-label-value label="Recovery since"
ng-if="$ctrl.summary.recovery.started"
value="{{$ctrl.summary.recovery.started | date:'dd/MM/yyyy'}}">
</vn-label-value>
</vn-vertical>
</vn-one>
</vn-horizontal>
<vn-horizontal>
<vn-one>
<h4 translate>Latest tickets</h4>
<vn-table model="ticketsModel" class="scrollable sm">
<vn-thead>
<vn-tr>
<vn-th field="id" number>Id</vn-th>
<vn-th field="nickname" expand>Client</vn-th>
<vn-th field="packages" shrink>Packages</vn-th>
<vn-th field="shipped" shrink-date>Date</vn-th>
<vn-th>State</vn-th>
<vn-th shrink>Total</vn-th>
<vn-th></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="ticket in ticketsModel.data"
class="clickable vn-tr search-result"
ui-sref="ticket.card.summary({id: {{::ticket.id}}})">
<vn-td number>{{::ticket.id}}</vn-td>
<vn-td expand>
<span
title="{{::ticket.nickname}}"
vn-click-stop="clientDescriptor.show($event, ticket.clientFk)"
class="link">
{{::ticket.nickname}}
</span>
</vn-td>
<vn-td shrink>
{{::ticket.packages}}
</vn-td>
<vn-td shrink-date>
<span class="chip {{::$ctrl.chipColor(ticket.shipped)}}">
{{::ticket.shipped | date: 'dd/MM/yyyy'}}
</span>
</vn-td>
<vn-td>
<span
ng-show="::ticket.refFk"
title="{{::ticket.refFk}}"
vn-click-stop="invoiceOutDescriptor.show($event, ticket.invoiceOutId)"
class="link">
{{::ticket.refFk}}
</span>
<span
ng-show="::!ticket.refFk"
class="chip {{::$ctrl.stateColor(ticket)}}">
{{::ticket.ticketState.state.name}}
</span>
</vn-td>
<vn-td shrink>
<span class="chip {{$ctrl.totalPriceColor(ticket)}}">
{{::(ticket.totalWithVat ? ticket.totalWithVat : 0) | currency: 'EUR': 2}}
</span>
</vn-td>
<vn-td actions>
<vn-icon-button
vn-anchor="::{
state: 'ticket.card.sale',
params: {id: ticket.id},
target: '_blank'
}"
vn-tooltip="Go to lines"
icon="icon-lines">
</vn-icon-button>
<vn-icon-button
vn-click-stop="$ctrl.preview(ticket)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</vn-td>
</a>
</vn-tbody>
</vn-table>
<vn-pagination
model="ticketsModel"
class="vn-pt-xs"
scroll-selector="vn-table[model='ticketsModel']"
scroll-offset="100">
</vn-pagination>
</vn-one>
</vn-horizontal>
</vn-card>
<vn-client-descriptor-popover
vn-id="clientDescriptor">
</vn-client-descriptor-popover>
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
</vn-worker-descriptor-popover>
<vn-invoice-out-descriptor-popover
vn-id="invoiceOutDescriptor">
</vn-invoice-out-descriptor-popover>
<vn-popup vn-id="summary">
<vn-ticket-summary
ticket="$ctrl.selectedTicket"
model="model">
</vn-ticket-summary>
</vn-popup>

View File

@ -3,6 +3,21 @@ import Summary from 'salix/components/summary';
import './style.scss';
class Controller extends Summary {
constructor($element, $) {
super($element, $);
this.ticketFilter = {
include: {
relation: 'ticketState',
scope: {
fields: ['stateFk', 'code', 'alertLevel'],
include: {
relation: 'state'
}
}
}
};
}
$onChanges() {
if (!this.client)
return;
@ -18,6 +33,7 @@ class Controller extends Summary {
}
});
}
get isEmployee() {
return this.aclService.hasAny(['employee']);
}
@ -39,6 +55,45 @@ class Controller extends Summary {
if (rate)
return rate * 100;
}
stateColor(ticket) {
const ticketState = ticket.ticketState;
if (ticketState.code === 'OK')
return 'success';
else if (ticketState.code === 'FREE')
return 'notice';
else if (ticketState.alertLevel === 1)
return 'warning';
else if (ticketState.alertLevel === 0)
return 'alert';
}
chipColor(date) {
const today = new Date();
today.setHours(0, 0, 0, 0);
const ticketShipped = new Date(date);
ticketShipped.setHours(0, 0, 0, 0);
const difference = today - ticketShipped;
if (difference == 0)
return 'warning';
if (difference < 0)
return 'success';
}
totalPriceColor(ticket) {
const total = parseInt(ticket.totalWithVat);
if (total > 0 && total < 50)
return 'warning';
}
preview(ticket) {
this.selectedTicket = ticket;
this.$.summary.show();
}
}
ngModule.vnComponent('vnClientSummary', {

View File

@ -4,15 +4,15 @@ describe('Client', () => {
describe('Component vnClientSummary', () => {
let controller;
let $httpBackend;
let $scope;
let $window;
beforeEach(ngModule('client'));
beforeEach(inject(($componentController, _$httpBackend_, $rootScope) => {
beforeEach(inject(($componentController, _$httpBackend_, _$window_) => {
$window = _$window_;
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
const $element = angular.element('<vn-client-summary></vn-client-summary>');
controller = $componentController('vnClientSummary', {$element, $scope});
controller = $componentController('vnClientSummary', {$element});
controller.client = {id: 101};
}));
@ -48,5 +48,82 @@ describe('Client', () => {
expect(result).toEqual(300);
});
});
describe('chipColor()', () => {
it('should return warning when the date is the present', () => {
let today = new Date();
let result = controller.chipColor(today);
expect(result).toEqual('warning');
});
it('should return success when the date is in the future', () => {
let futureDate = new Date();
futureDate = futureDate.setDate(futureDate.getDate() + 10);
let result = controller.chipColor(futureDate);
expect(result).toEqual('success');
});
it('should return undefined when the date is in the past', () => {
let pastDate = new Date();
pastDate = pastDate.setDate(pastDate.getDate() - 10);
let result = controller.chipColor(pastDate);
expect(result).toEqual(undefined);
});
});
describe('stateColor()', () => {
it('should return "success" when the alertLevelCode property is "OK"', () => {
const result = controller.stateColor({ticketState: {code: 'OK'}});
expect(result).toEqual('success');
});
it('should return "notice" when the alertLevelCode property is "FREE"', () => {
const result = controller.stateColor({ticketState: {code: 'FREE'}});
expect(result).toEqual('notice');
});
it('should return "warning" when the alertLevel property is "1', () => {
const result = controller.stateColor({ticketState: {code: 'PACKING', alertLevel: 1}});
expect(result).toEqual('warning');
});
it('should return "alert" when the alertLevel property is "0"', () => {
const result = controller.stateColor({ticketState: {code: 'FIXING', alertLevel: 0}});
expect(result).toEqual('alert');
});
});
describe('totalPriceColor()', () => {
it('should return "warning" when the ticket amount is less than 50€', () => {
const result = controller.totalPriceColor({totalWithVat: '8.50'});
expect(result).toEqual('warning');
});
});
describe('preview()', () => {
it('should show the dialog summary', () => {
controller.$.summary = {show: () => {}};
jest.spyOn(controller.$.summary, 'show');
const ticket = {id: 1, clientFk: 101};
const event = new MouseEvent('click', {
view: $window,
bubbles: true,
cancelable: true
});
controller.preview(event, ticket);
expect(controller.$.summary.show).toHaveBeenCalledWith();
});
});
});
});

View File

@ -19,3 +19,4 @@ Solunion's maximum risk: Riesgo máximo asumido por Solunion
Invoices minus payments: Facturas menos recibos
Deviated invoices minus payments: Facturas fuera de plazo menos recibos
Go to the client: Ir al cliente
Latest tickets: Últimos tickets

View File

@ -1,6 +1,8 @@
@import "variables";
vn-client-summary {
vn-client-summary .summary {
max-width: $width-lg;
.alert span {
color: $color-alert !important
}

View File

@ -25,7 +25,7 @@
<vn-tr ng-repeat="transaction in transactions">
<vn-td shrink>
<vn-icon
vn-tooltip="{{$ctrl.getFormattedMessage(transaction)}}"
vn-tooltip="{{::$ctrl.getFormattedMessage(transaction)}}"
ng-show="::((transaction.errorMessage || transaction.responseMessage) && !transaction.isConfirmed)"
icon="clear">
</vn-icon>

View File

@ -89,7 +89,7 @@ module.exports = Self => {
const newBuy = await models.Buy.create(ctx.args, myOptions);
let filter = {
const filter = {
fields: [
'id',
'itemFk',
@ -136,7 +136,7 @@ module.exports = Self => {
}
};
let stmts = [];
const stmts = [];
let stmt;
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.buyRecalc');

View File

@ -32,7 +32,7 @@ module.exports = Self => {
}
try {
let promises = [];
const promises = [];
for (let buy of ctx.args.buys) {
const buysToDelete = models.Buy.destroyById(buy.id, myOptions);
promises.push(buysToDelete);

View File

@ -30,28 +30,33 @@ module.exports = Self => {
}
});
Self.editLatestBuys = async(field, newValue, lines) => {
Self.editLatestBuys = async(field, newValue, lines, options) => {
let tx;
let myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
let modelName;
let identifier;
switch (field) {
case 'size':
case 'density':
case 'minPrice':
case 'description':
case 'packingOut':
modelName = 'Item';
identifier = 'itemFk';
break;
case 'quantity':
case 'buyingValue':
case 'freightValue':
case 'packing':
case 'grouping':
case 'groupingMode':
case 'comissionValue':
case 'packageValue':
case 'price2':
case 'price3':
case 'weight':
modelName = 'Buy';
identifier = 'id';
@ -60,28 +65,27 @@ module.exports = Self => {
const models = Self.app.models;
const model = models[modelName];
let tx = await model.beginTransaction({});
try {
let promises = [];
let options = {transaction: tx};
let targets = lines.map(line => {
const targets = lines.map(line => {
return line[identifier];
});
let value = {};
const value = {};
value[field] = newValue;
// intentarlo con updateAll
for (let target of targets)
promises.push(model.upsertWithWhere({id: target}, value, options));
promises.push(model.upsertWithWhere({id: target}, value, myOptions));
await Promise.all(promises);
await tx.commit();
} catch (error) {
await tx.rollback();
throw error;
const result = await Promise.all(promises, myOptions);
if (tx) await tx.commit();
return result;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -13,71 +13,85 @@ module.exports = Self => {
type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'}
}, {
},
{
arg: 'search',
type: 'string',
description: 'Searchs the entry by id',
http: {source: 'query'}
}, {
},
{
arg: 'id',
type: 'integer',
description: 'The entry id',
http: {source: 'query'}
}, {
},
{
arg: 'created',
type: 'date',
description: 'The created date to filter',
http: {source: 'query'}
}, {
},
{
arg: 'travelFk',
type: 'number',
description: 'The travel id to filter',
http: {source: 'query'}
}, {
},
{
arg: 'companyFk',
type: 'number',
description: 'The company to filter',
http: {source: 'query'}
}, {
},
{
arg: 'isBooked',
type: 'boolean',
description: 'The isBokked filter',
http: {source: 'query'}
}, {
},
{
arg: 'isConfirmed',
type: 'boolean',
description: 'The isConfirmed filter',
http: {source: 'query'}
}, {
},
{
arg: 'isOrdered',
type: 'boolean',
description: 'The isOrdered filter',
http: {source: 'query'}
}, {
},
{
arg: 'ref',
type: 'string',
description: 'The ref filter',
http: {source: 'query'}
}, {
},
{
arg: 'supplierFk',
type: 'number',
description: 'The supplier id to filter',
http: {source: 'query'}
}, {
},
{
arg: 'invoiceInFk',
type: 'number',
description: 'The invoiceIn id to filter',
http: {source: 'query'}
}, {
},
{
arg: 'currencyFk',
type: 'number',
description: 'The currency id to filter',
http: {source: 'query'}
}, {
},
{
arg: 'from',
type: 'date',
description: `The from date filter`
}, {
},
{
arg: 'to',
type: 'date',
description: `The to date filter`
@ -93,9 +107,14 @@ module.exports = Self => {
}
});
Self.filter = async(ctx, filter) => {
let conn = Self.dataSource.connector;
let where = buildFilter(ctx.args, (param, value) => {
Self.filter = async(ctx, filter, options) => {
let myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const conn = Self.dataSource.connector;
const where = buildFilter(ctx.args, (param, value) => {
switch (param) {
case 'search':
return /^\d+$/.test(value)
@ -128,7 +147,7 @@ module.exports = Self => {
});
filter = mergeFilters(ctx.args.filter, {where});
let stmts = [];
const stmts = [];
let stmt;
stmt = new ParameterizedSQL(
`SELECT
@ -163,10 +182,10 @@ module.exports = Self => {
);
stmt.merge(conn.makeSuffix(filter));
let itemsIndex = stmts.push(stmt) - 1;
const itemsIndex = stmts.push(stmt) - 1;
let sql = ParameterizedSQL.join(stmts, ';');
let result = await conn.executeStmt(sql);
const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql, myOptions);
return itemsIndex === 0 ? result : result[itemsIndex];
};
};

View File

@ -1,14 +1,22 @@
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethod('getBuys', {
description: 'Returns buys for one entry',
accessType: 'READ',
accepts: {
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The entry id',
http: {source: 'path'}
},
{
arg: 'filter',
type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string'
}
],
returns: {
type: ['Object'],
root: true
@ -19,8 +27,14 @@ module.exports = Self => {
}
});
Self.getBuys = async id => {
let filter = {
Self.getBuys = async(id, filter, options) => {
const models = Self.app.models;
let myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
let defaultFilter = {
where: {entryFk: id},
fields: [
'id',
@ -68,7 +82,8 @@ module.exports = Self => {
}
};
let buys = await Self.app.models.Buy.find(filter);
return buys;
defaultFilter = mergeFilters(defaultFilter, filter);
return models.Buy.find(defaultFilter, myOptions);
};
};

View File

@ -19,8 +19,14 @@ module.exports = Self => {
}
});
Self.getEntry = async id => {
let filter = {
Self.getEntry = async(id, options) => {
const models = Self.app.models;
let myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const filter = {
where: {id: id},
include: [
{
@ -70,7 +76,6 @@ module.exports = Self => {
],
};
let entry = await Self.app.models.Entry.findOne(filter);
return entry;
return models.Entry.findOne(filter, myOptions);
};
};

View File

@ -45,8 +45,8 @@ module.exports = Self => {
const conn = Self.dataSource.connector;
const args = ctx.args;
const models = Self.app.models;
let tx;
if (!options.transaction) {
tx = await Self.beginTransaction({});
options.transaction = tx;
@ -76,7 +76,7 @@ module.exports = Self => {
const createdBuys = await models.Buy.create(buys, options);
const buyIds = createdBuys.map(buy => buy.id);
let stmts = [];
const stmts = [];
let stmt;
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.buyRecalc');

View File

@ -24,14 +24,19 @@ module.exports = Self => {
}
});
Self.importBuysPreview = async(id, buys) => {
Self.importBuysPreview = async(id, buys, options) => {
const models = Self.app.models;
let myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
for (let buy of buys) {
const packaging = await models.Packaging.findOne({
fields: ['id'],
where: {volume: {gte: buy.volume}},
order: 'volume ASC'
});
}, myOptions);
buy.packageFk = packaging.id;
}

View File

@ -10,46 +10,59 @@ module.exports = Self => {
accepts: [
{
arg: 'filter',
type: 'Object',
type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
},
{
arg: 'search',
type: 'String',
type: 'string',
description: `If it's and integer searchs by id, otherwise it searchs by name`,
}, {
},
{
arg: 'id',
type: 'Integer',
type: 'integer',
description: 'Item id',
}, {
},
{
arg: 'tags',
type: ['Object'],
type: ['object'],
description: 'List of tags to filter with',
http: {source: 'query'}
}, {
},
{
arg: 'description',
type: 'String',
type: 'string',
description: 'The item description',
}, {
},
{
arg: 'salesPersonFk',
type: 'Integer',
type: 'integer',
description: 'The buyer of the item',
}, {
},
{
arg: 'active',
type: 'Boolean',
type: 'boolean',
description: 'Whether the item is or not active',
}, {
},
{
arg: 'visible',
type: 'Boolean',
type: 'boolean',
description: 'Whether the item is or not visible',
}, {
},
{
arg: 'typeFk',
type: 'Integer',
type: 'integer',
description: 'Type id',
}, {
},
{
arg: 'categoryFk',
type: 'Integer',
type: 'integer',
description: 'Category id',
},
{
arg: 'packingOut',
type: 'integer',
description: 'the packingOut',
}
],
returns: {
@ -62,16 +75,24 @@ module.exports = Self => {
}
});
Self.latestBuysFilter = async(ctx, filter) => {
let conn = Self.dataSource.connector;
let where = buildFilter(ctx.args, (param, value) => {
Self.latestBuysFilter = async(ctx, filter, options) => {
let myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const conn = Self.dataSource.connector;
const where = buildFilter(ctx.args, (param, value) => {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {'i.id': value}
: {'i.name': {like: `%${value}%`}};
case 'id':
return {'i.id': value};
case 'packingOut':
case 'typeFk':
param = `i.${param}`;
return {[param]: value};
case 'description':
return {'i.description': {like: `%${value}%`}};
case 'categoryFk':
@ -80,8 +101,6 @@ module.exports = Self => {
return {'it.workerFk': value};
case 'code':
return {'it.code': value};
case 'typeFk':
return {'i.typeFk': value};
case 'active':
return {'i.isActive': value};
case 'visible':
@ -93,7 +112,7 @@ module.exports = Self => {
});
filter = mergeFilters(ctx.args.filter, {where});
let stmts = [];
const stmts = [];
let stmt;
stmts.push('CALL cache.last_buy_refresh(FALSE)');
@ -113,6 +132,7 @@ module.exports = Self => {
i.description,
i.name,
i.subName,
i.packingOut,
i.tag5,
i.value5,
i.tag6,
@ -179,10 +199,10 @@ module.exports = Self => {
}
stmt.merge(conn.makeSuffix(filter));
let buysIndex = stmts.push(stmt) - 1;
const buysIndex = stmts.push(stmt) - 1;
let sql = ParameterizedSQL.join(stmts, ';');
let result = await conn.executeStmt(sql);
const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql, myOptions);
return buysIndex === 0 ? result : result[buysIndex];
};
};

View File

@ -3,29 +3,32 @@ const model = app.models.Buy;
describe('Buy editLatestsBuys()', () => {
it('should change the value of a given column for the selected buys', async() => {
let ctx = {
args: {
search: 'Ranged weapon longbow 2m'
}
};
const tx = await app.models.Entry.beginTransaction({});
const options = {transaction: tx};
let [original] = await model.latestBuysFilter(ctx);
try {
let ctx = {
args: {
search: 'Ranged weapon longbow 2m'
}
};
const field = 'size';
let newValue = 99;
const lines = [{itemFk: original.itemFk, id: original.id}];
const [original] = await model.latestBuysFilter(ctx, null, options);
await model.editLatestBuys(field, newValue, lines);
const field = 'size';
const newValue = 99;
const lines = [{itemFk: original.itemFk, id: original.id}];
let [result] = await model.latestBuysFilter(ctx);
await model.editLatestBuys(field, newValue, lines, options);
expect(result.size).toEqual(99);
const [result] = await model.latestBuysFilter(ctx, null, options);
newValue = original.size;
await model.editLatestBuys(field, newValue, lines);
expect(result[field]).toEqual(newValue);
let [restoredFixture] = await model.latestBuysFilter(ctx);
expect(restoredFixture.size).toEqual(original.size);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -6,7 +6,7 @@
},
"options": {
"mysql": {
"table": "entry"
"table": "entry"
}
},
"properties": {

View File

@ -10,182 +10,185 @@
</vn-watcher>
<div class="vn-w-xl">
<vn-card class="vn-pa-lg">
<vn-horizontal class="header">
<vn-tool-bar class="vn-mb-md">
<vn-button
disabled="$ctrl.selectedBuys() == 0"
ng-click="deleteBuys.show()"
vn-tooltip="Delete buy(s)"
icon="delete">
</vn-button>
</vn-tool-bar>
<vn-one class="taxes" ng-if="$ctrl.sales.length > 0">
<p><vn-label translate>Subtotal</vn-label> {{$ctrl.ticket.totalWithoutVat | currency: 'EUR':2}}</p>
<p><vn-label translate>VAT</vn-label> {{$ctrl.ticket.totalWithVat - $ctrl.ticket.totalWithoutVat | currency: 'EUR':2}}</p>
<p><vn-label><strong>Total</strong></vn-label> <strong>{{$ctrl.ticket.totalWithVat | currency: 'EUR':2}}</strong></p>
</vn-one>
</vn-horizontal>
<table class="vn-table">
<thead>
<tr>
<th shrink>
<vn-multi-check model="model" on-change="$ctrl.resetChanges()">
</vn-multi-check>
</th>
<th translate center>Item</th>
<th translate center>Quantity</th>
<th translate center>Package</th>
<th translate>Stickers</th>
<th translate>Weight</th>
<th translate>Packing</th>
<th translate>Grouping</th>
<th translate>Buying value</th>
<th translate expand>Grouping price</th>
<th translate expand>Packing price</th>
<th translate>Import</th>
</tr>
</thead>
<tbody ng-repeat="buy in $ctrl.buys">
<tr>
<td shrink>
<vn-check tabindex="-1" ng-model="buy.checked">
</vn-check>
</td>
<td shrink>
<span
ng-if="buy.id"
ng-click="itemDescriptor.show($event, buy.item.id)"
class="link">
{{::buy.item.id | zeroFill:6}}
</span>
<vn-autocomplete ng-if="!buy.id" class="dense"
vn-focus
url="Items"
ng-model="buy.itemFk"
show-field="name"
value-field="id"
search-function="$ctrl.itemSearchFunc($search)"
on-change="$ctrl.saveBuy(buy)"
order="id DESC"
tabindex="1">
<tpl-item>
{{::id}} - {{::name}}
</tpl-item>
</vn-autocomplete>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.quantity | dashIfEmpty}}"
ng-model="buy.quantity"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td center>
<vn-autocomplete
vn-one
title="{{::buy.packageFk | dashIfEmpty}}"
url="Packagings"
show-field="id"
value-field="id"
where="{isBox: true}"
ng-model="buy.packageFk"
on-change="$ctrl.saveBuy(buy)">
</vn-autocomplete>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.stickers | dashIfEmpty}}"
ng-model="buy.stickers"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.weight | dashIfEmpty}}"
ng-model="buy.weight"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.packing | dashIfEmpty}}"
ng-model="buy.packing"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.grouping | dashIfEmpty}}"
ng-model="buy.grouping"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.buyingValue | dashIfEmpty}}"
ng-model="buy.buyingValue"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.price2 | dashIfEmpty}}"
ng-model="buy.price2"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.price3 | dashIfEmpty}}"
ng-model="buy.price3"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<span
ng-if="buy.quantity != null && buy.buyingValue != null"
title="{{buy.quantity * buy.buyingValue | currency: 'EUR':2}}">
{{buy.quantity * buy.buyingValue | currency: 'EUR':2}}
</span>
</td>
</tr>
<tr class="dark-row">
<td shrink>
</td>
<td shrink>
<span translate-attr="{title: 'Item type'}">
{{::buy.item.itemType.code}}
</span>
</td>
<td number shrink>
<span translate-attr="{title: 'Item size'}">
{{::buy.item.size}}
</span>
</td>
<td center>
<span translate-attr="{title: 'Minimum price'}">
{{::buy.item.minPrice | currency: 'EUR':2}}
</span>
</td>
<td vn-fetched-tags colspan="9">
<vn-one title="{{::buy.item.name}}">{{::buy.item.name}}</vn-one>
<vn-fetched-tags
max-length="6"
item="::buy.item"
tabindex="-1">
</vn-fetched-tags>
</td>
</tr>
</tbody>
</table>
<div>
<vn-icon-button
vn-one
vn-tooltip="Add buy"
vn-bind="+"
icon="add_circle"
ng-click="model.insert({})">
</vn-icon-button>
<div class="tableWrapper">
<vn-horizontal class="header">
<vn-tool-bar class="vn-mb-md">
<vn-button
disabled="$ctrl.selectedBuys() == 0"
ng-click="deleteBuys.show()"
vn-tooltip="Delete buy(s)"
icon="delete">
</vn-button>
</vn-tool-bar>
<vn-one class="taxes" ng-if="$ctrl.sales.length > 0">
<p><vn-label translate>Subtotal</vn-label> {{$ctrl.ticket.totalWithoutVat | currency: 'EUR':2}}</p>
<p><vn-label translate>VAT</vn-label> {{$ctrl.ticket.totalWithVat - $ctrl.ticket.totalWithoutVat | currency: 'EUR':2}}</p>
<p><vn-label><strong>Total</strong></vn-label> <strong>{{$ctrl.ticket.totalWithVat | currency: 'EUR':2}}</strong></p>
</vn-one>
</vn-horizontal>
<table class="vn-table">
<thead>
<tr>
<th shrink>
<vn-multi-check model="model" on-change="$ctrl.resetChanges()">
</vn-multi-check>
</th>
<th translate center>Item</th>
<th translate center>Quantity</th>
<th translate center>Package</th>
<th translate>Stickers</th>
<th translate>Weight</th>
<th translate>Packing</th>
<th translate>Grouping</th>
<th translate>Buying value</th>
<th translate expand>Grouping price</th>
<th translate expand>Packing price</th>
<th translate>Import</th>
</tr>
</thead>
<tbody ng-repeat="buy in $ctrl.buys">
<tr>
<td shrink>
<vn-check tabindex="-1" ng-model="buy.checked">
</vn-check>
</td>
<td shrink>
<span
ng-if="buy.id"
ng-click="itemDescriptor.show($event, buy.item.id)"
class="link">
{{::buy.item.id | zeroFill:6}}
</span>
<vn-autocomplete ng-if="!buy.id" class="dense"
vn-focus
url="Items"
ng-model="buy.itemFk"
show-field="name"
value-field="id"
search-function="$ctrl.itemSearchFunc($search)"
on-change="$ctrl.saveBuy(buy)"
order="id DESC"
tabindex="1">
<tpl-item>
{{::id}} - {{::name}}
</tpl-item>
</vn-autocomplete>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.quantity | dashIfEmpty}}"
ng-model="buy.quantity"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td center>
<vn-autocomplete
vn-one
title="{{::buy.packageFk | dashIfEmpty}}"
url="Packagings"
show-field="id"
value-field="id"
where="{isBox: true}"
ng-model="buy.packageFk"
on-change="$ctrl.saveBuy(buy)">
</vn-autocomplete>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.stickers | dashIfEmpty}}"
ng-model="buy.stickers"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.weight | dashIfEmpty}}"
ng-model="buy.weight"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.packing | dashIfEmpty}}"
ng-model="buy.packing"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.grouping | dashIfEmpty}}"
ng-model="buy.grouping"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.buyingValue | dashIfEmpty}}"
ng-model="buy.buyingValue"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.price2 | dashIfEmpty}}"
ng-model="buy.price2"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.price3 | dashIfEmpty}}"
ng-model="buy.price3"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<span
ng-if="buy.quantity != null && buy.buyingValue != null"
title="{{buy.quantity * buy.buyingValue | currency: 'EUR':2}}">
{{buy.quantity * buy.buyingValue | currency: 'EUR':2}}
</span>
</td>
</tr>
<tr class="dark-row">
<td shrink>
</td>
<td shrink>
<span translate-attr="{title: 'Item type'}">
{{::buy.item.itemType.code}}
</span>
</td>
<td number shrink>
<span translate-attr="{title: 'Item size'}">
{{::buy.item.size}}
</span>
</td>
<td center>
<span translate-attr="{title: 'Minimum price'}">
{{::buy.item.minPrice | currency: 'EUR':2}}
</span>
</td>
<td vn-fetched-tags colspan="8">
<vn-one title="{{::buy.item.name}}">{{::buy.item.name}}</vn-one>
<vn-fetched-tags
max-length="6"
item="::buy.item"
tabindex="-1">
</vn-fetched-tags>
</td>
</tr>
<tr><td></td></tr>
</tbody>
</table>
<div>
<vn-icon-button
vn-one
vn-tooltip="Add buy"
vn-bind="+"
icon="add_circle"
ng-click="model.insert({})">
</vn-icon-button>
</div>
</div>
</vn-card>
</div>

View File

@ -3,19 +3,34 @@
vn-entry-buy-index vn-card {
max-width: $width-xl;
.dark-row {
background-color: lighten($color-marginal, 10%);
}
tbody tr:nth-child(1) {
border-top: 1px solid $color-marginal;
thead tr {
border-left: 1px solid white;
border-right: 1px solid white;
}
tbody tr:nth-child(1),
tbody tr:nth-child(2) {
border-left: 1px solid $color-marginal;
border-right: 1px solid $color-marginal;
}
tbody tr:nth-child(2) {
border-bottom: 1px solid $color-marginal;
}
tbody{
border-bottom: 1px solid $color-marginal;
}
tbody:last-child {
border-bottom: 0;
}
tbody tr:nth-child(3) {
height: inherit
}

View File

@ -10,7 +10,7 @@ Commission: Comisión
Landed: F. entrega
Reference: Referencia
Created: Creado
Booked: Facturado
Booked: Contabilizada
Is inventory: Inventario
Notes: Notas
Status: Estado

View File

@ -57,6 +57,7 @@
<vn-th field="ektFk">Ekt</vn-th>
<vn-th field="weight">Weight</vn-th>
<vn-th field="packageFk" expand>PackageName</vn-th>
<vn-th field="packingOut" expand>PackingOut</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
@ -147,6 +148,7 @@
<vn-td number>{{::buy.ektFk | dashIfEmpty}}</vn-td>
<vn-td number>{{::buy.weight}}</vn-td>
<vn-td number>{{::buy.packageFk}}</vn-td>
<vn-td number>{{::buy.packingOut}}</vn-td>
</a>
</vn-tbody>
</vn-table>

View File

@ -16,20 +16,14 @@ export default class Controller extends Section {
if (this._columns) return this._columns;
this._columns = [
{field: 'quantity', displayName: this.$t('Quantity')},
{field: 'buyingValue', displayName: this.$t('Buying value')},
{field: 'freightValue', displayName: this.$t('Freight value')},
{field: 'packing', displayName: this.$t('Packing')},
{field: 'grouping', displayName: this.$t('Grouping')},
{field: 'comissionValue', displayName: this.$t('Commission value')},
{field: 'packageValue', displayName: this.$t('Package value')},
{field: 'price2', displayName: this.$t('Grouping price')},
{field: 'price3', displayName: this.$t('Packing price')},
{field: 'weight', displayName: this.$t('Weight')},
{field: 'description', displayName: this.$t('Description')},
{field: 'minPrice', displayName: this.$t('Min price')},
{field: 'size', displayName: this.$t('Size')},
{field: 'density', displayName: this.$t('Density')}
{field: 'density', displayName: this.$t('Density')},
{field: 'packingOut', displayName: this.$t('PackingOut')}
];
return this._columns;
@ -59,10 +53,14 @@ export default class Controller extends Section {
}
onEditAccept() {
const rowsToEdit = [];
for (let row of this.checked)
rowsToEdit.push({id: row.id, itemFk: row.itemFk});
let data = {
field: this.editedColumn.field,
newValue: this.editedColumn.newValue,
lines: this.checked
lines: rowsToEdit
};
return this.$http.post('Buys/editLatestBuys', data)

View File

@ -13,4 +13,5 @@ Minimun amount: Cantidad mínima de compra
Field to edit: Campo a editar
PackageName: Cubo
Edit: Editar
buy(s): compra(s)
buy(s): compra(s)
PackingOut: Packing envíos

View File

@ -26,30 +26,35 @@
"abstract": true,
"component": "vn-entry",
"description": "Entries"
}, {
},
{
"url": "/index?q",
"state": "entry.index",
"component": "vn-entry-index",
"description": "Entries",
"acl": ["buyer", "administrative"]
}, {
},
{
"url": "/latest-buys?q",
"state": "entry.latestBuys",
"component": "vn-entry-latest-buys",
"description": "Latest buys",
"acl": ["buyer", "administrative"]
}, {
},
{
"url": "/create?supplierFk&travelFk&companyFk",
"state": "entry.create",
"component": "vn-entry-create",
"description": "New entry",
"acl": ["buyer", "administrative"]
}, {
},
{
"url": "/:id",
"state": "entry.card",
"abstract": true,
"component": "vn-entry-card"
}, {
},
{
"url": "/summary",
"state": "entry.card.summary",
"component": "vn-entry-summary",
@ -58,7 +63,8 @@
"entry": "$ctrl.entry"
},
"acl": ["buyer", "administrative"]
}, {
},
{
"url": "/basic-data",
"state": "entry.card.basicData",
"component": "vn-entry-basic-data",
@ -67,7 +73,8 @@
"entry": "$ctrl.entry"
},
"acl": ["buyer", "administrative"]
},{
},
{
"url": "/observation",
"state": "entry.card.observation",
"component": "vn-entry-observation",
@ -76,7 +83,8 @@
"entry": "$ctrl.entry"
},
"acl": ["buyer", "administrative"]
},{
},
{
"url" : "/log",
"state": "entry.card.log",
"component": "vn-entry-log",

View File

@ -1,3 +1,10 @@
<vn-crud-model
vn-id="buysModel"
url="Entries/{{$ctrl.$params.id}}/getBuys"
limit="5"
data="buys"
auto-load="true">
</vn-crud-model>
<vn-card class="summary">
<h5>
<a ng-if="::$ctrl.entryData.id"
@ -95,7 +102,7 @@
<th translate center expand field="price3">Packing price</th>
</tr>
</thead>
<tbody ng-repeat="line in $ctrl.buys">
<tbody ng-repeat="line in buys">
<tr>
<td center title="{{::line.quantity}}">{{::line.quantity}}</td>
<td center title="{{::line.stickers | dashIfEmpty}}">{{::line.stickers | dashIfEmpty}}</td>
@ -156,6 +163,10 @@
</tr>
</tbody>
</table>
<vn-pagination
model="buysModel"
class="vn-pt-xs">
</vn-pagination>
</vn-auto>
</vn-horizontal>
</vn-card>

View File

@ -10,10 +10,8 @@ class Controller extends Summary {
set entry(value) {
this._entry = value;
if (value && value.id) {
if (value && value.id)
this.getEntryData();
this.getBuys();
}
}
getEntryData() {
@ -21,12 +19,6 @@ class Controller extends Summary {
this.entryData = response.data;
});
}
getBuys() {
return this.$http.get(`Entries/${this.entry.id}/getBuys`).then(response => {
this.buys = response.data;
});
}
}
ngModule.vnComponent('vnEntrySummary', {

View File

@ -46,20 +46,4 @@ describe('component vnEntrySummary', () => {
expect(controller.entryData).toEqual('I am the entryData');
});
});
describe('getBuys()', () => {
it('should perform a get asking for the buys of an entry', () => {
controller._entry = {id: 999};
const thatQuery = `Entries/${controller._entry.id}/getEntry`;
const query = `Entries/${controller._entry.id}/getBuys`;
$httpBackend.whenGET(thatQuery).respond('My Entries');
$httpBackend.expectGET(query).respond('Some buys');
controller.getBuys();
$httpBackend.flush();
expect(controller.buys).toEqual('Some buys');
});
});
});

View File

@ -109,8 +109,9 @@ module.exports = Self => {
return {'ii.created': {gte: value}};
case 'to':
return {'ii.created': {lte: value}};
case 'account':
case 'fi':
return {'s.nif': value};
case 'account':
return {[`s.${param}`]: value};
case 'supplierRef':
case 'supplierFk':
@ -139,6 +140,7 @@ module.exports = Self => {
ii.supplierRef,
ii.docFk AS dmsFk,
ii.supplierFk,
ii.expenceFkDeductible deductibleExpenseFk,
s.name AS supplierName,
s.account,
SUM(iid.amount) AS amount,

View File

@ -39,6 +39,18 @@ module.exports = Self => {
scope: {
fields: ['withholding']
}
},
{
relation: 'expenseDeductible',
scope: {
fields: ['id', 'name', 'taxTypeFk']
}
},
{
relation: 'currency',
scope: {
fields: ['id', 'name']
}
}
]
};

View File

@ -4,5 +4,8 @@
},
"InvoiceInDueDay": {
"dataSource": "vn"
},
"InvoiceInLog": {
"dataSource": "vn"
}
}

View File

@ -0,0 +1,61 @@
{
"name": "InvoiceInLog",
"base": "VnModel",
"options": {
"mysql": {
"table": "invoiceInLog"
}
},
"properties": {
"id": {
"id": true,
"type": "number",
"forceId": false
},
"originFk": {
"type": "number",
"required": true
},
"userFk": {
"type": "number"
},
"action": {
"type": "string",
"required": true
},
"changedModel": {
"type": "string"
},
"oldInstance": {
"type": "object"
},
"newInstance": {
"type": "object"
},
"creationDate": {
"type": "date"
},
"changedModelId": {
"type": "string"
},
"changedModelValue": {
"type": "string"
},
"description": {
"type": "string"
}
},
"relations": {
"user": {
"type": "belongsTo",
"model": "Account",
"foreignKey": "userFk"
}
},
"scope": {
"order": [
"creationDate DESC",
"id DESC"
]
}
}

View File

@ -1,6 +1,9 @@
{
"name": "InvoiceIn",
"base": "VnModel",
"base": "Loggable",
"log": {
"model": "InvoiceInLog"
},
"options": {
"mysql": {
"table": "invoiceIn"
@ -30,9 +33,6 @@
"isBooked": {
"type": "boolean"
},
"isVatDeductible": {
"type": "boolean"
},
"booked": {
"type": "date"
},
@ -47,6 +47,12 @@
"mysql": {
"columnName": "docFk"
}
},
"deductibleExpenseFk": {
"type": "number",
"mysql": {
"columnName": "expenceFkDeductible"
}
}
},
"relations": {
@ -60,6 +66,11 @@
"model": "SageWithholding",
"foreignKey": "withholdingSageFk"
},
"expenseDeductible": {
"type": "belongsTo",
"model": "Expense",
"foreignKey": "deductibleExpenseFk"
},
"company": {
"type": "belongsTo",
"model": "Company",

View File

@ -7,21 +7,6 @@
</vn-watcher>
<form name="form" ng-submit="watcher.submit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-date-picker
vn-one
label="Expedition date"
ng-model="$ctrl.invoiceIn.issued"
vn-focus
rule>
</vn-date-picker>
<vn-date-picker
vn-one
label="Operation date"
ng-model="$ctrl.invoiceIn.operated"
rule>
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
@ -44,6 +29,35 @@
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-date-picker
vn-one
label="Expedition date"
ng-model="$ctrl.invoiceIn.issued"
vn-focus
rule>
</vn-date-picker>
<vn-date-picker
vn-one
label="Operation date"
ng-model="$ctrl.invoiceIn.operated"
rule>
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>
<vn-datalist vn-one
label="Undeductible VAT"
ng-model="$ctrl.invoiceIn.deductibleExpenseFk"
value-field="id"
order="name"
url="Expenses"
fields="['id','name']"
rule>
<tpl-item>
{{id}} - {{name}}
</tpl-item>
</vn-datalist>
</vn-horizontal>
<vn-horizontal>
<vn-date-picker
vn-one

View File

@ -9,3 +9,4 @@ import './descriptor-popover';
import './summary';
import './basic-data';
import './create';
import './log';

View File

@ -54,19 +54,22 @@
</vn-icon-button>
</vn-td>
<vn-td shrink>
<vn-icon-button
<!-- <vn-icon-button
ng-show="invoiceIn.dmsFk"
vn-click-stop="$ctrl.openPdf(invoiceIn.dmsFk)"
icon="cloud_download"
title="Download PDF"
vn-tooltip="Download PDF">
</vn-icon-button>
</vn-icon-button> -->
</vn-td>
</a>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-supplier-descriptor-popover
vn-id="supplierDescriptor">
</vn-supplier-descriptor-popover>
<vn-popup vn-id="summary">
<vn-invoice-in-summary
invoice-in="$ctrl.selectedInvoiceIn">

View File

@ -0,0 +1 @@
<vn-log url="InvoiceInLogs" origin-id="$ctrl.$params.id"></vn-log>

View File

@ -0,0 +1,7 @@
import ngModule from '../module';
import Section from 'salix/components/section';
ngModule.vnComponent('vnInvoiceInLog', {
template: require('./index.html'),
controller: Section,
});

View File

@ -2,16 +2,26 @@
"module": "invoiceIn",
"name": "Invoices in",
"icon": "icon-invoiceIn",
"validations" : true,
"dependencies": ["worker", "supplier"],
"validations": true,
"dependencies": [
"worker",
"supplier"
],
"menus": {
"main": [
{"state": "invoiceIn.index", "icon": "icon-invoiceIn"}
{
"state": "invoiceIn.index",
"icon": "icon-invoiceIn"
}
],
"card": [
{
"state": "invoiceIn.card.basicData",
"icon": "settings"
},
{
"state": "invoiceIn.card.log",
"icon": "history"
}
]
},
@ -28,7 +38,9 @@
"state": "invoiceIn.index",
"component": "vn-invoice-in-index",
"description": "InvoiceIn",
"acl": ["administrative"]
"acl": [
"administrative"
]
},
{
"url": "/:id",
@ -44,7 +56,9 @@
"params": {
"invoice-in": "$ctrl.invoiceIn"
},
"acl": ["administrative"]
"acl": [
"administrative"
]
},
{
"url": "/basic-data",
@ -53,13 +67,28 @@
"description": "Basic data",
"params": {
"invoice-in": "$ctrl.invoiceIn"
}
},
"acl": [
"administrative"
]
},
{
"url" : "/create?supplierFk",
"url": "/create?supplierFk",
"state": "invoiceIn.create",
"component": "vn-invoice-in-create",
"description": "New InvoiceIn"
"description": "New InvoiceIn",
"acl": [
"administrative"
]
},
{
"url": "/log",
"state": "invoiceIn.card.log",
"component": "vn-invoice-in-log",
"description": "Log",
"acl": [
"administrative"
]
}
]
}

View File

@ -27,11 +27,12 @@
label="Account"
ng-model="filter.account">
</vn-textfield>
<vn-textfield
<vn-input-number
vn-one
label="Amount"
ng-model="filter.amount">
</vn-textfield>
ng-model="filter.amount"
step="0.01">
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-date-picker

View File

@ -18,6 +18,8 @@
</vn-label-value>
<vn-label-value label="Supplier ref" value="{{$ctrl.summary.supplierRef}}">
</vn-label-value>
<vn-label-value label="Currency" value="{{$ctrl.summary.currency.name}}">
</vn-label-value>
<vn-label-value label="Doc number" value="{{$ctrl.summary.serial}}/{{$ctrl.summary.serialNumber}}">
</vn-label-value>
</vn-one>
@ -34,11 +36,11 @@
<vn-one>
<vn-label-value label="Sage withholding" value="{{$ctrl.summary.sageWithholding.withholding}}">
</vn-label-value>
<vn-label-value label="Undeductible VAT" value="{{$ctrl.summary.expenseDeductible.name}}">
</vn-label-value>
<vn-label-value label="Company" value="{{$ctrl.summary.company.code}}">
</vn-label-value>
<vn-vertical>
<vn-check label="Deductible" ng-model="$ctrl.summary.isVatDeductible" disabled="true">
</vn-check>
<vn-check label="Booked" ng-model="$ctrl.summary.isBooked" disabled="true">
</vn-check>
</vn-vertical>

View File

@ -7,4 +7,4 @@ Booked date: Fecha contable
Accounted date: Fecha contable
Doc number: Numero documento
Sage withholding: Retención sage
Deductible: Deducible
Undeductible VAT: Iva no deducible

View File

@ -0,0 +1,55 @@
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethod('getTickets', {
description: 'Returns tickets for one invoiceOut',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The invoiceOut id',
http: {source: 'path'}
},
{
arg: 'filter',
type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string'
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/:id/getTickets`,
verb: 'GET'
}
});
Self.getTickets = async(id, filter, options) => {
const models = Self.app.models;
let myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const invoiceOut = await models.InvoiceOut.findById(id, {fields: 'ref'}, myOptions);
let defaultFilter = {
where: {refFk: invoiceOut.ref},
fields: [
'id',
'nickname',
'shipped',
'totalWithVat',
'clientFk'
]
};
defaultFilter = mergeFilters(defaultFilter, filter);
return models.Ticket.find(defaultFilter, myOptions);
};
};

View File

@ -0,0 +1,10 @@
const app = require('vn-loopback/server/server');
describe('entry getTickets()', () => {
const invoiceOutId = 4;
it('should get the ticket of an invoiceOut', async() => {
const result = await app.models.InvoiceOut.getTickets(invoiceOutId);
expect(result.length).toEqual(1);
});
});

View File

@ -7,14 +7,6 @@ describe('invoiceOut summary()', () => {
expect(result.invoiceOut.id).toEqual(1);
});
it(`should return a summary object containing data from it's tickets`, async() => {
const summary = await app.models.InvoiceOut.summary(1);
const tickets = summary.invoiceOut.tickets();
expect(summary.invoiceOut.ref).toEqual('T1111111');
expect(tickets.length).toEqual(2);
});
it(`should return a summary object containing it's supplier country`, async() => {
const summary = await app.models.InvoiceOut.summary(1);
const supplier = summary.invoiceOut.supplier();

View File

@ -1,5 +1,3 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethod('summary', {
description: 'The invoiceOut summary',
@ -22,7 +20,6 @@ module.exports = Self => {
});
Self.summary = async id => {
const conn = Self.dataSource.connector;
let summary = {};
const filter = {
@ -57,54 +54,20 @@ module.exports = Self => {
scope: {
fields: ['id', 'socialName']
}
},
{
relation: 'tickets'
}
]
};
summary.invoiceOut = await Self.app.models.InvoiceOut.findOne(filter);
let stmts = [];
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.ticket');
stmt = new ParameterizedSQL(`
CREATE TEMPORARY TABLE tmp.ticket
(INDEX (ticketFk)) ENGINE = MEMORY
SELECT id ticketFk FROM vn.ticket WHERE refFk=?`, [summary.invoiceOut.ref]);
stmts.push(stmt);
stmts.push('CALL ticketGetTotal()');
let ticketTotalsIndex = stmts.push('SELECT * FROM tmp.ticketTotal') - 1;
stmt = new ParameterizedSQL(`
const invoiceOutTaxes = await Self.rawSql(`
SELECT iot.* , pgc.*, IF(pe.equFk IS NULL, taxableBase, 0) AS Base, pgc.rate / 100 as vatPercent
FROM vn.invoiceOutTax iot
JOIN vn.pgc ON pgc.code = iot.pgcFk
LEFT JOIN vn.pgcEqu pe ON pe.equFk = pgc.code
WHERE invoiceOutFk = ?`, [summary.invoiceOut.id]);
let invoiceOutTaxesIndex = stmts.push(stmt) - 1;
stmts.push(
`DROP TEMPORARY TABLE
tmp.ticket,
tmp.ticketTotal`);
let sql = ParameterizedSQL.join(stmts, ';');
let result = await conn.executeStmt(sql);
totalMap = {};
for (ticketTotal of result[ticketTotalsIndex])
totalMap[ticketTotal.ticketFk] = ticketTotal.total;
summary.invoiceOut.tickets().forEach(ticket => {
ticket.total = totalMap[ticket.id];
});
summary.invoiceOut.taxesBreakdown = result[invoiceOutTaxesIndex];
summary.invoiceOut.taxesBreakdown = invoiceOutTaxes;
return summary;
};

View File

@ -1,6 +1,7 @@
module.exports = Self => {
require('../methods/invoiceOut/filter')(Self);
require('../methods/invoiceOut/summary')(Self);
require('../methods/invoiceOut/getTickets')(Self);
require('../methods/invoiceOut/download')(Self);
require('../methods/invoiceOut/delete')(Self);
require('../methods/invoiceOut/book')(Self);

View File

@ -36,7 +36,7 @@
<vn-td expand>{{::invoiceOut.created | date:'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
<vn-td>{{::invoiceOut.companyCode | dashIfEmpty}}</vn-td>
<vn-td shrink>{{::invoiceOut.dued | date:'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
<vn-td>
<vn-td shrink>
<vn-icon-button
ng-show="invoiceOut.hasPdf"
vn-click-stop="$ctrl.openPdf(invoiceOut.id)"
@ -45,7 +45,7 @@
vn-tooltip="Download PDF">
</vn-icon-button>
</vn-td>
<vn-td>
<vn-td shrink>
<vn-icon-button
vn-click-stop="$ctrl.preview(invoiceOut)"
vn-tooltip="Preview"

View File

@ -1,3 +1,10 @@
<vn-crud-model
vn-id="ticketsModel"
url="InvoiceOuts/{{$ctrl.$params.id}}/getTickets"
limit="10"
data="tickets"
auto-load="true">
</vn-crud-model>
<vn-card class="summary">
<h5>
<a ng-if="::$ctrl.summary.invoiceOut.id"
@ -59,7 +66,7 @@
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="ticket in $ctrl.summary.invoiceOut.tickets">
<vn-tr ng-repeat="ticket in tickets">
<vn-td number>
<span
ng-click="ticketDescriptor.show($event, ticket.id)"
@ -75,10 +82,14 @@
</span>
</vn-td>
<vn-td expand>{{ticket.shipped | date: 'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
<vn-td number>{{ticket.total | currency: 'EUR': 2}}</vn-td>
<vn-td number>{{ticket.totalWithVat | currency: 'EUR': 2}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
<vn-pagination
model="ticketsModel"
class="vn-pt-xs">
</vn-pagination>
</vn-auto>
</vn-horizontal>
</vn-card>

View File

@ -9,14 +9,17 @@
"properties": {
"id": {
"id": true,
"type": "Number",
"type": "number",
"description": "Identifier"
},
"name": {
"type": "String"
},
"isWithheld": {
"type": "Number"
"type": "number"
},
"taxTypeFk": {
"type": "number"
}
},
"relations": {

View File

@ -117,6 +117,9 @@
"value10": {
"type": "string"
},
"itemPackingTypeFk": {
"type": "string"
},
"compression": {
"type": "number"
},
@ -137,6 +140,9 @@
"minPrice": {
"type": "number"
},
"packingOut": {
"type": "number"
},
"hasMinPrice": {
"type": "boolean"
},

View File

@ -304,7 +304,6 @@ module.exports = Self => {
stmts.push(
`DROP TEMPORARY TABLE
tmp.filter,
tmp.ticket,
tmp.ticket_problems`);
let sql = ParameterizedSQL.join(stmts, ';');

View File

@ -10,6 +10,11 @@
Clients on website
</vn-one>
<vn-none>
<vn-icon class="arrow"
icon="keyboard_arrow_up"
vn-tooltip="Minimize/Maximize"
ng-click="$ctrl.main.toggle()">
</vn-icon>
<vn-icon
icon="refresh"
vn-tooltip="Refresh"
@ -17,7 +22,7 @@
</vn-icon>
</vn-none>
</vn-horizontal>
<vn-card>
<vn-card vn-id="card">
<vn-table model="model" class="scrollable sm">
<vn-thead>
<vn-tr>
@ -52,12 +57,6 @@
</vn-tr>
</vn-tbody>
</vn-table>
<div
ng-if="!model.data.length"
class="empty-rows vn-pa-sm"
translate>
No results
</div>
<vn-pagination
model="model"
class="vn-pt-xs"

View File

@ -26,5 +26,8 @@ export default class Controller extends Section {
ngModule.vnComponent('vnMonitorSalesClients', {
template: require('./index.html'),
controller: Controller
controller: Controller,
require: {
main: '^vnMonitorIndex'
}
});

View File

@ -1,11 +1,5 @@
<vn-horizontal>
<vn-three class="vn-mr-sm">
<vn-monitor-sales-tickets></vn-monitor-sales-tickets>
</vn-three>
<vn-one>
<vn-vertical>
<vn-monitor-sales-clients class="vn-mb-sm"></vn-monitor-sales-clients>
<vn-monitor-sales-orders></vn-monitor-sales-orders>
</vn-vertical>
</vn-one>
</vn-horizontal>
<vn-monitor-sales-clients class="vn-mb-sm"></vn-monitor-sales-clients>
<vn-monitor-sales-orders></vn-monitor-sales-orders>
</vn-horizontal>
<vn-monitor-sales-tickets></vn-monitor-sales-tickets>

View File

@ -2,7 +2,22 @@ import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
export default class Controller extends Section {
toggle() {
const monitor = this.element.querySelector('vn-horizontal');
const isHidden = monitor.classList.contains('hidden');
if (!isHidden)
monitor.classList.add('hidden');
else
monitor.classList.remove('hidden');
}
}
ngModule.vnComponent('vnMonitorIndex', {
template: require('./index.html'),
controller: Section
controller: Controller,
require: {
main: '^vnMonitorIndex'
}
});

Some files were not shown because too many files have changed in this diff Show More