Merge branch 'dev' into 5635-contadotTicketExpedition
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Carlos Satorres 2023-06-07 05:57:05 +00:00
commit d09075d490
162 changed files with 12829 additions and 14422 deletions

View File

@ -5,18 +5,33 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2322.01] - 2023-06-08 ## [2324.01] - 2023-06-08
### Added
-
### Changed
-
### Fixed
-
## [2322.01] - 2023-06-01
### Added ### Added
- (Tickets -> Crear Factura) Al facturar se envia automáticamente el pdf al cliente - (Tickets -> Crear Factura) Al facturar se envia automáticamente el pdf al cliente
- (Artículos -> Histórico) Filtro para mostrar lo anterior al inventario
- (Trabajadores -> Nuevo trabajador) Permite elegir el método de pago
### Changed ### Changed
- (Trabajadores -> Nuevo trabajador) Los clientes se crean sin 'TR' pero se añade tipo de negocio 'Trabajador' - (Trabajadores -> Nuevo trabajador) Los clientes se crean sin 'TR' pero se añade tipo de negocio 'Trabajador'
- (Tickets -> Expediciones) Interfaz mejorada y contador añadido - (Tickets -> Expediciones) Interfaz mejorada y contador añadido
### Fixed ### Fixed
- (Tickets -> Líneas) Se permite hacer split de líneas al mismo ticket - (Tickets -> Líneas) Se permite hacer split de líneas al mismo ticket
- (Tickets -> Cambiar estado) Ahora muestra la lista completa de todos los estados
@ -39,10 +54,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- (Usuarios -> Histórico) Nueva sección - (Usuarios -> Histórico) Nueva sección
- (Roles -> Histórico) Nueva sección - (Roles -> Histórico) Nueva sección
- (General -> Traducciones) Correo de bienvenida a clientes al portugués y al francés - (Trabajadores -> Dar de alta) Permite elegir el método de pago
### Changed ### Changed
- (Artículo -> Precio fijado) Modificado el buscador superior por uno lateral - (Artículo -> Precio fijado) Modificado el buscador superior por uno lateral
- (Trabajadores -> Dar de alta) Quitada obligatoriedad del iban
### Fixed ### Fixed
- (Ticket -> Boxing) Arreglado selección de horas - (Ticket -> Boxing) Arreglado selección de horas

View File

@ -58,7 +58,10 @@ module.exports = Self => {
for (const param in args) for (const param in args)
params[param] = args[param]; params[param] = args[param];
if (!recipient) params.recipient = models.Client.findById(recipientId, {fields: ['email']}); if (!recipient) {
client = await models.Client.findById(recipientId, {fields: ['email']});
params.recipient = client.email;
}
const email = new Email('delivery-note', params); const email = new Email('delivery-note', params);

View File

@ -1,9 +1,9 @@
LOAD DATA LOCAL INFILE ? LOAD DATA LOCAL INFILE ?
INTO TABLE `edi`.`item` INTO TABLE `edi`.`item`
CHARACTER SET ascii
FIELDS TERMINATED BY ';' FIELDS TERMINATED BY ';'
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8, @col9, @col10, @col11, @col12) LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8, @col9, @col10, @col11, @col12)
SET SET id = @col2,
id = @col2,
product_name = @col4, product_name = @col4,
name = @col5, name = @col5,
plant_id = @col7, plant_id = @col7,
@ -11,3 +11,4 @@ LOAD DATA LOCAL INFILE ?
entry_date = STR_TO_DATE(@col10, '%Y%m%d'), entry_date = STR_TO_DATE(@col10, '%Y%m%d'),
expiry_date = IFNULL(NULL,STR_TO_DATE(@col11, '%Y%m%d')), expiry_date = IFNULL(NULL,STR_TO_DATE(@col11, '%Y%m%d')),
change_date_time = STR_TO_DATE(@col12, '%Y%m%d%H%i') change_date_time = STR_TO_DATE(@col12, '%Y%m%d%H%i')

View File

@ -121,7 +121,8 @@ module.exports = Self => {
host: ftpConfig.host, host: ftpConfig.host,
username: ftpConfig.user, username: ftpConfig.user,
password: ftpConfig.password, password: ftpConfig.password,
procotol: 'ftp' procotol: 'ftp',
additionalLftpCommands: 'set ssl:verify-certificate no'
}); });
} }
@ -132,7 +133,7 @@ module.exports = Self => {
const ftpClient = await getFtpClient(); const ftpClient = await getFtpClient();
console.debug(`Checking checksum for file ${file.name}...`); console.debug(`Checking checksum for file ${file.name}...`);
ftpClient.cat(`codes/${file.name}.txt`); ftpClient.cat(`codes/${file.name}.TXT`);
const response = await new Promise((resolve, reject) => { const response = await new Promise((resolve, reject) => {
ftpClient.exec((err, response) => { ftpClient.exec((err, response) => {

View File

@ -67,7 +67,7 @@ module.exports = Self => {
if (!image) return false; if (!image) return false;
const hasReadRole = models.ImageCollection.hasReadRole(ctx, collection); const hasReadRole = await models.ImageCollection.hasReadRole(ctx, collection);
if (!hasReadRole) if (!hasReadRole)
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);

View File

@ -9,10 +9,11 @@ module.exports = Self => {
* @return {boolean} True for user with read privileges * @return {boolean} True for user with read privileges
*/ */
Self.hasReadRole = async(ctx, name, options) => { Self.hasReadRole = async(ctx, name, options) => {
const collection = await Self.findOne({where: {name}}, { const collection = await Self.findOne({
include: { include: {
relation: 'readRole' relation: 'readRole'
} },
where: {name}
}, options); }, options);
return await hasRole(ctx, collection, options); return await hasRole(ctx, collection, options);

View File

@ -56,8 +56,6 @@ CREATE TABLE `vn`.`collectionWagonTicket` (
ALTER TABLE `vn`.`wagon` ADD `typeFk` int(11) unsigned NOT NULL; ALTER TABLE `vn`.`wagon` ADD `typeFk` int(11) unsigned NOT NULL;
ALTER TABLE `vn`.`wagon` ADD `label` int(11) unsigned NOT NULL; ALTER TABLE `vn`.`wagon` ADD `label` int(11) unsigned NOT NULL;
ALTER TABLE `vn`.`wagon` ADD CONSTRAINT `wagon_type` FOREIGN KEY (`typeFk`) REFERENCES `wagonType` (`id`) ON UPDATE CASCADE;
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES VALUES
('WagonType', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'), ('WagonType', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
@ -70,3 +68,4 @@ INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `pri
('WagonType', 'createWagonType', '*', 'ALLOW', 'ROLE', 'productionAssi'), ('WagonType', 'createWagonType', '*', 'ALLOW', 'ROLE', 'productionAssi'),
('WagonType', 'deleteWagonType', '*', 'ALLOW', 'ROLE', 'productionAssi'), ('WagonType', 'deleteWagonType', '*', 'ALLOW', 'ROLE', 'productionAssi'),
('WagonType', 'editWagonType', '*', 'ALLOW', 'ROLE', 'productionAssi'); ('WagonType', 'editWagonType', '*', 'ALLOW', 'ROLE', 'productionAssi');

View File

@ -27,7 +27,6 @@ INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalTyp
('Client', 'summary', '*', 'ALLOW', 'ROLE', 'employee'), ('Client', 'summary', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'updateAddress', '*', 'ALLOW', 'ROLE', 'employee'), ('Client', 'updateAddress', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'updateFiscalData', '*', 'ALLOW', 'ROLE', 'employee'), ('Client', 'updateFiscalData', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'updateUser', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'uploadFile', '*', 'ALLOW', 'ROLE', 'employee'), ('Client', 'uploadFile', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'campaignMetricsPdf', '*', 'ALLOW', 'ROLE', 'employee'), ('Client', 'campaignMetricsPdf', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'campaignMetricsEmail', '*', 'ALLOW', 'ROLE', 'employee'), ('Client', 'campaignMetricsEmail', '*', 'ALLOW', 'ROLE', 'employee'),

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`wagon` ADD CONSTRAINT `wagon_type` FOREIGN KEY (`typeFk`) REFERENCES `wagonType` (`id`) ON UPDATE CASCADE;

View File

@ -0,0 +1,7 @@
ALTER TABLE `vn`.`workerConfig` ADD payMethodFk tinyint(3) unsigned NULL;
ALTER TABLE `vn`.`workerConfig` ADD CONSTRAINT workerConfig_FK FOREIGN KEY (roleFk) REFERENCES account.`role`(id) ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE `vn`.`workerConfig` ADD CONSTRAINT workerConfig_FK_1 FOREIGN KEY (payMethodFk) REFERENCES `vn`.`payMethod`(id) ON DELETE SET NULL ON UPDATE CASCADE;
UPDATE `vn`.`workerConfig`
SET payMethodFk = 4
WHERE id=1;

View File

@ -0,0 +1,28 @@
CREATE TABLE `vn`.`buyConfig` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`monthsAgo` int(11) NOT NULL DEFAULT 6 COMMENT 'Meses desde la última compra',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
CREATE TABLE `vn`.`travelConfig` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`warehouseInFk` smallint(6) unsigned NOT NULL DEFAULT 8 COMMENT 'Warehouse de origen',
`warehouseOutFk` smallint(6) unsigned NOT NULL DEFAULT 60 COMMENT 'Warehouse destino',
`agencyFk` int(11) NOT NULL DEFAULT 1378 COMMENT 'Agencia por defecto',
`companyFk` smallint(5) unsigned NOT NULL DEFAULT 442 COMMENT 'Compañía por defecto',
PRIMARY KEY (`id`),
KEY `travelConfig_FK` (`warehouseInFk`),
KEY `travelConfig_FK_1` (`warehouseOutFk`),
KEY `travelConfig_FK_2` (`agencyFk`),
KEY `travelConfig_FK_3` (`companyFk`),
CONSTRAINT `travelConfig_FK` FOREIGN KEY (`warehouseInFk`) REFERENCES `warehouse` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `travelConfig_FK_1` FOREIGN KEY (`warehouseOutFk`) REFERENCES `warehouse` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `travelConfig_FK_2` FOREIGN KEY (`agencyFk`) REFERENCES `agencyMode` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `travelConfig_FK_3` FOREIGN KEY (`companyFk`) REFERENCES `company` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Entry', 'addFromPackaging', 'WRITE', 'ALLOW', 'ROLE', 'production'),
('Entry', 'addFromBuy', 'WRITE', 'ALLOW', 'ROLE', 'production'),
('Supplier', 'getItemsPackaging', 'READ', 'ALLOW', 'ROLE', 'production');

View File

@ -0,0 +1,121 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Ticket', 'editDiscount', 'WRITE', 'ALLOW', 'ROLE', 'claimManager'),
('Ticket', 'editDiscount', 'WRITE', 'ALLOW', 'ROLE', 'salesPerson'),
('Ticket', 'isRoleAdvanced', '*', 'ALLOW', 'ROLE', 'salesAssistant'),
('Ticket', 'isRoleAdvanced', '*', 'ALLOW', 'ROLE', 'deliveryBoss'),
('Ticket', 'isRoleAdvanced', '*', 'ALLOW', 'ROLE', 'buyer'),
('Ticket', 'isRoleAdvanced', '*', 'ALLOW', 'ROLE', 'claimManager'),
('Ticket', 'deleteTicketWithPartPrepared', 'WRITE', 'ALLOW', 'ROLE', 'salesAssistant'),
('Ticket', 'editZone', 'WRITE', 'ALLOW', 'ROLE', 'deliveryBoss'),
('State', 'editableStates', 'READ', 'ALLOW', 'ROLE', 'employee'),
('State', 'seeEditableStates', 'READ', 'ALLOW', 'ROLE', 'administrative'),
('State', 'seeEditableStates', 'READ', 'ALLOW', 'ROLE', 'production'),
('State', 'isSomeEditable', 'READ', 'ALLOW', 'ROLE', 'salesPerson'),
('State', 'isAllEditable', 'READ', 'ALLOW', 'ROLE', 'production'),
('State', 'isAllEditable', 'READ', 'ALLOW', 'ROLE', 'administrative'),
('Agency', 'seeExpired', 'READ', 'ALLOW', 'ROLE', 'administrative'),
('Agency', 'seeExpired', 'READ', 'ALLOW', 'ROLE', 'productionBoss'),
('Claim', 'createAfterDeadline', 'WRITE', 'ALLOW', 'ROLE', 'claimManager'),
('Client', 'editAddressLogifloraAllowed', 'WRITE', 'ALLOW', 'ROLE', 'salesAssistant'),
('Client', 'editFiscalDataWithoutTaxDataCheck', 'WRITE', 'ALLOW', 'ROLE', 'salesAssistant'),
('Client', 'editVerifiedDataWithoutTaxDataCheck', 'WRITE', 'ALLOW', 'ROLE', 'salesAssistant'),
('Client', 'editCredit', 'WRITE', 'ALLOW', 'ROLE', 'financialBoss'),
('Client', 'isNotEditableCredit', 'WRITE', 'ALLOW', 'ROLE', 'financialBoss'),
('InvoiceOut', 'canCreatePdf', 'WRITE', 'ALLOW', 'ROLE', 'invoicing'),
('Supplier', 'editPayMethodCheck', 'WRITE', 'ALLOW', 'ROLE', 'financial'),
('Worker', 'isTeamBoss', 'WRITE', 'ALLOW', 'ROLE', 'teamBoss'),
('Worker', 'forceIsSubordinate', 'READ', 'ALLOW', 'ROLE', 'hr'),
('Claim', 'editState', 'WRITE', 'ALLOW', 'ROLE', 'claimManager');
DELETE FROM `salix`.`ACL`
WHERE
model = 'Claim'
AND property = '*'
AND accessType = '*';
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Claim', 'find', 'READ', 'ALLOW', 'ROLE', 'salesPerson'),
('Claim', 'findById', 'READ', 'ALLOW', 'ROLE', 'salesPerson'),
('Claim', 'findOne', 'READ', 'ALLOW', 'ROLE', 'salesPerson'),
('Claim', 'getSummary', 'READ', 'ALLOW', 'ROLE', 'salesPerson'),
('Claim', 'updateClaim', 'WRITE', 'ALLOW', 'ROLE', 'salesPerson'),
('Claim', 'regularizeClaim', 'WRITE', 'ALLOW', 'ROLE', 'claimManager'),
('Claim', 'updateClaimDestination', 'WRITE', 'ALLOW', 'ROLE', 'claimManager'),
('Claim', 'downloadFile', 'READ', 'ALLOW', 'ROLE', 'claimManager'),
('Claim', 'deleteById', 'WRITE', 'ALLOW', 'ROLE', 'claimManager'),
('Claim', 'filter', 'READ', 'ALLOW', 'ROLE', 'salesPerson'),
('Claim', 'logs', 'READ', 'ALLOW', 'ROLE', 'claimManager');
DELETE FROM `salix`.`ACL`
WHERE
model = 'Ticket'
AND property = '*'
AND accessType = '*';
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Ticket', 'find', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'findById', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'findOne', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'getVolume', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'getTotalVolume', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'summary', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'priceDifference', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'componentUpdate', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'new', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'isEditable', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'setDeleted', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'restore', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'getSales', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'getSalesPersonMana', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'filter', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'makeInvoice', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'updateEditableTicket', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'updateDiscount', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'transferSales', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'sendSms', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'isLocked', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'freightCost', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'getComponentsSum', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'updateAttributes', 'WRITE', 'ALLOW', 'ROLE', 'delivery'), -- Change Priority in Route tickets
('Ticket', 'deliveryNoteCsv', 'READ', 'ALLOW', 'ROLE', 'employee');
DELETE FROM `salix`.`ACL`
WHERE
model = 'State'
AND property = '*'
AND accessType = 'READ';
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('State', 'find', 'READ', 'ALLOW', 'ROLE', 'employee'),
('State', 'findById', 'READ', 'ALLOW', 'ROLE', 'employee'),
('State', 'findOne', 'READ', 'ALLOW', 'ROLE', 'employee');
DELETE FROM `salix`.`ACL`
WHERE
model = 'Worker'
AND property = '*'
AND accessType = 'READ';
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Worker', 'find', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'findById', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'findOne', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'filter', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'getWorkedHours', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'active', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'activeWithRole', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'uploadFile', 'WRITE', 'ALLOW', 'ROLE', 'hr'),
('Worker', 'contracts', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'holidays', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'activeContract', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'activeWithInheritedRole', 'READ', 'ALLOW', 'ROLE', 'employee');
DELETE FROM `salix`.`ACL`
WHERE model = 'Client'
AND property = 'updateUser'
AND accessType = '*';

View File

@ -905,7 +905,7 @@ INSERT INTO `vn`.`itemFamily`(`code`, `description`)
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenceFk`, INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenceFk`,
`comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`, `genericFk`, `itemPackingTypeFk`, `hasMinPrice`, `packingShelve`, `weightByPiece`) `comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`, `genericFk`, `itemPackingTypeFk`, `hasMinPrice`, `packingShelve`, `weightByPiece`)
VALUES VALUES
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT', 0, NULL, 'V', 0, 15,3), (1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'EMB', 0, NULL, 'V', 0, 15,3),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT', 0, NULL, 'H', 0, 10,2), (2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT', 0, NULL, 'H', 0, 10,2),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, 5,5), (3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, 5,5),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL,NULL), (4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL,NULL),
@ -2824,9 +2824,9 @@ INSERT INTO `vn`.`payDemDetail` (`id`, `detail`)
VALUES VALUES
(1, 1); (1, 1);
INSERT INTO `vn`.`workerConfig` (`id`, `businessUpdated`, `roleFk`, `businessTypeFk`) INSERT INTO `vn`.`workerConfig` (`id`, `businessUpdated`, `roleFk`, `payMethodFk`, `businessTypeFk`)
VALUES VALUES
(1, NULL, 1, 'worker'); (1, NULL, 1, 4, 'worker');
INSERT INTO `vn`.`ticketRefund`(`refundTicketFk`, `originalTicketFk`) INSERT INTO `vn`.`ticketRefund`(`refundTicketFk`, `originalTicketFk`)
VALUES VALUES
@ -2886,6 +2886,10 @@ INSERT INTO `vn`.`wagonTypeTray` (`id`, `typeFk`, `height`, `colorFk`)
(2, 1, 50, 2), (2, 1, 50, 2),
(3, 1, 0, 3); (3, 1, 0, 3);
INSERT INTO `vn`.`travelConfig` (`id`, `warehouseInFk`, `warehouseOutFk`, `agencyFk`, `companyFk`)
VALUES
(1, 1, 1, 1, 442);
INSERT INTO `vn`.`buyConfig` (`id`, `monthsAgo`)
VALUES
(1, 6);

View File

@ -61943,69 +61943,75 @@ DELIMITER ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ; /*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;; DELIMITER ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `item_getBalance`(IN vItemId int, IN vWarehouse int) CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`item_getBalance`(vItemFk int, vWarehouseFk int, vDate DATETIME)
BEGIN BEGIN
/**
* @vItemFk item a buscar
* @vWarehouseFk almacen donde buscar
* @vDate Si la fecha es null, muestra el histórico desde el inventario. Si la fecha no es null, muestra histórico desde la fecha pasada.
*/
DECLARE vDateInventory DATETIME; DECLARE vDateInventory DATETIME;
DECLARE vCurdate DATE DEFAULT util.VN_CURDATE(); DECLARE vInvCalculated INT;
DECLARE vDayEnd DATETIME DEFAULT util.dayEnd(vCurdate);
SELECT inventoried INTO vDateInventory FROM config; IF vDate IS NULL THEN
SET @a = 0; SELECT inventoried INTO vDateInventory
SET @currentLineFk = 0; FROM config;
SET @shipped = ''; ELSE
SELECT mockUtcTime INTO vDateInventory
FROM util.config;
END IF;
SELECT DATE(@shipped:= shipped) shipped, CREATE OR REPLACE TEMPORARY TABLE itemDiary(
alertLevel, shipped DATE,
stateName, `in` INT(11),
origin, `out` INT(11),
reference, alertLevel INT(11),
clientFk, stateName VARCHAR(20),
name, `name` VARCHAR(50),
`in` AS invalue, reference VARCHAR(50),
`out`, origin INT(11),
@a := @a + IFNULL(`in`,0) - IFNULL(`out`,0) as balance, clientFk INT(11),
@currentLineFk := IF (@shipped < util.VN_CURDATE() isPicked INT(11),
OR (@shipped = util.VN_CURDATE() AND (isPicked OR alertLevel >= 2)), isTicket TINYINT(1),
lineFk,@currentLineFk) lastPreparedLineFk, lineFk INT(11),
isTicket, `order` TINYINT(3) UNSIGNED,
lineFk, clientType VARCHAR(20),
isPicked, claimFk INT(10) UNSIGNED
clientType, );
claimFk
FROM INSERT INTO itemDiary
( SELECT tr.landed AS shipped, SELECT tr.landed shipped,
b.quantity AS `in`, b.quantity `in`,
NULL AS `out`, NULL `out`,
al.id AS alertLevel, al.id alertLevel,
st.name AS stateName, st.name stateName,
s.name AS name, s.name `name`,
e.invoiceNumber AS reference, e.invoiceNumber reference,
e.id AS origin, e.id origin,
s.id AS clientFk, s.id clientFk,
IF(al.id = 3, TRUE, FALSE) isPicked, IF(al.code = 'DELIVERED', TRUE, FALSE) isPicked,
FALSE AS isTicket, FALSE isTicket,
b.id lineFk, b.id lineFk,
NULL `order`, NULL `order`,
NULL AS clientType, NULL clientType,
NULL AS claimFk NULL claimFk
FROM buy b FROM buy b
JOIN entry e ON e.id = b.entryFk JOIN entry e ON e.id = b.entryFk
JOIN travel tr ON tr.id = e.travelFk JOIN travel tr ON tr.id = e.travelFk
JOIN supplier s ON s.id = e.supplierFk JOIN supplier s ON s.id = e.supplierFk
JOIN alertLevel al ON al.id = JOIN alertLevel al ON al.code =
CASE CASE
WHEN tr.landed < util.VN_CURDATE() THEN 3 WHEN tr.landed < util.VN_CURDATE() THEN 'DELIVERED'
WHEN tr.landed = util.VN_CURDATE() AND tr.isReceived = TRUE THEN 3 WHEN tr.landed = util.VN_CURDATE() AND tr.isReceived = TRUE THEN 'DELIVERED'
ELSE 0 ELSE 'FREE'
END END
JOIN state st ON st.code = al.code JOIN state st ON st.code = al.code
WHERE tr.landed >= vDateInventory WHERE tr.landed >= vDateInventory
AND vWarehouse = tr.warehouseInFk AND vWarehouseFk = tr.warehouseInFk
AND b.itemFk = vItemId AND b.itemFk = vItemFk
AND e.isExcludedFromAvailable = FALSE AND e.isExcludedFromAvailable = FALSE
AND e.isRaid = FALSE AND e.isRaid = FALSE
UNION ALL UNION ALL
SELECT tr.shipped, SELECT tr.shipped,
NULL, NULL,
b.quantity, b.quantity,
@ -62015,7 +62021,7 @@ BEGIN
e.invoiceNumber, e.invoiceNumber,
e.id, e.id,
s.id, s.id,
IF(al.id = 3, TRUE, FALSE), IF(al.code = 'DELIVERED', TRUE, FALSE),
FALSE, FALSE,
b.id, b.id,
NULL, NULL,
@ -62026,26 +62032,26 @@ BEGIN
JOIN travel tr ON tr.id = e.travelFk JOIN travel tr ON tr.id = e.travelFk
JOIN warehouse w ON w.id = tr.warehouseOutFk JOIN warehouse w ON w.id = tr.warehouseOutFk
JOIN supplier s ON s.id = e.supplierFk JOIN supplier s ON s.id = e.supplierFk
JOIN alertLevel al ON al.id = JOIN alertLevel al ON al.code =
CASE CASE
WHEN tr.shipped < util.VN_CURDATE() THEN 3 WHEN tr.shipped < util.VN_CURDATE() THEN 'DELIVERED'
WHEN tr.shipped = util.VN_CURDATE() AND tr.isReceived = TRUE THEN 3 WHEN tr.shipped = util.VN_CURDATE() AND tr.isReceived = TRUE THEN 'DELIVERED'
ELSE 0 ELSE 'FREE'
END END
JOIN state st ON st.code = al.code JOIN state st ON st.code = al.code
JOIN entryConfig ec
WHERE tr.shipped >= vDateInventory WHERE tr.shipped >= vDateInventory
AND vWarehouse =tr.warehouseOutFk AND vWarehouseFk =tr.warehouseOutFk
AND s.id <> 4 AND s.id <> ec.inventorySupplierFk
AND b.itemFk = vItemId AND b.itemFk = vItemFk
AND e.isExcludedFromAvailable = FALSE AND e.isExcludedFromAvailable = FALSE
AND w.isFeedStock = FALSE AND w.isFeedStock = FALSE
AND e.isRaid = FALSE AND e.isRaid = FALSE
UNION ALL UNION ALL
SELECT DATE(t.shipped), SELECT DATE(t.shipped),
NULL, NULL,
s.quantity, s.quantity,
al.id, al3.id,
st.name, st.name,
t.nickname, t.nickname,
t.refFk, t.refFk,
@ -62063,21 +62069,78 @@ BEGIN
LEFT JOIN state st ON st.code = ts.code LEFT JOIN state st ON st.code = ts.code
JOIN client c ON c.id = t.clientFk JOIN client c ON c.id = t.clientFk
JOIN clientType ct ON ct.id = c.clientTypeFk JOIN clientType ct ON ct.id = c.clientTypeFk
JOIN alertLevel al ON al.id = JOIN alertLevel al ON al.code = 'DELIVERED'
JOIN alertLevel al2 ON al2.code = 'FREE'
JOIN alertLevel al3 ON al3.id =
CASE CASE
WHEN t.shipped < util.VN_CURDATE() THEN 3 WHEN t.shipped < util.VN_CURDATE() THEN al.code
WHEN t.shipped > util.dayEnd(util.VN_CURDATE()) THEN 0 WHEN t.shipped > util.dayEnd(util.VN_CURDATE()) THEN al2.code
ELSE IFNULL(ts.alertLevel, 0) ELSE IFNULL(ts.alertLevel, al2.code)
END END
LEFT JOIN state stPrep ON stPrep.`code` = 'PREPARED' LEFT JOIN state stPrep ON stPrep.`code` = 'PREPARED'
LEFT JOIN saleTracking stk ON stk.saleFk = s.id AND stk.stateFk = stPrep.id LEFT JOIN saleTracking stk ON stk.saleFk = s.id AND stk.stateFk = stPrep.id
LEFT JOIN claimBeginning cb ON s.id = cb.saleFk LEFT JOIN claimBeginning cb ON s.id = cb.saleFk
WHERE t.shipped >= vDateInventory WHERE t.shipped >= vDateInventory
AND s.itemFk = vItemId AND s.itemFk = vItemFk
AND vWarehouse =t.warehouseFk AND vWarehouseFk =t.warehouseFk
ORDER BY shipped, alertLevel DESC, isTicket, `order` DESC, isPicked DESC, `in` DESC, `out` DESC ORDER BY shipped, alertLevel DESC, isTicket, `order` DESC, isPicked DESC, `in` DESC, `out` DESC;
) AS itemDiary;
IF vDate IS NULL THEN
SET @a = 0;
SET @currentLineFk = 0;
SET @shipped = '';
SELECT DATE(@shipped:= shipped) shipped,
alertLevel,
stateName,
origin,
reference,
clientFk,
name,
`in` AS invalue,
`out`,
@a := @a + IFNULL(`in`,0) - IFNULL(`out`,0) as balance,
@currentLineFk := IF (@shipped < util.VN_CURDATE()
OR (@shipped = util.VN_CURDATE() AND (isPicked OR a.code >= 'ON_PREPARATION')),
lineFk, @currentLineFk) lastPreparedLineFk,
isTicket,
lineFk,
isPicked,
clientType,
claimFk
FROM itemDiary
JOIN alertLevel a ON a.id = itemDiary.alertLevel;
ELSE
SELECT sum(`in`) - sum(`out`) INTO vInvCalculated
FROM itemDiary
WHERE shipped < vDate;
SELECT p1.*
FROM(
SELECT vDate shipped,
0 alertLevel,
0 stateName,
0 origin,
'' reference,
0 clientFk,
'Inventario calculado',
vInvCalculated invalue,
NULL `out`,
0 balance,
0 lastPreparedLineFk,
0 isTicket,
0 lineFk,
0 isPicked,
0 clientType,
0 claimFk
UNION ALL
SELECT shipped, alertlevel, stateName, origin, reference, clientFk, name, `in`, `out`, 0,0, isTicket, lineFk, isPicked, clientType, claimFk
FROM itemDiary
WHERE shipped >= vDate
)as p1;
END IF;
DROP TEMPORARY TABLE itemDiary;
END ;; END ;;
DELIMITER ; DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET sql_mode = @saved_sql_mode */ ;

View File

@ -43,7 +43,7 @@ services:
- node.role == worker - node.role == worker
resources: resources:
limits: limits:
memory: 4G memory: 8G
configs: configs:
datasources: datasources:
external: true external: true

View File

@ -22,7 +22,8 @@ export async function getBrowser() {
env.E2E_SHOW = true; env.E2E_SHOW = true;
} }
const headless = !env.E2E_SHOW; const headless = env.E2E_SHOW ? false : 'new';
const browser = await Puppeteer.launch({ const browser = await Puppeteer.launch({
args, args,
defaultViewport: null, defaultViewport: null,

View File

@ -625,6 +625,7 @@ export default {
selectAllSalesCheckbox: 'vn-ticket-sale vn-thead vn-check', selectAllSalesCheckbox: 'vn-ticket-sale vn-thead vn-check',
secondSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(2) vn-check[ng-model="sale.checked"]', secondSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(2) vn-check[ng-model="sale.checked"]',
thirdSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(3) vn-check[ng-model="sale.checked"]', thirdSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(3) vn-check[ng-model="sale.checked"]',
fourthSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(4) vn-check[ng-model="sale.checked"]',
deleteSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="delete"]', deleteSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="delete"]',
transferSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="call_split"]', transferSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="call_split"]',
moveToTicketInput: 'form vn-input-number[ng-model="$ctrl.transfer.ticketId"] input', moveToTicketInput: 'form vn-input-number[ng-model="$ctrl.transfer.ticketId"] input',

View File

@ -251,7 +251,6 @@ describe('Ticket Edit sale path', () => {
await page.waitToClick(selectors.ticketSales.moreMenu); await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuCreateClaim); await page.waitToClick(selectors.ticketSales.moreMenuCreateClaim);
await page.waitToClick(selectors.globalItems.acceptButton); await page.waitToClick(selectors.globalItems.acceptButton);
await page.waitToClick(selectors.globalItems.acceptButton);
await page.waitForState('claim.card.basicData'); await page.waitForState('claim.card.basicData');
}); });

View File

@ -17,7 +17,7 @@ describe('Claim summary path', () => {
}); });
it('should navigate to the target claim summary section', async() => { it('should navigate to the target claim summary section', async() => {
await page.loginAndModule('employee', 'claim'); await page.loginAndModule('salesPerson', 'claim');
await page.accessToSearchResult(claimId); await page.accessToSearchResult(claimId);
await page.waitForState('claim.card.summary'); await page.waitForState('claim.card.summary');
}); });

View File

@ -16,7 +16,7 @@ describe('Claim descriptor path', () => {
}); });
it('should now navigate to the target claim summary section', async() => { it('should now navigate to the target claim summary section', async() => {
await page.loginAndModule('employee', 'claim'); await page.loginAndModule('salesPerson', 'claim');
await page.accessToSearchResult(claimId); await page.accessToSearchResult(claimId);
await page.waitForState('claim.card.summary'); await page.waitForState('claim.card.summary');
}); });

View File

@ -1,5 +1,20 @@
import getBrowser from '../../helpers/puppeteer'; import getBrowser from '../../helpers/puppeteer';
const $ = {
saveButton: 'vn-supplier-fiscal-data button[type="submit"]',
};
const $inputs = {
province: 'vn-supplier-fiscal-data [name="province"]',
country: 'vn-supplier-fiscal-data [name="country"]',
postcode: 'vn-supplier-fiscal-data [name="postcode"]',
city: 'vn-supplier-fiscal-data [name="city"]',
socialName: 'vn-supplier-fiscal-data [name="socialName"]',
taxNumber: 'vn-supplier-fiscal-data [name="taxNumber"]',
account: 'vn-supplier-fiscal-data [name="account"]',
sageWithholding: 'vn-supplier-fiscal-data [ng-model="$ctrl.supplier.sageWithholdingFk"]',
sageTaxType: 'vn-supplier-fiscal-data [ng-model="$ctrl.supplier.sageTaxTypeFk"]'
};
describe('Supplier fiscal data path', () => { describe('Supplier fiscal data path', () => {
let browser; let browser;
let page; let page;

View File

@ -7,11 +7,13 @@ vn-descriptor-content {
.photo { .photo {
position: relative; position: relative;
width: 100%;
text-align: center;
overflow: hidden;
& > img[ng-src] { & > img[ng-src] {
min-height: 16em; min-height: 16em;
display: block; display: block;
max-width: 100%;
height: 256px; height: 256px;
} }

View File

@ -150,10 +150,10 @@ export default class Controller extends Section {
if (value == null || value == '') return null; if (value == null || value == '') return null;
switch (prop) { switch (prop) {
case 'search': case 'search':
const or = [{changedModelId: value}]; if (/^[0-9]+$/.test(value))
if (!/^[0-9]+$/.test(value)) return {changedModelId: value};
or.push({changedModelValue: {like: `%${value}%`}}); else
return {or}; return {changedModelValue: {like: `%${value}%`}};
case 'changes': case 'changes':
return {or: [ return {or: [
{oldInstance: {like: `%${value}%`}}, {oldInstance: {like: `%${value}%`}},

View File

@ -1,6 +1,8 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
const utils = require('loopback/lib/utils');
const {util} = require('webpack');
module.exports = function(Self) { module.exports = function(Self) {
Self.ParameterizedSQL = ParameterizedSQL; Self.ParameterizedSQL = ParameterizedSQL;
@ -164,23 +166,21 @@ module.exports = function(Self) {
function rewriteMethod(methodName) { function rewriteMethod(methodName) {
const realMethod = this[methodName]; const realMethod = this[methodName];
return async(data, options, cb) => { return function(...args) {
if (options instanceof Function) { let cb;
cb = options; const lastArg = args[args.length - 1];
options = null; if (lastArg instanceof Function) {
} cb = lastArg;
args.pop();
} else
cb = utils.createPromiseCallback();
try { args.push(function(err, res) {
const result = await realMethod.call(this, data, options); if (err) err = replaceErr(err, replaceErrFunc);
cb(err, res);
if (cb) cb(null, result); });
else return result; realMethod.apply(this, args);
} catch (err) { return cb.promise;
let myErr = replaceErr(err, replaceErrFunc);
if (cb) cb(myErr);
else
throw myErr;
}
}; };
} }

View File

@ -171,5 +171,7 @@
"Added observation": "Added observation", "Added observation": "Added observation",
"Comment added to client": "Comment added to client", "Comment added to client": "Comment added to client",
"This ticket is already a refund": "This ticket is already a refund", "This ticket is already a refund": "This ticket is already a refund",
"A claim with that sale already exists": "A claim with that sale already exists" "A claim with that sale already exists": "A claim with that sale already exists",
"Can't transfer claimed sales": "Can't transfer claimed sales",
"Invalid quantity": "Invalid quantity"
} }

View File

@ -84,6 +84,7 @@
"The current ticket can't be modified": "El ticket actual no puede ser modificado", "The current ticket can't be modified": "El ticket actual no puede ser modificado",
"The current claim can't be modified": "La reclamación actual no puede ser modificada", "The current claim can't be modified": "La reclamación actual no puede ser modificada",
"The sales of this ticket can't be modified": "Las lineas de este ticket no pueden ser modificadas", "The sales of this ticket can't be modified": "Las lineas de este ticket no pueden ser modificadas",
"The sales do not exists": "La(s) línea(s) seleccionada(s) no existe(n)",
"Please select at least one sale": "Por favor selecciona al menos una linea", "Please select at least one sale": "Por favor selecciona al menos una linea",
"All sales must belong to the same ticket": "Todas las lineas deben pertenecer al mismo ticket", "All sales must belong to the same ticket": "Todas las lineas deben pertenecer al mismo ticket",
"NO_ZONE_FOR_THIS_PARAMETERS": "Para este día no hay ninguna zona configurada", "NO_ZONE_FOR_THIS_PARAMETERS": "Para este día no hay ninguna zona configurada",
@ -290,5 +291,7 @@
"isTaxDataChecked": "Datos comprobados", "isTaxDataChecked": "Datos comprobados",
"comercialId": "Id comercial", "comercialId": "Id comercial",
"comercialName": "Comercial", "comercialName": "Comercial",
"Invalid NIF for VIES": "Invalid NIF for VIES" "Invalid NIF for VIES": "Invalid NIF for VIES",
"Ticket does not exist": "Este ticket no existe",
"Ticket is already signed": "Este ticket ya ha sido firmado"
} }

View File

@ -15,3 +15,7 @@ columns:
image: image image: image
hasGrant: has grant hasGrant: has grant
userFk: user userFk: user
recoverPass: recover password
role: role
sync: pending sync
lastPassChange: password changed

View File

@ -15,3 +15,7 @@ columns:
image: imagen image: imagen
hasGrant: puede delegar hasGrant: puede delegar
userFk: usuario userFk: usuario
recoverPass: recuperar contraseña
role: rol
sync: Pendiente de sincronizar
lastPassChange: contraseña modificada

View File

@ -59,12 +59,14 @@ module.exports = Self => {
const landedPlusWeek = new Date(ticket.landed); const landedPlusWeek = new Date(ticket.landed);
landedPlusWeek.setDate(landedPlusWeek.getDate() + 7); landedPlusWeek.setDate(landedPlusWeek.getDate() + 7);
const hasClaimManagerRole = await models.VnUser.hasRole(userId, 'claimManager', myOptions);
const isClaimable = landedPlusWeek >= Date.vnNew(); const isClaimable = landedPlusWeek >= Date.vnNew();
const canCreateClaimAfterDeadline =
await models.ACL.checkAccessAcl(ctx, 'Claim', 'createAfterDeadline', 'WRITE');
if (ticket.isDeleted) if (ticket.isDeleted)
throw new UserError(`You can't create a claim for a removed ticket`); throw new UserError(`You can't create a claim for a removed ticket`);
if (!isClaimable && !hasClaimManagerRole) if (!isClaimable && !canCreateClaimAfterDeadline)
throw new UserError(`You can't create a claim from a ticket delivered more than seven days ago`); throw new UserError(`You can't create a claim from a ticket delivered more than seven days ago`);
const newClaim = await Self.create({ const newClaim = await Self.create({

View File

@ -46,7 +46,6 @@ module.exports = Self => {
Self.updateClaim = async(ctx, id, options) => { Self.updateClaim = async(ctx, id, options) => {
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const args = ctx.args; const args = ctx.args;
let tx; let tx;
const myOptions = {}; const myOptions = {};
@ -81,9 +80,9 @@ module.exports = Self => {
if (args.claimStateFk) { if (args.claimStateFk) {
const canEditOldState = await models.ClaimState.isEditable(ctx, claim.claimStateFk, myOptions); const canEditOldState = await models.ClaimState.isEditable(ctx, claim.claimStateFk, myOptions);
const canEditNewState = await models.ClaimState.isEditable(ctx, args.claimStateFk, myOptions); const canEditNewState = await models.ClaimState.isEditable(ctx, args.claimStateFk, myOptions);
const isClaimManager = await models.VnUser.hasRole(userId, 'claimManager', myOptions); const canEditState = await models.ACL.checkAccessAcl(ctx, 'Claim', 'editState', 'WRITE');
if (!canEditOldState || !canEditNewState || changedHasToPickUp && !isClaimManager) if (!canEditOldState || !canEditNewState || changedHasToPickUp && !canEditState)
throw new UserError(`You don't have enough privileges to change that field`); throw new UserError(`You don't have enough privileges to change that field`);
} }

View File

@ -59,6 +59,12 @@ module.exports = function(Self) {
fields: ['id', 'name'] fields: ['id', 'name']
} }
}, },
{
relation: 'businessType',
scope: {
fields: ['description']
}
},
{ {
relation: 'account', relation: 'account',
scope: { scope: {

View File

@ -87,15 +87,15 @@ module.exports = function(Self) {
Self.updateAddress = async(ctx, clientId, addressId, options) => { Self.updateAddress = async(ctx, clientId, addressId, options) => {
const models = Self.app.models; const models = Self.app.models;
const args = ctx.args; const args = ctx.args;
const userId = ctx.req.accessToken.userId;
const myOptions = {}; const myOptions = {};
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const isSalesAssistant = await models.VnUser.hasRole(userId, 'salesAssistant', myOptions); const canEditAddressLogifloraAllowed =
await models.ACL.checkAccessAcl(ctx, 'Client', 'editAddressLogifloraAllowed');
if (args.isLogifloraAllowed && !isSalesAssistant) if (args.isLogifloraAllowed && !canEditAddressLogifloraAllowed)
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
const address = await models.Address.findOne({ const address = await models.Address.findOne({

View File

@ -131,9 +131,10 @@ module.exports = Self => {
myOptions.transaction = tx; myOptions.transaction = tx;
} }
try { try {
const isSalesAssistant = await models.VnUser.hasRole(userId, 'salesAssistant', myOptions); const canEditNotTaxDataChecked =
await models.ACL.checkAccessAcl(ctx, 'Client', 'editFiscalDataWithoutTaxDataCheck', 'WRITE');
const client = await models.Client.findById(clientId, null, myOptions); const client = await models.Client.findById(clientId, null, myOptions);
if (!isSalesAssistant && client.isTaxDataChecked) if (!canEditNotTaxDataChecked && client.isTaxDataChecked)
throw new UserError(`Not enough privileges to edit a client with verified data`); throw new UserError(`Not enough privileges to edit a client with verified data`);
// Sage data validation // Sage data validation
const taxDataChecked = args.isTaxDataChecked; const taxDataChecked = args.isTaxDataChecked;

View File

@ -2,6 +2,7 @@ const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('updateUser', { Self.remoteMethodCtx('updateUser', {
description: 'Updates the user information', description: 'Updates the user information',
accessType: 'WRITE',
accepts: [ accepts: [
{ {
arg: 'id', arg: 'id',
@ -32,7 +33,6 @@ module.exports = Self => {
Self.updateUser = async function(ctx, id, options) { Self.updateUser = async function(ctx, id, options) {
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
let tx; let tx;
const myOptions = {}; const myOptions = {};
@ -45,9 +45,8 @@ module.exports = Self => {
} }
try { try {
const isSalesPerson = await models.VnUser.hasRole(userId, 'salesPerson', myOptions); const canEdit = await models.ACL.checkAccessAcl(ctx, 'Client', 'updateUser', 'WRITE');
if (!canEdit)
if (!isSalesPerson)
throw new UserError(`Not enough privileges to edit a client`); throw new UserError(`Not enough privileges to edit a client`);
const isClient = await models.Client.findById(id, null, myOptions); const isClient = await models.Client.findById(id, null, myOptions);

View File

@ -218,9 +218,9 @@ module.exports = Self => {
const models = Self.app.models; const models = Self.app.models;
const loopBackContext = LoopBackContext.getCurrentContext(); const loopBackContext = LoopBackContext.getCurrentContext();
const userId = loopBackContext.active.accessToken.userId; const accessToken = {req: loopBackContext.active.accessToken};
const isSalesAssistant = await models.VnUser.hasRole(userId, 'salesAssistant', ctx.options); const editVerifiedDataWithoutTaxDataChecked = models.ACL.checkAccessAcl(accessToken, 'Client', 'editVerifiedDataWithoutTaxDataCheck', 'WRITE');
const hasChanges = orgData && changes; const hasChanges = orgData && changes;
const isTaxDataChecked = hasChanges && (changes.isTaxDataChecked || orgData.isTaxDataChecked); const isTaxDataChecked = hasChanges && (changes.isTaxDataChecked || orgData.isTaxDataChecked);
@ -232,8 +232,8 @@ module.exports = Self => {
const sageTransactionType = hasChanges && (changes.sageTransactionTypeFk || orgData.sageTransactionTypeFk); const sageTransactionType = hasChanges && (changes.sageTransactionTypeFk || orgData.sageTransactionTypeFk);
const sageTransactionTypeChanged = hasChanges && orgData.sageTransactionTypeFk != sageTransactionType; const sageTransactionTypeChanged = hasChanges && orgData.sageTransactionTypeFk != sageTransactionType;
const cantEditVerifiedData = isTaxDataCheckedChanged && !isSalesAssistant; const cantEditVerifiedData = isTaxDataCheckedChanged && !editVerifiedDataWithoutTaxDataChecked;
const cantChangeSageData = (sageTaxTypeChanged || sageTransactionTypeChanged) && !isSalesAssistant; const cantChangeSageData = (sageTaxTypeChanged || sageTransactionTypeChanged) && !editVerifiedDataWithoutTaxDataChecked;
if (cantEditVerifiedData || cantChangeSageData) if (cantEditVerifiedData || cantChangeSageData)
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
@ -401,9 +401,10 @@ module.exports = Self => {
Self.changeCredit = async function changeCredit(ctx, finalState, changes) { Self.changeCredit = async function changeCredit(ctx, finalState, changes) {
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.options.accessToken.userId; const userId = ctx.options.accessToken.userId;
const accessToken = {req: {accessToken: ctx.options.accessToken} };
const isFinancialBoss = await models.VnUser.hasRole(userId, 'financialBoss', ctx.options); const canEditCredit = await models.ACL.checkAccessAcl(accessToken, 'Client', 'editCredit', 'WRITE');
if (!isFinancialBoss) { if (!canEditCredit) {
const lastCredit = await models.ClientCredit.findOne({ const lastCredit = await models.ClientCredit.findOne({
where: { where: {
clientFk: finalState.id clientFk: finalState.id
@ -412,10 +413,9 @@ module.exports = Self => {
}, ctx.options); }, ctx.options);
const lastAmount = lastCredit && lastCredit.amount; const lastAmount = lastCredit && lastCredit.amount;
const lastWorkerId = lastCredit && lastCredit.workerFk; const lastCreditIsNotEditable = !await models.ACL.checkAccessAcl(accessToken, 'Client', 'isNotEditableCredit', 'WRITE');
const lastWorkerIsFinancialBoss = await models.VnUser.hasRole(lastWorkerId, 'financialBoss', ctx.options);
if (lastAmount == 0 && lastWorkerIsFinancialBoss) if (lastAmount == 0 && lastCreditIsNotEditable)
throw new UserError(`You can't change the credit set to zero from a financialBoss`); throw new UserError(`You can't change the credit set to zero from a financialBoss`);
const creditLimits = await models.ClientCreditLimit.find({ const creditLimits = await models.ClientCreditLimit.find({

View File

@ -43,6 +43,10 @@
{{$ctrl.client.salesPersonUser.name}} {{$ctrl.client.salesPersonUser.name}}
</span> </span>
</vn-label-value> </vn-label-value>
<vn-label-value
label="Business type"
value="{{$ctrl.client.businessType.description}}">
</vn-label-value>
</div> </div>
<div class="icons"> <div class="icons">
<vn-icon <vn-icon

View File

@ -8,3 +8,4 @@ Client invoices list: Listado de facturas del cliente
Pay method: Forma de pago Pay method: Forma de pago
Unpaid Dated: "Fecha: {{dated | date:'dd/MM/yyyy'}}" Unpaid Dated: "Fecha: {{dated | date:'dd/MM/yyyy'}}"
Unpaid Amount: "Importe: {{amount | currency: 'EUR':2}}" Unpaid Amount: "Importe: {{amount | currency: 'EUR':2}}"
Business type: Tipo de negocio

View File

@ -2,6 +2,11 @@
vn-id="watcher" vn-id="watcher"
data="$ctrl.dms"> data="$ctrl.dms">
</vn-watcher> </vn-watcher>
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form <form
name="form" name="form"
ng-submit="$ctrl.onSubmit()" ng-submit="$ctrl.onSubmit()"
@ -29,7 +34,7 @@
<vn-autocomplete vn-one required="true" <vn-autocomplete vn-one required="true"
label="Warehouse" label="Warehouse"
ng-model="$ctrl.dms.warehouseId" ng-model="$ctrl.dms.warehouseId"
url="Warehouses" data="warehouses"
show-field="name" show-field="name"
value-field="id"> value-field="id">
</vn-autocomplete> </vn-autocomplete>

View File

@ -0,0 +1,107 @@
module.exports = Self => {
Self.remoteMethodCtx('addFromBuy', {
description: 'Modify a field of a buy or creates a new one with default values',
accessType: 'WRITE',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The entry id',
http: {source: 'path'}
}, {
arg: 'item',
type: 'number',
required: true,
description: 'The item id',
}, {
arg: 'printedStickers',
type: 'number',
required: true,
description: 'The field to modify',
}],
returns: {
type: 'object',
root: true
},
http: {
path: `/:id/addFromBuy`,
verb: 'POST'
}
});
Self.addFromBuy = async(ctx, options) => {
const args = ctx.args;
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const myOptions = {userId};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
let buy = await models.Buy.findOne({where: {entryFk: args.id}}, myOptions);
if (buy)
await buy.updateAttribute('printedStickers', args.printedStickers);
else {
const userConfig = await models.UserConfig.findById(userId, {fields: ['warehouseFk']}, myOptions);
await Self.rawSql(
'CALL vn.buyUltimate(?,?)',
[userConfig.warehouseFk, null],
myOptions
);
let buyUltimate = await Self.rawSql(
`SELECT buyFk
FROM tmp.buyUltimate
WHERE itemFk = ?`,
[args.item],
myOptions
);
buyUltimate = await models.Buy.findById(buyUltimate[0].buyFk, null, myOptions);
buy = await models.Buy.create({
entryFk: args.id,
itemFk: args.item,
quantity: 0,
dispatched: buyUltimate.dispatched,
buyingValue: buyUltimate.buyingValue,
freightValue: buyUltimate.freightValue,
isIgnored: buyUltimate.isIgnored,
stickers: buyUltimate.stickers,
packing: buyUltimate.packing,
grouping: buyUltimate.grouping,
groupingMode: buyUltimate.groupingMode,
containerFk: buyUltimate.containerFk,
comissionValue: buyUltimate.comissionValue,
packageValue: buyUltimate.packageValue,
location: buyUltimate.location,
packageFk: buyUltimate.packageFk,
price1: buyUltimate.price1,
price2: buyUltimate.price2,
price3: buyUltimate.price3,
minPrice: buyUltimate.minPrice,
printedStickers: args.printedStickers,
workerFk: buyUltimate.workerFk,
isChecked: buyUltimate.isChecked,
isPickedOff: buyUltimate.isPickedOff,
created: buyUltimate.created,
ektFk: buyUltimate.ektFk,
weight: buyUltimate.weight,
deliveryFk: buyUltimate.deliveryFk,
itemOriginalFk: buyUltimate.itemOriginalFk
}, myOptions);
}
if (tx) await tx.commit();
return buy;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,72 @@
module.exports = Self => {
Self.remoteMethodCtx('addFromPackaging', {
description: 'Create a receipt or return entry for a supplier with a specific travel',
accessType: 'WRITE',
accepts: [{
arg: 'supplier',
type: 'number',
required: true,
description: 'The supplier id',
},
{
arg: 'isTravelReception',
type: 'boolean',
required: true,
description: 'Indicates if the travel associated with the entry is a return or receipt travel'
}],
returns: {
type: 'object',
root: true
},
http: {
path: `/addFromPackaging`,
verb: 'POST'
}
});
Self.addFromPackaging = async(ctx, options) => {
const args = ctx.args;
const models = Self.app.models;
let tx;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const travelConfig = await models.TravelConfig.findOne({}, myOptions);
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(today.getDate() - 1);
const tomorrow = new Date(today);
tomorrow.setDate(today.getDate() + 1);
const travel = await models.Travel.create({
shipped: args.isTravelReception ? yesterday : today,
landed: args.isTravelReception ? today : tomorrow,
agencyModeFk: travelConfig.agencyFk,
warehouseInFk: travelConfig.warehouseOutFk,
warehouseOutFk: travelConfig.warehouseInFk
}, myOptions);
const entry = await models.Entry.create({
supplierFk: args.supplier,
travelFk: travel.id,
companyFk: travelConfig.companyFk
}, myOptions);
if (tx) await tx.commit();
return entry;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,51 @@
const models = require('vn-loopback/server/server').models;
describe('entry addFromBuy()', () => {
const ctx = {req: {accessToken: {userId: 18}}};
it('should change the printedStickers of an existent buy', async() => {
const id = 1;
const item = 1;
const buy = 1;
const tx = await models.Entry.beginTransaction({});
const options = {transaction: tx};
try {
const currentBuy = await models.Buy.findById(buy, {fields: ['printedStickers']}, options);
const printedStickers = currentBuy.printedStickers + 10;
ctx.args = {id, item, printedStickers};
const newBuy = await models.Entry.addFromBuy(ctx, options);
expect(newBuy.printedStickers).toEqual(printedStickers);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should create for an entry without a concrete item a new buy', async() => {
const id = 8;
const item = 1;
const printedStickers = 10;
const tx = await models.Entry.beginTransaction({});
const options = {transaction: tx};
try {
const emptyBuy = await models.Buy.findOne({where: {entryFk: id}}, options);
ctx.args = {id, item, printedStickers};
const newBuy = await models.Entry.addFromBuy(ctx, options);
expect(emptyBuy).toEqual(null);
expect(newBuy.entryFk).toEqual(id);
expect(newBuy.printedStickers).toEqual(printedStickers);
expect(newBuy.itemFk).toEqual(item);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,49 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('entry addFromPackaging()', () => {
const supplier = 442;
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(today.getDate() - 1);
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 49},
http: {
req: {
headers: {origin: 'http://localhost'},
},
},
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx,
});
});
it('should create an incoming travel', async() => {
const ctx = {args: {isTravelReception: true, supplier}};
const tx = await models.Entry.beginTransaction({});
const options = {transaction: tx};
try {
const entry = await models.Entry.addFromPackaging(ctx, options);
const travelConfig = await models.TravelConfig.findOne({}, options);
const travel = await models.Travel.findOne({order: 'id DESC'}, options);
expect(new Date(travel.shipped).getDate()).toEqual(yesterday.getDate());
expect(new Date(travel.landed).getDate()).toEqual(today.getDate());
expect(travel.agencyModeFk).toEqual(travelConfig.agencyFk);
expect(travel.warehouseInFk).toEqual(travelConfig.warehouseOutFk);
expect(travel.warehouseOutFk).toEqual(travelConfig.warehouseInFk);
expect(entry.supplierFk).toEqual(supplier);
expect(entry.travelFk).toEqual(travel.id);
expect(entry.companyFk).toEqual(travelConfig.companyFk);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -5,6 +5,9 @@
"Buy": { "Buy": {
"dataSource": "vn" "dataSource": "vn"
}, },
"BuyConfig": {
"dataSource": "vn"
},
"ItemMatchProperties": { "ItemMatchProperties": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -0,0 +1,18 @@
{
"name": "BuyConfig",
"base": "VnModel",
"options": {
"mysql": {
"table": "buyConfig"
}
},
"properties": {
"id": {
"type": "number",
"id": true
},
"showLastBuy": {
"type": "number"
}
}
}

View File

@ -39,6 +39,9 @@
"packageValue": { "packageValue": {
"type": "number" "type": "number"
}, },
"price1": {
"type": "number"
},
"price2": { "price2": {
"type": "number" "type": "number"
}, },
@ -47,7 +50,44 @@
}, },
"weight": { "weight": {
"type": "number" "type": "number"
},
"printedStickers": {
"type": "number"
},
"dispatched": {
"type": "number"
},
"isIgnored": {
"type": "boolean"
},
"containerFk": {
"type": "number"
},
"location": {
"type": "number"
},
"minPrice": {
"type": "number"
},
"isChecked": {
"type": "boolean"
},
"isPickedOff": {
"type": "boolean"
},
"created": {
"type": "date"
},
"ektFk": {
"type": "number"
},
"itemOriginalFk": {
"type": "number"
},
"editorFk": {
"type": "number"
} }
}, },
"relations": { "relations": {
"entry": { "entry": {
@ -64,6 +104,16 @@
"type": "belongsTo", "type": "belongsTo",
"model": "Packaging", "model": "Packaging",
"foreignKey": "packageFk" "foreignKey": "packageFk"
},
"worker": {
"type": "belongsTo",
"model": "Worker",
"foreignKey": "workerFk"
},
"delivery": {
"type": "belongsTo",
"model": "Delivery",
"foreignKey": "deliveryFk"
} }
} }
} }

View File

@ -8,6 +8,8 @@ module.exports = Self => {
require('../methods/entry/importBuysPreview')(Self); require('../methods/entry/importBuysPreview')(Self);
require('../methods/entry/lastItemBuys')(Self); require('../methods/entry/lastItemBuys')(Self);
require('../methods/entry/entryOrderPdf')(Self); require('../methods/entry/entryOrderPdf')(Self);
require('../methods/entry/addFromPackaging')(Self);
require('../methods/entry/addFromBuy')(Self);
Self.observe('before save', async function(ctx, options) { Self.observe('before save', async function(ctx, options) {
if (ctx.isNewInstance) return; if (ctx.isNewInstance) return;

View File

@ -5,6 +5,11 @@
form="form" form="form"
save="patch"> save="patch">
</vn-watcher> </vn-watcher>
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form name="form" ng-submit="watcher.submit()" class="vn-w-md"> <form name="form" ng-submit="watcher.submit()" class="vn-w-md">
<vn-card class="vn-pa-lg"> <vn-card class="vn-pa-lg">
<vn-horizontal> <vn-horizontal>
@ -152,14 +157,14 @@
<vn-autocomplete <vn-autocomplete
label="Warehouse Out" label="Warehouse Out"
ng-model="$ctrl.travelFilterParams.warehouseOutFk" ng-model="$ctrl.travelFilterParams.warehouseOutFk"
url="Warehouses" data="warehouses"
show-field="name" show-field="name"
value-field="id"> value-field="id">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete <vn-autocomplete
label="Warehouse In" label="Warehouse In"
ng-model="$ctrl.travelFilterParams.warehouseInFk" ng-model="$ctrl.travelFilterParams.warehouseInFk"
url="Warehouses" data="warehouses"
show-field="name" show-field="name"
value-field="id"> value-field="id">
</vn-autocomplete> </vn-autocomplete>

View File

@ -25,7 +25,6 @@ module.exports = Self => {
Self.createPdf = async function(ctx, id, options) { Self.createPdf = async function(ctx, id, options) {
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
if (process.env.NODE_ENV == 'test') if (process.env.NODE_ENV == 'test')
throw new UserError(`Action not allowed on the test environment`); throw new UserError(`Action not allowed on the test environment`);
@ -43,9 +42,9 @@ module.exports = Self => {
try { try {
const invoiceOut = await Self.findById(id, null, myOptions); const invoiceOut = await Self.findById(id, null, myOptions);
const hasInvoicing = await models.VnUser.hasRole(userId, 'invoicing', myOptions); const canCreatePdf = await models.ACL.checkAccessAcl(ctx, 'InvoiceOut', 'canCreatePdf', 'WRITE');
if (invoiceOut.hasPdf && !hasInvoicing) if (invoiceOut.hasPdf && !canCreatePdf)
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
await invoiceOut.updateAttributes({ await invoiceOut.updateAttributes({

View File

@ -1,4 +1,4 @@
name: botanical name: botanical data
columns: columns:
itemFk: item itemFk: item
genusFk: genus genusFk: genus

View File

@ -1,4 +1,4 @@
name: botánico name: datos botánicos
columns: columns:
itemFk: artículo itemFk: artículo
genusFk: género genusFk: género

View File

@ -35,7 +35,7 @@ columns:
packingOut: packing out packingOut: packing out
hasMinPrice: has min price hasMinPrice: has min price
isFragile: fragile isFragile: fragile
isFloramondo: is floramondo isFloramondo: floramondo
packingShelve: packing shelve packingShelve: packing shelve
isLaid: laid isLaid: laid
inkFk: ink inkFk: ink

View File

@ -35,7 +35,7 @@ columns:
packingOut: empaquetar packingOut: empaquetar
hasMinPrice: tiene precio mínimo hasMinPrice: tiene precio mínimo
isFragile: frágil isFragile: frágil
isFloramondo: es floramondo isFloramondo: floramondo
packingShelve: estantería embalaje packingShelve: estantería embalaje
isLaid: puesto isLaid: puesto
inkFk: tinta inkFk: tinta

View File

@ -26,8 +26,8 @@ module.exports = Self => {
Object.assign(myOptions, options); Object.assign(myOptions, options);
const where = filter.where; const where = filter.where;
const query = 'CALL vn.item_getBalance(?, ?)'; const query = 'CALL vn.item_getBalance(?, ?, ?)';
const [diary] = await Self.rawSql(query, [where.itemFk, where.warehouseFk], myOptions); const [diary] = await Self.rawSql(query, [where.itemFk, where.warehouseFk, where.date], myOptions);
for (const entry of diary) for (const entry of diary)
if (entry.clientType === 'loses') entry.highlighted = true; if (entry.clientType === 'loses') entry.highlighted = true;

View File

@ -21,7 +21,8 @@ describe('item getBalance()', () => {
const filter = { const filter = {
where: { where: {
itemFk: 1, itemFk: 1,
warehouseFk: 1 warehouseFk: 1,
date: null
} }
}; };
const results = await models.Item.getBalance(filter, options); const results = await models.Item.getBalance(filter, options);
@ -45,14 +46,16 @@ describe('item getBalance()', () => {
const firstFilter = { const firstFilter = {
where: { where: {
itemFk: 1, itemFk: 1,
warehouseFk: 1 warehouseFk: 1,
date: null
} }
}; };
const secondFilter = { const secondFilter = {
where: { where: {
itemFk: 2, itemFk: 2,
warehouseFk: 1 warehouseFk: 1,
date: null
} }
}; };

View File

@ -1,3 +1,8 @@
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<vn-descriptor-content <vn-descriptor-content
module="item" module="item"
description="$ctrl.item.name" description="$ctrl.item.name"
@ -108,7 +113,9 @@
<vn-autocomplete <vn-autocomplete
label="Warehouse" label="Warehouse"
ng-model="$ctrl.warehouseFk" ng-model="$ctrl.warehouseFk"
url="Warehouses"> data="warehouses"
show-field="name"
value="id">
</vn-autocomplete> </vn-autocomplete>
</tpl-body> </tpl-body>
<tpl-buttons> <tpl-buttons>

View File

@ -25,6 +25,16 @@
ng-model="$ctrl.warehouseFk" ng-model="$ctrl.warehouseFk"
label="Select warehouse"> label="Select warehouse">
</vn-autocomplete> </vn-autocomplete>
<vn-check
ng-class="{'table-check':$ctrl.showOld}"
label="Show what's before the inventory"
ng-model="$ctrl.showOld">
</vn-check>
<vn-date-picker
label="Since"
ng-model="$ctrl.date"
ng-show="$ctrl.showOld">
</vn-date-picker>
</vn-horizontal> </vn-horizontal>
<vn-table model="model"> <vn-table model="model">
<vn-thead> <vn-thead>

View File

@ -38,12 +38,8 @@ class Controller extends Section {
if (value && value != this._warehouseFk) { if (value && value != this._warehouseFk) {
this._warehouseFk = value; this._warehouseFk = value;
this.card.warehouseFk = value; this.card.warehouseFk = value;
this.filter.where.warehouseFk = this.warehouseFk;
this.$state.go(this.$state.current.name, {
warehouseFk: value
});
this.filter.where.warehouseFk = value;
this.$.model.refresh(); this.$.model.refresh();
} }
} }
@ -52,6 +48,28 @@ class Controller extends Section {
return this._warehouseFk; return this._warehouseFk;
} }
set date(value) {
this._date = value;
this.filter.where.date = value;
this.filter.where.warehouseFk = this.warehouseFk;
this.$.model.refresh();
}
get date() {
return this._date;
}
set showOld(value) {
this._showOld = value;
if (!this._showOld) this.date = null;
else this.date = new Date();
}
get showOld() {
return this._showOld;
}
scrollToLine(lineFk) { scrollToLine(lineFk) {
this.$.$applyAsync(() => { this.$.$applyAsync(() => {
const hashFk = this.lineFk || lineFk; const hashFk = this.lineFk || lineFk;

View File

@ -2,3 +2,4 @@ In: Entrada
Out: Salida Out: Salida
Visible quantity: Cantidad visible Visible quantity: Cantidad visible
Ticket/Entry: Ticket/Entrada Ticket/Entry: Ticket/Entrada
Show what's before the inventory: Mostrar lo anterior al inventario

View File

@ -27,4 +27,7 @@ vn-item-diary {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.table-check{
justify-content: center;
}
} }

View File

@ -1,4 +1,9 @@
<div class="search-panel"> <div class="search-panel">
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form id="manifold-form" ng-submit="$ctrl.onSearch()"> <form id="manifold-form" ng-submit="$ctrl.onSearch()">
<vn-horizontal class="vn-px-lg vn-pt-lg"> <vn-horizontal class="vn-px-lg vn-pt-lg">
<vn-textfield <vn-textfield
@ -35,7 +40,9 @@
vn-one vn-one
label="Warehouse" label="Warehouse"
ng-model="filter.warehouseFk" ng-model="filter.warehouseFk"
url="Warehouses"> data="warehouses"
show-field="name"
value-field="id">
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal class="vn-px-lg"> <vn-horizontal class="vn-px-lg">

View File

@ -1,4 +1,9 @@
<div class="search-panel"> <div class="search-panel">
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form id="manifold-form" ng-submit="$ctrl.onSearch()"> <form id="manifold-form" ng-submit="$ctrl.onSearch()">
<vn-horizontal class="vn-px-lg vn-pt-lg"> <vn-horizontal class="vn-px-lg vn-pt-lg">
<vn-textfield <vn-textfield
@ -83,7 +88,7 @@
vn-one vn-one
label="Warehouse" label="Warehouse"
ng-model="filter.warehouseFk" ng-model="filter.warehouseFk"
url="Warehouses"> data="warehouses">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete <vn-autocomplete
vn-one vn-one

View File

@ -1,4 +1,9 @@
<div class="search-panel"> <div class="search-panel">
<vn-crud-model
auto-load="true"
url="Agencies"
data="agencies">
</vn-crud-model>
<form id="manifold-form" ng-submit="$ctrl.onSearch()"> <form id="manifold-form" ng-submit="$ctrl.onSearch()">
<vn-horizontal class="vn-px-lg vn-pt-lg"> <vn-horizontal class="vn-px-lg vn-pt-lg">
<vn-textfield <vn-textfield
@ -17,7 +22,7 @@
ng-model="filter.agencyModeFk"> ng-model="filter.agencyModeFk">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete vn-one <vn-autocomplete vn-one
url="Agencies" data="agencies"
label="Agency Agreement" label="Agency Agreement"
show-field="name" show-field="name"
value-field="id" value-field="id"

View File

@ -1,4 +1,9 @@
<div class="search-panel"> <div class="search-panel">
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form id="manifold-form" ng-submit="$ctrl.onSearch()"> <form id="manifold-form" ng-submit="$ctrl.onSearch()">
<vn-horizontal class="vn-px-lg vn-pt-lg"> <vn-horizontal class="vn-px-lg vn-pt-lg">
<vn-textfield <vn-textfield
@ -79,7 +84,7 @@
vn-one vn-one
label="Warehouse" label="Warehouse"
ng-model="filter.warehouseFk" ng-model="filter.warehouseFk"
url="Warehouses" data="warehouses"
show-field="name" show-field="name"
value-field="id"> value-field="id">
</vn-autocomplete> </vn-autocomplete>

View File

@ -0,0 +1,11 @@
name: shelving
columns:
id: id
code: code
parkingFk: parking
isPrinted: printed
priority: priority
parked: parked
userFk: user
isSpam: SPAM
isRecyclable: recyclable

View File

@ -0,0 +1,11 @@
name: estantería
columns:
id: id
code: código
parkingFk: parking
isPrinted: impreso
priority: prioridad
parked: aparcado
userFk: usuario
isSpam: SPAM
isRecyclable: reciclable

View File

@ -0,0 +1,50 @@
module.exports = Self => {
Self.remoteMethod('getItemsPackaging', {
description: 'Returns the list of items from the supplier of type packing',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The supplier id',
http: {source: 'path'}
}, {
arg: 'entry',
type: 'number',
required: true,
description: 'The entry id',
}],
returns: {
type: 'object',
root: true
},
http: {
path: `/:id/getItemsPackaging`,
verb: 'GET'
}
});
Self.getItemsPackaging = async(id, entry) => {
return Self.rawSql(`
WITH entryTmp AS (
SELECT i.id, SUM(b.quantity) quantity
FROM vn.entry e
JOIN vn.buy b ON b.entryFk = e.id
JOIN vn.supplier s ON s.id = e.supplierFk
JOIN vn.item i ON i.id = b.itemFk
WHERE e.id = ? AND e.supplierFk = ?
GROUP BY i.id
) SELECT i.id, i.name, et.quantity, SUM(b.quantity) quantityTotal
FROM vn.buy b
JOIN vn.item i ON i.id = b.itemFk
JOIN vn.entry e ON e.id = b.entryFk
JOIN vn.supplier s ON s.id = e.supplierFk
JOIN vn.buyConfig bc ON bc.monthsAgo
JOIN vn.travel t ON t.id = e.travelFk
LEFT JOIN entryTmp et ON et.id = i.id
WHERE e.supplierFk = ?
AND i.family IN ('EMB', 'CONT')
AND b.created > (util.VN_CURDATE() - INTERVAL bc.monthsAgo MONTH)
GROUP BY b.itemFk
ORDER BY et.quantity DESC, quantityTotal DESC`, [entry, id, id]);
};
};

View File

@ -0,0 +1,12 @@
const app = require('vn-loopback/server/server');
describe('Supplier getItemsPackaging()', () => {
it('should return a summary of the list of items from a specific supplier', async() => {
const [item] = await app.models.Supplier.getItemsPackaging(1, 1);
expect(item.id).toEqual(1);
expect(item.name).toEqual('Ranged weapon longbow 2m');
expect(item.quantity).toEqual(5000);
expect(item.quantityTotal).toEqual(5100);
});
});

View File

@ -11,6 +11,7 @@ module.exports = Self => {
require('../methods/supplier/campaignMetricsPdf')(Self); require('../methods/supplier/campaignMetricsPdf')(Self);
require('../methods/supplier/campaignMetricsEmail')(Self); require('../methods/supplier/campaignMetricsEmail')(Self);
require('../methods/supplier/newSupplier')(Self); require('../methods/supplier/newSupplier')(Self);
require('../methods/supplier/getItemsPackaging')(Self);
Self.validatesPresenceOf('name', { Self.validatesPresenceOf('name', {
message: 'The social name cannot be empty' message: 'The social name cannot be empty'
@ -99,18 +100,20 @@ module.exports = Self => {
Self.observe('before save', async function(ctx) { Self.observe('before save', async function(ctx) {
if (ctx.isNewInstance) return; if (ctx.isNewInstance) return;
const loopbackContext = LoopBackContext.getCurrentContext();
const changes = ctx.data || ctx.instance; const changes = ctx.data || ctx.instance;
const orgData = ctx.currentInstance; const orgData = ctx.currentInstance;
const userId = loopbackContext.active.accessToken.userId; const loopBackContext = LoopBackContext.getCurrentContext();
const accessToken = {req: loopBackContext.active.accessToken};
const editPayMethodCheck =
await Self.app.models.ACL.checkAccessAcl(accessToken, 'Supplier', 'editPayMethodCheck', 'WRITE');
const isNotFinancial = !await Self.app.models.VnUser.hasRole(userId, 'financial');
const isPayMethodChecked = changes.isPayMethodChecked || orgData.isPayMethodChecked; const isPayMethodChecked = changes.isPayMethodChecked || orgData.isPayMethodChecked;
const hasChanges = orgData && changes; const hasChanges = orgData && changes;
const isPayMethodCheckedChanged = hasChanges const isPayMethodCheckedChanged = hasChanges
&& orgData.isPayMethodChecked != isPayMethodChecked; && orgData.isPayMethodChecked != isPayMethodChecked;
if (isNotFinancial && isPayMethodCheckedChanged) if (!editPayMethodCheck && isPayMethodCheckedChanged)
throw new UserError('You can not modify is pay method checked'); throw new UserError('You can not modify is pay method checked');
}); });

View File

@ -12,3 +12,5 @@ columns:
hostFk: PC hostFk: PC
isBox: box isBox: box
itemPackingTypeFk: packing type itemPackingTypeFk: packing type
externalId: external id
stateTypeFk: status

View File

@ -12,3 +12,5 @@ columns:
hostFk: PC hostFk: PC
isBox: caja isBox: caja
itemPackingTypeFk: tipo empaquetado itemPackingTypeFk: tipo empaquetado
externalId: id externo
stateTypeFk: estado

View File

@ -38,6 +38,9 @@ module.exports = Self => {
} }
}, myOptions); }, myOptions);
if (!salesData.length)
throw new UserError(`The sales do not exists`);
const ticketId = salesData[0].ticketFk; const ticketId = salesData[0].ticketFk;
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId, myOptions); const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId, myOptions);
@ -62,7 +65,5 @@ module.exports = Self => {
throw new UserError('It is not possible to modify cloned sales'); throw new UserError('It is not possible to modify cloned sales');
if (!shouldEditFloramondo) if (!shouldEditFloramondo)
throw new UserError('It is not possible to modify sales that their articles are from Floramondo'); throw new UserError('It is not possible to modify sales that their articles are from Floramondo');
return true;
}; };
}; };

View File

@ -101,6 +101,9 @@ module.exports = Self => {
} }
} }
const query = `CALL vn.ticket_recalc(?)`;
await Self.rawSql(query, [refundTicket.id], myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();
return refundTicket; return refundTicket;

View File

@ -17,6 +17,32 @@ describe('sale canEdit()', () => {
}); });
}); });
describe('sale not exists', () => {
it('should return error if sale not exists', async() => {
const tx = await models.Sale.beginTransaction({});
try {
const options = {transaction: tx};
const developerId = 9;
const ctx = {req: {accessToken: {userId: developerId}}};
let max = await models.Sale.findOne({fields: ['id'], order: 'id DESC'}, options);
max.id = max.id + 1;
const sales = [max.id];
await models.Sale.canEdit(ctx, sales, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e.message;
}
expect(error).toEqual('The sales do not exists');
});
});
describe('sale editTracked', () => { describe('sale editTracked', () => {
it('should return true if the role is production regardless of the saleTrackings', async() => { it('should return true if the role is production regardless of the saleTrackings', async() => {
const tx = await models.Sale.beginTransaction({}); const tx = await models.Sale.beginTransaction({});
@ -29,9 +55,7 @@ describe('sale canEdit()', () => {
const sales = [25]; const sales = [25];
const result = await models.Sale.canEdit(ctx, sales, options); await models.Sale.canEdit(ctx, sales, options);
expect(result).toEqual(true);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -51,9 +75,7 @@ describe('sale canEdit()', () => {
const sales = [10]; const sales = [10];
const result = await models.Sale.canEdit(ctx, sales, options); await models.Sale.canEdit(ctx, sales, options);
expect(result).toEqual(true);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -87,9 +109,7 @@ describe('sale canEdit()', () => {
}); });
const ctx = {req: {accessToken: {userId: role.id}}}; const ctx = {req: {accessToken: {userId: role.id}}};
const result = await models.Sale.canEdit(ctx, saleCloned, options); await models.Sale.canEdit(ctx, saleCloned, options);
expect(result).toEqual(true);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -150,9 +170,7 @@ describe('sale canEdit()', () => {
const saleToEdit = await models.Sale.findById(sales[0], null, options); const saleToEdit = await models.Sale.findById(sales[0], null, options);
await saleToEdit.updateAttribute('itemFk', 9, options); await saleToEdit.updateAttribute('itemFk', 9, options);
const result = await models.Sale.canEdit(ctx, sales, options); await models.Sale.canEdit(ctx, sales, options);
expect(result).toEqual(true);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

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