6005-backupLabeler #2276

Merged
pablone merged 50 commits from 6005-backupLabeler into dev 2024-04-12 07:12:54 +00:00
62 changed files with 1760 additions and 414 deletions
Showing only changes of commit 3e2cac5544 - Show all commits

View File

@ -11,6 +11,9 @@
"[javascript]": { "[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint" "editor.defaultFormatter": "dbaeumer.vscode-eslint"
}, },
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"cSpell.words": [ "cSpell.words": [
"salix", "salix",
"fdescribe" "fdescribe"

View File

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2352.01] - 2023-12-28
### Added
### Changed
### Fixed
## [2350.01] - 2023-12-14 ## [2350.01] - 2023-12-14
### Added ### Added
@ -14,14 +20,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [2348.01] - 2023-11-30 ## [2348.01] - 2023-11-30
### Added ### Características Añadidas 🆕
- (Ticket -> Adelantar) Permite mover lineas sin generar negativos - **Tickets → Adelantar:** Permite mover lineas sin generar negativos
- (Ticket -> Adelantar) Permite modificar la fecha de los tickets - **Tickets → Adelantar:** Permite modificar la fecha de los tickets
- (Trabajadores -> Notificaciones) Nueva sección (lilium) - **Trabajadores → Notificaciones:** Nueva sección (lilium)
### Changed ### Correcciones 🛠️
### Fixed - **Tickets → RocketChat:** Arreglada detección de cambios
- (Ticket -> RocketChat) Arreglada detección de cambios
## [2346.01] - 2023-11-16 ## [2346.01] - 2023-11-16

View File

@ -22,8 +22,8 @@ module.exports = Self => {
Self.removeFile = async(ctx, id, options) => { Self.removeFile = async(ctx, id, options) => {
const models = Self.app.models; const models = Self.app.models;
let tx;
const myOptions = {}; const myOptions = {};
let tx;
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);

View File

@ -49,13 +49,7 @@ module.exports = Self => {
if (vnUser.twoFactor) if (vnUser.twoFactor)
throw new ForbiddenError(null, 'REQUIRES_2FA'); throw new ForbiddenError(null, 'REQUIRES_2FA');
} }
const validateLogin = await Self.validateLogin(user, password); return Self.validateLogin(user, password, ctx);
await Self.app.models.SignInLog.create({
token: validateLogin.token,
userFk: vnUser.id,
ip: ctx.req.ip
});
return validateLogin;
}; };
Self.passExpired = async vnUser => { Self.passExpired = async vnUser => {

View File

@ -2,7 +2,7 @@ const {models} = require('vn-loopback/server/server');
describe('VnUser Sign-in()', () => { describe('VnUser Sign-in()', () => {
const employeeId = 1; const employeeId = 1;
const unauthCtx = { const unAuthCtx = {
req: { req: {
headers: {}, headers: {},
connection: { connection: {
@ -15,20 +15,21 @@ describe('VnUser Sign-in()', () => {
const {VnUser, AccessToken, SignInLog} = models; const {VnUser, AccessToken, SignInLog} = models;
describe('when credentials are correct', () => { describe('when credentials are correct', () => {
it('should return the token if user uses email', async() => { it('should return the token if user uses email', async() => {
let login = await VnUser.signIn(unauthCtx, 'salesAssistant@mydomain.com', 'nightmare'); let login = await VnUser.signIn(unAuthCtx, 'salesAssistant@mydomain.com', 'nightmare');
let accessToken = await AccessToken.findById(login.token); let accessToken = await AccessToken.findById(login.token);
let ctx = {req: {accessToken: accessToken}}; let ctx = {req: {accessToken: accessToken}};
let signInLog = await SignInLog.find({where: {token: accessToken.id}}); let signInLog = await SignInLog.find({where: {token: accessToken.id}});
expect(signInLog.length).toEqual(1); expect(signInLog.length).toEqual(1);
expect(signInLog[0].userFk).toEqual(accessToken.userId); expect(signInLog[0].userFk).toEqual(accessToken.userId);
expect(signInLog[0].owner).toEqual(true);
expect(login.token).toBeDefined(); expect(login.token).toBeDefined();
await VnUser.logout(ctx.req.accessToken.id); await VnUser.logout(ctx.req.accessToken.id);
}); });
it('should return the token', async() => { it('should return the token', async() => {
let login = await VnUser.signIn(unauthCtx, 'salesAssistant', 'nightmare'); let login = await VnUser.signIn(unAuthCtx, 'salesAssistant', 'nightmare');
let accessToken = await AccessToken.findById(login.token); let accessToken = await AccessToken.findById(login.token);
let ctx = {req: {accessToken: accessToken}}; let ctx = {req: {accessToken: accessToken}};
@ -38,7 +39,7 @@ describe('VnUser Sign-in()', () => {
}); });
it('should return the token if the user doesnt exist but the client does', async() => { it('should return the token if the user doesnt exist but the client does', async() => {
let login = await VnUser.signIn(unauthCtx, 'PetterParker', 'nightmare'); let login = await VnUser.signIn(unAuthCtx, 'PetterParker', 'nightmare');
let accessToken = await AccessToken.findById(login.token); let accessToken = await AccessToken.findById(login.token);
let ctx = {req: {accessToken: accessToken}}; let ctx = {req: {accessToken: accessToken}};
@ -53,7 +54,7 @@ describe('VnUser Sign-in()', () => {
let error; let error;
try { try {
await VnUser.signIn(unauthCtx, 'IDontExist', 'TotallyWrongPassword'); await VnUser.signIn(unAuthCtx, 'IDontExist', 'TotallyWrongPassword');
} catch (e) { } catch (e) {
error = e; error = e;
} }
@ -74,7 +75,7 @@ describe('VnUser Sign-in()', () => {
const options = {transaction: tx}; const options = {transaction: tx};
await employee.updateAttribute('twoFactor', 'email', options); await employee.updateAttribute('twoFactor', 'email', options);
await VnUser.signIn(unauthCtx, 'employee', 'nightmare', options); await VnUser.signIn(unAuthCtx, 'employee', 'nightmare', options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback(); await tx.rollback();
@ -99,7 +100,7 @@ describe('VnUser Sign-in()', () => {
const options = {transaction: tx}; const options = {transaction: tx};
await employee.updateAttribute('passExpired', yesterday, options); await employee.updateAttribute('passExpired', yesterday, options);
await VnUser.signIn(unauthCtx, 'employee', 'nightmare', options); await VnUser.signIn(unAuthCtx, 'employee', 'nightmare', options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback(); await tx.rollback();

View File

@ -124,20 +124,42 @@ module.exports = function(Self) {
return email.send(); return email.send();
}); });
Self.signInValidate = (user, userToken) => {
/**
* Sign-in validate
* @param {String} user The user
* @param {Object} userToken Options
* @param {Object} token accessToken
* @param {Object} ctx context
*/
Self.signInValidate = async(user, userToken, token, ctx) => {
const [[key, value]] = Object.entries(Self.userUses(user)); const [[key, value]] = Object.entries(Self.userUses(user));
if (userToken[key].toLowerCase().trim() !== value.toLowerCase().trim()) { const isOwner = Self.rawSql(`SELECT ? = ? `, [userToken[key], value]);
console.error('ERROR!!! - Signin with other user', userToken, user); await Self.app.models.SignInLog.create({
userName: user,
token: token.id,
userFk: userToken.id,
ip: ctx.req.ip,
owner: isOwner
});
if (!isOwner)
throw new UserError('Try again'); throw new UserError('Try again');
}
}; };
Self.validateLogin = async function(user, password) { /**
* Validate login params
* @param {String} user The user
* @param {String} password
* @param {Object} ctx context
*/
Self.validateLogin = async function(user, password, ctx) {
const loginInfo = Object.assign({password}, Self.userUses(user)); const loginInfo = Object.assign({password}, Self.userUses(user));
const token = await Self.login(loginInfo, 'user'); const token = await Self.login(loginInfo, 'user');
const userToken = await token.user.get(); const userToken = await token.user.get();
Self.signInValidate(user, userToken);
if (ctx)
await Self.signInValidate(user, userToken, token, ctx);
try { try {
await Self.app.models.Account.sync(userToken.name, password); await Self.app.models.Account.sync(userToken.name, password);
@ -226,10 +248,12 @@ module.exports = function(Self) {
const env = process.env.NODE_ENV; const env = process.env.NODE_ENV;
const liliumUrl = await Self.app.models.Url.findOne({ const liliumUrl = await Self.app.models.Url.findOne({
where: {and: [ where: {
and: [
{appName: 'lilium'}, {appName: 'lilium'},
{environment: env} {environment: env}
]} ]
}
}); });
class Mailer { class Mailer {

View File

@ -7,6 +7,10 @@ process.on('warning', warning => {
console.log(warning.stack); console.log(warning.stack);
}); });
process.on('SIGUSR2', async() => {
if (container) await container.rm();
});
process.on('exit', async function() { process.on('exit', async function() {
if (container) await container.rm(); if (container) await container.rm();
}); });

View File

@ -1,5 +1,4 @@
-- --
-- Table structure for table `signInLog` -- Table structure for table `signInLog`
-- Description: log to debug cross-login error -- Description: log to debug cross-login error
@ -13,7 +12,9 @@ CREATE TABLE `account`.`signInLog` (
`token` varchar(255) NOT NULL , `token` varchar(255) NOT NULL ,
`userFk` int(10) unsigned DEFAULT NULL, `userFk` int(10) unsigned DEFAULT NULL,
`creationDate` timestamp NULL DEFAULT current_timestamp(), `creationDate` timestamp NULL DEFAULT current_timestamp(),
`userName` varchar(30) NOT NULL,
`ip` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, `ip` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`owner` tinyint(1) DEFAULT 1,
KEY `userFk` (`userFk`), KEY `userFk` (`userFk`),
CONSTRAINT `signInLog_ibfk_1` FOREIGN KEY (`userFk`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE CONSTRAINT `signInLog_ibfk_1` FOREIGN KEY (`userFk`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
); );

View File

@ -0,0 +1,46 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`delivery_beforeInsert`
BEFORE INSERT ON `delivery`
FOR EACH ROW
BEGIN
IF (NEW.longitude IS NOT NULL AND NEW.latitude IS NOT NULL AND NEW.ticketFK IS NOT NULL)
THEN
UPDATE address
SET longitude = NEW.longitude,
latitude = NEW.latitude
WHERE id IN (
SELECT addressFK
FROM ticket
WHERE id = NEW.ticketFk
);
END IF;
END$$
DELIMITER ;
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`delivery_beforeUpdate`
BEFORE UPDATE ON `delivery`
FOR EACH ROW
BEGIN
IF (NEW.longitude IS NOT NULL AND NEW.latitude IS NOT NULL AND NEW.ticketFK IS NOT NULL)
THEN
UPDATE address
SET longitude = NEW.longitude,
latitude = NEW.latitude
WHERE id IN (
SELECT addressFK
FROM ticket
WHERE id = NEW.ticketFk
);
END IF;
END$$
DELIMITER ;
ALTER TABLE `vn`.`address` MODIFY COLUMN longitude decimal(11,7) DEFAULT NULL NULL COMMENT 'Indica la última longitud proporcionada por tabla delivery';
ALTER TABLE `vn`.`address` MODIFY COLUMN latitude decimal(11,7) DEFAULT NULL NULL COMMENT 'Indica la última latitud proporcionada por tabla delivery';

View File

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`ticketTracking` CHANGE `workerFk` `userFk` int(10) unsigned DEFAULT NULL NULL;

View File

@ -0,0 +1,4 @@
RENAME TABLE `vn`.`clientCreditLimit` TO `vn`.`roleCreditLimit`;
ALTER TABLE `vn`.`clientCreditLimit` DROP FOREIGN KEY `clientCreditLimit_FK`;
ALTER TABLE `vn`.`roleCreditLimit` ADD CONSTRAINT `roleCreditLimit_FK` FOREIGN KEY (`roleFk`) REFERENCES `account`.`role`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,52 @@
CREATE OR REPLACE DEFINER=`root`@`localhost`
SQL SECURITY DEFINER
VIEW `vn`.`ticketState`
AS SELECT `tt`.`created` AS `updated`,
`tt`.`stateFk` AS `stateFk`,
`tt`.`userFk` AS `userFk`,
`tls`.`ticketFk` AS `ticketFk`,
`s`.`id` AS `state`,
`s`.`order` AS `productionOrder`,
`s`.`alertLevel` AS `alertLevel`,
`s`.`code` AS `code`,
`tls`.`ticketFk` AS `ticket`,
`tt`.`userFk` AS `worker`,
`s`.`isPreviousPreparable` AS `isPreviousPreparable`,
`s`.`isPicked` AS `isPicked`
FROM (
(
`vn`.`ticketLastState` `tls`
JOIN `vn`.`ticketTracking` `tt` ON(`tt`.`id` = `tls`.`ticketTrackingFk`)
)
JOIN `vn`.`state` `s` ON(`s`.`id` = `tt`.`stateFk`)
);
CREATE OR REPLACE DEFINER=`root`@`localhost`
SQL SECURITY DEFINER
VIEW `vn2008`.`v_inter`
AS SELECT `tt`.`id` AS `inter_id`,
`tt`.`stateFk` AS `state_id`,
`tt`.`notes` AS `nota`,
`tt`.`created` AS `odbc_date`,
`tt`.`ticketFk` AS `Id_Ticket`,
`tt`.`userFk` AS `Id_Trabajador`,
`tt`.`supervisorFk` AS `Id_supervisor`
FROM `vn`.`ticketTracking` `tt`;
CREATE OR REPLACE
ALGORITHM = UNDEFINED VIEW `ticketStateToday` AS
SELECT
`ts`.`ticket` AS `ticket`,
`ts`.`state` AS `state`,
`ts`.`productionOrder` AS `productionOrder`,
`ts`.`alertLevel` AS `alertLevel`,
`ts`.`userFk` AS `userFk`,
`ts`.`code` AS `code`,
`ts`.`updated` AS `updated`,
`ts`.`isPicked` AS `isPicked`
FROM
(`ticketState` `ts`
JOIN `ticket` `t` ON
(`t`.`id` = `ts`.`ticket`))
WHERE
`t`.`shipped` BETWEEN `util`.`VN_CURDATE`() AND `MIDNIGHT`(`util`.`VN_CURDATE`());

View File

@ -367,7 +367,7 @@ INSERT INTO `vn`.`contactChannel`(`id`, `name`)
INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city`,`postcode`,`phone`,`mobile`,`isRelevant`,`email`,`iban`,`dueDay`,`accountingAccount`,`isEqualizated`,`provinceFk`,`hasToInvoice`,`credit`,`countryFk`,`isActive`,`gestdocFk`,`quality`,`payMethodFk`,`created`,`isToBeMailed`,`contactChannelFk`,`hasSepaVnl`,`hasCoreVnl`,`hasCoreVnh`,`riskCalculated`,`clientTypeFk`, `hasToInvoiceByAddress`,`isTaxDataChecked`,`isFreezed`,`creditInsurance`,`isCreatedAsServed`,`hasInvoiceSimplified`,`salesPersonFk`,`isVies`,`eypbc`, `businessTypeFk`,`typeFk`) INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city`,`postcode`,`phone`,`mobile`,`isRelevant`,`email`,`iban`,`dueDay`,`accountingAccount`,`isEqualizated`,`provinceFk`,`hasToInvoice`,`credit`,`countryFk`,`isActive`,`gestdocFk`,`quality`,`payMethodFk`,`created`,`isToBeMailed`,`contactChannelFk`,`hasSepaVnl`,`hasCoreVnl`,`hasCoreVnh`,`riskCalculated`,`clientTypeFk`, `hasToInvoiceByAddress`,`isTaxDataChecked`,`isFreezed`,`creditInsurance`,`isCreatedAsServed`,`hasInvoiceSimplified`,`salesPersonFk`,`isVies`,`eypbc`, `businessTypeFk`,`typeFk`)
VALUES VALUES
(1101, 'Bruce Wayne', '84612325V', 'BATMAN', 'Alfred', '1007 MOUNTAIN DRIVE, GOTHAM', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceWayne@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist','loses'), (1101, 'Bruce Wayne', '84612325V', 'BATMAN', 'Alfred', '1007 MOUNTAIN DRIVE, GOTHAM', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceWayne@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist','normal'),
(1102, 'Petter Parker', '87945234L', 'SPIDER MAN', 'Aunt May', '20 INGRAM STREET, QUEENS, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'PetterParker@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist','normal'), (1102, 'Petter Parker', '87945234L', 'SPIDER MAN', 'Aunt May', '20 INGRAM STREET, QUEENS, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'PetterParker@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist','normal'),
(1103, 'Clark Kent', '06815934E', 'SUPER MAN', 'lois lane', '344 CLINTON STREET, APARTAMENT 3-D', 'Gotham', 46460, 1111111111, 222222222, 1, 'ClarkKent@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 0, 19, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist','normal'), (1103, 'Clark Kent', '06815934E', 'SUPER MAN', 'lois lane', '344 CLINTON STREET, APARTAMENT 3-D', 'Gotham', 46460, 1111111111, 222222222, 1, 'ClarkKent@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 0, 19, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist','normal'),
(1104, 'Tony Stark', '06089160W', 'IRON MAN', 'Pepper Potts', '10880 MALIBU POINT, 90265', 'Gotham', 46460, 1111111111, 222222222, 1, 'TonyStark@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist','normal'), (1104, 'Tony Stark', '06089160W', 'IRON MAN', 'Pepper Potts', '10880 MALIBU POINT, 90265', 'Gotham', 46460, 1111111111, 222222222, 1, 'TonyStark@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist','normal'),
@ -377,8 +377,8 @@ INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city
(1108, 'Charles Xavier', '22641921P', 'PROFESSOR X', 'Beast', '3800 VICTORY PKWY, CINCINNATI, OH 45207, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 5, 1, 300, 13, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1, 'florist','normal'), (1108, 'Charles Xavier', '22641921P', 'PROFESSOR X', 'Beast', '3800 VICTORY PKWY, CINCINNATI, OH 45207, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 5, 1, 300, 13, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1, 'florist','normal'),
(1109, 'Bruce Banner', '16104829E', 'HULK', 'Black widow', 'SOMEWHERE IN NEW YORK', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 9, 0, 1, 'florist','normal'), (1109, 'Bruce Banner', '16104829E', 'HULK', 'Black widow', 'SOMEWHERE IN NEW YORK', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 9, 0, 1, 'florist','normal'),
(1110, 'Jessica Jones', '58282869H', 'JESSICA JONES', 'Luke Cage', 'NYCC 2015 POSTER', 'Gotham', 46460, 1111111111, 222222222, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, NULL, 0, 1, 'florist','normal'), (1110, 'Jessica Jones', '58282869H', 'JESSICA JONES', 'Luke Cage', 'NYCC 2015 POSTER', 'Gotham', 46460, 1111111111, 222222222, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, NULL, 0, 1, 'florist','normal'),
(1111, 'Missing', NULL, 'MISSING MAN', 'Anton', 'THE SPACE, UNIVERSE FAR AWAY', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, 0, 1, 0, NULL, 1, 0, NULL, 0, 1, 'others','normal'), (1111, 'Missing', NULL, 'MISSING MAN', 'Anton', 'THE SPACE, UNIVERSE FAR AWAY', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, 0, 1, 0, NULL, 1, 0, NULL, 0, 1, 'others','loses'),
(1112, 'Trash', NULL, 'GARBAGE MAN', 'Unknown name', 'NEW YORK CITY, UNDERGROUND', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, 0, 1, 0, NULL, 1, 0, NULL, 0, 1, 'others','normal'); (1112, 'Trash', NULL, 'GARBAGE MAN', 'Unknown name', 'NEW YORK CITY, UNDERGROUND', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, 0, 1, 0, NULL, 1, 0, NULL, 0, 1, 'others','loses');
INSERT INTO `vn`.`client`(`id`, `name`, `fi`, `socialName`, `contact`, `street`, `city`, `postcode`, `isRelevant`, `email`, `iban`,`dueDay`,`accountingAccount`, `isEqualizated`, `provinceFk`, `hasToInvoice`, `credit`, `countryFk`, `isActive`, `gestdocFk`, `quality`, `payMethodFk`,`created`, `isTaxDataChecked`) INSERT INTO `vn`.`client`(`id`, `name`, `fi`, `socialName`, `contact`, `street`, `city`, `postcode`, `isRelevant`, `email`, `iban`,`dueDay`,`accountingAccount`, `isEqualizated`, `provinceFk`, `hasToInvoice`, `credit`, `countryFk`, `isActive`, `gestdocFk`, `quality`, `payMethodFk`,`created`, `isTaxDataChecked`)
SELECT id, name, CONCAT(RPAD(CONCAT(id,9),8,id),'A'), CONCAT(name, 'Social'), CONCAT(name, 'Contact'), CONCAT(name, 'Street'), 'GOTHAM', 46460, 1, CONCAT(name,'@mydomain.com'), NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1,NULL, 10, 5, util.VN_CURDATE(), 1 SELECT id, name, CONCAT(RPAD(CONCAT(id,9),8,id),'A'), CONCAT(name, 'Social'), CONCAT(name, 'Contact'), CONCAT(name, 'Street'), 'GOTHAM', 46460, 1, CONCAT(name,'@mydomain.com'), NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1,NULL, 10, 5, util.VN_CURDATE(), 1
@ -494,7 +494,7 @@ INSERT INTO `vn`.`clientCredit`(`clientFk`, `workerFk`, `amount`, `created`)
(1104, 9, 90 , util.VN_CURDATE()), (1104, 9, 90 , util.VN_CURDATE()),
(1105, 9, 90 , util.VN_CURDATE()); (1105, 9, 90 , util.VN_CURDATE());
INSERT INTO `vn`.`clientCreditLimit`(`id`, `maxAmount`, `roleFk`) INSERT INTO `vn`.`roleCreditLimit`(`id`, `maxAmount`, `roleFk`)
VALUES VALUES
(1, 9999999, 20), (1, 9999999, 20),
(2, 10000, 21), (2, 10000, 21),
@ -772,7 +772,7 @@ INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `des
-- FIX for state hours on local, inter_afterInsert -- FIX for state hours on local, inter_afterInsert
-- UPDATE vncontrol.inter SET odbc_date = DATE_ADD(util.VN_CURDATE(), INTERVAL -10 SECOND); -- UPDATE vncontrol.inter SET odbc_date = DATE_ADD(util.VN_CURDATE(), INTERVAL -10 SECOND);
INSERT INTO `vn`.`ticketTracking`(`ticketFk`, `stateFk`, `workerFk`, `created`) INSERT INTO `vn`.`ticketTracking`(`ticketFk`, `stateFk`, `userFk`, `created`)
VALUES VALUES
(1, 16, 5 , DATE_ADD(util.VN_NOW(), INTERVAL -1 MONTH)), (1, 16, 5 , DATE_ADD(util.VN_NOW(), INTERVAL -1 MONTH)),
(2, 16, 5 , DATE_ADD(util.VN_NOW(), INTERVAL -1 MONTH)), (2, 16, 5 , DATE_ADD(util.VN_NOW(), INTERVAL -1 MONTH)),
@ -816,6 +816,7 @@ INSERT INTO `vn`.`config`(`id`, `mdbServer`, `fakeEmail`, `defaultersMaxAmount`,
VALUES VALUES
(1, 'beta-server', 'nightmare@mydomain.com', '200', DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH)); (1, 'beta-server', 'nightmare@mydomain.com', '200', DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH));
INSERT INTO `vn`.`greugeType`(`id`, `name`, `code`) INSERT INTO `vn`.`greugeType`(`id`, `name`, `code`)
VALUES VALUES
(1, 'Diff', 'diff'), (1, 'Diff', 'diff'),
@ -2348,9 +2349,11 @@ INSERT INTO `vn`.`zoneEvent`(`zoneFk`, `type`, `weekDays`)
(8, 'indefinitely', 'mon,tue,wed,thu,fri,sat,sun'), (8, 'indefinitely', 'mon,tue,wed,thu,fri,sat,sun'),
(10, 'indefinitely', 'mon,tue,wed,thu,fri,sat,sun'); (10, 'indefinitely', 'mon,tue,wed,thu,fri,sat,sun');
INSERT INTO `vn`.`zoneEvent`(`zoneFk`, `type`, `started`, `ended`) INSERT INTO `vn`.`zoneEvent`(`zoneFk`, `type`, `started`, `ended`, `weekDays`)
VALUES VALUES
(9, 'range', DATE_ADD(util.VN_CURDATE(), INTERVAL -1 YEAR), DATE_ADD(util.VN_CURDATE(), INTERVAL +1 YEAR)); (9, 'range', DATE_ADD(util.VN_CURDATE(), INTERVAL -1 YEAR), DATE_ADD(util.VN_CURDATE(), INTERVAL +1 YEAR), 'mon'),
(9, 'range', util.VN_CURDATE(), NULL, 'tue'),
(9, 'range', NULL, util.VN_CURDATE(), 'wed');
INSERT INTO `vn`.`workerTimeControl`(`userFk`, `timed`, `manual`, `direction`, `isSendMail`) INSERT INTO `vn`.`workerTimeControl`(`userFk`, `timed`, `manual`, `direction`, `isSendMail`)
VALUES VALUES

View File

@ -26,7 +26,7 @@ describe('Route create path', () => {
await page.waitToClick(selectors.createRouteView.submitButton); await page.waitToClick(selectors.createRouteView.submitButton);
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
expect(message.text).toContain('Access denied'); expect(message.text).toContain('Access Denied');
}); });
}); });

View File

@ -3,4 +3,4 @@ Could not contact the server: Could not contact the server, make sure you have a
Please enter your username: Please enter your username Please enter your username: Please enter your username
It seems that the server has fall down: It seems that the server has fall down, wait a few minutes and try again It seems that the server has fall down: It seems that the server has fall down, wait a few minutes and try again
Session has expired: Your session has expired, please login again Session has expired: Your session has expired, please login again
Access denied: Access denied Access Denied: Access Denied

View File

@ -3,5 +3,5 @@ Could not contact the server: No se ha podido contactar con el servidor, asegura
Please enter your username: Por favor introduce tu nombre de usuario Please enter your username: Por favor introduce tu nombre de usuario
It seems that the server has fall down: Parece que el servidor se ha caído, espera unos minutos e inténtalo de nuevo It seems that the server has fall down: Parece que el servidor se ha caído, espera unos minutos e inténtalo de nuevo
Session has expired: Tu sesión ha expirado, por favor vuelve a iniciar sesión Session has expired: Tu sesión ha expirado, por favor vuelve a iniciar sesión
Access denied: Acción no permitida Access Denied: Acción no permitida
Direction not found: Dirección no encontrada Direction not found: Dirección no encontrada

View File

@ -18,6 +18,7 @@ Show summary: Mostrar vista previa
What is new: Novedades de la versión What is new: Novedades de la versión
Settings: Ajustes Settings: Ajustes
There is a new version, click here to reload: Hay una nueva versión, pulse aquí para recargar There is a new version, click here to reload: Hay una nueva versión, pulse aquí para recargar
This ticket is locked.: Este ticket está bloqueado
# Actions # Actions

View File

@ -120,7 +120,7 @@ function $exceptionHandler(vnApp, $window, $state, $injector) {
messageT = 'Invalid login'; messageT = 'Invalid login';
break; break;
case 403: case 403:
messageT = 'Access denied'; messageT = exception.data?.error?.message || 'Access Denied';
break; break;
case 502: case 502:
messageT = 'It seems that the server has fall down'; messageT = 'It seems that the server has fall down';

View File

@ -26,12 +26,19 @@ module.exports = Self => {
}); });
Self.sync = async function(userName, password, force, options) { Self.sync = async function(userName, password, force, options) {
const models = Self.app.models;
const myOptions = {}; const myOptions = {};
let tx;
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const models = Self.app.models; if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
};
try {
const user = await models.VnUser.findOne({ const user = await models.VnUser.findOne({
fields: ['id', 'password'], fields: ['id', 'password'],
where: {name: userName} where: {name: userName}
@ -42,8 +49,23 @@ module.exports = Self => {
const isSync = !await models.UserSync.exists(userName, myOptions); const isSync = !await models.UserSync.exists(userName, myOptions);
if (!force && isSync && user) return; if (!force && isSync && user) {
if (tx) await tx.rollback();
return;
}
await Self.rawSql(`
SELECT id
FROM account.user
WHERE id = ?
FOR UPDATE`, [user.id], myOptions);
await models.AccountConfig.syncUser(userName, password); await models.AccountConfig.syncUser(userName, password);
await models.UserSync.destroyById(userName, myOptions); await models.UserSync.destroyById(userName, myOptions);
if (tx) await tx.commit();
} catch (err) {
if (tx) await tx.rollback();
throw err;
}
}; };
}; };

View File

@ -26,6 +26,14 @@
}, },
"ip": { "ip": {
"type": "string" "type": "string"
},
"userName": {
"type": "string"
},
"owner": {
"type": "boolean",
"required": true,
"default": true
} }
}, },
"relations": { "relations": {

View File

@ -123,7 +123,7 @@ module.exports = Self => {
await models.TicketTracking.create({ await models.TicketTracking.create({
ticketFk: newRefundTicket.id, ticketFk: newRefundTicket.id,
stateFk: state.id, stateFk: state.id,
workerFk: worker.id userFk: worker.id
}, myOptions); }, myOptions);
const salesToRefund = await models.ClaimBeginning.find(salesFilter, myOptions); const salesToRefund = await models.ClaimBeginning.find(salesFilter, myOptions);

View File

@ -1,3 +1,5 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('removeFile', { Self.remoteMethodCtx('removeFile', {
description: 'Removes a claim document', description: 'Removes a claim document',
@ -19,8 +21,8 @@ module.exports = Self => {
}); });
Self.removeFile = async(ctx, id, options) => { Self.removeFile = async(ctx, id, options) => {
let tx;
const myOptions = {}; const myOptions = {};
let tx;
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
@ -31,19 +33,18 @@ module.exports = Self => {
} }
try { try {
const models = Self.app.models; const claimDms = await Self.findById(id, null, myOptions);
const targetClaimDms = await models.ClaimDms.findById(id, null, myOptions);
const targetDms = await models.Dms.findById(targetClaimDms.dmsFk, null, myOptions);
const trashDmsType = await models.DmsType.findOne({where: {code: 'trash'}}, myOptions);
await models.Dms.removeFile(ctx, targetClaimDms.dmsFk, myOptions); const targetDms = await Self.app.models.Dms.removeFile(ctx, claimDms.dmsFk, myOptions);
await targetClaimDms.destroy(myOptions);
await targetDms.updateAttribute('dmsTypeFk', trashDmsType.id, myOptions); if (!targetDms || ! claimDms)
throw new UserError('Try again');
const claimDmsDestroyed = await claimDms.destroy(myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();
return targetDms; return claimDmsDestroyed;
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();
throw e; throw e;

View File

@ -1,6 +1,3 @@
const UserError = require('vn-loopback/util/user-error');
const fs = require('fs-extra');
const path = require('path');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('uploadFile', { Self.remoteMethodCtx('uploadFile', {
@ -57,96 +54,33 @@ module.exports = Self => {
}); });
Self.uploadFile = async(ctx, id, options) => { Self.uploadFile = async(ctx, id, options) => {
const tx = await Self.beginTransaction({}); const {Dms, ClaimDms} = Self.app.models;
const myOptions = {}; const myOptions = {};
let tx;
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
if (!myOptions.transaction) if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx; myOptions.transaction = tx;
const models = Self.app.models;
const promises = [];
const TempContainer = models.TempContainer;
const ClaimContainer = models.ClaimContainer;
const fileOptions = {};
const args = ctx.args;
let srcFile;
try {
const hasWriteRole = await models.DmsType.hasWriteRole(ctx, args.dmsTypeId, myOptions);
if (!hasWriteRole)
throw new UserError(`You don't have enough privileges`);
// Upload file to temporary path
const tempContainer = await TempContainer.container('dms');
const uploaded = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions);
const files = Object.values(uploaded.files).map(file => {
return file[0];
});
const addedDms = [];
for (const uploadedFile of files) {
const newDms = await createDms(ctx, uploadedFile, myOptions);
const pathHash = ClaimContainer.getHash(newDms.id);
const file = await TempContainer.getFile(tempContainer.name, uploadedFile.name);
srcFile = path.join(file.client.root, file.container, file.name);
const claimContainer = await ClaimContainer.container(pathHash);
const dstFile = path.join(claimContainer.client.root, pathHash, newDms.file);
await fs.move(srcFile, dstFile, {
overwrite: true
});
addedDms.push(newDms);
} }
addedDms.forEach(dms => { try {
const newClaimDms = models.ClaimDms.create({ const uploadedFiles = await Dms.uploadFile(ctx, myOptions);
const promises = uploadedFiles.map(dms => ClaimDms.create({
claimFk: id, claimFk: id,
dmsFk: dms.id dmsFk: dms.id
}, myOptions); }, myOptions));
await Promise.all(promises);
promises.push(newClaimDms);
});
const resolvedPromises = await Promise.all(promises);
if (tx) await tx.commit(); if (tx) await tx.commit();
return resolvedPromises; return uploadedFiles;
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();
if (fs.existsSync(srcFile))
await fs.unlink(srcFile);
throw e; throw e;
} }
}; };
async function createDms(ctx, file, myOptions) {
const models = Self.app.models;
const myUserId = ctx.req.accessToken.userId;
const args = ctx.args;
const newDms = await models.Dms.create({
workerFk: myUserId,
dmsTypeFk: args.dmsTypeId,
companyFk: args.companyId,
warehouseFk: args.warehouseId,
reference: args.reference,
description: args.description,
contentType: file.type,
hasFile: args.hasFile
}, myOptions);
let fileName = file.name;
const extension = models.DmsContainer.getFileExtension(fileName);
fileName = `${newDms.id}.${extension}`;
return newDms.updateAttribute('file', fileName, myOptions);
}
}; };

View File

@ -19,9 +19,8 @@ module.exports = Self => {
}); });
Self.removeFile = async(ctx, id, options) => { Self.removeFile = async(ctx, id, options) => {
const models = Self.app.models;
let tx;
const myOptions = {}; const myOptions = {};
let tx;
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
@ -34,13 +33,16 @@ module.exports = Self => {
try { try {
const clientDms = await Self.findById(id, null, myOptions); const clientDms = await Self.findById(id, null, myOptions);
await models.Dms.removeFile(ctx, clientDms.dmsFk, myOptions); const targetDms = await Self.app.models.Dms.removeFile(ctx, clientDms.dmsFk, myOptions);
const destroyedClient = await clientDms.destroy(myOptions); if (!targetDms || !clientDms)
throw new UserError('Try again');
const clientDmsDestroyed = await clientDms.destroy(myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();
return destroyedClient; return clientDmsDestroyed;
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();
throw e; throw e;

View File

@ -55,9 +55,9 @@ module.exports = Self => {
}); });
Self.uploadFile = async(ctx, id, options) => { Self.uploadFile = async(ctx, id, options) => {
const models = Self.app.models; const {Dms, ClientDms} = Self.app.models;
let tx;
const myOptions = {}; const myOptions = {};
let tx;
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
@ -67,23 +67,20 @@ module.exports = Self => {
myOptions.transaction = tx; myOptions.transaction = tx;
} }
const promises = [];
try { try {
const uploadedFiles = await models.Dms.uploadFile(ctx, myOptions); const uploadedFiles = await Dms.uploadFile(ctx, myOptions);
uploadedFiles.forEach(dms => { const promises = uploadedFiles.map(dms =>
const newClientDms = models.ClientDms.create({ ClientDms.create({
clientFk: id, clientFk: id,
dmsFk: dms.id dmsFk: dms.id
}, myOptions); }, myOptions)
promises.push(newClientDms); );
}); await Promise.all(promises);
const resolvedPromises = await Promise.all(promises);
if (tx) await tx.commit(); if (tx) await tx.commit();
return resolvedPromises; return uploadedFiles;
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();
throw e; throw e;

View File

@ -29,7 +29,7 @@
"ClientCredit": { "ClientCredit": {
"dataSource": "vn" "dataSource": "vn"
}, },
"ClientCreditLimit": { "RoleCreditLimit": {
"dataSource": "vn" "dataSource": "vn"
}, },
"ClientConsumptionQueue": { "ClientConsumptionQueue": {

View File

@ -463,7 +463,7 @@ module.exports = Self => {
throw new UserError(`You can't change the credit set to zero from a financialBoss`); throw new UserError(`You can't change the credit set to zero from a financialBoss`);
} }
const creditLimits = await models.ClientCreditLimit.find({ const creditLimits = await models.RoleCreditLimit.find({
fields: ['roleFk'], fields: ['roleFk'],
where: { where: {
maxAmount: {gte: changes.credit} maxAmount: {gte: changes.credit}

View File

@ -1,9 +1,9 @@
{ {
"name": "ClientCreditLimit", "name": "RoleCreditLimit",
"base": "VnModel", "base": "VnModel",
"options": { "options": {
"mysql": { "mysql": {
"table": "clientCreditLimit" "table": "roleCreditLimit"
} }
}, },
"properties": { "properties": {

View File

@ -62,7 +62,7 @@
<vn-worker-autocomplete <vn-worker-autocomplete
vn-one vn-one
ng-model="$ctrl.client.salesPersonFk" ng-model="$ctrl.client.salesPersonFk"
departments="['VT']" departments="['VT', 'shopping']"
show-field="nickname" show-field="nickname"
label="Salesperson" label="Salesperson"
vn-acl="salesAssistant"> vn-acl="salesAssistant">

View File

@ -112,7 +112,7 @@ module.exports = Self => {
{ {
relation: 'taxTypeSage', relation: 'taxTypeSage',
scope: { scope: {
fields: ['vat'] fields: ['vat', 'rate']
} }
}] }]
} }

View File

@ -151,7 +151,7 @@ describe('SalesMonitor salesFilter()', () => {
const result = await models.SalesMonitor.salesFilter(ctx, filter, options); const result = await models.SalesMonitor.salesFilter(ctx, filter, options);
const firstRow = result[0]; const firstRow = result[0];
expect(result.length).toEqual(15); expect(result.length).toEqual(12);
expect(firstRow.alertLevel).not.toEqual(0); expect(firstRow.alertLevel).not.toEqual(0);
await tx.rollback(); await tx.rollback();

View File

@ -130,13 +130,15 @@ module.exports = Self => {
am.name agencyName, am.name agencyName,
u.name AS workerUserName, u.name AS workerUserName,
v.numberPlate AS vehiclePlateNumber, v.numberPlate AS vehiclePlateNumber,
Date_format(r.time, '%H:%i') hour Date_format(r.time, '%H:%i') hour,
eu.email
FROM route r FROM route r
LEFT JOIN agencyMode am ON am.id = r.agencyModeFk LEFT JOIN agencyMode am ON am.id = r.agencyModeFk
LEFT JOIN agency a ON a.id = am.agencyFk LEFT JOIN agency a ON a.id = am.agencyFk
LEFT JOIN vehicle v ON v.id = r.vehicleFk LEFT JOIN vehicle v ON v.id = r.vehicleFk
LEFT JOIN worker w ON w.id = r.workerFk LEFT JOIN worker w ON w.id = r.workerFk
LEFT JOIN account.user u ON u.id = w.id` LEFT JOIN account.user u ON u.id = w.id
LEFT JOIN account.emailUser eu ON eu.userFk = r.workerFk`
); );
stmt.merge(conn.makeSuffix(filter)); stmt.merge(conn.makeSuffix(filter));

View File

@ -0,0 +1,64 @@
module.exports = Self => {
Self.remoteMethod('getExpeditionSummary', {
description: 'Get summary of expeditions for a given route',
accepts: [
{
arg: 'routeFk',
type: 'number',
required: true,
description: 'Foreign key for Route'
}
],
returns: {
type: 'object',
root: true
},
http: {
path: '/getExpeditionSummary',
verb: 'get'
}
});
Self.getExpeditionSummary = async(routeFk, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const query = `
SELECT routeFk,
addressFk,
SUM(total) total,
SUM(delivery) delivery,
SUM(lost) lost,
SUM(delivered) delivered,
GROUP_CONCAT(totalPacking ORDER BY total DESC SEPARATOR ' ') itemPackingType
FROM (
SELECT r.id AS routeFk,
t.addressFk,
CONCAT (IFNULL(e.itemPackingTypeFk,'-'), '', COUNT(*)) totalPacking,
COUNT(*) total,
SUM(est.code = 'ON DELIVERY') delivery,
SUM(est.code = 'LOST') lost,
SUM(est.code = 'DELIVERED') delivered,
t.priority
FROM vn.ticket t
JOIN vn.route r ON r.id = t.routeFk
JOIN vn.expedition e ON e.ticketFk = t.id
LEFT JOIN vn.expeditionStateType est ON est.id = e.stateTypeFk
JOIN vn.agencyMode am ON am.id = r.agencyModeFk
JOIN vn.agency ag ON ag.id = am.agencyFk
LEFT JOIN vn.userConfig uc ON uc.userFk = account.myUser_getId()
WHERE (r.created = util.VN_CURDATE() OR r.created = util.yesterday())
AND t.routeFk = ?
GROUP BY t.addressFk, e.itemPackingTypeFk
) sub
GROUP BY addressFk
ORDER BY priority DESC
`;
const results = await Self.rawSql(query, [routeFk], myOptions);
return results;
};
};

View File

@ -80,13 +80,15 @@ module.exports = Self => {
}; };
const conn = Self.dataSource.connector; const conn = Self.dataSource.connector;
let where = buildFilter(params, (param, value) => {return {[param]: value}}); let where = buildFilter(params, (param, value) => {
return {[param]: value};
});
filter = mergeFilters(filter, {where}); filter = mergeFilters(filter, {where});
if (!filter.where) { if (!filter.where) {
const yesterday = new Date(); const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1); yesterday.setDate(yesterday.getDate() - 1);
filter.where = {'shipped': yesterday.toISOString().split('T')[0]} filter.where = {'shipped': yesterday.toISOString().split('T')[0]};
} }
const myOptions = {}; const myOptions = {};

View File

@ -3,7 +3,7 @@ const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('getTickets', { Self.remoteMethod('getTickets', {
description: 'Return the tickets information displayed on the route module', description: 'Find all instances of the model matched by filter from the data source.',
accessType: 'READ', accessType: 'READ',
accepts: [ accepts: [
{ {
@ -40,22 +40,32 @@ module.exports = Self => {
t.clientFk, t.clientFk,
t.priority, t.priority,
t.addressFk, t.addressFk,
st.code AS ticketStateCode, st.code ticketStateCode,
st.name AS ticketStateName, st.name ticketStateName,
wh.name AS warehouseName, wh.name warehouseName,
tob.description AS ticketObservation, tob.description ticketObservation,
a.street, a.street,
a.postalCode, a.postalCode,
a.city, a.city,
am.name AS agencyModeName, am.name agencyModeName,
u.nickname AS userNickname, u.nickname userNickname,
vn.ticketTotalVolume(t.id) AS volume, vn.ticketTotalVolume(t.id) volume,
tob.description, tob.description,
GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) ipt GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) ipt,
c.phone clientPhone,
c.mobile clientMobile,
a.phone addressPhone,
a.mobile addressMobile,
a.longitude,
a.latitude,
wm.mediaValue salePersonPhone,
t.cmrFk,
t.isSigned signed
FROM vn.route r FROM vn.route r
JOIN ticket t ON t.routeFk = r.id JOIN ticket t ON t.routeFk = r.id
JOIN vn.sale s ON s.ticketFk = t.id JOIN client c ON t.clientFk = c.id
JOIN vn.item i ON i.id = s.itemFk LEFT JOIN vn.sale s ON s.ticketFk = t.id
LEFT JOIN vn.item i ON i.id = s.itemFk
LEFT JOIN ticketState ts ON ts.ticketFk = t.id LEFT JOIN ticketState ts ON ts.ticketFk = t.id
LEFT JOIN state st ON st.id = ts.stateFk LEFT JOIN state st ON st.id = ts.stateFk
LEFT JOIN warehouse wh ON wh.id = t.warehouseFk LEFT JOIN warehouse wh ON wh.id = t.warehouseFk
@ -65,7 +75,8 @@ module.exports = Self => {
LEFT JOIN address a ON a.id = t.addressFk LEFT JOIN address a ON a.id = t.addressFk
LEFT JOIN agencyMode am ON am.id = t.agencyModeFk LEFT JOIN agencyMode am ON am.id = t.agencyModeFk
LEFT JOIN account.user u ON u.id = r.workerFk LEFT JOIN account.user u ON u.id = r.workerFk
LEFT JOIN vehicle v ON v.id = r.vehicleFk` LEFT JOIN vehicle v ON v.id = r.vehicleFk
LEFT JOIN workerMedia wm ON wm.workerFk = c.salesPersonFk`
); );
if (!filter.where) filter.where = {}; if (!filter.where) filter.where = {};

View File

@ -0,0 +1,10 @@
const app = require('vn-loopback/server/server');
describe('route getExpeditionSummary()', () => {
const routeId = 1;
it('should return a summary of expeditions for a route', async() => {
const result = await app.models.Route.getExpeditionSummary(routeId);
expect(result.every(route => route.id = routeId)).toBeTruthy();
});
});

View File

@ -17,6 +17,7 @@ module.exports = Self => {
require('../methods/route/cmr')(Self); require('../methods/route/cmr')(Self);
require('../methods/route/getExternalCmrs')(Self); require('../methods/route/getExternalCmrs')(Self);
require('../methods/route/downloadCmrsZip')(Self); require('../methods/route/downloadCmrsZip')(Self);
require('../methods/route/getExpeditionSummary')(Self);
require('../methods/route/getByWorker')(Self); require('../methods/route/getByWorker')(Self);
Self.validate('kmStart', validateDistance, { Self.validate('kmStart', validateDistance, {

View File

@ -19,7 +19,6 @@ module.exports = Self => {
}); });
Self.removeFile = async(ctx, id, options) => { Self.removeFile = async(ctx, id, options) => {
const models = Self.app.models;
const myOptions = {}; const myOptions = {};
let tx; let tx;
@ -32,18 +31,18 @@ module.exports = Self => {
} }
try { try {
const targetTicketDms = await models.TicketDms.findById(id, null, myOptions); const ticketDms = await Self.findById(id, null, myOptions);
const targetDms = await models.Dms.findById(targetTicketDms.dmsFk, null, myOptions);
const trashDmsType = await models.DmsType.findOne({where: {code: 'trash'}}, myOptions);
await models.Dms.removeFile(ctx, targetTicketDms.dmsFk, myOptions); const targetDms = await Self.app.models.Dms.removeFile(ctx, ticketDms.dmsFk, myOptions);
await targetTicketDms.destroy(myOptions);
await targetDms.updateAttribute('dmsTypeFk', trashDmsType.id, myOptions); if (!targetDms || !ticketDms)
throw new UserError('Try again');
const ticketDmsDestroyed = await ticketDms.destroy(myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();
return targetDms; return ticketDmsDestroyed;
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();
throw e; throw e;

View File

@ -63,7 +63,7 @@ module.exports = Self => {
const isAvailable = itemStock.available > 0; const isAvailable = itemStock.available > 0;
if (!isAvailable) if (!isAvailable || !ctx.args.quantity)
throw new UserError(`This item is not available`); throw new UserError(`This item is not available`);
if (request.saleFk) if (request.saleFk)

View File

@ -68,7 +68,7 @@ describe('ticket filter()', () => {
const filter = {}; const filter = {};
const result = await models.Ticket.filter(ctx, filter, options); const result = await models.Ticket.filter(ctx, filter, options);
expect(result.length).toEqual(9); expect(result.length).toEqual(6);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -141,7 +141,6 @@ describe('ticket filter()', () => {
}); });
it('should return the tickets that are not pending', async() => { it('should return the tickets that are not pending', async() => {
pending('#6010 test intermitente');
const tx = await models.Ticket.beginTransaction({}); const tx = await models.Ticket.beginTransaction({});
try { try {

View File

@ -9,7 +9,7 @@ describe('ticket getSalesPersonMana()', () => {
const mana = await models.Ticket.getSalesPersonMana(1, options); const mana = await models.Ticket.getSalesPersonMana(1, options);
expect(mana).toEqual(73); expect(mana).toEqual(124);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -94,10 +94,10 @@ describe('ticket state()', () => {
const ticketTracking = await models.Ticket.state(ctx, params, options); const ticketTracking = await models.Ticket.state(ctx, params, options);
expect(ticketTracking.__data.ticketFk).toBe(params.ticketFk); expect(ticketTracking.ticketFk).toBe(params.ticketFk);
expect(ticketTracking.__data.stateFk).toBe(params.stateFk); expect(ticketTracking.stateFk).toBe(params.stateFk);
expect(ticketTracking.__data.workerFk).toBe(49); expect(ticketTracking.userFk).toBe(49);
expect(ticketTracking.__data.id).toBeDefined(); expect(ticketTracking.id).toBeDefined();
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -116,14 +116,14 @@ describe('ticket state()', () => {
const ticket = await models.Ticket.create(sampleTicket, options); const ticket = await models.Ticket.create(sampleTicket, options);
const ctx = {req: {accessToken: {userId: 18}}}; const ctx = {req: {accessToken: {userId: 18}}};
const assignedState = await models.State.findOne({where: {code: 'PICKER_DESIGNED'}}, options); const assignedState = await models.State.findOne({where: {code: 'PICKER_DESIGNED'}}, options);
const params = {ticketFk: ticket.id, stateFk: assignedState.id, workerFk: 1}; const params = {ticketFk: ticket.id, stateFk: assignedState.id, userFk: 1};
const res = await models.Ticket.state(ctx, params, options); const res = await models.Ticket.state(ctx, params, options);
expect(res.__data.ticketFk).toBe(params.ticketFk); expect(res.ticketFk).toBe(params.ticketFk);
expect(res.__data.stateFk).toBe(params.stateFk); expect(res.stateFk).toBe(params.stateFk);
expect(res.__data.workerFk).toBe(params.workerFk); expect(res.userFk).toBe(params.userFk);
expect(res.__data.workerFk).toBe(1); expect(res.userFk).toBe(1);
expect(res.__data.id).toBeDefined(); expect(res.id).toBeDefined();
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -51,12 +51,12 @@ module.exports = Self => {
params.stateFk = state.id; params.stateFk = state.id;
} }
if (!params.workerFk) { if (!params.userFk) {
const worker = await models.Worker.findOne({ const worker = await models.Worker.findOne({
where: {id: userId} where: {id: userId}
}, myOptions); }, myOptions);
params.workerFk = worker.id; params.userFk = worker.id;
} }
const ticketState = await models.TicketState.findById(params.ticketFk, { const ticketState = await models.TicketState.findById(params.ticketFk, {

View File

@ -47,7 +47,7 @@ module.exports = Self => {
}); });
Self.uploadFile = async(ctx, id, options) => { Self.uploadFile = async(ctx, id, options) => {
const models = Self.app.models; const {Dms, TicketDms} = Self.app.models;
const myOptions = {}; const myOptions = {};
let tx; let tx;
@ -59,22 +59,19 @@ module.exports = Self => {
myOptions.transaction = tx; myOptions.transaction = tx;
} }
const promises = [];
try { try {
const uploadedFiles = await models.Dms.uploadFile(ctx, myOptions); const uploadedFiles = await Dms.uploadFile(ctx, myOptions);
uploadedFiles.forEach(dms => {
const newTicketDms = models.TicketDms.create({ const promises = uploadedFiles.map(dms => TicketDms.create({
ticketFk: id, ticketFk: id,
dmsFk: dms.id dmsFk: dms.id
}, myOptions); }, myOptions));
promises.push(newTicketDms); await Promise.all(promises);
});
const resolvedPromises = await Promise.all(promises);
if (tx) await tx.commit(); if (tx) await tx.commit();
return resolvedPromises; return uploadedFiles;
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();
throw e; throw e;

View File

@ -24,5 +24,12 @@
"userFk": { "userFk": {
"type": "number" "type": "number"
} }
},
"relations": {
"expeditionStateType": {
"type": "belongsTo",
"model": "ExpeditionStateType",
"foreignKey": "typeFk"
}
} }
} }

View File

@ -33,10 +33,10 @@
"model": "State", "model": "State",
"foreignKey": "stateFk" "foreignKey": "stateFk"
}, },
"worker": { "user": {
"type": "belongsTo", "type": "belongsTo",
"model": "Worker", "model": "VnUser",
"foreignKey": "workerFk" "foreignKey": "userFk"
} }
} }
} }

View File

@ -2,5 +2,5 @@ module.exports = function(Self) {
require('../methods/ticket-tracking/setDelivered')(Self); require('../methods/ticket-tracking/setDelivered')(Self);
Self.validatesPresenceOf('stateFk', {message: 'State cannot be blank'}); Self.validatesPresenceOf('stateFk', {message: 'State cannot be blank'});
Self.validatesPresenceOf('workerFk', {message: 'Worker cannot be blank'}); Self.validatesPresenceOf('userFk', {message: 'Worker cannot be blank'});
}; };

View File

@ -21,7 +21,7 @@
"stateFk": { "stateFk": {
"type": "number" "type": "number"
}, },
"workerFk": { "userFk": {
"type": "number" "type": "number"
} }
}, },
@ -36,10 +36,10 @@
"model": "State", "model": "State",
"foreignKey": "stateFk" "foreignKey": "stateFk"
}, },
"worker": { "user": {
"type": "belongsTo", "type": "belongsTo",
"model": "Worker", "model": "VnUser",
"foreignKey": "workerFk" "foreignKey": "userFk"
} }
} }
} }

View File

@ -202,9 +202,9 @@ export default class Controller extends Section {
if (!ticket.landed) { if (!ticket.landed) {
const newLanded = await this.getLanded({ const newLanded = await this.getLanded({
shipped: this.$.model.userParams.dateToAdvance, shipped: this.$.model.userParams.dateToAdvance,
addressFk: ticket.addressFk, addressFk: ticket.futureAddressFk,
agencyModeFk: ticket.agencyModeFk, agencyModeFk: ticket.agencyModeFk ?? ticket.futureAgencyModeFk,
warehouseFk: ticket.warehouseFk warehouseFk: ticket.futureWarehouseFk
}); });
if (!newLanded) if (!newLanded)
throw new Error(this.$t(`No delivery zone available for this landing date`)); throw new Error(this.$t(`No delivery zone available for this landing date`));
@ -213,13 +213,13 @@ export default class Controller extends Section {
ticket.zoneFk = newLanded.zoneFk; ticket.zoneFk = newLanded.zoneFk;
} }
const params = { const params = {
clientFk: ticket.clientFk, clientFk: ticket.futureClientFk,
nickname: ticket.nickname, nickname: ticket.nickname,
agencyModeFk: ticket.agencyModeFk ?? ticket.futureAgencyModeFk, agencyModeFk: ticket.agencyModeFk ?? ticket.futureAgencyModeFk,
addressFk: ticket.addressFk, addressFk: ticket.futureAddressFk,
zoneFk: ticket.zoneFk ?? ticket.futureZoneFk, zoneFk: ticket.zoneFk ?? ticket.futureZoneFk,
warehouseFk: ticket.warehouseFk, warehouseFk: ticket.futureWarehouseFk,
companyFk: ticket.companyFk, companyFk: ticket.futureCompanyFk,
shipped: this.$.model.userParams.dateToAdvance, shipped: this.$.model.userParams.dateToAdvance,
landed: ticket.landed, landed: ticket.landed,
isDeleted: false, isDeleted: false,

View File

@ -7,16 +7,10 @@ class Controller extends Section {
this.filter = { this.filter = {
include: [ include: [
{ {
relation: 'worker',
scope: {
fields: ['id'],
include: {
relation: 'user', relation: 'user',
scope: { scope: {
fields: ['name'] fields: ['name']
} }
}
}
}, { }, {
relation: 'state', relation: 'state',
scope: { scope: {

View File

@ -18,13 +18,35 @@ module.exports = Self => {
} }
}); });
Self.removeFile = async(ctx, id) => { Self.removeFile = async(ctx, dmsFk, options) => {
const models = Self.app.models; const myOptions = {};
const workerDms = await Self.findById(id); let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
await models.Dms.removeFile(ctx, workerDms.dmsFk); if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
return workerDms.destroy(); try {
const WorkerDms = await Self.findOne({
where: {document: dmsFk}
}, myOptions);
const targetDms = await Self.app.models.Dms.removeFile(ctx, dmsFk, myOptions);
if (!targetDms || !WorkerDms)
throw new UserError('Try again');
const workerDmsDestroyed = await WorkerDms.destroy(myOptions);
if (tx) await tx.commit();
return workerDmsDestroyed;
} catch (e) {
await tx.rollback();
throw e;
}
}; };
}; };

View File

@ -47,30 +47,33 @@ module.exports = Self => {
}); });
Self.uploadFile = async(ctx, id) => { Self.uploadFile = async(ctx, id) => {
const models = Self.app.models; const {Dms, WorkerDms} = Self.app.models;
const promises = []; const myOptions = {};
const tx = await Self.beginTransaction({}); let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try { try {
const options = {transaction: tx}; const uploadedFiles = await Dms.uploadFile(ctx, myOptions);
const promises = uploadedFiles.map(dms =>
const uploadedFiles = await models.Dms.uploadFile(ctx, options); WorkerDms.create({
uploadedFiles.forEach(dms => {
const newWorkerDms = models.WorkerDms.create({
workerFk: id, workerFk: id,
dmsFk: dms.id dmsFk: dms.id
}, options); }, myOptions));
await Promise.all(promises);
promises.push(newWorkerDms); if (tx) await tx.commit();
});
const resolvedPromises = await Promise.all(promises);
await tx.commit(); return uploadedFiles;
} catch (e) {
return resolvedPromises; if (tx) await tx.rollback();
} catch (err) { throw e;
await tx.rollback();
throw err;
} }
}; };
}; };

View File

@ -111,9 +111,11 @@ class Controller extends Section {
dayIndex.setDate(dayIndex.getDate() + 1); dayIndex.setDate(dayIndex.getDate() + 1);
} }
if (this.worker) {
this.fetchHours(); this.fetchHours();
this.getWeekData(); this.getWeekData();
} }
}
set weekTotalHours(totalHours) { set weekTotalHours(totalHours) {
this._weekTotalHours = this.formatHours(totalHours); this._weekTotalHours = this.formatHours(totalHours);
@ -171,8 +173,6 @@ class Controller extends Section {
]} ]}
}; };
this.$.model.applyFilter(filter, params).then(() => { this.$.model.applyFilter(filter, params).then(() => {
if (!this.card.hasWorkCenter) return;
this.getWorkedHours(this.started, this.ended); this.getWorkedHours(this.started, this.ended);
this.getAbsences(); this.getAbsences();
}); });

View File

@ -64,7 +64,7 @@ module.exports = Self => {
promises.push(models.TicketTracking.create({ promises.push(models.TicketTracking.create({
ticketFk: ticket.id, ticketFk: ticket.id,
stateFk: fixingState.id, stateFk: fixingState.id,
workerFk: worker.id userFk: worker.id
}, myOptions)); }, myOptions));
} }
} }

View File

@ -35,25 +35,17 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
ended = simpleDate(ended);
started = simpleDate(started);
query = ` query = `
SELECT * SELECT *
FROM vn.zoneEvent FROM vn.zoneEvent
WHERE zoneFk = ? WHERE zoneFk = ?
AND ((type = 'indefinitely') AND (IFNULL(started, ?) <= ? AND IFNULL(ended,?) >= ?)
OR (type = 'day' AND dated BETWEEN ? AND ?)
OR (type = 'range'
AND (
(started BETWEEN ? AND ?)
OR
(ended BETWEEN ? AND ?)
OR
(started <= ? AND ended >= ?)
)
)
)
ORDER BY type='indefinitely' DESC, type='range' DESC, type='day' DESC;`; ORDER BY type='indefinitely' DESC, type='range' DESC, type='day' DESC;`;
const events = await Self.rawSql(query, const events = await Self.rawSql(query,
[zoneFk, started, ended, started, ended, started, ended, started, ended], myOptions); [zoneFk, started, ended, ended, started], myOptions);
query = ` query = `
SELECT e.* SELECT e.*
@ -75,4 +67,7 @@ module.exports = Self => {
return {events, exclusions, geoExclusions}; return {events, exclusions, geoExclusions};
}; };
function simpleDate(date) {
return date.toISOString().split('T')[0];
}
}; };

View File

@ -30,7 +30,7 @@ describe('zone getEventsFiltered()', () => {
const result = await models.Zone.getEventsFiltered(9, today, today, options); const result = await models.Zone.getEventsFiltered(9, today, today, options);
expect(result.events.length).toEqual(1); expect(result.events.length).toEqual(3);
expect(result.exclusions.length).toEqual(0); expect(result.exclusions.length).toEqual(0);
await tx.rollback(); await tx.rollback();
@ -47,11 +47,12 @@ describe('zone getEventsFiltered()', () => {
const options = {transaction: tx}; const options = {transaction: tx};
const date = Date.vnNew(); const date = Date.vnNew();
date.setFullYear(date.getFullYear() - 2); date.setFullYear(date.getFullYear() - 2);
const dateTomorrow = new Date(date.setDate(date.getDate() + 1)); const dateTomorrow = new Date(date);
dateTomorrow.setDate(dateTomorrow.getDate() + 1);
const result = await models.Zone.getEventsFiltered(9, date, dateTomorrow, options); const result = await models.Zone.getEventsFiltered(9, date, dateTomorrow, options);
expect(result.events.length).toEqual(0); expect(result.events.length).toEqual(1);
expect(result.exclusions.length).toEqual(0); expect(result.exclusions.length).toEqual(0);
await tx.rollback(); await tx.rollback();

View File

@ -1,6 +1,6 @@
{ {
"name": "salix-back", "name": "salix-back",
"version": "23.50.01", "version": "23.52.01",
"author": "Verdnatura Levante SL", "author": "Verdnatura Levante SL",
"description": "Salix backend", "description": "Salix backend",
"license": "GPL-3.0", "license": "GPL-3.0",

View File

@ -20,7 +20,7 @@ SELECT
u.nickName salesPersonName, u.nickName salesPersonName,
ipkg.itemPackingTypes ipkg.itemPackingTypes
FROM route r FROM route r
LEFT JOIN ticket t ON t.routeFk = r.id JOIN ticket t ON t.routeFk = r.id
LEFT JOIN address a ON a.id = t.addressFk LEFT JOIN address a ON a.id = t.addressFk
LEFT JOIN client c ON c.id = t.clientFk LEFT JOIN client c ON c.id = t.clientFk
LEFT JOIN worker w ON w.id = client_getSalesPerson(t.clientFk, CURDATE()) LEFT JOIN worker w ON w.id = client_getSalesPerson(t.clientFk, CURDATE())

View File

@ -16,6 +16,7 @@
<tr> <tr>
<td class="font gray uppercase">{{$t('clientId')}}</td> <td class="font gray uppercase">{{$t('clientId')}}</td>
<th>{{client.id}}</th> <th>{{client.id}}</th>
</tr> </tr>
<tr> <tr>
<td class="font gray uppercase">{{$t('invoice')}}</td> <td class="font gray uppercase">{{$t('invoice')}}</td>
@ -80,6 +81,9 @@
<span>{{formatDate(ticket.shipped, '%d-%m-%Y')}}</span> <span>{{formatDate(ticket.shipped, '%d-%m-%Y')}}</span>
</div> </div>
</div> </div>
<span class="pull-right">
<h2>{{ticket.street}}</h2>
</span>
<span id="nickname" class="pull-right"> <span id="nickname" class="pull-right">
<h2>{{ticket.nickname}}</h2> <h2>{{ticket.nickname}}</h2>
</span> </span>

View File

@ -2,9 +2,12 @@ SELECT
t.id, t.id,
t.shipped, t.shipped,
t.nickname, t.nickname,
tto.description tto.description,
t.addressFk,
a.street
FROM invoiceOut io FROM invoiceOut io
JOIN ticket t ON t.refFk = io.REF JOIN ticket t ON t.refFk = io.REF
JOIN `address` a ON a.id = t.addressFk
LEFT JOIN observationType ot ON ot.code = 'invoiceOut' LEFT JOIN observationType ot ON ot.code = 'invoiceOut'
LEFT JOIN ticketObservation tto ON tto.ticketFk = t.id LEFT JOIN ticketObservation tto ON tto.ticketFk = t.id
AND tto.observationTypeFk = ot.id AND tto.observationTypeFk = ot.id