Merge branch 'dev' into 5439-newFilterEnPeticionesDeCompra
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
This commit is contained in:
commit
8741a9f913
|
@ -6,5 +6,9 @@
|
|||
"source.fixAll.eslint": true
|
||||
},
|
||||
"search.useIgnoreFiles": false,
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
|
||||
"eslint.format.enable": true,
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
}
|
||||
}
|
||||
|
|
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -5,10 +5,10 @@ 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/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2312.01] - 2023-04-06
|
||||
## [2314.01] - 2023-04-20
|
||||
|
||||
### Added
|
||||
-
|
||||
- (Facturas recibidas -> Bases negativas) Nueva sección
|
||||
|
||||
### Changed
|
||||
-
|
||||
|
@ -16,6 +16,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Fixed
|
||||
-
|
||||
|
||||
## [2312.01] - 2023-04-06
|
||||
|
||||
### Added
|
||||
- (Monitor tickets) Muestra un icono al lado de la zona, si el ticket es frágil y se envía por agencia
|
||||
|
||||
### Changed
|
||||
- (Monitor tickets) Cuando se filtra por 'Pendiente' ya no muestra los estados de 'Previa'
|
||||
- (Envíos -> Extra comunitarios) Se agrupan las entradas del mismo travel. Añadidos campos Referencia y Importe.
|
||||
- (Envíos -> Índice) Cambiado el buscador superior por uno lateral
|
||||
|
||||
## [2310.01] - 2023-03-23
|
||||
|
||||
### Added
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('changePassword', {
|
||||
description: 'Changes the user password',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('setPassword', {
|
||||
description: 'Sets the user password',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
|
|
|
@ -30,16 +30,23 @@ module.exports = Self => {
|
|||
const recipient = to.replace('@', '');
|
||||
|
||||
if (sender.name != recipient) {
|
||||
await models.Chat.create({
|
||||
const chat = await models.Chat.create({
|
||||
senderFk: sender.id,
|
||||
recipient: to,
|
||||
dated: Date.vnNew(),
|
||||
checkUserStatus: 0,
|
||||
message: message,
|
||||
status: 0,
|
||||
status: 'sending',
|
||||
attempts: 0
|
||||
});
|
||||
|
||||
try {
|
||||
await Self.sendMessage(chat.senderFk, chat.recipient, chat.message);
|
||||
await Self.updateChat(chat, 'sent');
|
||||
} catch (error) {
|
||||
await Self.updateChat(chat, 'error', error);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,18 +24,13 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.sendCheckingPresence = async(ctx, recipientId, message, options) => {
|
||||
Self.sendCheckingPresence = async(ctx, recipientId, message) => {
|
||||
if (!recipientId) return false;
|
||||
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const models = Self.app.models;
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
const sender = await models.Account.findById(userId);
|
||||
const recipient = await models.Account.findById(recipientId, null, myOptions);
|
||||
const sender = await models.Account.findById(userId, {fields: ['id']});
|
||||
const recipient = await models.Account.findById(recipientId, null);
|
||||
|
||||
// Prevent sending messages to yourself
|
||||
if (recipientId == userId) return false;
|
||||
|
@ -46,16 +41,23 @@ module.exports = Self => {
|
|||
if (process.env.NODE_ENV == 'test')
|
||||
message = `[Test:Environment to user ${userId}] ` + message;
|
||||
|
||||
await models.Chat.create({
|
||||
const chat = await models.Chat.create({
|
||||
senderFk: sender.id,
|
||||
recipient: `@${recipient.name}`,
|
||||
dated: Date.vnNew(),
|
||||
checkUserStatus: 1,
|
||||
message: message,
|
||||
status: 0,
|
||||
status: 'sending',
|
||||
attempts: 0
|
||||
});
|
||||
|
||||
try {
|
||||
await Self.sendCheckingUserStatus(chat);
|
||||
await Self.updateChat(chat, 'sent');
|
||||
} catch (error) {
|
||||
await Self.updateChat(chat, 'error', error);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -3,7 +3,6 @@ module.exports = Self => {
|
|||
Self.remoteMethodCtx('sendQueued', {
|
||||
description: 'Send a RocketChat message',
|
||||
accessType: 'WRITE',
|
||||
accepts: [],
|
||||
returns: {
|
||||
type: 'object',
|
||||
root: true
|
||||
|
@ -16,14 +15,17 @@ module.exports = Self => {
|
|||
|
||||
Self.sendQueued = async() => {
|
||||
const models = Self.app.models;
|
||||
const maxAttempts = 3;
|
||||
const sentStatus = 1;
|
||||
const errorStatus = 2;
|
||||
|
||||
const chats = await models.Chat.find({
|
||||
where: {
|
||||
status: {neq: sentStatus},
|
||||
attempts: {lt: maxAttempts}
|
||||
status: {
|
||||
nin: [
|
||||
'sent',
|
||||
'sending'
|
||||
]
|
||||
|
||||
},
|
||||
attempts: {lt: 3}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -31,16 +33,16 @@ module.exports = Self => {
|
|||
if (chat.checkUserStatus) {
|
||||
try {
|
||||
await Self.sendCheckingUserStatus(chat);
|
||||
await updateChat(chat, sentStatus);
|
||||
await Self.updateChat(chat, 'sent');
|
||||
} catch (error) {
|
||||
await updateChat(chat, errorStatus, error);
|
||||
await Self.updateChat(chat, 'error', error);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await Self.sendMessage(chat.senderFk, chat.recipient, chat.message);
|
||||
await updateChat(chat, sentStatus);
|
||||
await Self.updateChat(chat, 'sent');
|
||||
} catch (error) {
|
||||
await updateChat(chat, errorStatus, error);
|
||||
await Self.updateChat(chat, 'error', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -128,15 +130,17 @@ module.exports = Self => {
|
|||
* @param {object} chat - The chat
|
||||
* @param {string} status - The new status
|
||||
* @param {string} error - The error
|
||||
* @param {object} options - Query options
|
||||
* @return {Promise} - The request promise
|
||||
*/
|
||||
async function updateChat(chat, status, error) {
|
||||
*/
|
||||
|
||||
Self.updateChat = async(chat, status, error) => {
|
||||
return chat.updateAttributes({
|
||||
status: status,
|
||||
attempts: ++chat.attempts,
|
||||
error: error
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the current user status on Rocketchat
|
||||
|
|
|
@ -10,7 +10,7 @@ describe('Chat sendCheckingPresence()', () => {
|
|||
|
||||
const chat = {
|
||||
checkUserStatus: 1,
|
||||
status: 0,
|
||||
status: 'pending',
|
||||
attempts: 0
|
||||
};
|
||||
|
||||
|
@ -27,7 +27,7 @@ describe('Chat sendCheckingPresence()', () => {
|
|||
|
||||
const chat = {
|
||||
checkUserStatus: 0,
|
||||
status: 0,
|
||||
status: 'pending',
|
||||
attempts: 0
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('setSaleQuantity()', () => {
|
||||
beforeAll(async() => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
it('should change quantity sale', async() => {
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
|
|
|
@ -69,15 +69,15 @@ module.exports = Self => {
|
|||
|
||||
const result = response.headers.get('set-cookie');
|
||||
const [firtHeader] = result.split(' ');
|
||||
const firtCookie = firtHeader.substring(0, firtHeader.length - 1);
|
||||
const cookie = firtHeader.substring(0, firtHeader.length - 1);
|
||||
const body = await response.text();
|
||||
const dom = new jsdom.JSDOM(body);
|
||||
const token = dom.window.document.querySelector('[name="__CSRFToken__"]').value;
|
||||
|
||||
await login(token, firtCookie);
|
||||
await login(token, cookie);
|
||||
}
|
||||
|
||||
async function login(token, firtCookie) {
|
||||
async function login(token, cookie) {
|
||||
const data = {
|
||||
__CSRFToken__: token,
|
||||
do: 'scplogin',
|
||||
|
@ -90,21 +90,18 @@ module.exports = Self => {
|
|||
body: new URLSearchParams(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'Cookie': firtCookie
|
||||
'Cookie': cookie
|
||||
}
|
||||
};
|
||||
const response = await fetch(ostUri, params);
|
||||
const result = response.headers.get('set-cookie');
|
||||
const [firtHeader] = result.split(' ');
|
||||
const secondCookie = firtHeader.substring(0, firtHeader.length - 1);
|
||||
await fetch(ostUri, params);
|
||||
|
||||
await close(token, secondCookie);
|
||||
await close(token, cookie);
|
||||
}
|
||||
|
||||
async function close(token, secondCookie) {
|
||||
async function close(token, cookie) {
|
||||
for (const ticketId of ticketsId) {
|
||||
try {
|
||||
const lock = await getLockCode(token, secondCookie, ticketId);
|
||||
const lock = await getLockCode(token, cookie, ticketId);
|
||||
if (!lock.code) {
|
||||
let error = `Can't get lock code`;
|
||||
if (lock.msg) error += `: ${lock.msg}`;
|
||||
|
@ -127,7 +124,7 @@ module.exports = Self => {
|
|||
method: 'POST',
|
||||
body: form,
|
||||
headers: {
|
||||
'Cookie': secondCookie
|
||||
'Cookie': cookie
|
||||
}
|
||||
};
|
||||
await fetch(ostUri, params);
|
||||
|
@ -139,13 +136,13 @@ module.exports = Self => {
|
|||
}
|
||||
}
|
||||
|
||||
async function getLockCode(token, secondCookie, ticketId) {
|
||||
async function getLockCode(token, cookie, ticketId) {
|
||||
const ostUri = `${config.host}/ajax.php/lock/ticket/${ticketId}`;
|
||||
const params = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRFToken': token,
|
||||
'Cookie': secondCookie
|
||||
'Cookie': cookie
|
||||
}
|
||||
};
|
||||
const response = await fetch(ostUri, params);
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
"options": {
|
||||
"mysql": {
|
||||
"table": "salix.User"
|
||||
}
|
||||
},
|
||||
"resetPasswordTokenTTL": "604800"
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
ALTER TABLE `vn`.`chat` ADD statusNew enum('pending','sent','error','sending') DEFAULT 'pending' NOT NULL;
|
||||
|
||||
UPDATE `vn`.`chat`
|
||||
SET statusNew = 'pending'
|
||||
WHERE status = 0;
|
||||
|
||||
UPDATE `vn`.`chat`
|
||||
SET statusNew = 'sent'
|
||||
WHERE status = 1;
|
||||
|
||||
UPDATE `vn`.`chat`
|
||||
SET statusNew = 'error'
|
||||
WHERE status = 2;
|
||||
|
||||
ALTER TABLE `vn`.`chat` CHANGE status status__ tinyint(1) DEFAULT NULL NULL;
|
||||
ALTER TABLE `vn`.`chat` CHANGE statusNew status enum('pending','sent','error','sending') CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT 'pending' NOT NULL;
|
|
@ -0,0 +1,4 @@
|
|||
ALTER TABLE `vn`.`invoiceInConfig` ADD daysAgo INT UNSIGNED DEFAULT 45 COMMENT 'Días en el pasado para mostrar facturas en invoiceIn series en salix';
|
||||
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||
VALUES
|
||||
('InvoiceIn', 'getSerial', 'READ', 'ALLOW', 'ROLE', 'administrative');
|
|
@ -0,0 +1,14 @@
|
|||
ALTER TABLE `vn`.`itemType` ADD isFragile tinyint(1) NULL;
|
||||
ALTER TABLE `vn`.`itemType` MODIFY COLUMN isFragile tinyint(1) DEFAULT 0 NOT NULL;
|
||||
|
||||
UPDATE `vn`.`itemType`
|
||||
SET isFragile = 1
|
||||
WHERE code IN ('ZKA', 'ZKE');
|
||||
|
||||
UPDATE `vn`.`itemType`
|
||||
SET isFragile = 1
|
||||
WHERE id IN (SELECT it.id
|
||||
FROM `vn`.`itemCategory` ic
|
||||
JOIN `vn`.`itemType` it ON it.categoryFk = ic.id
|
||||
WHERE ic.code = 'plant');
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
DROP TRIGGER `vn`.`supplierAccount_afterInsert`;
|
||||
DROP TRIGGER `vn`.`supplierAccount_afterUpdate`;
|
||||
DROP TRIGGER `vn`.`supplierAccount_afterDelete`;
|
|
@ -0,0 +1,47 @@
|
|||
DROP PROCEDURE IF EXISTS `vn`.`ticket_getWarnings`;
|
||||
|
||||
DELIMITER $$
|
||||
$$
|
||||
CREATE PROCEDURE `vn`.`ticket_getWarnings`()
|
||||
BEGIN
|
||||
/**
|
||||
* Calcula las adventencias para un conjunto de tickets.
|
||||
* Agrupados por ticket
|
||||
*
|
||||
* @table tmp.sale_getWarnings(ticketFk) Identificadores de los tickets a calcular
|
||||
* @return tmp.ticket_warnings
|
||||
*/
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp.sale_warnings;
|
||||
CREATE TEMPORARY TABLE tmp.sale_warnings (
|
||||
ticketFk INT(11),
|
||||
saleFk INT(11),
|
||||
isFragile INTEGER(1) DEFAULT 0,
|
||||
PRIMARY KEY (ticketFk, saleFk)
|
||||
) ENGINE = MEMORY;
|
||||
|
||||
-- Frágil
|
||||
INSERT INTO tmp.sale_warnings(ticketFk, saleFk, isFragile)
|
||||
SELECT tt.ticketFk, s.id, TRUE
|
||||
FROM tmp.sale_getWarnings tt
|
||||
LEFT JOIN sale s ON s.ticketFk = tt.ticketFk
|
||||
LEFT JOIN item i ON i.id = s.itemFk
|
||||
LEFT JOIN itemType it ON it.id = i.typeFk
|
||||
LEFT JOIN agencyMode am ON am.id = tt.agencyModeFk
|
||||
LEFT JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk
|
||||
WHERE dm.code IN ('AGENCY')
|
||||
AND it.isFragile;
|
||||
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp.ticket_warnings;
|
||||
CREATE TEMPORARY TABLE tmp.ticket_warnings
|
||||
(PRIMARY KEY (ticketFk))
|
||||
ENGINE = MEMORY
|
||||
SELECT
|
||||
sw.ticketFk,
|
||||
MAX(sw.isFragile) AS isFragile
|
||||
FROM tmp.sale_warnings sw
|
||||
GROUP BY sw.ticketFk;
|
||||
|
||||
DROP TEMPORARY TABLE
|
||||
tmp.sale_warnings;
|
||||
END$$
|
||||
DELIMITER ;
|
|
@ -0,0 +1,72 @@
|
|||
CREATE TABLE `vn`.`wagonType` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(30) NOT NULL UNIQUE,
|
||||
`divisible` tinyint(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
CREATE TABLE `vn`.`wagonTypeColor` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(30) NOT NULL UNIQUE,
|
||||
`rgb` varchar(30) NOT NULL UNIQUE,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
CREATE TABLE `vn`.`wagonTypeTray` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`typeFk` int(11) unsigned,
|
||||
`height` int(11) unsigned NOT NULL,
|
||||
`colorFk` int(11) unsigned,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY (`typeFk`,`height`),
|
||||
CONSTRAINT `wagonTypeTray_type` FOREIGN KEY (`typeFk`) REFERENCES `wagonType` (`id`) ON UPDATE CASCADE,
|
||||
CONSTRAINT `wagonTypeTray_color` FOREIGN KEY (`colorFk`) REFERENCES `wagonTypeColor` (`id`) ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
CREATE TABLE `vn`.`wagonConfig` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`width` int(11) unsigned DEFAULT 1350,
|
||||
`height` int(11) unsigned DEFAULT 1900,
|
||||
`maxWagonHeight` int(11) unsigned DEFAULT 200,
|
||||
`minHeightBetweenTrays` int(11) unsigned DEFAULT 50,
|
||||
`maxTrays` int(11) unsigned DEFAULT 6,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
CREATE TABLE `vn`.`collectionWagon` (
|
||||
`collectionFk` int(11) NOT NULL,
|
||||
`wagonFk` int(11) NOT NULL,
|
||||
`position` int(11) unsigned,
|
||||
PRIMARY KEY (`collectionFk`,`position`),
|
||||
UNIQUE KEY `collectionWagon_unique` (`collectionFk`,`wagonFk`),
|
||||
CONSTRAINT `collectionWagon_collection` FOREIGN KEY (`collectionFk`) REFERENCES `collection` (`id`) ON UPDATE CASCADE,
|
||||
CONSTRAINT `collectionWagon_wagon` FOREIGN KEY (`wagonFk`) REFERENCES `wagon` (`id`) ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
CREATE TABLE `vn`.`collectionWagonTicket` (
|
||||
`ticketFk` int(11) NOT NULL,
|
||||
`wagonFk` int(11) NOT NULL,
|
||||
`trayFk` int(11) unsigned NOT NULL,
|
||||
`side` SET('L', 'R') NULL,
|
||||
PRIMARY KEY (`ticketFk`),
|
||||
CONSTRAINT `collectionWagonTicket_ticket` FOREIGN KEY (`ticketFk`) REFERENCES `ticket` (`id`) ON UPDATE CASCADE,
|
||||
CONSTRAINT `collectionWagonTicket_wagon` FOREIGN KEY (`wagonFk`) REFERENCES `wagon` (`id`) ON UPDATE CASCADE,
|
||||
CONSTRAINT `collectionWagonTicket_tray` FOREIGN KEY (`trayFk`) REFERENCES `wagonTypeTray` (`id`) ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
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 CONSTRAINT `wagon_type` FOREIGN KEY (`typeFk`) REFERENCES `wagonType` (`id`) ON UPDATE CASCADE;
|
||||
|
||||
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||
VALUES
|
||||
('WagonType', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
|
||||
('WagonTypeColor', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
|
||||
('WagonTypeTray', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
|
||||
('WagonConfig', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
|
||||
('CollectionWagon', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
|
||||
('CollectionWagonTicket', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
|
||||
('Wagon', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
|
||||
('WagonType', 'createWagonType', '*', 'ALLOW', 'ROLE', 'productionAssi'),
|
||||
('WagonType', 'deleteWagonType', '*', 'ALLOW', 'ROLE', 'productionAssi'),
|
||||
('WagonType', 'editWagonType', '*', 'ALLOW', 'ROLE', 'productionAssi');
|
|
@ -0,0 +1,4 @@
|
|||
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||
VALUES
|
||||
('InvoiceIn', 'negativeBases', 'READ', 'ALLOW', 'ROLE', 'administrative'),
|
||||
('InvoiceIn', 'negativeBasesCsv', 'READ', 'ALLOW', 'ROLE', 'administrative');
|
|
@ -501,7 +501,8 @@ INSERT INTO `vn`.`observationType`(`id`,`description`, `code`)
|
|||
(3, 'Delivery', 'delivery'),
|
||||
(4, 'SalesPerson', 'salesPerson'),
|
||||
(5, 'Administrative', 'administrative'),
|
||||
(6, 'Weight', 'weight');
|
||||
(6, 'Weight', 'weight'),
|
||||
(7, 'InvoiceOut', 'invoiceOut');
|
||||
|
||||
INSERT INTO `vn`.`addressObservation`(`id`,`addressFk`,`observationTypeFk`,`description`)
|
||||
VALUES
|
||||
|
@ -738,7 +739,9 @@ INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `des
|
|||
(9, 23, 5, 'care with the dog'),
|
||||
(10, 23, 4, 'Reclama ticket: 8'),
|
||||
(11, 24, 4, 'Reclama ticket: 7'),
|
||||
(12, 11, 3, 'Delivery after 10am');
|
||||
(12, 11, 3, 'Delivery after 10am'),
|
||||
(13, 1, 7, 'observation of ticket one'),
|
||||
(14, 2, 7, 'observation of ticket two');
|
||||
|
||||
-- FIX for state hours on local, inter_afterInsert
|
||||
-- UPDATE vncontrol.inter SET odbc_date = DATE_ADD(util.VN_CURDATE(), INTERVAL -10 SECOND);
|
||||
|
@ -838,14 +841,14 @@ INSERT INTO `vn`.`temperature`(`code`, `name`, `description`)
|
|||
('warm', 'Warm', 'Warm'),
|
||||
('cool', 'Cool', 'Cool');
|
||||
|
||||
INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `life`, `workerFk`, `isPackaging`, `temperatureFk`)
|
||||
INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `life`, `workerFk`, `isPackaging`, `temperatureFk`, `isFragile`)
|
||||
VALUES
|
||||
(1, 'CRI', 'Crisantemo', 2, 31, 35, 0, 'cool'),
|
||||
(2, 'ITG', 'Anthurium', 1, 31, 35, 0, 'cool'),
|
||||
(3, 'WPN', 'Paniculata', 2, 31, 35, 0, 'cool'),
|
||||
(4, 'PRT', 'Delivery ports', 3, NULL, 35, 1, 'warm'),
|
||||
(5, 'CON', 'Container', 3, NULL, 35, 1, 'warm'),
|
||||
(6, 'ALS', 'Alstroemeria', 1, 31, 16, 0, 'warm');
|
||||
(1, 'CRI', 'Crisantemo', 2, 31, 35, 0, 'cool', 0),
|
||||
(2, 'ITG', 'Anthurium', 1, 31, 35, 0, 'cool', 1),
|
||||
(3, 'WPN', 'Paniculata', 2, 31, 35, 0, 'cool', 0),
|
||||
(4, 'PRT', 'Delivery ports', 3, NULL, 35, 1, 'warm', 0),
|
||||
(5, 'CON', 'Container', 3, NULL, 35, 1, 'warm', 0),
|
||||
(6, 'ALS', 'Alstroemeria', 1, 31, 16, 0, 'warm', 1);
|
||||
|
||||
INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`, `hex`)
|
||||
VALUES
|
||||
|
@ -2487,9 +2490,9 @@ REPLACE INTO `vn`.`invoiceIn`(`id`, `serialNumber`,`serial`, `supplierFk`, `issu
|
|||
(9, 1009, 'R', 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1242, 1, 442, 1),
|
||||
(10, 1010, 'R', 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1243, 1, 442, 1);
|
||||
|
||||
INSERT INTO `vn`.`invoiceInConfig` (`id`, `retentionRate`, `retentionName`, `sageWithholdingFk`)
|
||||
INSERT INTO `vn`.`invoiceInConfig` (`id`, `retentionRate`, `retentionName`, `sageWithholdingFk`, `daysAgo`)
|
||||
VALUES
|
||||
(1, -2, '2% retention', 2);
|
||||
(1, -2, '2% retention', 2, 45);
|
||||
|
||||
INSERT INTO `vn`.`invoiceInDueDay`(`invoiceInFk`, `dueDated`, `bankFk`, `amount`)
|
||||
VALUES
|
||||
|
@ -2631,8 +2634,8 @@ INSERT INTO `vn`.`supplierAgencyTerm` (`agencyFk`, `supplierFk`, `minimumPackage
|
|||
|
||||
INSERT INTO `vn`.`chat` (`senderFk`, `recipient`, `dated`, `checkUserStatus`, `message`, `status`, `attempts`)
|
||||
VALUES
|
||||
(1101, '@PetterParker', util.VN_CURDATE(), 1, 'First test message', 0, 0),
|
||||
(1101, '@PetterParker', util.VN_CURDATE(), 0, 'Second test message', 0, 0);
|
||||
(1101, '@PetterParker', util.VN_CURDATE(), 1, 'First test message', 0, 'sent'),
|
||||
(1101, '@PetterParker', util.VN_CURDATE(), 0, 'Second test message', 0, 'pending');
|
||||
|
||||
|
||||
INSERT INTO `vn`.`mobileAppVersionControl` (`appName`, `version`, `isVersionCritical`)
|
||||
|
@ -2837,4 +2840,27 @@ INSERT INTO `vn`.`workerTimeControlMail` (`id`, `workerFk`, `year`, `week`, `sta
|
|||
(3, 9, 2000, 51, 'CONFIRMED', util.VN_NOW(), 1, NULL),
|
||||
(4, 9, 2001, 1, 'SENDED', util.VN_NOW(), 1, NULL);
|
||||
|
||||
INSERT INTO `vn`.`wagonConfig` (`id`, `width`, `height`, `maxWagonHeight`, `minHeightBetweenTrays`, `maxTrays`)
|
||||
VALUES
|
||||
(1, 1350, 1900, 200, 50, 6);
|
||||
|
||||
INSERT INTO `vn`.`wagonTypeColor` (`id`, `name`, `rgb`)
|
||||
VALUES
|
||||
(1, 'white', '#ffffff'),
|
||||
(2, 'red', '#ff0000'),
|
||||
(3, 'green', '#00ff00'),
|
||||
(4, 'blue', '#0000ff');
|
||||
|
||||
INSERT INTO `vn`.`wagonType` (`id`, `name`, `divisible`)
|
||||
VALUES
|
||||
(1, 'Wagon Type #1', 1);
|
||||
|
||||
INSERT INTO `vn`.`wagonTypeTray` (`id`, `typeFk`, `height`, `colorFk`)
|
||||
VALUES
|
||||
(1, 1, 100, 1),
|
||||
(2, 1, 50, 2),
|
||||
(3, 1, 0, 3);
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -524,7 +524,6 @@ export default {
|
|||
},
|
||||
itemLog: {
|
||||
anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr',
|
||||
fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) table tr:nth-child(5) td.after',
|
||||
},
|
||||
ticketSummary: {
|
||||
header: 'vn-ticket-summary > vn-card > h5',
|
||||
|
@ -1128,6 +1127,15 @@ export default {
|
|||
saveButton: 'vn-invoice-in-tax vn-submit',
|
||||
|
||||
},
|
||||
invoiceInIndex: {
|
||||
topbarSearchParams: 'vn-searchbar div.search-params > span',
|
||||
},
|
||||
invoiceInSerial: {
|
||||
daysAgo: 'vn-invoice-in-serial-search-panel vn-input-number[ng-model="$ctrl.filter.daysAgo"]',
|
||||
serial: 'vn-invoice-in-serial-search-panel vn-textfield[ng-model="$ctrl.filter.serial"]',
|
||||
chip: 'vn-chip > vn-icon',
|
||||
goToIndex: 'vn-invoice-in-serial vn-icon-button[icon="icon-invoice-in"]',
|
||||
},
|
||||
travelIndex: {
|
||||
anySearchResult: 'vn-travel-index vn-tbody > a',
|
||||
firstSearchResult: 'vn-travel-index vn-tbody > a:nth-child(1)',
|
||||
|
@ -1139,7 +1147,16 @@ export default {
|
|||
landingDate: 'vn-travel-create vn-date-picker[ng-model="$ctrl.travel.landed"]',
|
||||
warehouseOut: 'vn-travel-create vn-autocomplete[ng-model="$ctrl.travel.warehouseOutFk"]',
|
||||
warehouseIn: 'vn-travel-create vn-autocomplete[ng-model="$ctrl.travel.warehouseInFk"]',
|
||||
save: 'vn-travel-create vn-submit > button'
|
||||
save: 'vn-travel-create vn-submit > button',
|
||||
generalSearchFilter: 'vn-travel-search-panel vn-textfield[ng-model="$ctrl.search"]',
|
||||
agencyFilter: 'vn-travel-search-panel vn-autocomplete[ng-model="$ctrl.filter.agencyModeFk"]',
|
||||
warehouseOutFilter: 'vn-travel-search-panel vn-autocomplete[ng-model="$ctrl.filter.warehouseOutFk"]',
|
||||
warehouseInFilter: 'vn-travel-search-panel vn-autocomplete[ng-model="$ctrl.filter.warehouseInFk"]',
|
||||
scopeDaysFilter: 'vn-travel-search-panel vn-input-number[ng-model="$ctrl.filter.scopeDays"]',
|
||||
continentFilter: 'vn-travel-search-panel vn-autocomplete[ng-model="$ctrl.filter.continent"]',
|
||||
totalEntriesFilter: 'vn-travel-search-panel vn-input-number[ng-model="$ctrl.totalEntries"]',
|
||||
chip: 'vn-travel-search-panel vn-chip > vn-icon',
|
||||
|
||||
},
|
||||
travelExtraCommunity: {
|
||||
anySearchResult: 'vn-travel-extra-community > vn-card div > tbody > tr[ng-attr-id="{{::travel.id}}"]',
|
||||
|
|
|
@ -48,14 +48,14 @@ describe('Item log path', () => {
|
|||
await page.accessToSection('item.card.log');
|
||||
});
|
||||
|
||||
it(`should confirm the log is showing 5 entries`, async() => {
|
||||
it(`should confirm the log is showing 4 entries`, async() => {
|
||||
await page.waitForSelector(selectors.itemLog.anyLineCreated);
|
||||
const anyLineCreatedCount = await page.countElement(selectors.itemLog.anyLineCreated);
|
||||
|
||||
expect(anyLineCreatedCount).toEqual(5);
|
||||
expect(anyLineCreatedCount).toEqual(4);
|
||||
});
|
||||
|
||||
it(`should confirm the log is showing the intrastat for the created item`, async() => {
|
||||
xit(`should confirm the log is showing the intrastat for the created item`, async() => {
|
||||
const fifthLineCreatedProperty = await page
|
||||
.waitToGetProperty(selectors.itemLog.fifthLineCreatedProperty, 'innerText');
|
||||
|
||||
|
|
|
@ -197,6 +197,7 @@ describe('Ticket Edit sale path', () => {
|
|||
});
|
||||
|
||||
it('should check in the history that logs has been added', async() => {
|
||||
pending('https://redmine.verdnatura.es/issues/5455');
|
||||
await page.reload({waitUntil: ['networkidle0', 'domcontentloaded']});
|
||||
await page.waitToClick(selectors.ticketSales.firstSaleHistoryButton);
|
||||
await page.waitForSelector(selectors.ticketSales.firstSaleHistory);
|
||||
|
|
|
@ -9,7 +9,7 @@ describe('Ticket Create notes path', () => {
|
|||
browser = await getBrowser();
|
||||
page = browser.page;
|
||||
await page.loginAndModule('employee', 'ticket');
|
||||
await page.accessToSearchResult('1');
|
||||
await page.accessToSearchResult('5');
|
||||
await page.accessToSection('ticket.card.observation');
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import getBrowser from '../../helpers/puppeteer';
|
||||
|
||||
describe('InvoiceIn negative bases path', () => {
|
||||
let browser;
|
||||
let page;
|
||||
const httpRequests = [];
|
||||
|
||||
beforeAll(async() => {
|
||||
browser = await getBrowser();
|
||||
page = browser.page;
|
||||
page.on('request', req => {
|
||||
if (req.url().includes(`InvoiceIns/negativeBases`))
|
||||
httpRequests.push(req.url());
|
||||
});
|
||||
await page.loginAndModule('administrative', 'invoiceIn');
|
||||
await page.accessToSection('invoiceIn.negative-bases');
|
||||
});
|
||||
|
||||
afterAll(async() => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
it('should show negative bases in a date range', async() => {
|
||||
const request = httpRequests.find(req =>
|
||||
req.includes(`from`) && req.includes(`to`));
|
||||
|
||||
expect(request).toBeDefined();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
import selectors from '../../helpers/selectors.js';
|
||||
import getBrowser from '../../helpers/puppeteer';
|
||||
|
||||
describe('InvoiceIn serial path', () => {
|
||||
let browser;
|
||||
let page;
|
||||
let httpRequest;
|
||||
|
||||
beforeAll(async() => {
|
||||
browser = await getBrowser();
|
||||
page = browser.page;
|
||||
await page.loginAndModule('administrative', 'invoiceIn');
|
||||
await page.accessToSection('invoiceIn.serial');
|
||||
page.on('request', req => {
|
||||
if (req.url().includes(`InvoiceIns/getSerial`))
|
||||
httpRequest = req.url();
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async() => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
it('should check that passes the correct params to back', async() => {
|
||||
await page.overwrite(selectors.invoiceInSerial.daysAgo, '30');
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
expect(httpRequest).toContain('daysAgo=30');
|
||||
|
||||
await page.overwrite(selectors.invoiceInSerial.serial, 'R');
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
expect(httpRequest).toContain('serial=R');
|
||||
await page.click(selectors.invoiceInSerial.chip);
|
||||
});
|
||||
|
||||
it('should go to index and check if the search-panel has the correct params', async() => {
|
||||
await page.click(selectors.invoiceInSerial.goToIndex);
|
||||
const params = await page.$$(selectors.invoiceInIndex.topbarSearchParams);
|
||||
const serial = await params[0].getProperty('title');
|
||||
const isBooked = await params[1].getProperty('title');
|
||||
const from = await params[2].getProperty('title');
|
||||
|
||||
expect(await serial.jsonValue()).toContain('serial');
|
||||
expect(await isBooked.jsonValue()).toContain('not isBooked');
|
||||
expect(await from.jsonValue()).toContain('from');
|
||||
});
|
||||
});
|
|
@ -9,7 +9,8 @@ describe('Travel basic data path', () => {
|
|||
browser = await getBrowser();
|
||||
page = browser.page;
|
||||
await page.loginAndModule('buyer', 'travel');
|
||||
await page.accessToSearchResult('3');
|
||||
await page.write(selectors.travelIndex.generalSearchFilter, '3');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.accessToSection('travel.card.basicData');
|
||||
});
|
||||
|
||||
|
@ -89,11 +90,13 @@ describe('Travel basic data path', () => {
|
|||
});
|
||||
|
||||
it('should navigate to the travel logs', async() => {
|
||||
pending('https://redmine.verdnatura.es/issues/5455');
|
||||
await page.accessToSection('travel.card.log');
|
||||
await page.waitForState('travel.card.log');
|
||||
});
|
||||
|
||||
it('should check the 1st log contains details from the changes made', async() => {
|
||||
pending('https://redmine.verdnatura.es/issues/5455');
|
||||
const result = await page.waitToGetProperty(selectors.travelLog.firstLogFirstTD, 'innerText');
|
||||
|
||||
expect(result).toContain('new reference!');
|
||||
|
|
|
@ -9,7 +9,8 @@ describe('Travel descriptor path', () => {
|
|||
browser = await getBrowser();
|
||||
page = browser.page;
|
||||
await page.loginAndModule('buyer', 'travel');
|
||||
await page.accessToSearchResult('1');
|
||||
await page.write(selectors.travelIndex.generalSearchFilter, '1');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForState('travel.card.summary');
|
||||
});
|
||||
|
||||
|
@ -81,7 +82,8 @@ describe('Travel descriptor path', () => {
|
|||
await page.waitToClick('.cancel');
|
||||
await page.waitToClick(selectors.globalItems.homeButton);
|
||||
await page.selectModule('travel');
|
||||
await page.accessToSearchResult('3');
|
||||
await page.write(selectors.travelIndex.generalSearchFilter, '3');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForState('travel.card.summary');
|
||||
const state = await page.getState();
|
||||
|
||||
|
|
|
@ -10,7 +10,8 @@ describe('Travel thermograph path', () => {
|
|||
browser = await getBrowser();
|
||||
page = browser.page;
|
||||
await page.loginAndModule('buyer', 'travel');
|
||||
await page.accessToSearchResult('3');
|
||||
await page.write(selectors.travelIndex.generalSearchFilter, '3');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.accessToSection('travel.card.thermograph.index');
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import selectors from '../../helpers/selectors.js';
|
||||
import getBrowser from '../../helpers/puppeteer';
|
||||
|
||||
describe('Travel search panel path', () => {
|
||||
let browser;
|
||||
let page;
|
||||
let httpRequest;
|
||||
|
||||
beforeAll(async() => {
|
||||
browser = await getBrowser();
|
||||
page = browser.page;
|
||||
await page.loginAndModule('buyer', 'travel');
|
||||
page.on('request', req => {
|
||||
if (req.url().includes(`Travels/filter`))
|
||||
httpRequest = req.url();
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async() => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
it('should filter using all the fields', async() => {
|
||||
await page.click(selectors.travelIndex.chip);
|
||||
await page.write(selectors.travelIndex.generalSearchFilter, 'travel');
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
expect(httpRequest).toContain('search=travel');
|
||||
|
||||
await page.click(selectors.travelIndex.chip);
|
||||
await page.autocompleteSearch(selectors.travelIndex.agencyFilter, 'Entanglement');
|
||||
|
||||
expect(httpRequest).toContain('agencyModeFk');
|
||||
|
||||
await page.click(selectors.travelIndex.chip);
|
||||
await page.autocompleteSearch(selectors.travelIndex.warehouseOutFilter, 'Warehouse One');
|
||||
|
||||
expect(httpRequest).toContain('warehouseOutFk');
|
||||
|
||||
await page.click(selectors.travelIndex.chip);
|
||||
await page.autocompleteSearch(selectors.travelIndex.warehouseInFilter, 'Warehouse Two');
|
||||
|
||||
expect(httpRequest).toContain('warehouseInFk');
|
||||
|
||||
await page.click(selectors.travelIndex.chip);
|
||||
await page.overwrite(selectors.travelIndex.scopeDaysFilter, '15');
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
expect(httpRequest).toContain('scopeDays=15');
|
||||
|
||||
await page.click(selectors.travelIndex.chip);
|
||||
await page.autocompleteSearch(selectors.travelIndex.continentFilter, 'Asia');
|
||||
|
||||
expect(httpRequest).toContain('continent');
|
||||
|
||||
await page.click(selectors.travelIndex.chip);
|
||||
await page.write(selectors.travelIndex.totalEntriesFilter, '1');
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
expect(httpRequest).toContain('totalEntries=1');
|
||||
});
|
||||
});
|
|
@ -26,7 +26,7 @@ Value should have at most %s characters: El valor debe tener un máximo de %s ca
|
|||
Enter a new search: Introduce una nueva búsqueda
|
||||
No results: Sin resultados
|
||||
Ups! It seems there was an error: ¡Vaya! Parece que ha habido un error
|
||||
General search: Busqueda general
|
||||
General search: Búsqueda general
|
||||
January: Enero
|
||||
February: Febrero
|
||||
March: Marzo
|
||||
|
@ -42,9 +42,9 @@ December: Diciembre
|
|||
Monday: Lunes
|
||||
Tuesday: Martes
|
||||
Wednesday: Miércoles
|
||||
Thursday: Jueves
|
||||
Friday: Viernes
|
||||
Saturday: Sábado
|
||||
Thursday: Jueves
|
||||
Friday: Viernes
|
||||
Saturday: Sábado
|
||||
Sunday: Domingo
|
||||
Has delivery: Hay reparto
|
||||
Loading: Cargando
|
||||
|
@ -63,4 +63,4 @@ Loading...: Cargando...
|
|||
No results found: Sin resultados
|
||||
No data: Sin datos
|
||||
Undo changes: Deshacer cambios
|
||||
Load more results: Cargar más resultados
|
||||
Load more results: Cargar más resultados
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
$font-size: 11pt;
|
||||
$menu-width: 256px;
|
||||
$right-menu-width: 318px;
|
||||
$topbar-height: 56px;
|
||||
$mobile-width: 800px;
|
||||
$float-spacing: 20px;
|
||||
|
|
|
@ -88,13 +88,13 @@ vn-layout {
|
|||
}
|
||||
&.right-menu {
|
||||
& > vn-topbar > .end {
|
||||
width: 80px + $menu-width;
|
||||
width: 80px + $right-menu-width;
|
||||
}
|
||||
& > .main-view {
|
||||
padding-right: $menu-width;
|
||||
padding-right: $right-menu-width;
|
||||
}
|
||||
[fixed-bottom-right] {
|
||||
right: $menu-width;
|
||||
right: $right-menu-width;
|
||||
}
|
||||
}
|
||||
& > .main-view {
|
||||
|
|
|
@ -271,5 +271,6 @@
|
|||
"This locker has already been assigned": "Esta taquilla ya ha sido asignada",
|
||||
"Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº {{id}}",
|
||||
"Not exist this branch": "La rama no existe",
|
||||
"This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado"
|
||||
"This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado",
|
||||
"Insert a date range": "Inserte un rango de fechas"
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
const mysql = require('mysql');
|
||||
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
||||
const MySQL = require('loopback-connector-mysql').MySQL;
|
||||
const EnumFactory = require('loopback-connector-mysql').EnumFactory;
|
||||
const Transaction = require('loopback-connector').Transaction;
|
||||
const { Transaction, SQLConnector, ParameterizedSQL } = require('loopback-connector');
|
||||
const fs = require('fs');
|
||||
|
||||
const limitSet = new Set([
|
||||
|
@ -254,49 +253,49 @@ class VnMySQL extends MySQL {
|
|||
}
|
||||
|
||||
create(model, data, opts, cb) {
|
||||
const ctx = {data};
|
||||
const ctx = { data };
|
||||
this.invokeMethod('create',
|
||||
arguments, model, ctx, opts, cb);
|
||||
}
|
||||
|
||||
createAll(model, data, opts, cb) {
|
||||
const ctx = {data};
|
||||
const ctx = { data };
|
||||
this.invokeMethod('createAll',
|
||||
arguments, model, ctx, opts, cb);
|
||||
}
|
||||
|
||||
save(model, data, opts, cb) {
|
||||
const ctx = {data};
|
||||
const ctx = { data };
|
||||
this.invokeMethod('save',
|
||||
arguments, model, ctx, opts, cb);
|
||||
}
|
||||
|
||||
updateOrCreate(model, data, opts, cb) {
|
||||
const ctx = {data};
|
||||
const ctx = { data };
|
||||
this.invokeMethod('updateOrCreate',
|
||||
arguments, model, ctx, opts, cb);
|
||||
}
|
||||
|
||||
replaceOrCreate(model, data, opts, cb) {
|
||||
const ctx = {data};
|
||||
const ctx = { data };
|
||||
this.invokeMethod('replaceOrCreate',
|
||||
arguments, model, ctx, opts, cb);
|
||||
}
|
||||
|
||||
destroyAll(model, where, opts, cb) {
|
||||
const ctx = {where};
|
||||
const ctx = { where };
|
||||
this.invokeMethod('destroyAll',
|
||||
arguments, model, ctx, opts, cb);
|
||||
}
|
||||
|
||||
update(model, where, data, opts, cb) {
|
||||
const ctx = {where, data};
|
||||
const ctx = { where, data };
|
||||
this.invokeMethod('update',
|
||||
arguments, model, ctx, opts, cb);
|
||||
}
|
||||
|
||||
replaceById(model, id, data, opts, cb) {
|
||||
const ctx = {id, data};
|
||||
const ctx = { id, data };
|
||||
this.invokeMethod('replaceById',
|
||||
arguments, model, ctx, opts, cb);
|
||||
}
|
||||
|
@ -321,16 +320,16 @@ class VnMySQL extends MySQL {
|
|||
let tx;
|
||||
if (!opts.transaction) {
|
||||
tx = await Transaction.begin(this, {});
|
||||
opts = Object.assign({transaction: tx, httpCtx: opts.httpCtx}, opts);
|
||||
opts = Object.assign({ transaction: tx, httpCtx: opts.httpCtx }, opts);
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch old values (update|delete) or login
|
||||
let where, id, data, idName, limit, op, oldInstances, newInstances;
|
||||
const hasGrabUser = settings.log && settings.log.grabUser;
|
||||
if(hasGrabUser){
|
||||
if (hasGrabUser) {
|
||||
const userId = opts.httpCtx && opts.httpCtx.active.accessToken.userId;
|
||||
const user = await Model.app.models.Account.findById(userId, {fields: ['name']}, opts);
|
||||
const user = await Model.app.models.Account.findById(userId, { fields: ['name'] }, opts);
|
||||
await this.executeP(`CALL account.myUser_loginWithName(?)`, [user.name], opts);
|
||||
}
|
||||
else {
|
||||
|
@ -344,18 +343,18 @@ class VnMySQL extends MySQL {
|
|||
op = opMap.get(method);
|
||||
|
||||
if (!where) {
|
||||
if (id) where = {[idName]: id};
|
||||
else where = {[idName]: data[idName]};
|
||||
if (id) where = { [idName]: id };
|
||||
else where = { [idName]: data[idName] };
|
||||
}
|
||||
|
||||
// Fetch old values
|
||||
switch (op) {
|
||||
case 'update':
|
||||
case 'delete':
|
||||
// Single entity operation
|
||||
const stmt = this.buildSelectStmt(op, data, idName, model, where, limit);
|
||||
stmt.merge(`FOR UPDATE`);
|
||||
oldInstances = await this.executeStmt(stmt, opts);
|
||||
case 'update':
|
||||
case 'delete':
|
||||
// Single entity operation
|
||||
const stmt = this.buildSelectStmt(op, data, idName, model, where, limit);
|
||||
stmt.merge(`FOR UPDATE`);
|
||||
oldInstances = await this.executeStmt(stmt, opts);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -365,30 +364,30 @@ class VnMySQL extends MySQL {
|
|||
super[method].apply(this, fnArgs);
|
||||
});
|
||||
|
||||
if(hasGrabUser)
|
||||
if (hasGrabUser)
|
||||
await this.executeP(`CALL account.myUser_logout()`, null, opts);
|
||||
else {
|
||||
// Fetch new values
|
||||
const ids = [];
|
||||
|
||||
switch (op) {
|
||||
case 'insert':
|
||||
case 'update': {
|
||||
case 'insert':
|
||||
case 'update': {
|
||||
switch (method) {
|
||||
case 'createAll':
|
||||
for (const row of res[1])
|
||||
ids.push(row[idName]);
|
||||
break;
|
||||
case 'create':
|
||||
ids.push(res[1]);
|
||||
break;
|
||||
case 'update':
|
||||
if (data[idName] != null)
|
||||
ids.push(data[idName]);
|
||||
break;
|
||||
case 'createAll':
|
||||
for (const row of res[1])
|
||||
ids.push(row[idName]);
|
||||
break;
|
||||
case 'create':
|
||||
ids.push(res[1]);
|
||||
break;
|
||||
case 'update':
|
||||
if (data[idName] != null)
|
||||
ids.push(data[idName]);
|
||||
break;
|
||||
}
|
||||
|
||||
const newWhere = ids.length ? {[idName]: ids} : where;
|
||||
const newWhere = ids.length ? { [idName]: ids } : where;
|
||||
|
||||
const stmt = this.buildSelectStmt(op, data, idName, model, newWhere, limit);
|
||||
newInstances = await this.executeStmt(stmt, opts);
|
||||
|
@ -431,9 +430,9 @@ class VnMySQL extends MySQL {
|
|||
|
||||
const stmt = new ParameterizedSQL(
|
||||
'SELECT ' +
|
||||
this.buildColumnNames(model, {fields}) +
|
||||
' FROM ' +
|
||||
this.tableEscaped(model)
|
||||
this.buildColumnNames(model, { fields }) +
|
||||
' FROM ' +
|
||||
this.tableEscaped(model)
|
||||
);
|
||||
stmt.merge(this.buildWhere(model, where));
|
||||
if (limit) stmt.merge(`LIMIT 1`);
|
||||
|
@ -505,8 +504,8 @@ class VnMySQL extends MySQL {
|
|||
if (oldI) {
|
||||
Object.keys(oldI).forEach(prop => {
|
||||
const hasChanges = oldI[prop] instanceof Date ?
|
||||
oldI[prop]?.getTime() != newI[prop]?.getTime() :
|
||||
oldI[prop] != newI[prop];
|
||||
oldI[prop]?.getTime() != newI[prop]?.getTime() :
|
||||
oldI[prop] != newI[prop];
|
||||
|
||||
if (!hasChanges) {
|
||||
delete oldI[prop];
|
||||
|
@ -537,13 +536,13 @@ exports.initialize = function initialize(dataSource, callback) {
|
|||
modelBuilder.defineValueType.bind(modelBuilder) :
|
||||
modelBuilder.constructor.registerType.bind(modelBuilder.constructor);
|
||||
|
||||
defineType(function Point() {});
|
||||
defineType(function Point() { });
|
||||
|
||||
dataSource.EnumFactory = EnumFactory;
|
||||
|
||||
if (callback) {
|
||||
if (dataSource.settings.lazyConnect) {
|
||||
process.nextTick(function() {
|
||||
process.nextTick(function () {
|
||||
callback();
|
||||
});
|
||||
} else
|
||||
|
@ -551,13 +550,13 @@ exports.initialize = function initialize(dataSource, callback) {
|
|||
}
|
||||
};
|
||||
|
||||
MySQL.prototype.connect = function(callback) {
|
||||
MySQL.prototype.connect = function (callback) {
|
||||
const self = this;
|
||||
const options = generateOptions(this.settings);
|
||||
|
||||
if (this.client) {
|
||||
if (callback) {
|
||||
process.nextTick(function() {
|
||||
process.nextTick(function () {
|
||||
callback(null, self.client);
|
||||
});
|
||||
}
|
||||
|
@ -566,7 +565,7 @@ MySQL.prototype.connect = function(callback) {
|
|||
|
||||
function connectionHandler(options, callback) {
|
||||
const client = mysql.createPool(options);
|
||||
client.getConnection(function(err, connection) {
|
||||
client.getConnection(function (err, connection) {
|
||||
const conn = connection;
|
||||
if (!err) {
|
||||
if (self.debug)
|
||||
|
@ -645,3 +644,31 @@ function generateOptions(settings) {
|
|||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
SQLConnector.prototype.all = function find(model, filter, options, cb) {
|
||||
const self = this;
|
||||
// Order by id if no order is specified
|
||||
filter = filter || {};
|
||||
const stmt = this.buildSelect(model, filter, options);
|
||||
this.execute(stmt.sql, stmt.params, options, function (err, data) {
|
||||
if (err) {
|
||||
return cb(err, []);
|
||||
}
|
||||
|
||||
try {
|
||||
const objs = data.map(function (obj) {
|
||||
return self.fromRow(model, obj);
|
||||
});
|
||||
if (filter && filter.include) {
|
||||
self.getModelDefinition(model).model.include(
|
||||
objs, filter.include, options, cb,
|
||||
);
|
||||
} else {
|
||||
cb(null, objs);
|
||||
}
|
||||
} catch (error) {
|
||||
cb(error, [])
|
||||
}
|
||||
});
|
||||
};
|
|
@ -26,13 +26,13 @@ module.exports = Self => {
|
|||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const state = await models.ClaimState.findById(id, {
|
||||
include: {
|
||||
relation: 'writeRole'
|
||||
}
|
||||
}, myOptions);
|
||||
const roleWithGrants = state && state.writeRole().name;
|
||||
return await models.Account.hasRole(userId, roleWithGrants, myOptions);
|
||||
|
||||
const state = await models.ClaimState.findById(id, {
|
||||
include: {
|
||||
relation: 'writeRole'
|
||||
}
|
||||
}, myOptions);
|
||||
const roleWithGrants = state && state.writeRole().name;
|
||||
return await models.Account.hasRole(userId, roleWithGrants, myOptions);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
||||
const buildFilter = require('vn-loopback/util/filter').buildFilter;
|
||||
const {mergeFilters, mergeWhere} = require('vn-loopback/util/filter');
|
||||
const { mergeFilters, mergeWhere } = require('vn-loopback/util/filter');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('logs', {
|
||||
|
@ -12,27 +12,27 @@ module.exports = Self => {
|
|||
arg: 'id',
|
||||
type: 'Number',
|
||||
description: 'The claim id',
|
||||
http: {source: 'path'}
|
||||
http: { source: 'path' }
|
||||
},
|
||||
{
|
||||
arg: 'filter',
|
||||
type: 'object',
|
||||
http: {source: 'query'}
|
||||
http: { source: 'query' }
|
||||
},
|
||||
{
|
||||
arg: 'search',
|
||||
type: 'string',
|
||||
http: {source: 'query'}
|
||||
http: { source: 'query' }
|
||||
},
|
||||
{
|
||||
arg: 'userFk',
|
||||
type: 'number',
|
||||
http: {source: 'query'}
|
||||
http: { source: 'query' }
|
||||
},
|
||||
{
|
||||
arg: 'created',
|
||||
type: 'date',
|
||||
http: {source: 'query'}
|
||||
http: { source: 'query' }
|
||||
},
|
||||
],
|
||||
returns: {
|
||||
|
@ -45,7 +45,7 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.logs = async(ctx, id, filter, options) => {
|
||||
Self.logs = async (ctx, id, filter, options) => {
|
||||
const conn = Self.dataSource.connector;
|
||||
const args = ctx.args;
|
||||
const myOptions = {};
|
||||
|
@ -56,25 +56,25 @@ module.exports = Self => {
|
|||
|
||||
let where = buildFilter(args, (param, value) => {
|
||||
switch (param) {
|
||||
case 'search':
|
||||
return {
|
||||
or: [
|
||||
{changedModel: {like: `%${value}%`}},
|
||||
{oldInstance: {like: `%${value}%`}}
|
||||
]
|
||||
};
|
||||
case 'userFk':
|
||||
return {'cl.userFk': value};
|
||||
case 'created':
|
||||
value.setHours(0, 0, 0, 0);
|
||||
to = new Date(value);
|
||||
to.setHours(23, 59, 59, 999);
|
||||
case 'search':
|
||||
return {
|
||||
or: [
|
||||
{ changedModel: { like: `%${value}%` } },
|
||||
{ oldInstance: { like: `%${value}%` } }
|
||||
]
|
||||
};
|
||||
case 'userFk':
|
||||
return { 'cl.userFk': value };
|
||||
case 'created':
|
||||
value.setHours(0, 0, 0, 0);
|
||||
to = new Date(value);
|
||||
to.setHours(23, 59, 59, 999);
|
||||
|
||||
return {creationDate: {between: [value, to]}};
|
||||
return { creationDate: { between: [value, to] } };
|
||||
}
|
||||
});
|
||||
where = mergeWhere(where, {['cl.originFk']: id});
|
||||
filter = mergeFilters(args.filter, {where});
|
||||
where = mergeWhere(where, { ['cl.originFk']: id });
|
||||
filter = mergeFilters(args.filter, { where });
|
||||
|
||||
const stmts = [];
|
||||
|
||||
|
@ -102,8 +102,8 @@ module.exports = Self => {
|
|||
const logs = [];
|
||||
for (const row of result) {
|
||||
const changes = [];
|
||||
const oldInstance = JSON.parse(row.oldInstance);
|
||||
const newInstance = JSON.parse(row.newInstance);
|
||||
const oldInstance = JSON.parse(row.oldInstance) || {};
|
||||
const newInstance = JSON.parse(row.newInstance) || {};
|
||||
const mergedProperties = [...Object.keys(oldInstance), ...Object.keys(newInstance)];
|
||||
const properties = new Set(mergedProperties);
|
||||
for (const property of properties) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('claim regularizeClaim()', () => {
|
||||
const userId = 18;
|
||||
|
@ -39,6 +40,20 @@ describe('claim regularizeClaim()', () => {
|
|||
return await models.ClaimEnd.create(claimEnds, options);
|
||||
}
|
||||
|
||||
beforeAll(async() => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
it('should send a chat message with value "Trash" and then change claim state to resolved', async() => {
|
||||
const tx = await models.Claim.beginTransaction({});
|
||||
|
||||
|
|
|
@ -151,7 +151,7 @@ class Controller extends Section {
|
|||
isClaimEditable() {
|
||||
if (!this.claim) return;
|
||||
|
||||
this.$http.get(`ClaimStates/${this.claim.id}/isEditable`).then(res => {
|
||||
this.$http.get(`ClaimStates/${this.claim.claimStateFk}/isEditable`).then(res => {
|
||||
this.isRewritable = res.data;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -22,7 +22,8 @@ describe('claim', () => {
|
|||
controller = $componentController('vnClaimDetail', {$element, $scope});
|
||||
controller.claim = {
|
||||
ticketFk: 1,
|
||||
id: 2}
|
||||
id: 2,
|
||||
claimStateFk: 2}
|
||||
;
|
||||
controller.salesToClaim = [{saleFk: 1}, {saleFk: 2}];
|
||||
controller.salesClaimed = [{id: 1, sale: {}}];
|
||||
|
|
|
@ -63,4 +63,4 @@ Consumption: Consumo
|
|||
Compensation Account: Cuenta para compensar
|
||||
Amount to return: Cantidad a devolver
|
||||
Delivered amount: Cantidad entregada
|
||||
Unpaid: Impagado
|
||||
Unpaid: Impagado
|
||||
|
|
|
@ -1,6 +1,22 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('Buy editLatestsBuys()', () => {
|
||||
beforeAll(async() => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
it('should change the value of a given column for the selected buys', async() => {
|
||||
const tx = await models.Buy.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
||||
const buildFilter = require('vn-loopback/util/filter').buildFilter;
|
||||
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('getSerial', {
|
||||
description: 'Return invoiceIn serial',
|
||||
accessType: 'READ',
|
||||
accepts: [{
|
||||
arg: 'filter',
|
||||
type: 'object'
|
||||
}, {
|
||||
arg: 'daysAgo',
|
||||
type: 'number',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'serial',
|
||||
type: 'string'
|
||||
}],
|
||||
returns: {
|
||||
type: 'object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: '/getSerial',
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.getSerial = async(ctx, options) => {
|
||||
const conn = Self.dataSource.connector;
|
||||
const args = ctx.args;
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const issued = Date.vnNew();
|
||||
const where = buildFilter(args, (param, value) => {
|
||||
switch (param) {
|
||||
case 'daysAgo':
|
||||
issued.setDate(issued.getDate() - value);
|
||||
return {'i.issued': {gte: issued}};
|
||||
case 'serial':
|
||||
return {'i.serial': {like: `%${value}%`}};
|
||||
}
|
||||
});
|
||||
|
||||
filter = mergeFilters(args.filter, {where});
|
||||
|
||||
const stmt = new ParameterizedSQL(
|
||||
`SELECT i.serial, SUM(IF(i.isBooked, 0,1)) pending, COUNT(*) total
|
||||
FROM vn.invoiceIn i`
|
||||
);
|
||||
|
||||
stmt.merge(conn.makeWhere(filter.where));
|
||||
stmt.merge(`GROUP BY i.serial`);
|
||||
stmt.merge(conn.makeOrderBy(filter.order));
|
||||
stmt.merge(conn.makeLimit(filter));
|
||||
|
||||
const result = await conn.executeStmt(stmt, myOptions);
|
||||
|
||||
return result;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,112 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('negativeBases', {
|
||||
description: 'Find all negative bases',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'from',
|
||||
type: 'date',
|
||||
description: 'From date'
|
||||
},
|
||||
{
|
||||
arg: 'to',
|
||||
type: 'date',
|
||||
description: 'To date'
|
||||
},
|
||||
{
|
||||
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: `/negativeBases`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.negativeBases = async(ctx, options) => {
|
||||
const conn = Self.dataSource.connector;
|
||||
const args = ctx.args;
|
||||
|
||||
if (!args.from || !args.to)
|
||||
throw new UserError(`Insert a date range`);
|
||||
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const stmts = [];
|
||||
let stmt;
|
||||
stmts.push(`DROP TEMPORARY TABLE IF EXISTS tmp.ticket`);
|
||||
|
||||
stmts.push(new ParameterizedSQL(
|
||||
`CREATE TEMPORARY TABLE tmp.ticket
|
||||
(KEY (ticketFk))
|
||||
ENGINE = MEMORY
|
||||
SELECT id ticketFk
|
||||
FROM ticket t
|
||||
WHERE shipped BETWEEN ? AND ?
|
||||
AND refFk IS NULL`, [args.from, args.to]));
|
||||
stmts.push(`CALL vn.ticket_getTax(NULL)`);
|
||||
stmts.push(`DROP TEMPORARY TABLE IF EXISTS tmp.filter`);
|
||||
stmts.push(new ParameterizedSQL(
|
||||
`CREATE TEMPORARY TABLE tmp.filter
|
||||
ENGINE = MEMORY
|
||||
SELECT
|
||||
co.code company,
|
||||
cou.country,
|
||||
c.id clientId,
|
||||
c.socialName clientSocialName,
|
||||
SUM(s.quantity * s.price * ( 100 - s.discount ) / 100) amount,
|
||||
negativeBase.taxableBase,
|
||||
negativeBase.ticketFk,
|
||||
c.isActive,
|
||||
c.hasToInvoice,
|
||||
c.isTaxDataChecked,
|
||||
w.id comercialId,
|
||||
CONCAT(w.firstName, ' ', w.lastName) comercialName
|
||||
FROM vn.ticket t
|
||||
JOIN vn.company co ON co.id = t.companyFk
|
||||
JOIN vn.sale s ON s.ticketFk = t.id
|
||||
JOIN vn.client c ON c.id = t.clientFk
|
||||
JOIN vn.country cou ON cou.id = c.countryFk
|
||||
LEFT JOIN vn.worker w ON w.id = c.salesPersonFk
|
||||
LEFT JOIN (
|
||||
SELECT ticketFk, taxableBase
|
||||
FROM tmp.ticketAmount
|
||||
GROUP BY ticketFk
|
||||
HAVING taxableBase < 0
|
||||
) negativeBase ON negativeBase.ticketFk = t.id
|
||||
WHERE t.shipped BETWEEN ? AND ?
|
||||
AND t.refFk IS NULL
|
||||
AND c.typeFk IN ('normal','trust')
|
||||
GROUP BY t.clientFk, negativeBase.taxableBase
|
||||
HAVING amount <> 0`, [args.from, args.to]));
|
||||
|
||||
stmt = new ParameterizedSQL(`
|
||||
SELECT f.*
|
||||
FROM tmp.filter f`);
|
||||
|
||||
stmt.merge(conn.makeWhere(args.filter.where));
|
||||
stmt.merge(conn.makeOrderBy(args.filter.order));
|
||||
|
||||
const negativeBasesIndex = stmts.push(stmt) - 1;
|
||||
|
||||
stmts.push(`DROP TEMPORARY TABLE tmp.filter, tmp.ticket, tmp.ticketTax, tmp.ticketAmount`);
|
||||
|
||||
const sql = ParameterizedSQL.join(stmts, ';');
|
||||
const result = await conn.executeStmt(sql, myOptions);
|
||||
|
||||
return negativeBasesIndex === 0 ? result : result[negativeBasesIndex];
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
const {toCSV} = require('vn-loopback/util/csv');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('negativeBasesCsv', {
|
||||
description: 'Returns the negative bases as .csv',
|
||||
accessType: 'READ',
|
||||
accepts: [{
|
||||
arg: 'negativeBases',
|
||||
type: ['object'],
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'from',
|
||||
type: 'date',
|
||||
description: 'From date'
|
||||
},
|
||||
{
|
||||
arg: 'to',
|
||||
type: 'date',
|
||||
description: 'To date'
|
||||
}],
|
||||
returns: [
|
||||
{
|
||||
arg: 'body',
|
||||
type: 'file',
|
||||
root: true
|
||||
}, {
|
||||
arg: 'Content-Type',
|
||||
type: 'String',
|
||||
http: {target: 'header'}
|
||||
}, {
|
||||
arg: 'Content-Disposition',
|
||||
type: 'String',
|
||||
http: {target: 'header'}
|
||||
}
|
||||
],
|
||||
http: {
|
||||
path: '/negativeBasesCsv',
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.negativeBasesCsv = async ctx => {
|
||||
const args = ctx.args;
|
||||
const content = toCSV(args.negativeBases);
|
||||
|
||||
return [
|
||||
content,
|
||||
'text/csv',
|
||||
`attachment; filename="negative-bases-${new Date(args.from).toLocaleDateString()}-${new Date(args.to).toLocaleDateString()}.csv"`
|
||||
];
|
||||
};
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('invoiceIn getSerial()', () => {
|
||||
it('should check that returns without serial param', async() => {
|
||||
const ctx = {args: {daysAgo: 45}};
|
||||
const result = await models.InvoiceIn.getSerial(ctx);
|
||||
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should check that returns with serial param', async() => {
|
||||
const ctx = {args: {daysAgo: 45, serial: 'R'}};
|
||||
const result = await models.InvoiceIn.getSerial(ctx);
|
||||
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should check that returns with non exist serial param', async() => {
|
||||
const ctx = {args: {daysAgo: 45, serial: 'Mock serial'}};
|
||||
const result = await models.InvoiceIn.getSerial(ctx);
|
||||
|
||||
expect(result.length).toEqual(0);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('invoiceIn negativeBases()', () => {
|
||||
it('should return all negative bases in a date range', async() => {
|
||||
const tx = await models.InvoiceIn.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
const ctx = {
|
||||
args: {
|
||||
from: new Date().setMonth(new Date().getMonth() - 12),
|
||||
to: new Date(),
|
||||
filter: {}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await models.InvoiceIn.negativeBases(ctx, options);
|
||||
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw an error if a date range is not in args', async() => {
|
||||
let error;
|
||||
const tx = await models.InvoiceIn.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
const ctx = {
|
||||
args: {
|
||||
filter: {}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await models.InvoiceIn.negativeBases(ctx, options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`Insert a date range`);
|
||||
});
|
||||
});
|
|
@ -17,6 +17,9 @@
|
|||
},
|
||||
"retentionName": {
|
||||
"type": "string"
|
||||
},
|
||||
"daysAgo": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
|
|
|
@ -6,4 +6,7 @@ module.exports = Self => {
|
|||
require('../methods/invoice-in/getTotals')(Self);
|
||||
require('../methods/invoice-in/invoiceInPdf')(Self);
|
||||
require('../methods/invoice-in/invoiceInEmail')(Self);
|
||||
require('../methods/invoice-in/getSerial')(Self);
|
||||
require('../methods/invoice-in/negativeBases')(Self);
|
||||
require('../methods/invoice-in/negativeBasesCsv')(Self);
|
||||
};
|
||||
|
|
|
@ -13,3 +13,6 @@ import './dueDay';
|
|||
import './intrastat';
|
||||
import './create';
|
||||
import './log';
|
||||
import './serial';
|
||||
import './serial-search-panel';
|
||||
import './negative-bases';
|
||||
|
|
|
@ -7,6 +7,7 @@ Foreign value: Divisa
|
|||
InvoiceIn: Facturas recibidas
|
||||
InvoiceIn cloned: Factura clonada
|
||||
InvoiceIn deleted: Factura eliminada
|
||||
InvoiceIn Serial: Facturas por series
|
||||
Invoice list: Listado de facturas recibidas
|
||||
InvoiceIn booked: Factura contabilizada
|
||||
Net: Neto
|
||||
|
@ -22,3 +23,5 @@ Total stems: Total tallos
|
|||
Show agricultural receipt as PDF: Ver recibo agrícola como PDF
|
||||
Send agricultural receipt as PDF: Enviar recibo agrícola como PDF
|
||||
New InvoiceIn: Nueva Factura
|
||||
Days ago: Últimos días
|
||||
Negative bases: Bases negativas
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
<vn-crud-model
|
||||
vn-id="model"
|
||||
url="InvoiceIns/negativeBases"
|
||||
auto-load="true"
|
||||
params="$ctrl.params">
|
||||
</vn-crud-model>
|
||||
<vn-portal slot="topbar">
|
||||
</vn-portal>
|
||||
<vn-card>
|
||||
<smart-table
|
||||
model="model"
|
||||
options="$ctrl.smartTableOptions"
|
||||
expr-builder="$ctrl.exprBuilder(param, value)">
|
||||
<slot-actions>
|
||||
<vn-date-picker
|
||||
vn-one
|
||||
label="From"
|
||||
ng-model="$ctrl.params.from"
|
||||
on-change="model.refresh()">
|
||||
</vn-date-picker>
|
||||
<vn-date-picker
|
||||
vn-one
|
||||
label="To"
|
||||
ng-model="$ctrl.params.to"
|
||||
on-change="model.refresh()">
|
||||
</vn-date-picker>
|
||||
<vn-button
|
||||
disabled="model._orgData.length == 0"
|
||||
icon="download"
|
||||
ng-click="$ctrl.downloadCSV()"
|
||||
vn-tooltip="Download as CSV">
|
||||
</vn-button>
|
||||
</slot-actions>
|
||||
<slot-table>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th field="company">
|
||||
<span translate>Company</span>
|
||||
</th>
|
||||
<th field="country">
|
||||
<span translate>Country</span>
|
||||
</th>
|
||||
<th field="clientId">
|
||||
<span translate>Id Client</span>
|
||||
</th>
|
||||
<th field="clientSocialName">
|
||||
<span translate>Client</span>
|
||||
</th>
|
||||
<th field="amount">
|
||||
<span translate>Amount</span>
|
||||
</th>
|
||||
<th field="taxableBase">
|
||||
<span translate>Base</span>
|
||||
</th>
|
||||
<th field="ticketFk">
|
||||
<span translate>Id Ticket</span>
|
||||
</th>
|
||||
<th field="isActive">
|
||||
<span translate>Active</span>
|
||||
</th>
|
||||
<th field="hasToInvoice">
|
||||
<span translate>Has To Invoice</span>
|
||||
</th>
|
||||
<th field="isTaxDataChecked">
|
||||
<span translate>Verified data</span>
|
||||
</th>
|
||||
<th field="comercialName">
|
||||
<span translate>Comercial</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="client in model.data">
|
||||
<td>{{client.company | dashIfEmpty}}</td>
|
||||
<td>{{client.country | dashIfEmpty}}</td>
|
||||
<td>
|
||||
<vn-span
|
||||
class="link"
|
||||
ng-click="clientDescriptor.show($event, client.clientId)">
|
||||
{{::client.clientId | dashIfEmpty}}
|
||||
</vn-span>
|
||||
</td>
|
||||
<td>{{client.clientSocialName | dashIfEmpty}}</td>
|
||||
<td>{{client.amount | currency: 'EUR':2 | dashIfEmpty}}</td>
|
||||
<td>{{client.taxableBase | dashIfEmpty}}</td>
|
||||
<td>
|
||||
<vn-span
|
||||
class="link"
|
||||
ng-click="ticketDescriptor.show($event, client.ticketFk)">
|
||||
{{::client.ticketFk | dashIfEmpty}}
|
||||
</vn-span>
|
||||
</td>
|
||||
<td>
|
||||
<vn-check
|
||||
disabled="true"
|
||||
ng-model="client.isActive">
|
||||
</vn-check>
|
||||
</td>
|
||||
<td>
|
||||
<vn-check
|
||||
disabled="true"
|
||||
ng-model="client.hasToInvoice">
|
||||
</vn-check>
|
||||
</td>
|
||||
<td>
|
||||
<vn-check
|
||||
disabled="true"
|
||||
ng-model="client.isTaxDataChecked">
|
||||
</vn-check>
|
||||
</td>
|
||||
<td>
|
||||
<vn-span
|
||||
class="link"
|
||||
ng-click="workerDescriptor.show($event, client.comercialId)">
|
||||
{{::client.comercialName | dashIfEmpty}}
|
||||
</vn-span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</slot-table>
|
||||
</smart-table>
|
||||
</vn-card>
|
||||
<vn-ticket-descriptor-popover
|
||||
vn-id="ticket-descriptor">
|
||||
</vn-ticket-descriptor-popover>
|
||||
<vn-client-descriptor-popover
|
||||
vn-id="client-descriptor">
|
||||
</vn-client-descriptor-popover>
|
||||
<vn-worker-descriptor-popover
|
||||
vn-id="worker-descriptor">
|
||||
</vn-worker-descriptor-popover>
|
|
@ -0,0 +1,84 @@
|
|||
import ngModule from '../module';
|
||||
import Section from 'salix/components/section';
|
||||
import './style.scss';
|
||||
|
||||
export default class Controller extends Section {
|
||||
constructor($element, $, vnReport) {
|
||||
super($element, $);
|
||||
|
||||
this.vnReport = vnReport;
|
||||
const now = new Date();
|
||||
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||
this.params = {
|
||||
from: firstDayOfMonth,
|
||||
to: lastDayOfMonth
|
||||
};
|
||||
this.$checkAll = false;
|
||||
|
||||
this.smartTableOptions = {
|
||||
activeButtons: {
|
||||
search: true,
|
||||
}, columns: [
|
||||
{
|
||||
field: 'isActive',
|
||||
searchable: false
|
||||
},
|
||||
{
|
||||
field: 'hasToInvoice',
|
||||
searchable: false
|
||||
},
|
||||
{
|
||||
field: 'isTaxDataChecked',
|
||||
searchable: false
|
||||
},
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
exprBuilder(param, value) {
|
||||
switch (param) {
|
||||
case 'company':
|
||||
return {'company': value};
|
||||
case 'country':
|
||||
return {'country': value};
|
||||
case 'clientId':
|
||||
return {'clientId': value};
|
||||
case 'clientSocialName':
|
||||
return {'clientSocialName': value};
|
||||
case 'amount':
|
||||
return {'amount': value};
|
||||
case 'taxableBase':
|
||||
return {'taxableBase': value};
|
||||
case 'ticketFk':
|
||||
return {'ticketFk': value};
|
||||
case 'comercialName':
|
||||
return {'comercialName': value};
|
||||
}
|
||||
}
|
||||
|
||||
downloadCSV() {
|
||||
const data = [];
|
||||
this.$.model._orgData.forEach(element => {
|
||||
data.push(Object.keys(element).map(key => {
|
||||
return {newName: this.$t(key), value: element[key]};
|
||||
}).filter(item => item !== null)
|
||||
.reduce((result, item) => {
|
||||
result[item.newName] = item.value;
|
||||
return result;
|
||||
}, {}));
|
||||
});
|
||||
this.vnReport.show('InvoiceIns/negativeBasesCsv', {
|
||||
negativeBases: data,
|
||||
from: this.params.from,
|
||||
to: this.params.to
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$element', '$scope', 'vnReport'];
|
||||
|
||||
ngModule.vnComponent('vnNegativeBases', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
Has To Invoice: Facturar
|
||||
Download as CSV: Descargar como CSV
|
||||
company: Compañía
|
||||
country: País
|
||||
clientId: Id Cliente
|
||||
clientSocialName: Cliente
|
||||
amount: Importe
|
||||
taxableBase: Base
|
||||
ticketFk: Id Ticket
|
||||
isActive: Activo
|
||||
hasToInvoice: Facturar
|
||||
isTaxDataChecked: Datos comprobados
|
||||
comercialId: Id Comercial
|
||||
comercialName: Comercial
|
|
@ -0,0 +1,10 @@
|
|||
@import "./variables";
|
||||
|
||||
vn-negative-bases {
|
||||
vn-date-picker{
|
||||
padding-right: 5%;
|
||||
}
|
||||
slot-actions{
|
||||
align-items: center;
|
||||
}
|
||||
}
|
|
@ -9,10 +9,9 @@
|
|||
],
|
||||
"menus": {
|
||||
"main": [
|
||||
{
|
||||
"state": "invoiceIn.index",
|
||||
"icon": "icon-invoice-in"
|
||||
}
|
||||
{ "state": "invoiceIn.index", "icon": "icon-invoice-in"},
|
||||
{ "state": "invoiceIn.serial", "icon": "icon-invoice-in"},
|
||||
{ "state": "invoiceIn.negative-bases", "icon": "icon-ticket"}
|
||||
],
|
||||
"card": [
|
||||
{
|
||||
|
@ -54,6 +53,24 @@
|
|||
"administrative"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "/negative-bases",
|
||||
"state": "invoiceIn.negative-bases",
|
||||
"component": "vn-negative-bases",
|
||||
"description": "Negative bases",
|
||||
"acl": [
|
||||
"administrative"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "/serial",
|
||||
"state": "invoiceIn.serial",
|
||||
"component": "vn-invoice-in-serial",
|
||||
"description": "InvoiceIn Serial",
|
||||
"acl": [
|
||||
"administrative"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "/:id",
|
||||
"state": "invoiceIn.card",
|
||||
|
@ -133,4 +150,4 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<vn-side-menu side="right">
|
||||
<vn-horizontal class="input">
|
||||
<vn-input-number
|
||||
label="Days ago"
|
||||
ng-model="$ctrl.filter.daysAgo"
|
||||
vn-focus
|
||||
ng-keydown="$ctrl.onKeyPress($event)"
|
||||
min="0">
|
||||
</vn-input-number>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="input">
|
||||
<vn-textfield
|
||||
label="Serial"
|
||||
ng-model="$ctrl.filter.serial"
|
||||
ng-keydown="$ctrl.onKeyPress($event)">
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<div class="chips">
|
||||
<vn-chip
|
||||
ng-if="$ctrl.filter.serial"
|
||||
removable="true"
|
||||
on-remove="$ctrl.removeItemFilter('serial')"
|
||||
class="colored">
|
||||
<span>{{$ctrl.$t('Serial')}}: {{$ctrl.filter.serial}}</span>
|
||||
</vn-chip>
|
||||
</div>
|
||||
</vn-side-menu>
|
|
@ -0,0 +1,44 @@
|
|||
import ngModule from '../module';
|
||||
import SearchPanel from 'core/components/searchbar/search-panel';
|
||||
import './style.scss';
|
||||
|
||||
class Controller extends SearchPanel {
|
||||
constructor($element, $) {
|
||||
super($element, $);
|
||||
this.filter = {};
|
||||
const filter = {
|
||||
fields: ['daysAgo']
|
||||
};
|
||||
this.$http.get('InvoiceInConfigs', {filter}).then(res => {
|
||||
if (res.data) {
|
||||
this.invoiceInConfig = res.data[0];
|
||||
this.addFilters();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeItemFilter(param) {
|
||||
this.filter[param] = null;
|
||||
this.addFilters();
|
||||
}
|
||||
|
||||
onKeyPress($event) {
|
||||
if ($event.key === 'Enter')
|
||||
this.addFilters();
|
||||
}
|
||||
|
||||
addFilters() {
|
||||
if (!this.filter.daysAgo)
|
||||
this.filter.daysAgo = this.invoiceInConfig.daysAgo;
|
||||
|
||||
return this.model.addFilter({}, this.filter);
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.component('vnInvoiceInSerialSearchPanel', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller,
|
||||
bindings: {
|
||||
model: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
import './index.js';
|
||||
|
||||
describe('InvoiceIn', () => {
|
||||
describe('Component serial-search-panel', () => {
|
||||
let controller;
|
||||
let $scope;
|
||||
|
||||
beforeEach(ngModule('invoiceIn'));
|
||||
|
||||
beforeEach(inject(($componentController, $rootScope) => {
|
||||
$scope = $rootScope.$new();
|
||||
const $element = angular.element('<vn-invoice-in-serial-search-panel></vn-invoice-in-serial-search-panel>');
|
||||
controller = $componentController('vnInvoiceInSerialSearchPanel', {$element, $scope});
|
||||
controller.model = {
|
||||
addFilter: jest.fn(),
|
||||
};
|
||||
controller.invoiceInConfig = {
|
||||
daysAgo: 45,
|
||||
};
|
||||
}));
|
||||
|
||||
describe('addFilters()', () => {
|
||||
it('should add default daysAgo if it is not already set', () => {
|
||||
controller.filter = {
|
||||
serial: 'R',
|
||||
};
|
||||
controller.addFilters();
|
||||
|
||||
expect(controller.filter.daysAgo).toEqual(controller.invoiceInConfig.daysAgo);
|
||||
});
|
||||
|
||||
it('should not add default daysAgo if it is already set', () => {
|
||||
controller.filter = {
|
||||
daysAgo: 1,
|
||||
serial: 'R',
|
||||
};
|
||||
controller.addFilters();
|
||||
|
||||
expect(controller.filter.daysAgo).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
@import "variables";
|
||||
|
||||
vn-invoice-in-serial-search-panel vn-side-menu div {
|
||||
& > .input {
|
||||
padding-left: $spacing-md;
|
||||
padding-right: $spacing-md;
|
||||
border-color: $color-spacer;
|
||||
border-bottom: $border-thin;
|
||||
}
|
||||
& > .horizontal {
|
||||
grid-auto-flow: column;
|
||||
grid-column-gap: $spacing-sm;
|
||||
align-items: center;
|
||||
}
|
||||
& > .chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: $spacing-md;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
border-color: $color-spacer;
|
||||
border-top: $border-thin;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<vn-crud-model
|
||||
vn-id="model"
|
||||
url="InvoiceIns/getSerial"
|
||||
limit="20">
|
||||
</vn-crud-model>
|
||||
<vn-portal slot="topbar">
|
||||
</vn-portal>
|
||||
<vn-invoice-in-serial-search-panel
|
||||
model="model">
|
||||
</vn-invoice-in-serial-search-panel>
|
||||
<vn-data-viewer
|
||||
model="model"
|
||||
class="vn-w-lg">
|
||||
<vn-card>
|
||||
<vn-table model="model">
|
||||
<vn-thead>
|
||||
<vn-tr>
|
||||
<vn-th field="serial">Serial</vn-th>
|
||||
<vn-th field="pending">Pending</vn-th>
|
||||
<vn-th field="total">Total</vn-th>
|
||||
<vn-th></vn-th>
|
||||
</vn-tr>
|
||||
</vn-thead>
|
||||
<vn-tbody>
|
||||
<vn-tr ng-repeat="invoiceIn in model.data">
|
||||
<vn-td>{{::invoiceIn.serial}}</vn-td>
|
||||
<vn-td>{{::invoiceIn.pending}}</vn-td>
|
||||
<vn-td>{{::invoiceIn.total}}</vn-td>
|
||||
<vn-td shrink>
|
||||
<vn-icon-button
|
||||
vn-click-stop="$ctrl.goToIndex(model.userParams.daysAgo, invoiceIn.serial)"
|
||||
vn-tooltip="Go to InvoiceIn"
|
||||
icon="icon-invoice-in">
|
||||
</vn-icon-button>
|
||||
</vn-td>
|
||||
</vn-tr>
|
||||
</vn-tbody>
|
||||
</vn-table>
|
||||
</vn-card>
|
||||
</vn-data-viewer>
|
|
@ -0,0 +1,22 @@
|
|||
import ngModule from '../module';
|
||||
import Section from 'salix/components/section';
|
||||
|
||||
export default class Controller extends Section {
|
||||
constructor($element, $) {
|
||||
super($element, $);
|
||||
}
|
||||
|
||||
goToIndex(daysAgo, serial) {
|
||||
const issued = Date.vnNew();
|
||||
issued.setDate(issued.getDate() - daysAgo);
|
||||
this.$state.go('invoiceIn.index',
|
||||
{q: `{"serial": "${serial}", "isBooked": false, "from": ${issued.getTime()}}`});
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$element', '$scope'];
|
||||
|
||||
ngModule.vnComponent('vnInvoiceInSerial', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
Serial: Serie
|
||||
Pending: Pendientes
|
||||
Go to InvoiceIn: Ir al listado de facturas recibidas
|
|
@ -1,4 +1,5 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('upsertFixedPrice()', () => {
|
||||
const now = Date.vnNew();
|
||||
|
@ -7,6 +8,17 @@ describe('upsertFixedPrice()', () => {
|
|||
|
||||
beforeAll(async() => {
|
||||
originalFixedPrice = await models.FixedPrice.findById(fixedPriceId);
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
it(`should toggle the hasMinPrice boolean if there's a minPrice and update the rest of the data`, async() => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const axios = require('axios');
|
||||
const uuid = require('uuid');
|
||||
const fs = require('fs/promises');
|
||||
const { createWriteStream } = require('fs');
|
||||
const {createWriteStream} = require('fs');
|
||||
const path = require('path');
|
||||
const gm = require('gm');
|
||||
|
||||
|
@ -15,7 +15,7 @@ module.exports = Self => {
|
|||
},
|
||||
});
|
||||
|
||||
Self.download = async () => {
|
||||
Self.download = async() => {
|
||||
const models = Self.app.models;
|
||||
const tempContainer = await models.TempContainer.container(
|
||||
'salix-image'
|
||||
|
@ -32,13 +32,13 @@ module.exports = Self => {
|
|||
let tempFilePath;
|
||||
let queueRow;
|
||||
try {
|
||||
const myOptions = { transaction: tx };
|
||||
const myOptions = {transaction: tx};
|
||||
|
||||
queueRow = await Self.findOne(
|
||||
{
|
||||
fields: ['id', 'itemFk', 'url', 'attempts'],
|
||||
where: {
|
||||
url: { neq: null },
|
||||
url: {neq: null},
|
||||
attempts: {
|
||||
lt: maxAttempts,
|
||||
},
|
||||
|
@ -59,7 +59,7 @@ module.exports = Self => {
|
|||
'model',
|
||||
'property',
|
||||
],
|
||||
where: { name: collectionName },
|
||||
where: {name: collectionName},
|
||||
include: {
|
||||
relation: 'sizes',
|
||||
scope: {
|
||||
|
@ -116,16 +116,16 @@ module.exports = Self => {
|
|||
const collectionDir = path.join(rootPath, collectionName);
|
||||
|
||||
// To max size
|
||||
const { maxWidth, maxHeight } = collection;
|
||||
const {maxWidth, maxHeight} = collection;
|
||||
const fullSizePath = path.join(collectionDir, 'full');
|
||||
const toFullSizePath = `${fullSizePath}/${fileName}`;
|
||||
|
||||
await fs.mkdir(fullSizePath, { recursive: true });
|
||||
await fs.mkdir(fullSizePath, {recursive: true});
|
||||
await new Promise((resolve, reject) => {
|
||||
gm(tempFilePath)
|
||||
.resize(maxWidth, maxHeight, '>')
|
||||
.setFormat('png')
|
||||
.write(toFullSizePath, function (err) {
|
||||
.write(toFullSizePath, function(err) {
|
||||
if (err) reject(err);
|
||||
if (!err) resolve();
|
||||
});
|
||||
|
@ -133,12 +133,12 @@ module.exports = Self => {
|
|||
|
||||
// To collection sizes
|
||||
for (const size of collection.sizes()) {
|
||||
const { width, height } = size;
|
||||
const {width, height} = size;
|
||||
|
||||
const sizePath = path.join(collectionDir, `${width}x${height}`);
|
||||
const toSizePath = `${sizePath}/${fileName}`;
|
||||
|
||||
await fs.mkdir(sizePath, { recursive: true });
|
||||
await fs.mkdir(sizePath, {recursive: true});
|
||||
await new Promise((resolve, reject) => {
|
||||
const gmInstance = gm(tempFilePath);
|
||||
|
||||
|
@ -153,7 +153,7 @@ module.exports = Self => {
|
|||
|
||||
gmInstance
|
||||
.setFormat('png')
|
||||
.write(toSizePath, function (err) {
|
||||
.write(toSizePath, function(err) {
|
||||
if (err) reject(err);
|
||||
if (!err) resolve();
|
||||
});
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
const https = require('https');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const uuid = require('uuid');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('downloadImages', {
|
||||
description: 'Returns last entries',
|
||||
accessType: 'WRITE',
|
||||
returns: {
|
||||
type: ['Object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/downloadImages`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.downloadImages = async() => {
|
||||
const models = Self.app.models;
|
||||
const container = await models.TempContainer.container('salix-image');
|
||||
const tempPath = path.join(container.client.root, container.name);
|
||||
const maxAttempts = 3;
|
||||
|
||||
const images = await Self.find({
|
||||
where: {attempts: {eq: maxAttempts}}
|
||||
});
|
||||
|
||||
for (let image of images) {
|
||||
const currentStamp = Date.vnNew().getTime();
|
||||
const updatedStamp = image.updated.getTime();
|
||||
const graceTime = Math.abs(currentStamp - updatedStamp);
|
||||
const maxTTL = 3600 * 48 * 1000; // 48 hours in ms;
|
||||
|
||||
if (graceTime >= maxTTL)
|
||||
await Self.destroyById(image.itemFk);
|
||||
}
|
||||
|
||||
download();
|
||||
|
||||
async function download() {
|
||||
const image = await Self.findOne({
|
||||
where: {url: {neq: null}, attempts: {lt: maxAttempts}},
|
||||
order: 'priority, attempts, updated'
|
||||
});
|
||||
|
||||
if (!image) return;
|
||||
|
||||
const fileName = `${uuid.v4()}.png`;
|
||||
const filePath = path.join(tempPath, fileName);
|
||||
const imageUrl = image.url.replace('http://', 'https://');
|
||||
|
||||
https.get(imageUrl, async response => {
|
||||
if (response.statusCode != 200) {
|
||||
const error = new Error(`Could not download the image. Status code ${response.statusCode}`);
|
||||
|
||||
return await errorHandler(image.itemFk, error, filePath);
|
||||
}
|
||||
|
||||
const writeStream = fs.createWriteStream(filePath);
|
||||
writeStream.on('open', () => response.pipe(writeStream));
|
||||
writeStream.on('error', async error =>
|
||||
await errorHandler(image.itemFk, error, filePath));
|
||||
writeStream.on('finish', () => writeStream.end());
|
||||
|
||||
writeStream.on('close', async function() {
|
||||
try {
|
||||
await models.Image.registerImage('catalog', filePath, fileName, image.itemFk);
|
||||
await image.destroy();
|
||||
|
||||
download();
|
||||
} catch (error) {
|
||||
await errorHandler(image.itemFk, error, filePath);
|
||||
}
|
||||
});
|
||||
}).on('error', async error => {
|
||||
await errorHandler(image.itemFk, error, filePath);
|
||||
});
|
||||
}
|
||||
|
||||
async function errorHandler(rowId, error, filePath) {
|
||||
try {
|
||||
const row = await Self.findById(rowId);
|
||||
|
||||
if (!row) return;
|
||||
|
||||
if (row.attempts < maxAttempts) {
|
||||
await row.updateAttributes({
|
||||
error: error,
|
||||
attempts: row.attempts + 1,
|
||||
updated: Date.vnNew()
|
||||
});
|
||||
}
|
||||
|
||||
if (filePath && fs.existsSync(filePath))
|
||||
await fs.unlink(filePath);
|
||||
|
||||
download();
|
||||
} catch (err) {
|
||||
throw new Error(`Image download failed: ${err}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
|
@ -1,7 +1,21 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('item clone()', () => {
|
||||
let nextItemId;
|
||||
beforeAll(async() => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async() => {
|
||||
let query = `SELECT i1.id + 1 as id FROM vn.item i1
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('item new()', () => {
|
||||
beforeAll(async() => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new item, adding the name as a tag', async() => {
|
||||
const tx = await models.Item.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('regularize()', () => {
|
||||
beforeAll(async() => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 18},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new ticket and add a line', async() => {
|
||||
const tx = await models.Item.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
|
|
@ -40,11 +40,11 @@ Create: Crear
|
|||
Client card: Ficha del cliente
|
||||
Shipped: F. envío
|
||||
stems: Tallos
|
||||
Weight/Piece: Peso/tallo
|
||||
Weight/Piece: Peso (gramos)/tallo
|
||||
Search items by id, name or barcode: Buscar articulos por identificador, nombre o codigo de barras
|
||||
SalesPerson: Comercial
|
||||
Concept: Concepto
|
||||
Units/Box: Unidades/Caja
|
||||
Units/Box: Unidades/caja
|
||||
|
||||
# Sections
|
||||
Items: Artículos
|
||||
|
|
|
@ -295,11 +295,26 @@ module.exports = Self => {
|
|||
risk = t.debt + t.credit, totalProblems = totalProblems + 1
|
||||
`);
|
||||
|
||||
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.sale_getWarnings');
|
||||
|
||||
stmt = new ParameterizedSQL(`
|
||||
SELECT t.*, tp.*,
|
||||
((tp.risk) + cc.riskTolerance < 0) AS hasHighRisk
|
||||
CREATE TEMPORARY TABLE tmp.sale_getWarnings
|
||||
(INDEX (ticketFk, agencyModeFk))
|
||||
ENGINE = MEMORY
|
||||
SELECT f.id ticketFk, f.agencyModeFk
|
||||
FROM tmp.filter f`);
|
||||
stmts.push(stmt);
|
||||
|
||||
stmts.push('CALL ticket_getWarnings()');
|
||||
|
||||
stmt = new ParameterizedSQL(`
|
||||
SELECT t.*,
|
||||
tp.*,
|
||||
((tp.risk) + cc.riskTolerance < 0) AS hasHighRisk,
|
||||
tw.*
|
||||
FROM tmp.tickets t
|
||||
LEFT JOIN tmp.ticket_problems tp ON tp.ticketFk = t.id
|
||||
LEFT JOIN tmp.ticket_warnings tw ON tw.ticketFk = t.id
|
||||
JOIN clientConfig cc`);
|
||||
|
||||
const hasProblems = args.problems;
|
||||
|
@ -352,20 +367,37 @@ module.exports = Self => {
|
|||
return {'t.alertLevel': value};
|
||||
case 'pending':
|
||||
if (value) {
|
||||
return {and: [
|
||||
{'t.alertLevel': 0},
|
||||
{'t.alertLevelCode': {nin: [
|
||||
'OK',
|
||||
'BOARDING',
|
||||
'PRINTED',
|
||||
'PRINTED_AUTO',
|
||||
'PICKER_DESIGNED'
|
||||
]}}
|
||||
]};
|
||||
return {'t.alertLevelCode': {inq: [
|
||||
'FIXING',
|
||||
'FREE',
|
||||
'NOT_READY',
|
||||
'BLOCKED',
|
||||
'EXPANDABLE',
|
||||
'CHAINED',
|
||||
'WAITING_FOR_PAYMENT'
|
||||
]}};
|
||||
} else {
|
||||
return {and: [
|
||||
{'t.alertLevel': {gt: 0}}
|
||||
]};
|
||||
return {'t.alertLevelCode': {inq: [
|
||||
'ON_PREPARATION',
|
||||
'ON_CHECKING',
|
||||
'CHECKED',
|
||||
'PACKING',
|
||||
'PACKED',
|
||||
'INVOICED',
|
||||
'ON_DELIVERY',
|
||||
'PREPARED',
|
||||
'WAITING_FOR_PICKUP',
|
||||
'DELIVERED',
|
||||
'PRINTED_BACK',
|
||||
'LAST_CALL',
|
||||
'PREVIOUS_PREPARATION',
|
||||
'ASSISTED_PREPARATION',
|
||||
'BOARD',
|
||||
'PRINTED STOWAWAY',
|
||||
'OK STOWAWAY',
|
||||
'HALF_PACKED',
|
||||
'COOLER_PREPARATION'
|
||||
]}};
|
||||
}
|
||||
case 'agencyModeFk':
|
||||
case 'warehouseFk':
|
||||
|
@ -387,6 +419,8 @@ module.exports = Self => {
|
|||
tmp.filter,
|
||||
tmp.ticket_problems,
|
||||
tmp.sale_getProblems,
|
||||
tmp.sale_getWarnings,
|
||||
tmp.ticket_warnings,
|
||||
tmp.risk`);
|
||||
|
||||
const sql = ParameterizedSQL.join(stmts, ';');
|
||||
|
|
|
@ -12,4 +12,5 @@ Theoretical: Teórica
|
|||
Practical: Práctica
|
||||
Preparation: Preparación
|
||||
Auto-refresh: Auto-refresco
|
||||
Toggle auto-refresh every 2 minutes: Conmuta el refresco automático cada 2 minutos
|
||||
Toggle auto-refresh every 2 minutes: Conmuta el refresco automático cada 2 minutos
|
||||
Is fragile: Es frágil
|
||||
|
|
|
@ -19,13 +19,13 @@
|
|||
<vn-horizontal class="header">
|
||||
<vn-one translate>
|
||||
Tickets monitor
|
||||
</vn-one>
|
||||
</vn-one>
|
||||
</vn-horizontal>
|
||||
<vn-card>
|
||||
<smart-table
|
||||
model="model"
|
||||
view-config-id="ticketsMonitor"
|
||||
options="$ctrl.smartTableOptions"
|
||||
<smart-table
|
||||
model="model"
|
||||
view-config-id="ticketsMonitor"
|
||||
options="$ctrl.smartTableOptions"
|
||||
expr-builder="$ctrl.exprBuilder(param, value)">
|
||||
<slot-actions>
|
||||
<vn-check
|
||||
|
@ -68,6 +68,7 @@
|
|||
<th field="stateFk">
|
||||
<span translate>State</span>
|
||||
</th>
|
||||
<th field="isFragile"></th>
|
||||
<th field="zoneFk">
|
||||
<span translate>Zone</span>
|
||||
</th>
|
||||
|
@ -80,7 +81,7 @@
|
|||
<tbody>
|
||||
<tr ng-repeat="ticket in model.data track by ticket.id"
|
||||
vn-anchor="{
|
||||
state: 'ticket.card.summary',
|
||||
state: 'ticket.card.summary',
|
||||
params: {id: ticket.id},
|
||||
target: '_blank'
|
||||
}">
|
||||
|
@ -169,12 +170,20 @@
|
|||
class="link">
|
||||
{{ticket.refFk}}
|
||||
</span>
|
||||
<span
|
||||
<span
|
||||
ng-show="!ticket.refFk"
|
||||
class="chip {{ticket.classColor}}">
|
||||
{{ticket.state}}
|
||||
</span>
|
||||
</td>
|
||||
<td number>
|
||||
<vn-icon
|
||||
ng-show="ticket.isFragile"
|
||||
translate-attr="{title: 'Is fragile'}"
|
||||
class="bright"
|
||||
icon="local_bar">
|
||||
</vn-icon>
|
||||
</td>
|
||||
<td name="zone">
|
||||
<span
|
||||
title="{{ticket.zoneName}}"
|
||||
|
@ -191,8 +200,8 @@
|
|||
<td actions>
|
||||
<vn-icon-button
|
||||
vn-anchor="{
|
||||
state: 'ticket.card.sale',
|
||||
params: {id: ticket.id},
|
||||
state: 'ticket.card.sale',
|
||||
params: {id: ticket.id},
|
||||
target: '_blank'
|
||||
}"
|
||||
vn-tooltip="Go to lines"
|
||||
|
@ -213,7 +222,7 @@
|
|||
<vn-ticket-descriptor-popover
|
||||
vn-id="ticketDescriptor">
|
||||
</vn-ticket-descriptor-popover>
|
||||
<vn-worker-descriptor-popover
|
||||
<vn-worker-descriptor-popover
|
||||
vn-id="workerDescriptor">
|
||||
</vn-worker-descriptor-popover>
|
||||
<vn-client-descriptor-popover
|
||||
|
@ -236,22 +245,22 @@
|
|||
ng-click="contextmenu.filterBySelection()">
|
||||
Filter by selection
|
||||
</vn-item>
|
||||
<vn-item translate
|
||||
ng-if="contextmenu.isFilterAllowed()"
|
||||
<vn-item translate
|
||||
ng-if="contextmenu.isFilterAllowed()"
|
||||
ng-click="contextmenu.excludeSelection()">
|
||||
Exclude selection
|
||||
</vn-item>
|
||||
<vn-item translate
|
||||
ng-if="contextmenu.isFilterAllowed()"
|
||||
<vn-item translate
|
||||
ng-if="contextmenu.isFilterAllowed()"
|
||||
ng-click="contextmenu.removeFilter()">
|
||||
Remove filter
|
||||
</vn-item>
|
||||
<vn-item translate
|
||||
<vn-item translate
|
||||
ng-click="contextmenu.removeAllFilters()">
|
||||
Remove all filters
|
||||
</vn-item>
|
||||
<vn-item translate
|
||||
ng-if="contextmenu.isActionAllowed()"
|
||||
<vn-item translate
|
||||
ng-if="contextmenu.isActionAllowed()"
|
||||
ng-click="contextmenu.copyValue()">
|
||||
Copy value
|
||||
</vn-item>
|
||||
|
|
|
@ -1,6 +1,22 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('route clone()', () => {
|
||||
beforeAll(async() => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
const createdDate = Date.vnNew();
|
||||
it('should throw an error if the amount of ids pased to the clone function do no match the database', async() => {
|
||||
const ids = [996, 997, 998, 999];
|
||||
|
|
|
@ -1,6 +1,20 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('route updateWorkCenter()', () => {
|
||||
beforeAll(async() => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
const routeId = 1;
|
||||
|
||||
it('should set the commission work center if the worker has workCenter', async() => {
|
||||
|
|
|
@ -1,7 +1,21 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('sale canEdit()', () => {
|
||||
const employeeId = 1;
|
||||
beforeAll(async() => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
describe('sale editTracked', () => {
|
||||
it('should return true if the role is production regardless of the saleTrackings', async() => {
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('sale deleteSales()', () => {
|
||||
beforeAll(async() => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if the ticket of the given sales is not editable', async() => {
|
||||
const tx = await models.Sale.beginTransaction({});
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('sale reserve()', () => {
|
||||
const ctx = {
|
||||
|
@ -9,6 +10,20 @@ describe('sale reserve()', () => {
|
|||
}
|
||||
};
|
||||
|
||||
beforeAll(async() => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if the ticket can not be modified', async() => {
|
||||
const tx = await models.Sale.beginTransaction({});
|
||||
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('sale updateConcept()', () => {
|
||||
beforeAll(async() => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
const ctx = {req: {accessToken: {userId: 9}}};
|
||||
const saleId = 25;
|
||||
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('sale updatePrice()', () => {
|
||||
beforeAll(async() => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 18},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
const ctx = {
|
||||
req: {
|
||||
accessToken: {userId: 18},
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('sale updateQuantity()', () => {
|
||||
beforeAll(async() => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
const ctx = {
|
||||
req: {
|
||||
accessToken: {userId: 9},
|
||||
|
|
|
@ -1,7 +1,21 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('ticket addSale()', () => {
|
||||
const ticketId = 13;
|
||||
beforeAll(async() => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new sale for the ticket with id 13', async() => {
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
|
|
@ -10,11 +10,15 @@ describe('ticket merge()', () => {
|
|||
workerFk: 1
|
||||
};
|
||||
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
beforeAll(async() => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
|
@ -35,16 +39,16 @@ describe('ticket merge()', () => {
|
|||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
const chatNotificationBeforeMerge = await models.Chat.find();
|
||||
const chatNotificationBeforeMerge = await models.Chat.find(null, options);
|
||||
|
||||
await models.Ticket.merge(ctx, [tickets], options);
|
||||
|
||||
const createdTicketLog = await models.TicketLog.find({where: {originFk: tickets.originId}}, options);
|
||||
const createdTicketLog = await models.TicketLog.find({where: {originFk: tickets.destinationId}}, options);
|
||||
const deletedTicket = await models.Ticket.findOne({where: {id: tickets.originId}}, options);
|
||||
const salesTicketFuture = await models.Sale.find({where: {ticketFk: tickets.destinationId}}, options);
|
||||
const chatNotificationAfterMerge = await models.Chat.find();
|
||||
const chatNotificationAfterMerge = await models.Chat.find(null, options);
|
||||
|
||||
expect(createdTicketLog.length).toEqual(2);
|
||||
expect(createdTicketLog.length).toEqual(1);
|
||||
expect(deletedTicket.isDeleted).toEqual(true);
|
||||
expect(salesTicketFuture.length).toEqual(2);
|
||||
expect(chatNotificationBeforeMerge.length).toEqual(chatNotificationAfterMerge.length - 2);
|
||||
|
|
|
@ -1,6 +1,20 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('sale updateDiscount()', () => {
|
||||
beforeAll(async() => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
const originalSaleId = 8;
|
||||
|
||||
it('should throw an error if no sales were selected', async() => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Status log: Hitorial de estados
|
||||
Status log: Historial de estados
|
||||
Expedition removed: Expedición eliminada
|
||||
Move: Mover
|
||||
New ticket without route: Nuevo ticket sin ruta
|
||||
|
|
|
@ -28,8 +28,9 @@
|
|||
</vn-button-menu>
|
||||
<vn-button icon="keyboard_arrow_down"
|
||||
label="More"
|
||||
ng-click="moreOptions.show($event)"
|
||||
ng-show="$ctrl.hasSelectedSales()">
|
||||
disabled="!$ctrl.hasSelectedSales()"
|
||||
vn-tooltip="Select lines to see the options"
|
||||
ng-click="moreOptions.show($event)">
|
||||
</vn-button>
|
||||
<vn-button
|
||||
disabled="!$ctrl.hasSelectedSales() || !$ctrl.isEditable"
|
||||
|
|
|
@ -40,3 +40,4 @@ Refund: Abono
|
|||
Promotion mana: Maná promoción
|
||||
Claim mana: Maná reclamación
|
||||
History: Historial
|
||||
Select lines to see the options: Seleccione lineas para ver las opciones
|
||||
|
|
|
@ -130,6 +130,7 @@ module.exports = Self => {
|
|||
SUM(b.stickers) AS stickers,
|
||||
s.id AS cargoSupplierFk,
|
||||
s.nickname AS cargoSupplierNickname,
|
||||
s.name AS supplierName,
|
||||
CAST(SUM(b.weight * b.stickers) as DECIMAL(10,0)) as loadedKg,
|
||||
CAST(SUM(vc.aerealVolumetricDensity * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000) as DECIMAL(10,0)) as volumeKg
|
||||
FROM travel t
|
||||
|
@ -167,6 +168,7 @@ module.exports = Self => {
|
|||
SUM(b.stickers) AS stickers,
|
||||
e.evaNotes,
|
||||
e.notes,
|
||||
e.invoiceAmount,
|
||||
CAST(SUM(b.weight * b.stickers) AS DECIMAL(10,0)) as loadedkg,
|
||||
CAST(SUM(vc.aerealVolumetricDensity * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000) AS DECIMAL(10,0)) as volumeKg
|
||||
FROM tmp.travel tr
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
url="Travels/extraCommunityFilter"
|
||||
user-params="::$ctrl.defaultFilter"
|
||||
data="travels"
|
||||
order="shipped ASC, landed ASC, travelFk, loadPriority, agencyModeFk, evaNotes"
|
||||
order="landed ASC, shipped ASC, travelFk, loadPriority, agencyModeFk, supplierName, evaNotes"
|
||||
limit="20"
|
||||
auto-load="true">
|
||||
</vn-crud-model>
|
||||
|
@ -48,6 +48,9 @@
|
|||
<th field="agencyModeFk">
|
||||
<span translate>Agency</span>
|
||||
</th>
|
||||
<th field="invoiceAmount">
|
||||
<span translate>Amount</span>
|
||||
</th>
|
||||
<th field="ref">
|
||||
<span translate>Reference</span>
|
||||
</th>
|
||||
|
@ -107,6 +110,7 @@
|
|||
{{::travel.cargoSupplierNickname}}
|
||||
</span>
|
||||
</td>
|
||||
<td></td>
|
||||
<td>{{::travel.agencyModeName}}</td>
|
||||
<td vn-click-stop>
|
||||
<vn-td-editable name="reference" expand>
|
||||
|
@ -157,22 +161,15 @@
|
|||
{{::entry.supplierName}}
|
||||
</span>
|
||||
</td>
|
||||
<td number>{{::entry.invoiceAmount | currency: 'EUR': 2}}</td>
|
||||
<td></td>
|
||||
<td class="td-editable">{{::entry.ref}}</td>
|
||||
<td class="td-editable">{{::entry.invoiceNumber}}</td>
|
||||
<td number>{{::entry.stickers}}</td>
|
||||
<td number></td>
|
||||
<td number>{{::entry.loadedkg}}</td>
|
||||
<td number>{{::entry.volumeKg}}</td>
|
||||
<td>
|
||||
<span ng-if="::entry.notes" vn-tooltip="{{::entry.notes}}">
|
||||
{{::entry.notes}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span ng-if="::entry.evaNotes" vn-tooltip="{{::entry.evaNotes}}">
|
||||
{{::entry.evaNotes}}
|
||||
</span>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
|
|
@ -6,6 +6,6 @@ Phy. KG: KG físico
|
|||
Vol. KG: KG Vol.
|
||||
Search by travel id or reference: Buscar por id de travel o referencia
|
||||
Search by extra community travel: Buscar por envío extra comunitario
|
||||
Continent Out: Continente salida
|
||||
Continent Out: Cont. salida
|
||||
W. Shipped: F. envío
|
||||
W. Landed: F. llegada
|
||||
W. Landed: F. llegada
|
||||
|
|
|
@ -2,6 +2,15 @@
|
|||
<vn-auto-search
|
||||
model="model">
|
||||
</vn-auto-search>
|
||||
<vn-travel-search-panel
|
||||
model="model">
|
||||
</vn-travel-search-panel>
|
||||
<vn-crud-model
|
||||
vn-id="model"
|
||||
url="Travels/filter"
|
||||
limit="20"
|
||||
order="shipped DESC, landed DESC">
|
||||
</vn-crud-model>
|
||||
<vn-data-viewer
|
||||
model="model"
|
||||
class="vn-mb-xl vn-w-xl">
|
||||
|
@ -9,23 +18,22 @@
|
|||
<vn-table model="model">
|
||||
<vn-thead>
|
||||
<vn-tr>
|
||||
<vn-th field="id" number filter-enabled="false">Id</vn-th>
|
||||
<vn-th field="ref">Reference</vn-th>
|
||||
<vn-th field="agencyModeFk">Agency</vn-th>
|
||||
<vn-th field="warehouseOutFk">Warehouse Out</vn-th>
|
||||
<vn-th field="shipped" center shrink-date>Shipped</vn-th>
|
||||
<vn-th field="isDelivered" center>Delivered</vn-th>
|
||||
<vn-th shrink></vn-th>
|
||||
<vn-th field="warehouseInFk">Warehouse In</vn-th>
|
||||
<vn-th field="landed" center shrink-date>Landed</vn-th>
|
||||
<vn-th field="isReceived" center>Received</vn-th>
|
||||
<vn-th shrink></vn-th>
|
||||
<vn-th shrink field="totalEntries">Total entries</vn-th>
|
||||
<vn-th shrink></vn-th>
|
||||
</vn-tr>
|
||||
</vn-thead>
|
||||
<vn-tbody>
|
||||
<a ng-repeat="travel in model.data"
|
||||
<a ng-repeat="travel in model.data"
|
||||
class="clickable vn-tr search-result"
|
||||
ui-sref="travel.card.summary({id: {{::travel.id}}})">
|
||||
<vn-td number>{{::travel.id}}</vn-td>
|
||||
<vn-td>{{::travel.ref}}</vn-td>
|
||||
<vn-td>{{::travel.agencyModeName}}</vn-td>
|
||||
<vn-td>{{::travel.warehouseOutName}}</vn-td>
|
||||
|
@ -34,14 +42,27 @@
|
|||
{{::travel.shipped | date:'dd/MM/yyyy'}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td center><vn-check ng-model="travel.isDelivered" disabled="true"></vn-check></vn-td>
|
||||
<vn-td shrink>
|
||||
<vn-icon
|
||||
icon="flight_takeoff"
|
||||
translate-attr="{title: 'Delivered'}"
|
||||
ng-class="{active: travel.isDelivered}">
|
||||
</vn-icon>
|
||||
</vn-td>
|
||||
<vn-td expand>{{::travel.warehouseInName}}</vn-td>
|
||||
<vn-td center shrink-date>
|
||||
<span class="chip {{$ctrl.compareDate(travel.landed)}}">
|
||||
{{::travel.landed | date:'dd/MM/yyyy'}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td center><vn-check ng-model="travel.isReceived" disabled="true"></vn-check></vn-td>
|
||||
<vn-td shrink>
|
||||
<vn-icon
|
||||
icon="flight_land"
|
||||
translate-attr="{title: 'Received'}"
|
||||
ng-class="{active: travel.isReceived}">
|
||||
</vn-icon>
|
||||
</vn-td>
|
||||
<vn-td shrink>{{::travel.totalEntries}}</vn-td>
|
||||
<vn-td shrink>
|
||||
<vn-horizontal class="buttons">
|
||||
<vn-icon-button
|
||||
|
@ -49,7 +70,7 @@
|
|||
vn-tooltip="Clone"
|
||||
icon="icon-clone">
|
||||
</vn-icon-button>
|
||||
<vn-icon-button
|
||||
<vn-icon-button
|
||||
vn-anchor="::{state: 'entry.create', params: {travelFk: travel.id}}"
|
||||
vn-tooltip="Add entry"
|
||||
icon="icon-ticket">
|
||||
|
@ -78,38 +99,9 @@
|
|||
fixed-bottom-right>
|
||||
<vn-float-button icon="add"></vn-float-button>
|
||||
</a>
|
||||
<vn-confirm
|
||||
<vn-confirm
|
||||
vn-id="clone"
|
||||
on-accept="$ctrl.onCloneAccept($data)"
|
||||
question="Do you want to clone this travel?"
|
||||
message="All it's properties will be copied">
|
||||
</vn-confirm>
|
||||
<vn-contextmenu vn-id="contextmenu" targets="['vn-data-viewer']" model="model"
|
||||
expr-builder="$ctrl.exprBuilder(param, value)">
|
||||
<slot-menu>
|
||||
<vn-item translate
|
||||
ng-if="contextmenu.isFilterAllowed()"
|
||||
ng-click="contextmenu.filterBySelection()">
|
||||
Filter by selection
|
||||
</vn-item>
|
||||
<vn-item translate
|
||||
ng-if="contextmenu.isFilterAllowed()"
|
||||
ng-click="contextmenu.excludeSelection()">
|
||||
Exclude selection
|
||||
</vn-item>
|
||||
<vn-item translate
|
||||
ng-if="contextmenu.isFilterAllowed()"
|
||||
ng-click="contextmenu.removeFilter()">
|
||||
Remove filter
|
||||
</vn-item>
|
||||
<vn-item translate
|
||||
ng-click="contextmenu.removeAllFilters()" >
|
||||
Remove all filters
|
||||
</vn-item>
|
||||
<vn-item translate
|
||||
ng-if="contextmenu.isActionAllowed()"
|
||||
ng-click="contextmenu.copyValue()">
|
||||
Copy value
|
||||
</vn-item>
|
||||
</slot-menu>
|
||||
</vn-contextmenu>
|
|
@ -1,5 +1,6 @@
|
|||
import ngModule from '../module';
|
||||
import Section from 'salix/components/section';
|
||||
import './style.scss';
|
||||
|
||||
export default class Controller extends Section {
|
||||
preview(travel) {
|
||||
|
@ -30,37 +31,6 @@ export default class Controller extends Section {
|
|||
if (timeDifference == 0) return 'warning';
|
||||
if (timeDifference < 0) return 'success';
|
||||
}
|
||||
|
||||
exprBuilder(param, value) {
|
||||
switch (param) {
|
||||
case 'search':
|
||||
return /^\d+$/.test(value)
|
||||
? {'t.id': value}
|
||||
: {'t.ref': {like: `%${value}%`}};
|
||||
case 'ref':
|
||||
return {'t.ref': {like: `%${value}%`}};
|
||||
case 'shipped':
|
||||
return {'t.shipped': {between: this.dateRange(value)}};
|
||||
case 'landed':
|
||||
return {'t.landed': {between: this.dateRange(value)}};
|
||||
case 'id':
|
||||
case 'agencyModeFk':
|
||||
case 'warehouseOutFk':
|
||||
case 'warehouseInFk':
|
||||
case 'totalEntries':
|
||||
param = `t.${param}`;
|
||||
return {[param]: value};
|
||||
}
|
||||
}
|
||||
|
||||
dateRange(value) {
|
||||
const minHour = new Date(value);
|
||||
minHour.setHours(0, 0, 0, 0);
|
||||
const maxHour = new Date(value);
|
||||
maxHour.setHours(23, 59, 59, 59);
|
||||
|
||||
return [minHour, maxHour];
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.vnComponent('vnTravelIndex', {
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
@import "variables";
|
||||
|
||||
vn-travel-index {
|
||||
vn-icon {
|
||||
color: $color-font-secondary
|
||||
}
|
||||
|
||||
vn-icon.active {
|
||||
color: $color-success
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
#Ordenar alfabeticamente
|
||||
Reference: Referencia
|
||||
Warehouse Out: Almacén salida
|
||||
Warehouse In: Almacén llegada
|
||||
Warehouse Out: Alm salida
|
||||
Warehouse In: Alm llegada
|
||||
Shipped from: Salida desde
|
||||
Shipped to: Salida hasta
|
||||
Landed from: Llegada desde
|
||||
|
@ -10,12 +10,12 @@ Shipped: F. salida
|
|||
Landed: F. llegada
|
||||
Delivered: Enviado
|
||||
Received: Recibido
|
||||
Travel id: Id envío
|
||||
Search travels by id: Buscar envíos por identificador
|
||||
Travel id: Id
|
||||
Search travels by id: Buscar envíos por identificador o referencia
|
||||
New travel: Nuevo envío
|
||||
travel: envío
|
||||
|
||||
# Sections
|
||||
Travels: Envíos
|
||||
Log: Historial
|
||||
Thermographs: Termógrafos
|
||||
Thermographs: Termógrafos
|
||||
|
|
|
@ -1,20 +1,6 @@
|
|||
<vn-crud-model
|
||||
vn-id="model"
|
||||
url="Travels/filter"
|
||||
limit="20"
|
||||
order="shipped DESC, landed DESC">
|
||||
</vn-crud-model>
|
||||
<vn-portal slot="topbar">
|
||||
<vn-searchbar
|
||||
vn-focus
|
||||
panel="vn-travel-search-panel"
|
||||
info="Search travels by id"
|
||||
model="model"
|
||||
fetch-params="$ctrl.fetchParams($params)"
|
||||
suggested-filter="$ctrl.filterParams">
|
||||
</vn-searchbar>
|
||||
</vn-portal>
|
||||
<vn-portal slot="menu">
|
||||
<vn-left-menu></vn-left-menu>
|
||||
</vn-portal>
|
||||
<ui-view></ui-view>
|
||||
<ui-view></ui-view>
|
||||
|
|
|
@ -4,28 +4,6 @@ import ModuleMain from 'salix/components/module-main';
|
|||
export default class Travel extends ModuleMain {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.filterParams = {
|
||||
scopeDays: 1
|
||||
};
|
||||
}
|
||||
|
||||
fetchParams($params) {
|
||||
if (!Object.entries($params).length)
|
||||
$params.scopeDays = 1;
|
||||
|
||||
if (typeof $params.scopeDays === 'number') {
|
||||
const shippedFrom = Date.vnNew();
|
||||
shippedFrom.setHours(0, 0, 0, 0);
|
||||
|
||||
const shippedTo = new Date(shippedFrom.getTime());
|
||||
shippedTo.setDate(shippedTo.getDate() + $params.scopeDays);
|
||||
shippedTo.setHours(23, 59, 59, 999);
|
||||
|
||||
Object.assign($params, {shippedFrom, shippedTo});
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
import './index.js';
|
||||
|
||||
describe('Travel Component vnTravel', () => {
|
||||
let controller;
|
||||
|
||||
beforeEach(ngModule('travel'));
|
||||
|
||||
beforeEach(inject($componentController => {
|
||||
let $element = angular.element(`<div></div>`);
|
||||
controller = $componentController('vnTravel', {$element});
|
||||
}));
|
||||
|
||||
describe('fetchParams()', () => {
|
||||
it('should return a range of dates with passed scope days', () => {
|
||||
let params = controller.fetchParams({
|
||||
scopeDays: 2
|
||||
});
|
||||
const shippedFrom = Date.vnNew();
|
||||
shippedFrom.setHours(0, 0, 0, 0);
|
||||
const shippedTo = new Date(shippedFrom.getTime());
|
||||
shippedTo.setDate(shippedTo.getDate() + params.scopeDays);
|
||||
shippedTo.setHours(23, 59, 59, 999);
|
||||
|
||||
const expectedParams = {
|
||||
shippedFrom,
|
||||
scopeDays: params.scopeDays,
|
||||
shippedTo
|
||||
};
|
||||
|
||||
expect(params).toEqual(expectedParams);
|
||||
});
|
||||
|
||||
it('should return default value for scope days', () => {
|
||||
let params = controller.fetchParams({
|
||||
scopeDays: 1
|
||||
});
|
||||
|
||||
expect(params.scopeDays).toEqual(1);
|
||||
});
|
||||
|
||||
it('should return the given scope days', () => {
|
||||
let params = controller.fetchParams({
|
||||
scopeDays: 2
|
||||
});
|
||||
|
||||
expect(params.scopeDays).toEqual(2);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,109 +1,146 @@
|
|||
<div class="search-panel">
|
||||
<form ng-submit="$ctrl.onSearch()" id="manifold-form">
|
||||
<vn-horizontal class="vn-px-lg vn-pt-lg">
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="General search"
|
||||
ng-model="filter.search"
|
||||
info="Search travels by id"
|
||||
vn-focus>
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="vn-px-lg">
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="Reference"
|
||||
ng-model="filter.ref">
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="Total entries"
|
||||
ng-model="filter.totalEntries">
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="vn-px-lg">
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="Travel id"
|
||||
ng-model="filter.id">
|
||||
</vn-textfield>
|
||||
<vn-autocomplete vn-one
|
||||
label="Agency"
|
||||
ng-model="filter.agencyModeFk"
|
||||
url="AgencyModes"
|
||||
show-field="name"
|
||||
value-field="id">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<section class="vn-px-md">
|
||||
<vn-horizontal class="manifold-panel vn-pa-md">
|
||||
<vn-date-picker
|
||||
vn-one
|
||||
label="Shipped from"
|
||||
ng-model="filter.shippedFrom"
|
||||
on-change="$ctrl.shippedFrom = value">
|
||||
</vn-date-picker>
|
||||
<vn-date-picker
|
||||
vn-one
|
||||
label="Shipped to"
|
||||
ng-model="filter.shippedTo"
|
||||
on-change="$ctrl.shippedTo = value">
|
||||
</vn-date-picker>
|
||||
<vn-none class="or vn-px-md" translate>Or</vn-none>
|
||||
<vn-input-number
|
||||
vn-one
|
||||
min="0"
|
||||
step="1"
|
||||
label="Days onward"
|
||||
ng-model="filter.scopeDays"
|
||||
on-change="$ctrl.scopeDays = value"
|
||||
display-controls="true">
|
||||
</vn-input-number>
|
||||
<vn-icon color-marginal
|
||||
icon="info"
|
||||
vn-tooltip="Cannot choose a range of dates and days onward at the same time">
|
||||
</vn-icon>
|
||||
</vn-horizontal>
|
||||
</section>
|
||||
<vn-horizontal class="vn-px-lg">
|
||||
<vn-date-picker
|
||||
vn-one
|
||||
label="Landed from"
|
||||
ng-model="filter.landedFrom">
|
||||
</vn-date-picker>
|
||||
<vn-date-picker
|
||||
vn-one
|
||||
label="Landed to"
|
||||
ng-model="filter.landedTo">
|
||||
</vn-date-picker>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="vn-px-lg">
|
||||
<vn-autocomplete vn-one
|
||||
label="Warehouse Out"
|
||||
ng-model="filter.warehouseOutFk"
|
||||
url="Warehouses"
|
||||
show-field="name"
|
||||
value-field="id">
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete vn-one
|
||||
label="Warehouse In"
|
||||
ng-model="filter.warehouseInFk"
|
||||
url="Warehouses"
|
||||
show-field="name"
|
||||
value-field="id">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="vn-px-lg">
|
||||
<vn-autocomplete vn-one
|
||||
label="Continent Out"
|
||||
ng-model="filter.continent"
|
||||
url="Continents"
|
||||
show-field="name"
|
||||
value-field="code">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="vn-px-lg vn-pb-lg vn-mt-lg">
|
||||
<vn-submit label="Search"></vn-submit>
|
||||
</vn-horizontal>
|
||||
</form>
|
||||
</div>
|
||||
<vn-side-menu side="right">
|
||||
<vn-horizontal class="input">
|
||||
<vn-textfield
|
||||
label="General search"
|
||||
info="Search travels by id"
|
||||
ng-model="$ctrl.search"
|
||||
ng-keydown="$ctrl.onKeyPress($event, 'search')">
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="input horizontal">
|
||||
<vn-autocomplete
|
||||
vn-id="agency"
|
||||
label="Agency"
|
||||
ng-model="$ctrl.filter.agencyModeFk"
|
||||
data="$ctrl.agencyModes"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
on-change="$ctrl.applyFilters()">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="input horizontal">
|
||||
<vn-autocomplete
|
||||
vn-id="warehouseOut"
|
||||
label="Warehouse Out"
|
||||
ng-model="$ctrl.filter.warehouseOutFk"
|
||||
data="$ctrl.warehouses"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
on-change="$ctrl.applyFilters()">
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete
|
||||
vn-id="warehouseIn"
|
||||
label="Warehouse In"
|
||||
ng-model="$ctrl.filter.warehouseInFk"
|
||||
data="$ctrl.warehouses"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
on-change="$ctrl.applyFilters()">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="input horizontal">
|
||||
<vn-input-number
|
||||
min="0"
|
||||
step="1"
|
||||
label="Days onward"
|
||||
ng-model="$ctrl.filter.scopeDays"
|
||||
on-change="$ctrl.applyFilters()"
|
||||
display-controls="true"
|
||||
info="Cannot choose a range of dates and days onward at the same time">
|
||||
</vn-input-number>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="input horizontal">
|
||||
<vn-date-picker
|
||||
label="Landed from"
|
||||
ng-model="$ctrl.filter.landedFrom"
|
||||
on-change="$ctrl.applyFilters()">
|
||||
</vn-date-picker>
|
||||
<vn-date-picker
|
||||
label="Landed to"
|
||||
ng-model="$ctrl.filter.landedTo"
|
||||
on-change="$ctrl.applyFilters()">
|
||||
</vn-date-picker>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="input horizontal">
|
||||
<vn-autocomplete
|
||||
vn-id="continent"
|
||||
label="Continent Out"
|
||||
ng-model="$ctrl.filter.continent"
|
||||
data="$ctrl.continents"
|
||||
show-field="name"
|
||||
value-field="code"
|
||||
on-change="$ctrl.applyFilters()">
|
||||
</vn-autocomplete>
|
||||
<vn-input-number
|
||||
min="0"
|
||||
label="Total entries"
|
||||
ng-model="$ctrl.totalEntries"
|
||||
ng-keydown="$ctrl.onKeyPress($event, 'totalEntries')">
|
||||
</vn-input-number>
|
||||
</vn-horizontal>
|
||||
<div class="chips">
|
||||
<vn-chip
|
||||
ng-if="$ctrl.filter.search"
|
||||
removable="true"
|
||||
on-remove="$ctrl.removeParamFilter('search')"
|
||||
class="colored">
|
||||
<span>Id/{{$ctrl.$t('Reference')}}: {{$ctrl.filter.search}}</span>
|
||||
</vn-chip>
|
||||
<vn-chip
|
||||
ng-if="agency.selection"
|
||||
removable="true"
|
||||
on-remove="$ctrl.removeParamFilter('agencyModeFk')"
|
||||
class="colored">
|
||||
<span>{{$ctrl.$t('Agency')}}: {{agency.selection.name}}</span>
|
||||
</vn-chip>
|
||||
<vn-chip
|
||||
ng-if="warehouseOut.selection"
|
||||
removable="true"
|
||||
on-remove="$ctrl.removeParamFilter('warehouseOutFk')"
|
||||
class="colored">
|
||||
<span>{{$ctrl.$t('Warehouse Out')}}: {{warehouseOut.selection.name}}</span>
|
||||
</vn-chip>
|
||||
<vn-chip
|
||||
ng-if="warehouseIn.selection"
|
||||
removable="true"
|
||||
on-remove="$ctrl.removeParamFilter('warehouseInFk')"
|
||||
class="colored">
|
||||
<span>{{$ctrl.$t('Warehouse In')}}: {{warehouseIn.selection.name}}</span>
|
||||
</vn-chip>
|
||||
<vn-chip
|
||||
ng-if="$ctrl.filter.scopeDays"
|
||||
removable="true"
|
||||
on-remove="$ctrl.removeParamFilter('scopeDays')"
|
||||
class="colored">
|
||||
<span>{{$ctrl.$t('Days onward')}}: {{$ctrl.filter.scopeDays}}</span>
|
||||
</vn-chip>
|
||||
<vn-chip
|
||||
ng-if="$ctrl.filter.landedFrom"
|
||||
removable="true"
|
||||
on-remove="$ctrl.removeParamFilter('landedFrom')"
|
||||
class="colored">
|
||||
<span>{{$ctrl.$t('Landed from')}}: {{$ctrl.filter.landedFrom | date:'dd/MM/yyyy'}}</span>
|
||||
</vn-chip>
|
||||
<vn-chip
|
||||
ng-if="$ctrl.filter.landedTo"
|
||||
removable="true"
|
||||
on-remove="$ctrl.removeParamFilter('landedTo')"
|
||||
class="colored">
|
||||
<span>{{$ctrl.$t('Landed to')}}: {{$ctrl.filter.landedTo | date:'dd/MM/yyyy'}}</span>
|
||||
</vn-chip>
|
||||
<vn-chip
|
||||
ng-if="continent.selection"
|
||||
removable="true"
|
||||
on-remove="$ctrl.removeParamFilter('continent')"
|
||||
class="colored">
|
||||
<span>{{$ctrl.$t('Continent Out')}}: {{continent.selection.name}}</span>
|
||||
</vn-chip>
|
||||
<vn-chip
|
||||
ng-if="$ctrl.filter.totalEntries"
|
||||
removable="true"
|
||||
on-remove="$ctrl.removeParamFilter('totalEntries')"
|
||||
class="colored">
|
||||
<span>{{$ctrl.$t('Total entries')}}: {{$ctrl.filter.totalEntries}}</span>
|
||||
</vn-chip>
|
||||
</div>
|
||||
</vn-side-menu>
|
||||
|
|
|
@ -1,43 +1,69 @@
|
|||
import ngModule from '../module';
|
||||
import SearchPanel from 'core/components/searchbar/search-panel';
|
||||
import './style.scss';
|
||||
|
||||
class Controller extends SearchPanel {
|
||||
constructor($, $element) {
|
||||
super($, $element);
|
||||
this.filter = this.$.filter;
|
||||
this.initFilter();
|
||||
this.fetchData();
|
||||
}
|
||||
|
||||
get shippedFrom() {
|
||||
return this._shippedFrom;
|
||||
$onChanges() {
|
||||
if (this.model)
|
||||
this.applyFilters();
|
||||
}
|
||||
|
||||
set shippedFrom(value) {
|
||||
this._shippedFrom = value;
|
||||
this.filter.scopeDays = null;
|
||||
fetchData() {
|
||||
this.$http.get('AgencyModes').then(res => {
|
||||
this.agencyModes = res.data;
|
||||
});
|
||||
this.$http.get('Warehouses').then(res => {
|
||||
this.warehouses = res.data;
|
||||
});
|
||||
this.$http.get('Continents').then(res => {
|
||||
this.continents = res.data;
|
||||
});
|
||||
}
|
||||
|
||||
get shippedTo() {
|
||||
return this._shippedTo;
|
||||
initFilter() {
|
||||
this.filter = {};
|
||||
if (this.$params.q) {
|
||||
this.filter = JSON.parse(this.$params.q);
|
||||
this.search = this.filter.search;
|
||||
this.totalEntries = this.filter.totalEntries;
|
||||
}
|
||||
if (!this.filter.scopeDays) this.filter.scopeDays = 7;
|
||||
}
|
||||
|
||||
set shippedTo(value) {
|
||||
this._shippedTo = value;
|
||||
this.filter.scopeDays = null;
|
||||
applyFilters(param) {
|
||||
this.model.applyFilter({}, this.filter)
|
||||
.then(() => {
|
||||
if (param && this.model._orgData.length === 1)
|
||||
this.$state.go('travel.card.summary', {id: this.model._orgData[0].id});
|
||||
else
|
||||
this.$state.go(this.$state.current.name, {q: JSON.stringify(this.filter)}, {location: 'replace'});
|
||||
});
|
||||
}
|
||||
|
||||
get scopeDays() {
|
||||
return this._scopeDays;
|
||||
removeParamFilter(param) {
|
||||
if (this[param]) delete this[param];
|
||||
delete this.filter[param];
|
||||
this.applyFilters();
|
||||
}
|
||||
|
||||
set scopeDays(value) {
|
||||
this._scopeDays = value;
|
||||
|
||||
this.filter.shippedFrom = null;
|
||||
this.filter.shippedTo = null;
|
||||
onKeyPress($event, param) {
|
||||
if ($event.key === 'Enter') {
|
||||
this.filter[param] = this[param];
|
||||
this.applyFilters(param === 'search');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.vnComponent('vnTravelSearchPanel', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller
|
||||
controller: Controller,
|
||||
bindings: {
|
||||
model: '<'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -8,41 +8,31 @@ describe('Travel Component vnTravelSearchPanel', () => {
|
|||
beforeEach(inject($componentController => {
|
||||
controller = $componentController('vnTravelSearchPanel', {$element: null});
|
||||
controller.$t = () => {};
|
||||
controller.filter = {};
|
||||
}));
|
||||
|
||||
describe('shippedFrom() setter', () => {
|
||||
it('should clear the scope days when setting the from property', () => {
|
||||
controller.filter.scopeDays = 1;
|
||||
describe('applyFilters()', () => {
|
||||
it('should apply filters', async() => {
|
||||
controller.filter = {foo: 'bar'};
|
||||
controller.model = {
|
||||
applyFilter: jest.fn().mockResolvedValue(),
|
||||
_orgData: [{id: 1}]
|
||||
};
|
||||
controller.$state = {
|
||||
current: {
|
||||
name: 'foo'
|
||||
},
|
||||
go: jest.fn()
|
||||
};
|
||||
|
||||
controller.shippedFrom = Date.vnNew();
|
||||
await controller.applyFilters(true);
|
||||
|
||||
expect(controller.filter.scopeDays).toBeNull();
|
||||
expect(controller.shippedFrom).toBeDefined();
|
||||
});
|
||||
});
|
||||
expect(controller.model.applyFilter).toHaveBeenCalledWith({}, controller.filter);
|
||||
expect(controller.$state.go).toHaveBeenCalledWith('travel.card.summary', {id: 1});
|
||||
|
||||
describe('shippedTo() setter', () => {
|
||||
it('should clear the scope days when setting the to property', () => {
|
||||
controller.filter.scopeDays = 1;
|
||||
await controller.applyFilters(false);
|
||||
|
||||
controller.shippedTo = Date.vnNew();
|
||||
|
||||
expect(controller.filter.scopeDays).toBeNull();
|
||||
expect(controller.shippedTo).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('scopeDays() setter', () => {
|
||||
it('should clear the date range when setting the scopeDays property', () => {
|
||||
controller.filter.shippedFrom = Date.vnNew();
|
||||
controller.filter.shippedTo = Date.vnNew();
|
||||
|
||||
controller.scopeDays = 1;
|
||||
|
||||
expect(controller.filter.shippedFrom).toBeNull();
|
||||
expect(controller.filter.shippedTo).toBeNull();
|
||||
expect(controller.scopeDays).toBeDefined();
|
||||
expect(controller.$state.go).toHaveBeenCalledWith(controller.$state.current.name,
|
||||
{q: JSON.stringify(controller.filter)}, {location: 'replace'});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue