Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4045-incoterms_authorization

This commit is contained in:
Joan Sanchez 2022-06-22 11:38:01 +02:00
commit 2a758fd12a
82 changed files with 3695 additions and 3946 deletions

5
back/helpers.spec.js Normal file
View File

@ -0,0 +1,5 @@
const baseTime = null; // new Date(2022, 0, 19, 8, 0, 0, 0);
if (baseTime) {
jasmine.clock().install();
jasmine.clock().mockDate(baseTime);
}

View File

@ -1,16 +1,15 @@
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('setPassword', { Self.remoteMethod('setPassword', {
description: 'Sets the user password', description: 'Sets the user password',
accepts: [ accepts: [
{ {
arg: 'id', arg: 'id',
type: 'Number', type: 'number',
description: 'The user id', description: 'The user id',
http: {source: 'path'} http: {source: 'path'}
}, { }, {
arg: 'newPassword', arg: 'newPassword',
type: 'String', type: 'string',
description: 'The new password', description: 'The new password',
required: true required: true
} }

View File

@ -200,6 +200,31 @@ module.exports = Self => {
const toTable = table.toTable; const toTable = table.toTable;
const baseName = table.fileName; const baseName = table.fileName;
const firstEntry = entries[0];
const entryName = firstEntry.entryName;
const startIndex = (entryName.length - 10);
const endIndex = (entryName.length - 4);
const dateString = entryName.substring(startIndex, endIndex);
const lastUpdated = new Date();
let updated = null;
if (file.updated) {
updated = new Date(file.updated);
updated.setHours(0, 0, 0, 0);
}
// Format string date to a date object
lastUpdated.setFullYear(`20${dateString.substring(4, 6)}`);
lastUpdated.setMonth(parseInt(dateString.substring(2, 4)) - 1);
lastUpdated.setDate(dateString.substring(0, 2));
lastUpdated.setHours(0, 0, 0, 0);
if (updated && lastUpdated <= updated) {
console.debug(`Table ${toTable} already updated, skipping...`);
return;
}
const tx = await Self.beginTransaction({}); const tx = await Self.beginTransaction({});
try { try {

View File

@ -31,11 +31,13 @@ COPY \
import-changes.sh \ import-changes.sh \
config.ini \ config.ini \
dump/mysqlPlugins.sql \ dump/mysqlPlugins.sql \
dump/mockDate.sql \
dump/structure.sql \ dump/structure.sql \
dump/dumpedFixtures.sql \ dump/dumpedFixtures.sql \
./ ./
RUN gosu mysql docker-init.sh \ RUN gosu mysql docker-init.sh \
&& docker-dump.sh mysqlPlugins \ && docker-dump.sh mysqlPlugins \
&& docker-dump.sh mockDate \
&& docker-dump.sh structure \ && docker-dump.sh structure \
&& docker-dump.sh dumpedFixtures \ && docker-dump.sh dumpedFixtures \
&& gosu mysql docker-temp-stop.sh && gosu mysql docker-temp-stop.sh

View File

@ -1,14 +1,14 @@
CREATE TABLE `vn`.`mdbBranch` ( CREATE TABLE IF NOT EXISTS `vn`.`mdbBranch` (
`name` VARCHAR(255), `name` VARCHAR(255),
PRIMARY KEY(`name`) PRIMARY KEY(`name`)
); );
CREATE TABLE `vn`.`mdbVersion` ( CREATE TABLE IF NOT EXISTS `vn`.`mdbVersion` (
`app` VARCHAR(255) NOT NULL, `app` VARCHAR(255) NOT NULL,
`branchFk` VARCHAR(255) NOT NULL, `branchFk` VARCHAR(255) NOT NULL,
`version` INT, `version` INT,
CONSTRAINT `mdbVersion_branchFk` FOREIGN KEY (`branchFk`) REFERENCES `vn`.`mdbBranch` (`name`) ON DELETE CASCADE ON UPDATE CASCADE CONSTRAINT `mdbVersion_branchFk` FOREIGN KEY (`branchFk`) REFERENCES `vn`.`mdbBranch` (`name`) ON DELETE CASCADE ON UPDATE CASCADE
); );
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) INSERT IGNORE INTO `salix`.`ACL` (`id`, `model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES('MdbVersion', '*', '*', 'ALLOW', 'ROLE', 'developer'); VALUES(318, 'MdbVersion', '*', '*', 'ALLOW', 'ROLE', 'developer');

View File

@ -0,0 +1,4 @@
INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId)
VALUES
('Client','setPassword','WRITE','ALLOW','ROLE','salesPerson'),
('Client','updateUser','WRITE','ALLOW','ROLE','salesPerson');

File diff suppressed because it is too large Load Diff

43
db/dump/mockDate.sql Normal file
View File

@ -0,0 +1,43 @@
CREATE SCHEMA IF NOT EXISTS `util`;
USE `util`;
DELIMITER ;;
DROP FUNCTION IF EXISTS `util`.`mockedDate`;
CREATE FUNCTION `util`.`mockedDate`()
RETURNS DATETIME
DETERMINISTIC
BEGIN
RETURN NOW();
-- '2022-01-19 08:00:00'
END ;;
DELIMITER ;
DELIMITER ;;
DROP FUNCTION IF EXISTS `util`.`VN_CURDATE`;
CREATE FUNCTION `util`.`VN_CURDATE`()
RETURNS DATE
DETERMINISTIC
BEGIN
RETURN DATE(mockedDate());
END ;;
DELIMITER ;
DELIMITER ;;
DROP FUNCTION IF EXISTS `util`.`VN_CURTIME`;
CREATE FUNCTION `util`.`VN_CURTIME`()
RETURNS TIME
DETERMINISTIC
BEGIN
RETURN TIME(mockedDate());
END ;;
DELIMITER ;
DELIMITER ;;
DROP FUNCTION IF EXISTS `util`.`VN_NOW`;
CREATE FUNCTION `util`.`VN_NOW`()
RETURNS DATETIME
DETERMINISTIC
BEGIN
RETURN mockedDate();
END ;;
DELIMITER ;

File diff suppressed because it is too large Load Diff

View File

@ -96,5 +96,12 @@ mysqldump \
--databases \ --databases \
${SCHEMAS[@]} \ ${SCHEMAS[@]} \
${IGNORETABLES[@]} \ ${IGNORETABLES[@]} \
| sed 's/\bCURDATE\b/vn.VN_CURDATE/ig'\
| sed 's/\bCURTIME\b/vn.VN_CURTIME/ig' \
| sed 's/\bNOW\b/vn.VN_NOW/ig' \
| sed 's/\bCURRENT_DATE\b/vn.VN_CURDATE/ig' \
| sed 's/\bCURRENT_TIME\b/vn.VN_CURTIME/ig' \
| sed 's/\bLOCALTIME\b/vn.VN_NOW/ig' \
| sed 's/\bLOCALTIMESTAMP\b/vn.VN_NOW/ig' \
| sed 's/ AUTO_INCREMENT=[0-9]* //g' \ | sed 's/ AUTO_INCREMENT=[0-9]* //g' \
> dump/structure.sql > dump/structure.sql

View File

@ -5,8 +5,9 @@ describe('zone zone_getLanded()', () => {
it(`should return data for a shipped in the past`, async() => { it(`should return data for a shipped in the past`, async() => {
let stmts = []; let stmts = [];
let stmt; let stmt;
stmts.push('START TRANSACTION'); stmts.push('START TRANSACTION');
const date = new Date();
date.setHours(0, 0, 0, 0);
let params = { let params = {
addressFk: 121, addressFk: 121,
@ -14,7 +15,8 @@ describe('zone zone_getLanded()', () => {
warehouseFk: 1, warehouseFk: 1,
showExpiredZones: true}; showExpiredZones: true};
stmt = new ParameterizedSQL('CALL zone_getLanded(DATE_ADD(CURDATE(), INTERVAL -1 DAY), ?, ?, ?, ?)', [ stmt = new ParameterizedSQL('CALL zone_getLanded(DATE_ADD(?, INTERVAL -1 DAY), ?, ?, ?, ?)', [
date,
params.addressFk, params.addressFk,
params.agencyModeFk, params.agencyModeFk,
params.warehouseFk, params.warehouseFk,
@ -38,6 +40,8 @@ describe('zone zone_getLanded()', () => {
it(`should return data for a shipped tomorrow`, async() => { it(`should return data for a shipped tomorrow`, async() => {
let stmts = []; let stmts = [];
let stmt; let stmt;
const date = new Date();
date.setHours(0, 0, 0, 0);
stmts.push('START TRANSACTION'); stmts.push('START TRANSACTION');
@ -47,7 +51,8 @@ describe('zone zone_getLanded()', () => {
warehouseFk: 1, warehouseFk: 1,
showExpiredZones: false}; showExpiredZones: false};
stmt = new ParameterizedSQL('CALL zone_getLanded(DATE_ADD(CURDATE(), INTERVAL +2 DAY), ?, ?, ?, ?)', [ stmt = new ParameterizedSQL('CALL zone_getLanded(DATE_ADD(?, INTERVAL +2 DAY), ?, ?, ?, ?)', [
date,
params.addressFk, params.addressFk,
params.agencyModeFk, params.agencyModeFk,
params.warehouseFk, params.warehouseFk,

View File

@ -55,6 +55,7 @@ export default {
setPassword: '.vn-menu [name="setPassword"]', setPassword: '.vn-menu [name="setPassword"]',
activateAccount: '.vn-menu [name="enableAccount"]', activateAccount: '.vn-menu [name="enableAccount"]',
activateUser: '.vn-menu [name="activateUser"]', activateUser: '.vn-menu [name="activateUser"]',
deactivateUser: '.vn-menu [name="deactivateUser"]',
newPassword: 'vn-textfield[ng-model="$ctrl.newPassword"]', newPassword: 'vn-textfield[ng-model="$ctrl.newPassword"]',
repeatPassword: 'vn-textfield[ng-model="$ctrl.repeatPassword"]', repeatPassword: 'vn-textfield[ng-model="$ctrl.repeatPassword"]',
newRole: 'vn-autocomplete[ng-model="$ctrl.newRole"]', newRole: 'vn-autocomplete[ng-model="$ctrl.newRole"]',
@ -275,6 +276,7 @@ export default {
clientWebAccess: { clientWebAccess: {
enableWebAccessCheckbox: 'vn-check[label="Enable web access"]', enableWebAccessCheckbox: 'vn-check[label="Enable web access"]',
userName: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.name"]', userName: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.name"]',
email: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.email"]',
saveButton: 'button[type=submit]' saveButton: 'button[type=submit]'
}, },
clientNotes: { clientNotes: {

View File

@ -1,3 +1,4 @@
/* eslint max-len: ["error", { "code": 150 }]*/
import selectors from '../../helpers/selectors'; import selectors from '../../helpers/selectors';
import getBrowser from '../../helpers/puppeteer'; import getBrowser from '../../helpers/puppeteer';
@ -8,7 +9,7 @@ describe('Client Edit web access path', () => {
browser = await getBrowser(); browser = await getBrowser();
page = browser.page; page = browser.page;
await page.loginAndModule('employee', 'client'); await page.loginAndModule('employee', 'client');
await page.accessToSearchResult('Bruce Banner'); await page.accessToSearchResult('max');
await page.accessToSection('client.card.webAccess'); await page.accessToSection('client.card.webAccess');
}); });
@ -26,7 +27,16 @@ describe('Client Edit web access path', () => {
it(`should update the name`, async() => { it(`should update the name`, async() => {
await page.clearInput(selectors.clientWebAccess.userName); await page.clearInput(selectors.clientWebAccess.userName);
await page.write(selectors.clientWebAccess.userName, 'Hulk'); await page.write(selectors.clientWebAccess.userName, 'Legion');
await page.waitToClick(selectors.clientWebAccess.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it(`should update the email`, async() => {
await page.clearInput(selectors.clientWebAccess.email);
await page.write(selectors.clientWebAccess.email, 'legion@marvel.com');
await page.waitToClick(selectors.clientWebAccess.saveButton); await page.waitToClick(selectors.clientWebAccess.saveButton);
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
@ -43,30 +53,36 @@ describe('Client Edit web access path', () => {
it('should confirm web access name have been updated', async() => { it('should confirm web access name have been updated', async() => {
const result = await page.waitToGetProperty(selectors.clientWebAccess.userName, 'value'); const result = await page.waitToGetProperty(selectors.clientWebAccess.userName, 'value');
expect(result).toEqual('Hulk'); expect(result).toEqual('Legion');
});
it('should confirm web access email have been updated', async() => {
const result = await page.waitToGetProperty(selectors.clientWebAccess.email, 'value');
expect(result).toEqual('legion@marvel.com');
}); });
it(`should navigate to the log section`, async() => { it(`should navigate to the log section`, async() => {
await page.accessToSection('client.card.log'); await page.accessToSection('client.card.log');
}); });
it(`should confirm the last log is showing the updated client name and no modifications on the active checkbox`, async() => { it(`should confirm the last log shows the updated client name and no modifications on active checkbox`, async() => {
let lastModificationPreviousValue = await page let lastModificationPreviousValue = await page
.waitToGetProperty(selectors.clientLog.lastModificationPreviousValue, 'innerText'); .waitToGetProperty(selectors.clientLog.lastModificationPreviousValue, 'innerText');
let lastModificationCurrentValue = await page let lastModificationCurrentValue = await page
.waitToGetProperty(selectors.clientLog.lastModificationCurrentValue, 'innerText'); .waitToGetProperty(selectors.clientLog.lastModificationCurrentValue, 'innerText');
expect(lastModificationPreviousValue).toEqual('name BruceBanner active false'); expect(lastModificationPreviousValue).toEqual('name MaxEisenhardt active false');
expect(lastModificationCurrentValue).toEqual('name Hulk active false'); expect(lastModificationCurrentValue).toEqual('name Legion active false');
}); });
it(`should confirm the penultimate log is showing the updated avtive field and no modifications on the client name`, async() => { it(`should confirm the penultimate log shows the updated active and no modifications on client name`, async() => {
let penultimateModificationPreviousValue = await page let penultimateModificationPreviousValue = await page
.waitToGetProperty(selectors.clientLog.penultimateModificationPreviousValue, 'innerText'); .waitToGetProperty(selectors.clientLog.penultimateModificationPreviousValue, 'innerText');
let penultimateModificationCurrentValue = await page let penultimateModificationCurrentValue = await page
.waitToGetProperty(selectors.clientLog.penultimateModificationCurrentValue, 'innerText'); .waitToGetProperty(selectors.clientLog.penultimateModificationCurrentValue, 'innerText');
expect(penultimateModificationPreviousValue).toEqual('name BruceBanner active true'); expect(penultimateModificationPreviousValue).toEqual('name MaxEisenhardt active true');
expect(penultimateModificationCurrentValue).toEqual('name BruceBanner active false'); expect(penultimateModificationCurrentValue).toEqual('name MaxEisenhardt active false');
}); });
}); });

View File

@ -28,12 +28,12 @@ describe('Client defaulter path', () => {
const salesPersonName = const salesPersonName =
await page.waitToGetProperty(selectors.clientDefaulter.firstSalesPersonName, 'innerText'); await page.waitToGetProperty(selectors.clientDefaulter.firstSalesPersonName, 'innerText');
expect(clientName).toEqual('Ororo Munroe'); expect(clientName).toEqual('Bruce Banner');
expect(salesPersonName).toEqual('salesPersonNick'); expect(salesPersonName).toEqual('developer');
}); });
it('should first observation not changed', async() => { it('should first observation not changed', async() => {
const expectedObservation = 'Madness, as you know, is like gravity, all it takes is a little push'; const expectedObservation = 'Meeting with Black Widow 21st 9am';
const result = await page.waitToGetProperty(selectors.clientDefaulter.firstObservation, 'value'); const result = await page.waitToGetProperty(selectors.clientDefaulter.firstObservation, 'value');
expect(result).toContain(expectedObservation); expect(result).toContain(expectedObservation);

View File

@ -36,8 +36,7 @@ describe('Account create and basic data path', () => {
await page.waitForState('account.card.basicData'); await page.waitForState('account.card.basicData');
}); });
it('should reload the section and check the name is as expected', async() => { it('should check the name is as expected', async() => {
await page.reloadSection('account.card.basicData');
const result = await page.waitToGetProperty(selectors.accountBasicData.name, 'value'); const result = await page.waitToGetProperty(selectors.accountBasicData.name, 'value');
expect(result).toEqual('Remy'); expect(result).toEqual('Remy');
@ -103,25 +102,39 @@ describe('Account create and basic data path', () => {
}); });
}); });
// creating the account without the active property set to true seems to be creating an active user anyways describe('deactivate user', () => {
// describe('activate user', () => { it(`should check the inactive user icon isn't present in the descriptor just yet`, async() => {
// it(`should check the inactive user icon is present in the descriptor`, async() => { await page.waitForNumberOfElements(selectors.accountDescriptor.activeUserIcon, 0);
// await page.waitForSelector(selectors.accountDescriptor.activeUserIcon, {visible: true}); });
// });
// it('should activate the user using the descriptor menu', async() => { it('should deactivate the user using the descriptor menu', async() => {
// await page.waitToClick(selectors.accountDescriptor.menuButton); await page.waitToClick(selectors.accountDescriptor.menuButton);
// await page.waitToClick(selectors.accountDescriptor.activateUser); await page.waitToClick(selectors.accountDescriptor.deactivateUser);
// await page.waitToClick(selectors.accountDescriptor.acceptButton); await page.waitToClick(selectors.accountDescriptor.acceptButton);
// const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
// expect(message.text).toContain('user enabled?'); expect(message.text).toContain('User deactivated!');
// }); });
// it('should check the inactive user icon is not present anymore', async() => { it('should check the inactive user icon is now present', async() => {
// await page.waitForNumberOfElements(selectors.accountDescriptor.activeUserIcon, 0); await page.waitForNumberOfElements(selectors.accountDescriptor.activeUserIcon, 1);
// }); });
// }); });
describe('activate user', () => {
it('should activate the user using the descriptor menu', async() => {
await page.waitToClick(selectors.accountDescriptor.menuButton);
await page.waitToClick(selectors.accountDescriptor.activateUser);
await page.waitToClick(selectors.accountDescriptor.acceptButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('User activated!');
});
it('should check the inactive user icon is not present anymore', async() => {
await page.waitForNumberOfElements(selectors.accountDescriptor.activeUserIcon, 0);
});
});
describe('mail forwarding', () => { describe('mail forwarding', () => {
it('should activate the mail forwarding and set the recipent email', async() => { it('should activate the mail forwarding and set the recipent email', async() => {

View File

@ -56,7 +56,7 @@ describe('Account Alias create and basic data path', () => {
expect(result).toContain('psykers'); expect(result).toContain('psykers');
}); });
it('should search for the IT alias group then access to the users section then check the role listed is the expected one', async() => { it('should search IT alias then access the user section to check the role listed is the expected one', async() => {
await page.accessToSearchResult('IT'); await page.accessToSearchResult('IT');
await page.accessToSection('account.alias.card.users'); await page.accessToSection('account.alias.card.users');
const rolesCount = await page.countElement(selectors.accountAliasUsers.anyResult); const rolesCount = await page.countElement(selectors.accountAliasUsers.anyResult);

View File

@ -15,8 +15,9 @@ export default function currency($translate) {
maximumFractionDigits: fractionSize maximumFractionDigits: fractionSize
}; };
const lang = $translate.use() == 'es' ? 'de' : $translate.use();
if (typeof input == 'number') { if (typeof input == 'number') {
return new Intl.NumberFormat($translate.use(), options) return new Intl.NumberFormat(lang, options)
.format(input); .format(input);
} }

View File

@ -10,6 +10,7 @@ export default class App {
constructor() { constructor() {
this.loaderStatus = 0; this.loaderStatus = 0;
this.loading = false; this.loading = false;
this.versionInterval = setInterval(this.getVersion.bind(this), 300000);
} }
showMessage(message) { showMessage(message) {
@ -38,6 +39,21 @@ export default class App {
if (this.loaderStatus === 0) if (this.loaderStatus === 0)
this.loading = false; this.loading = false;
} }
getVersion() {
this.logger.$http.get('Applications/status');
}
setVersion(newVersion) {
if (newVersion) {
const currentVersion = localStorage.getItem('salix-version');
if (newVersion != currentVersion) {
this.hasNewVersion = true;
clearInterval(this.versionInterval);
}
localStorage.setItem('salix-version', newVersion);
}
}
} }
ngModule.service('vnApp', App); ngModule.service('vnApp', App);

View File

@ -30,14 +30,17 @@ function interceptor($q, vnApp, vnToken, $translate) {
}, },
response(response) { response(response) {
vnApp.popLoader(); vnApp.popLoader();
const newVersion = response.headers('salix-version');
vnApp.setVersion(newVersion);
return response; return response;
}, },
responseError(rejection) { responseError(rejection) {
vnApp.popLoader(); vnApp.popLoader();
let err = new HttpError(rejection.statusText); const err = new HttpError(rejection.statusText);
Object.assign(err, rejection); Object.assign(err, rejection);
return $q.reject(err); return $q.reject(err);
} },
}; };
} }
ngModule.factory('vnInterceptor', interceptor); ngModule.factory('vnInterceptor', interceptor);

View File

@ -19,6 +19,14 @@
</div> </div>
<vn-slot name="topbar"></vn-slot> <vn-slot name="topbar"></vn-slot>
<div class="side end"> <div class="side end">
<vn-icon-button
id="refresh"
icon="refresh"
ng-if="$ctrl.vnApp.hasNewVersion"
ng-click="$ctrl.refresh()"
class="refresh"
translate-attr="{title: 'There is a new version, click here to reload'}">
</vn-icon-button>
<vn-icon-button <vn-icon-button
id="apps" id="apps"
icon="apps" icon="apps"
@ -39,7 +47,6 @@
translate-attr="{title: 'Account'}" translate-attr="{title: 'Account'}"
on-error-src/> on-error-src/>
</button> </button>
</div> </div>
<vn-menu vn-id="apps-menu"> <vn-menu vn-id="apps-menu">
<vn-list class="modules-menu"> <vn-list class="modules-menu">

View File

@ -26,6 +26,10 @@ export class Layout extends Component {
const token = this.vnToken.token; const token = this.vnToken.token;
return `/api/Images/user/160x160/${userId}/download?access_token=${token}`; return `/api/Images/user/160x160/${userId}/download?access_token=${token}`;
} }
refresh() {
window.location.reload();
}
} }
Layout.$inject = ['$element', '$scope', 'vnModules']; Layout.$inject = ['$element', '$scope', 'vnModules'];

View File

@ -60,6 +60,9 @@ vn-layout {
font-size: 1.05rem; font-size: 1.05rem;
padding: 0; padding: 0;
} }
.vn-icon-button.refresh {
color: $color-alert;
}
} }
& > vn-side-menu > .menu { & > vn-side-menu > .menu {
display: flex; display: flex;

View File

@ -17,6 +17,7 @@ Go to module summary: Ir a la vista previa del módulo
Show summary: Mostrar vista previa 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
# Actions # Actions

View File

@ -123,5 +123,6 @@
"The worker has hours recorded that day": "The worker has hours recorded that day", "The worker has hours recorded that day": "The worker has hours recorded that day",
"isWithoutNegatives": "isWithoutNegatives", "isWithoutNegatives": "isWithoutNegatives",
"routeFk": "routeFk", "routeFk": "routeFk",
"Not enough privileges to edit a client with verified data": "Not enough privileges to edit a client with verified data" "Not enough privileges to edit a client with verified data": "Not enough privileges to edit a client with verified data",
"Can't change the password of another worker": "Can't change the password of another worker"
} }

View File

@ -226,5 +226,6 @@
"reference duplicated": "Referencia duplicada", "reference duplicated": "Referencia duplicada",
"This ticket is already a refund": "Este ticket ya es un abono", "This ticket is already a refund": "Este ticket ya es un abono",
"isWithoutNegatives": "isWithoutNegatives", "isWithoutNegatives": "isWithoutNegatives",
"routeFk": "routeFk" "routeFk": "routeFk",
"Can't change the password of another worker": "No se puede cambiar la contraseña de otro trabajador"
} }

View File

@ -31,7 +31,8 @@
"loopback#token": {} "loopback#token": {}
}, },
"auth:after": { "auth:after": {
"./middleware/current-user": {} "./middleware/current-user": {},
"./middleware/salix-version": {}
}, },
"parse": { "parse": {
"body-parser#json":{} "body-parser#json":{}

View File

@ -0,0 +1,8 @@
const packageJson = require('../../../package.json');
module.exports = function(options) {
return function(req, res, next) {
res.set('Salix-Version', packageJson.version);
next();
};
};

View File

@ -51,6 +51,9 @@ module.exports = function(Self) {
Self.createReceipt = async(ctx, options) => { Self.createReceipt = async(ctx, options) => {
const models = Self.app.models; const models = Self.app.models;
const args = ctx.args; const args = ctx.args;
const date = new Date();
date.setHours(0, 0, 0, 0);
let tx; let tx;
const myOptions = {}; const myOptions = {};
@ -92,8 +95,9 @@ module.exports = function(Self) {
throw new UserError('Invalid account'); throw new UserError('Invalid account');
await Self.rawSql( await Self.rawSql(
`CALL vn.ledger_doCompensation(CURDATE(), ?, ?, ?, ?, ?, ?)`, `CALL vn.ledger_doCompensation(?, ?, ?, ?, ?, ?, ?)`,
[ [
date,
args.compensationAccount, args.compensationAccount,
args.bankFk, args.bankFk,
accountingType.receiptDescription + originalClient.accountingAccount, accountingType.receiptDescription + originalClient.accountingAccount,
@ -106,9 +110,10 @@ module.exports = function(Self) {
} else if (accountingType.isAutoConciliated == true) { } else if (accountingType.isAutoConciliated == true) {
const description = `${originalClient.id} : ${originalClient.socialName} - ${accountingType.receiptDescription}`; const description = `${originalClient.id} : ${originalClient.socialName} - ${accountingType.receiptDescription}`;
const [xdiarioNew] = await Self.rawSql( const [xdiarioNew] = await Self.rawSql(
`SELECT xdiario_new(?, CURDATE(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ledger;`, `SELECT xdiario_new(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ledger;`,
[ [
null, null,
date,
bank.account, bank.account,
originalClient.accountingAccount, originalClient.accountingAccount,
description, description,
@ -126,9 +131,10 @@ module.exports = function(Self) {
); );
await Self.rawSql( await Self.rawSql(
`SELECT xdiario_new(?, CURDATE(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`, `SELECT xdiario_new(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`,
[ [
xdiarioNew.ledger, xdiarioNew.ledger,
date,
originalClient.accountingAccount, originalClient.accountingAccount,
bank.account, bank.account,
description, description,

View File

@ -62,7 +62,7 @@ module.exports = function(Self) {
{ {
relation: 'account', relation: 'account',
scope: { scope: {
fields: ['id', 'name', 'active'] fields: ['id', 'name', 'email', 'active']
} }
}, },
{ {
@ -74,8 +74,10 @@ module.exports = function(Self) {
] ]
}, myOptions); }, myOptions);
const query = `SELECT vn.clientGetDebt(?, CURDATE()) AS debt`; const date = new Date();
const data = await Self.rawSql(query, [id], myOptions); date.setHours(0, 0, 0, 0);
const query = `SELECT vn.clientGetDebt(?, ?) AS debt`;
const data = await Self.rawSql(query, [id, date], myOptions);
client.debt = data[0].debt; client.debt = data[0].debt;

View File

@ -25,8 +25,10 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const query = `SELECT vn.clientGetDebt(?, CURDATE()) AS debt`; const date = new Date();
const [debt] = await Self.rawSql(query, [clientFk], myOptions); date.setHours(0, 0, 0, 0);
const query = `SELECT vn.clientGetDebt(?, ?) AS debt`;
const [debt] = await Self.rawSql(query, [clientFk, date], myOptions);
return debt; return debt;
}; };

View File

@ -32,6 +32,8 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const date = new Date();
date.setHours(0, 0, 0, 0);
const ticket = await Self.app.models.Ticket.findById(ticketId, null, myOptions); const ticket = await Self.app.models.Ticket.findById(ticketId, null, myOptions);
const query = ` const query = `
SELECT SELECT
@ -50,12 +52,12 @@ module.exports = Self => {
JOIN vn.warehouse w ON t.warehouseFk = w.id JOIN vn.warehouse w ON t.warehouseFk = w.id
JOIN vn.address ad ON t.addressFk = ad.id JOIN vn.address ad ON t.addressFk = ad.id
JOIN vn.province pr ON ad.provinceFk = pr.id JOIN vn.province pr ON ad.provinceFk = pr.id
WHERE t.shipped >= CURDATE() AND t.clientFk = ? AND ts.alertLevel = 0 WHERE t.shipped >= ? AND t.clientFk = ? AND ts.alertLevel = 0
AND t.id <> ? AND t.warehouseFk = ? AND t.id <> ? AND t.warehouseFk = ?
ORDER BY t.shipped ORDER BY t.shipped
LIMIT 10`; LIMIT 10`;
return Self.rawSql(query, [id, ticketId, ticket.warehouseFk], myOptions); return Self.rawSql(query, [date, id, ticketId, ticket.warehouseFk], myOptions);
}; };
}; };

View File

@ -0,0 +1,32 @@
module.exports = Self => {
Self.remoteMethod('setPassword', {
description: 'Sets the password of a non-worker client',
accepts: [
{
arg: 'id',
type: 'number',
description: 'The user id',
http: {source: 'path'}
}, {
arg: 'newPassword',
type: 'string',
description: 'The new password',
required: true
}
],
http: {
path: `/:id/setPassword`,
verb: 'PATCH'
}
});
Self.setPassword = async function(id, newPassword) {
const models = Self.app.models;
const isWorker = await models.Worker.findById(id);
if (isWorker)
throw new Error(`Can't change the password of another worker`);
await models.Account.setPassword(id, newPassword);
};
};

View File

@ -0,0 +1,27 @@
const models = require('vn-loopback/server/server').models;
describe('Client setPassword', () => {
it('should throw an error the setPassword target is not just a client but a worker', async() => {
let error;
try {
await models.Client.setPassword(1106, 'newPass?');
} catch (e) {
error = e;
}
expect(error.message).toEqual(`Can't change the password of another worker`);
});
it('should change the password of the client', async() => {
let error;
try {
await models.Client.setPassword(1101, 't0pl3v3l.p455w0rd!');
} catch (e) {
error = e;
}
expect(error).toBeUndefined();
});
});

View File

@ -120,7 +120,6 @@ describe('client summary()', () => {
const result = await models.Client.summary(clientId, options); const result = await models.Client.summary(clientId, options);
expect(result.recovery.id).toEqual(3); expect(result.recovery.id).toEqual(3);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback(); await tx.rollback();

View File

@ -0,0 +1,58 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('Client updateUser', () => {
const employeeId = 1;
const activeCtx = {
accessToken: {userId: employeeId},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
const ctx = {
req: {accessToken: {userId: employeeId}},
args: {name: 'test', active: true}
};
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should throw an error the target user is not just a client but a worker', async() => {
let error;
try {
const clientID = 1106;
await models.Client.updateUser(ctx, clientID);
} catch (e) {
error = e;
}
expect(error.message).toEqual(`Can't update the user details of another worker`);
});
it('should update the user data', async() => {
let error;
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const clientID = 1105;
await models.Client.updateUser(ctx, clientID, options);
const client = await models.Account.findById(clientID, null, options);
expect(client.name).toEqual('test');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(error).toBeUndefined();
});
});

View File

@ -122,7 +122,6 @@ module.exports = Self => {
return clientModel.findOne(filter, options); return clientModel.findOne(filter, options);
} }
async function getRecoveries(recoveryModel, clientId, options) { async function getRecoveries(recoveryModel, clientId, options) {
const filter = { const filter = {
where: { where: {

View File

@ -0,0 +1,61 @@
module.exports = Self => {
Self.remoteMethodCtx('updateUser', {
description: 'Updates the user information',
accepts: [
{
arg: 'id',
type: 'number',
description: 'The user id',
http: {source: 'path'}
},
{
arg: 'name',
type: 'string',
description: 'the user name'
},
{
arg: 'email',
type: 'string',
description: 'the user email'
},
{
arg: 'active',
type: 'boolean',
description: 'whether the user is active or not'
},
],
http: {
path: '/:id/updateUser',
verb: 'PATCH'
}
});
Self.updateUser = async function(ctx, id, options) {
const models = Self.app.models;
let tx;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await models.Account.beginTransaction({});
myOptions.transaction = tx;
}
try {
const isWorker = await models.Worker.findById(id, null, myOptions);
if (isWorker)
throw new Error(`Can't update the user details of another worker`);
const user = await models.Account.findById(id, null, myOptions);
await user.updateAttributes(ctx.args, myOptions);
if (tx) await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -51,6 +51,8 @@ module.exports = Self => {
const stmts = []; const stmts = [];
const date = new Date();
date.setHours(0, 0, 0, 0);
const stmt = new ParameterizedSQL( const stmt = new ParameterizedSQL(
`SELECT * `SELECT *
FROM ( FROM (
@ -58,12 +60,12 @@ module.exports = Self => {
DISTINCT c.id clientFk, DISTINCT c.id clientFk,
c.name clientName, c.name clientName,
c.salesPersonFk, c.salesPersonFk,
u.nickname salesPersonName, u.name salesPersonName,
d.amount, d.amount,
co.created, co.created,
co.text observation, co.text observation,
uw.id workerFk, uw.id workerFk,
uw.nickname workerName, uw.name workerName,
c.creditInsurance, c.creditInsurance,
d.defaulterSinced d.defaulterSinced
FROM vn.defaulter d FROM vn.defaulter d
@ -72,10 +74,10 @@ module.exports = Self => {
LEFT JOIN account.user u ON u.id = c.salesPersonFk LEFT JOIN account.user u ON u.id = c.salesPersonFk
LEFT JOIN account.user uw ON uw.id = co.workerFk LEFT JOIN account.user uw ON uw.id = co.workerFk
WHERE WHERE
d.created = CURDATE() d.created = ?
AND d.amount > 0 AND d.amount > 0
ORDER BY co.created DESC) d` ORDER BY co.created DESC) d`
); , [date]);
stmt.merge(conn.makeWhere(filter.where)); stmt.merge(conn.makeWhere(filter.where));
stmt.merge(`GROUP BY d.clientFk`); stmt.merge(`GROUP BY d.clientFk`);

View File

@ -27,13 +27,15 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const date = new Date();
date.setHours(0, 0, 0, 0);
const query = ` const query = `
SELECT count(*) AS hasActiveRecovery SELECT count(*) AS hasActiveRecovery
FROM vn.recovery FROM vn.recovery
WHERE clientFk = ? WHERE clientFk = ?
AND IFNULL(finished,CURDATE()) >= CURDATE(); AND IFNULL(finished, ?) >= ?;
`; `;
const [result] = await Self.rawSql(query, [id], myOptions); const [result] = await Self.rawSql(query, [id, date, date], myOptions);
return result.hasActiveRecovery != 0; return result.hasActiveRecovery != 0;
}; };

View File

@ -8,30 +8,32 @@ const LoopBackContext = require('loopback-context');
module.exports = Self => { module.exports = Self => {
// Methods // Methods
require('../methods/client/getCard')(Self);
require('../methods/client/createWithUser')(Self);
require('../methods/client/hasCustomerRole')(Self);
require('../methods/client/canCreateTicket')(Self);
require('../methods/client/isValidClient')(Self);
require('../methods/client/addressesPropagateRe')(Self); require('../methods/client/addressesPropagateRe')(Self);
require('../methods/client/canBeInvoiced')(Self);
require('../methods/client/canCreateTicket')(Self);
require('../methods/client/checkDuplicated')(Self);
require('../methods/client/confirmTransaction')(Self);
require('../methods/client/consumption')(Self);
require('../methods/client/createAddress')(Self);
require('../methods/client/createReceipt')(Self);
require('../methods/client/createWithUser')(Self);
require('../methods/client/extendedListFilter')(Self);
require('../methods/client/getAverageInvoiced')(Self);
require('../methods/client/getCard')(Self);
require('../methods/client/getDebt')(Self); require('../methods/client/getDebt')(Self);
require('../methods/client/getMana')(Self); require('../methods/client/getMana')(Self);
require('../methods/client/getAverageInvoiced')(Self);
require('../methods/client/summary')(Self);
require('../methods/client/updateFiscalData')(Self);
require('../methods/client/getTransactions')(Self); require('../methods/client/getTransactions')(Self);
require('../methods/client/confirmTransaction')(Self); require('../methods/client/hasCustomerRole')(Self);
require('../methods/client/canBeInvoiced')(Self); require('../methods/client/isValidClient')(Self);
require('../methods/client/uploadFile')(Self);
require('../methods/client/lastActiveTickets')(Self); require('../methods/client/lastActiveTickets')(Self);
require('../methods/client/sendSms')(Self); require('../methods/client/sendSms')(Self);
require('../methods/client/createAddress')(Self); require('../methods/client/setPassword')(Self);
require('../methods/client/summary')(Self);
require('../methods/client/updateAddress')(Self); require('../methods/client/updateAddress')(Self);
require('../methods/client/consumption')(Self); require('../methods/client/updateFiscalData')(Self);
require('../methods/client/createReceipt')(Self);
require('../methods/client/updatePortfolio')(Self); require('../methods/client/updatePortfolio')(Self);
require('../methods/client/checkDuplicated')(Self); require('../methods/client/updateUser')(Self);
require('../methods/client/extendedListFilter')(Self); require('../methods/client/uploadFile')(Self);
// Validations // Validations
@ -446,7 +448,7 @@ module.exports = Self => {
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
app.on('started', function() { app.on('started', function() {
let account = app.models.Account; const account = app.models.Account;
account.observe('before save', async ctx => { account.observe('before save', async ctx => {
if (ctx.isNewInstance) return; if (ctx.isNewInstance) return;
@ -454,22 +456,26 @@ module.exports = Self => {
}); });
account.observe('after save', async ctx => { account.observe('after save', async ctx => {
let changes = ctx.data || ctx.instance; const changes = ctx.data || ctx.instance;
if (!ctx.isNewInstance && changes) { if (!ctx.isNewInstance && changes) {
let oldData = ctx.hookState.oldInstance; const oldData = ctx.hookState.oldInstance;
let hasChanges = oldData.name != changes.name || oldData.active != changes.active; const hasChanges = oldData.name != changes.name || oldData.active != changes.active;
if (!hasChanges) return; if (!hasChanges) return;
let userId = ctx.options.accessToken.userId; const isClient = await Self.app.models.Client.count({id: oldData.id});
let logRecord = { if (isClient) {
originFk: oldData.id, const loopBackContext = LoopBackContext.getCurrentContext();
userFk: userId, const userId = loopBackContext.active.accessToken.userId;
action: 'update', const logRecord = {
changedModel: 'Account', originFk: oldData.id,
oldInstance: {name: oldData.name, active: oldData.active}, userFk: userId,
newInstance: {name: changes.name, active: changes.active} action: 'update',
}; changedModel: 'Account',
await Self.app.models.ClientLog.create(logRecord); oldInstance: {name: oldData.name, active: oldData.active},
newInstance: {name: changes.name, active: changes.active}
};
await Self.app.models.ClientLog.create(logRecord);
}
} }
}); });
}); });

View File

@ -3,6 +3,7 @@
url="Defaulters/filter" url="Defaulters/filter"
filter="::$ctrl.filter" filter="::$ctrl.filter"
limit="20" limit="20"
order="amount DESC"
data="defaulters" data="defaulters"
auto-load="true"> auto-load="true">
</vn-crud-model> </vn-crud-model>
@ -70,13 +71,13 @@
</th> </th>
<th <th
vn-tooltip="Last observation date" vn-tooltip="Last observation date"
field="created" field="created">
shrink-datetime> <span translate>L. O. Date</span>
<span translate>Last observation D.</span>
</th> </th>
<th <th
vn-tooltip="Credit insurance" vn-tooltip="Credit insurance"
field="creditInsurance" > field="creditInsurance"
shrink>
<span translate>Credit I.</span> <span translate>Credit I.</span>
</th> </th>
<th field="defaulterSinced"> <th field="defaulterSinced">
@ -124,13 +125,13 @@
ng-model="defaulter.observation"> ng-model="defaulter.observation">
</vn-textarea> </vn-textarea>
</td> </td>
<td shrink-datetime> <td shrink-date>
<span class="chip {{::$ctrl.chipColor(defaulter.created)}}"> <span class="chip {{::$ctrl.chipColor(defaulter.created)}}">
{{::defaulter.created | date: 'dd/MM/yyyy'}} {{::defaulter.created | date: 'dd/MM/yyyy'}}
</span> </span>
</td> </td>
<td>{{::defaulter.creditInsurance | currency: 'EUR': 2}}</td> <td shrink>{{::defaulter.creditInsurance | currency: 'EUR': 2}}</td>
<td>{{::defaulter.defaulterSinced | date: 'dd/MM/yyyy'}}</td> <td shrink-date>{{::defaulter.defaulterSinced | date: 'dd/MM/yyyy'}}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -26,7 +26,7 @@ export default class Controller extends Section {
url: 'Workers/activeWithInheritedRole', url: 'Workers/activeWithInheritedRole',
where: `{role: 'salesPerson'}`, where: `{role: 'salesPerson'}`,
searchFunction: '{firstName: $search}', searchFunction: '{firstName: $search}',
showField: 'nickname', showField: 'name',
valueField: 'id', valueField: 'id',
} }
}, },
@ -35,7 +35,7 @@ export default class Controller extends Section {
autocomplete: { autocomplete: {
url: 'Workers/activeWithInheritedRole', url: 'Workers/activeWithInheritedRole',
searchFunction: '{firstName: $search}', searchFunction: '{firstName: $search}',
showField: 'nickname', showField: 'name',
valueField: 'id', valueField: 'id',
} }
}, },
@ -53,16 +53,8 @@ export default class Controller extends Section {
} }
] ]
}; };
}
get balanceDueTotal() { this.getBalanceDueTotal();
let balanceDueTotal = 0;
const defaulters = this.$.model.data || [];
for (let defaulter of defaulters)
balanceDueTotal += defaulter.amount;
return balanceDueTotal;
} }
get checked() { get checked() {
@ -76,6 +68,18 @@ export default class Controller extends Section {
return checkedLines; return checkedLines;
} }
getBalanceDueTotal() {
this.$http.get('Defaulters/filter')
.then(res => {
if (!res.data) return 0;
this.balanceDueTotal = res.data.reduce(
(accumulator, currentValue) => {
return accumulator + (currentValue['amount'] || 0);
}, 0);
});
}
chipColor(date) { chipColor(date) {
const day = 24 * 60 * 60 * 1000; const day = 24 * 60 * 60 * 1000;
const today = new Date(); const today = new Date();

View File

@ -36,17 +36,6 @@ describe('client defaulter', () => {
}); });
}); });
describe('balanceDueTotal() getter', () => {
it('should return balance due total', () => {
const data = controller.$.model.data;
const expectedAmount = data[0].amount + data[1].amount + data[2].amount;
const result = controller.balanceDueTotal;
expect(result).toEqual(expectedAmount);
});
});
describe('chipColor()', () => { describe('chipColor()', () => {
it('should return undefined when the date is the present', () => { it('should return undefined when the date is the present', () => {
let today = new Date(); let today = new Date();
@ -93,6 +82,7 @@ describe('client defaulter', () => {
const params = [{text: controller.defaulter.observation, clientFk: data[1].clientFk}]; const params = [{text: controller.defaulter.observation, clientFk: data[1].clientFk}];
jest.spyOn(controller.vnApp, 'showMessage'); jest.spyOn(controller.vnApp, 'showMessage');
$httpBackend.expect('GET', `Defaulters/filter`).respond(200);
$httpBackend.expect('POST', `ClientObservations`, params).respond(200, params); $httpBackend.expect('POST', `ClientObservations`, params).respond(200, params);
controller.onResponse(); controller.onResponse();
@ -115,5 +105,17 @@ describe('client defaulter', () => {
expect(expr).toEqual({'d.clientFk': '5'}); expect(expr).toEqual({'d.clientFk': '5'});
}); });
}); });
describe('getBalanceDueTotal()', () => {
it('should return balance due total', () => {
const defaulters = controller.$.model.data;
$httpBackend.when('GET', `Defaulters/filter`).respond(defaulters);
controller.getBalanceDueTotal();
$httpBackend.flush();
expect(controller.balanceDueTotal).toEqual(875);
});
});
}); });
}); });

View File

@ -3,7 +3,7 @@ Add observation to all selected clients: Añadir observación a {{total}} client
Balance D.: Saldo V. Balance D.: Saldo V.
Credit I.: Crédito A. Credit I.: Crédito A.
Last observation: Última observación Last observation: Última observación
Last observation D.: Fecha última O. L. O. Date: Fecha Ú. O.
Last observation date: Fecha última observación Last observation date: Fecha última observación
Search client: Buscar clientes Search client: Buscar clientes
Worker who made the last observation: Trabajador que ha realizado la última observación Worker who made the last observation: Trabajador que ha realizado la última observación

View File

@ -1,11 +1,16 @@
<vn-watcher <vn-watcher
vn-id="watcher" vn-id="watcher"
url="Accounts" url="Accounts"
id-field="id" id-field="id"
data="$ctrl.account" data="$ctrl.account"
form="form"> form="form">
</vn-watcher> </vn-watcher>
<form name="form" ng-submit="watcher.submit()" class="vn-w-md"> <vn-crud-model
auto-load="true"
url="UserPasswords"
data="$ctrl.passRequirements">
</vn-crud-model>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg"> <vn-card class="vn-pa-lg">
<vn-horizontal> <vn-horizontal>
<vn-check <vn-check
@ -28,6 +33,17 @@
rule> rule>
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-id="email"
disabled="watcher.orgData.active != $ctrl.account.active"
vn-one
label="Recovery email"
ng-model="$ctrl.account.email"
info="This email is used for user to regain access their account."
rule>
</vn-textfield>
</vn-horizontal>
</vn-card> </vn-card>
<vn-button-bar> <vn-button-bar>
<vn-submit <vn-submit
@ -55,6 +71,7 @@
<vn-textfield <vn-textfield
type="password" type="password"
label="New password" label="New password"
info="{{'Password requirements' | translate:$ctrl.passRequirements[0]}}"
ng-model="$ctrl.newPassword"> ng-model="$ctrl.newPassword">
</vn-textfield> </vn-textfield>
<vn-textfield <vn-textfield

View File

@ -44,11 +44,11 @@ export default class Controller extends Section {
throw new Error(`You must enter a new password`); throw new Error(`You must enter a new password`);
if (this.newPassword != this.repeatPassword) if (this.newPassword != this.repeatPassword)
throw new Error(`Passwords don't match`); throw new Error(`Passwords don't match`);
let account = { const data = {
password: this.newPassword newPassword: this.newPassword
}; };
this.$http.patch(`Accounts/${this.client.id}`, account).then(res => { this.$http.patch(`Clients/${this.client.id}/setPassword`, data).then(() => {
this.vnApp.showSuccess(this.$t('Data saved!')); this.vnApp.showSuccess(this.$t('Data saved!'));
}); });
} catch (e) { } catch (e) {
@ -59,6 +59,18 @@ export default class Controller extends Section {
return true; return true;
} }
onSubmit() {
const data = {
name: this.account.name,
email: this.account.email,
active: this.account.active
};
this.$http.patch(`Clients/${this.client.id}/updateUser`, data).then(() => {
this.$.watcher.notifySaved();
this.$.watcher.updateOriginalData();
});
}
} }
Controller.$inject = ['$element', '$scope']; Controller.$inject = ['$element', '$scope'];

View File

@ -52,7 +52,7 @@ describe('Component VnClientWebAccess', () => {
}); });
describe('checkConditions()', () => { describe('checkConditions()', () => {
it(`should perform a query to check if the client is valid and then store a boolean into the controller`, () => { it('should perform a query to check if the client is valid', () => {
controller.client = {id: '1234'}; controller.client = {id: '1234'};
expect(controller.canEnableCheckBox).toBeTruthy(); expect(controller.canEnableCheckBox).toBeTruthy();
@ -82,7 +82,9 @@ describe('Component VnClientWebAccess', () => {
controller.newPassword = 'm24x8'; controller.newPassword = 'm24x8';
controller.repeatPassword = 'm24x8'; controller.repeatPassword = 'm24x8';
controller.canChangePassword = true; controller.canChangePassword = true;
$httpBackend.expectPATCH('Accounts/1234', {password: 'm24x8'}).respond('done');
const query = `Clients/${controller.client.id}/setPassword`;
$httpBackend.expectPATCH(query, {newPassword: controller.newPassword}).respond('done');
controller.onPassChange(); controller.onPassChange();
$httpBackend.flush(); $httpBackend.flush();
}); });

View File

@ -4,4 +4,6 @@ New password: Nueva contraseña
Repeat password: Repetir contraseña Repeat password: Repetir contraseña
Change password: Cambiar contraseña Change password: Cambiar contraseña
Passwords don't match: Las contraseñas no coinciden Passwords don't match: Las contraseñas no coinciden
You must enter a new password: Debes introducir una nueva contraseña You must enter a new password: Debes introducir una nueva contraseña
Recovery email: Correo de recuperación
This email is used for user to regain access their account.: Este correo electrónico se usa para que el usuario recupere el acceso a su cuenta.

View File

@ -96,6 +96,7 @@ module.exports = Self => {
}); });
Self.latestBuysFilter = async(ctx, filter, options) => { Self.latestBuysFilter = async(ctx, filter, options) => {
const models = Self.app.models;
const myOptions = {}; const myOptions = {};
if (typeof options == 'object') if (typeof options == 'object')
@ -143,8 +144,12 @@ module.exports = Self => {
const stmts = []; const stmts = [];
let stmt; let stmt;
stmts.push('CALL cache.visible_refresh(@calc_id, FALSE, 1)'); const warehouse = await models.Warehouse.findOne({where: {code: 'ALG'}}, myOptions);
stmt = new ParameterizedSQL(`CALL cache.visible_refresh(@calc_id, FALSE, ?)`, [warehouse.id]);
stmts.push(stmt);
const date = new Date();
date.setHours(0, 0, 0, 0);
stmt = new ParameterizedSQL(` stmt = new ParameterizedSQL(`
SELECT SELECT
i.image, i.image,
@ -202,9 +207,9 @@ module.exports = Self => {
LEFT JOIN itemType t ON t.id = i.typeFk LEFT JOIN itemType t ON t.id = i.typeFk
LEFT JOIN intrastat intr ON intr.id = i.intrastatFk LEFT JOIN intrastat intr ON intr.id = i.intrastatFk
LEFT JOIN origin ori ON ori.id = i.originFk LEFT JOIN origin ori ON ori.id = i.originFk
LEFT JOIN entry e ON e.id = b.entryFk AND e.created >= DATE_SUB(CURDATE(),INTERVAL 1 YEAR) LEFT JOIN entry e ON e.id = b.entryFk AND e.created >= DATE_SUB(? ,INTERVAL 1 YEAR)
LEFT JOIN supplier s ON s.id = e.supplierFk` LEFT JOIN supplier s ON s.id = e.supplierFk`
); , [date]);
if (ctx.args.tags) { if (ctx.args.tags) {
let i = 1; let i = 1;

View File

@ -2,7 +2,7 @@
vn-id="model" vn-id="model"
url="InvoiceOuts/filter" url="InvoiceOuts/filter"
limit="20" limit="20"
order="issued DESC"> order="issued DESC, id DESC">
</vn-crud-model> </vn-crud-model>
<vn-portal slot="topbar"> <vn-portal slot="topbar">
<vn-searchbar <vn-searchbar

View File

@ -32,6 +32,8 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const date = new Date();
date.setHours(0, 0, 0, 0);
const wastes = await Self.rawSql(` const wastes = await Self.rawSql(`
SELECT *, 100 * dwindle / total AS percentage SELECT *, 100 * dwindle / total AS percentage
FROM ( FROM (
@ -42,11 +44,11 @@ module.exports = Self => {
sum(ws.saleWaste) AS dwindle sum(ws.saleWaste) AS dwindle
FROM bs.waste ws FROM bs.waste ws
WHERE buyer = ? AND family = ? WHERE buyer = ? AND family = ?
AND year = YEAR(TIMESTAMPADD(WEEK,-1,CURDATE())) AND year = YEAR(TIMESTAMPADD(WEEK,-1, ?))
AND week = WEEK(TIMESTAMPADD(WEEK,-1,CURDATE()), 1) AND week = WEEK(TIMESTAMPADD(WEEK,-1, ?), 1)
GROUP BY buyer, itemFk GROUP BY buyer, itemFk
) sub ) sub
ORDER BY family, percentage DESC`, [buyer, family], myOptions); ORDER BY family, percentage DESC`, [buyer, family, date, date], myOptions);
const details = []; const details = [];

View File

@ -19,6 +19,8 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const date = new Date();
date.setHours(0, 0, 0, 0);
const wastes = await Self.rawSql(` const wastes = await Self.rawSql(`
SELECT *, 100 * dwindle / total AS percentage SELECT *, 100 * dwindle / total AS percentage
FROM ( FROM (
@ -27,11 +29,11 @@ module.exports = Self => {
sum(ws.saleTotal) AS total, sum(ws.saleTotal) AS total,
sum(ws.saleWaste) AS dwindle sum(ws.saleWaste) AS dwindle
FROM bs.waste ws FROM bs.waste ws
WHERE year = YEAR(TIMESTAMPADD(WEEK,-1,CURDATE())) WHERE year = YEAR(TIMESTAMPADD(WEEK,-1, ?))
AND week = WEEK(TIMESTAMPADD(WEEK,-1,CURDATE()), 1) AND week = WEEK(TIMESTAMPADD(WEEK,-1, ?), 1)
GROUP BY buyer, family GROUP BY buyer, family
) sub ) sub
ORDER BY percentage DESC`, null, myOptions); ORDER BY percentage DESC`, [date, date], myOptions);
const details = []; const details = [];

View File

@ -34,6 +34,8 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const date = new Date();
date.setHours(0, 0, 0, 0);
const stmt = new ParameterizedSQL(` const stmt = new ParameterizedSQL(`
SELECT SELECT
u.name AS salesPerson, u.name AS salesPerson,
@ -47,9 +49,10 @@ module.exports = Self => {
JOIN client c ON c.id = v.userFk JOIN client c ON c.id = v.userFk
JOIN account.user u ON c.salesPersonFk = u.id JOIN account.user u ON c.salesPersonFk = u.id
LEFT JOIN sharingCart sc ON sc.workerFk = c.salesPersonFk LEFT JOIN sharingCart sc ON sc.workerFk = c.salesPersonFk
AND CURDATE() BETWEEN sc.started AND sc.ended AND ? BETWEEN sc.started AND sc.ended
LEFT JOIN workerTeamCollegues wtc LEFT JOIN workerTeamCollegues wtc
ON wtc.collegueFk = IFNULL(sc.workerSubstitute, c.salesPersonFk)`); ON wtc.collegueFk = IFNULL(sc.workerSubstitute, c.salesPersonFk)`,
[date]);
if (!filter.where) filter.where = {}; if (!filter.where) filter.where = {};

View File

@ -104,6 +104,8 @@ module.exports = Self => {
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const conn = Self.dataSource.connector; const conn = Self.dataSource.connector;
const models = Self.app.models; const models = Self.app.models;
const date = new Date();
date.setHours(0, 0, 0, 0);
const args = ctx.args; const args = ctx.args;
const myOptions = {}; const myOptions = {};
@ -264,15 +266,18 @@ module.exports = Self => {
`); `);
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.sale_getProblems'); stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.sale_getProblems');
stmts.push(`
stmt = new ParameterizedSQL(`
CREATE TEMPORARY TABLE tmp.sale_getProblems CREATE TEMPORARY TABLE tmp.sale_getProblems
(INDEX (ticketFk)) (INDEX (ticketFk))
ENGINE = MEMORY ENGINE = MEMORY
SELECT f.id ticketFk, f.clientFk, f.warehouseFk, f.shipped SELECT f.id ticketFk, f.clientFk, f.warehouseFk, f.shipped
FROM tmp.filter f FROM tmp.filter f
LEFT JOIN alertLevel al ON al.id = f.alertLevel LEFT JOIN alertLevel al ON al.id = f.alertLevel
WHERE (al.code = 'FREE' OR f.alertLevel IS NULL) WHERE (al.code = 'FREE' OR f.alertLevel IS NULL)
AND f.shipped >= CURDATE()`); AND f.shipped >= ?`, [date]);
stmts.push(stmt);
stmts.push('CALL ticket_getProblems(FALSE)'); stmts.push('CALL ticket_getProblems(FALSE)');
stmts.push(` stmts.push(`

View File

@ -74,6 +74,8 @@ module.exports = Self => {
filter = mergeFilters(filter, {where}); filter = mergeFilters(filter, {where});
const date = new Date();
date.setHours(0, 0, 0, 0);
const stmts = []; const stmts = [];
const stmt = new ParameterizedSQL( const stmt = new ParameterizedSQL(
`SELECT * `SELECT *
@ -101,10 +103,10 @@ module.exports = Self => {
LEFT JOIN vn.ticket t ON t.routeFk = r.id LEFT JOIN vn.ticket t ON t.routeFk = r.id
LEFT JOIN vn.supplierAgencyTerm sat ON sat.agencyFk = a.id LEFT JOIN vn.supplierAgencyTerm sat ON sat.agencyFk = a.id
LEFT JOIN vn.supplier s ON s.id = sat.supplierFk LEFT JOIN vn.supplier s ON s.id = sat.supplierFk
WHERE r.created > DATE_ADD(CURDATE(), INTERVAL -2 MONTH) AND sat.supplierFk IS NOT NULL WHERE r.created > DATE_ADD(?, INTERVAL -2 MONTH) AND sat.supplierFk IS NOT NULL
GROUP BY r.id GROUP BY r.id
) a` ) a`
); , [date]);
stmt.merge(conn.makeWhere(filter.where)); stmt.merge(conn.makeWhere(filter.where));
stmt.merge(conn.makePagination(filter)); stmt.merge(conn.makePagination(filter));

View File

@ -2,17 +2,17 @@ const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
describe('Route filter()', () => { describe('Route filter()', () => {
let today = new Date(); const today = new Date();
today.setHours(2, 0, 0, 0); today.setHours(2, 0, 0, 0);
it('should return the routes matching "search"', async() => { it('should return the routes matching "search"', async() => {
let ctx = { const ctx = {
args: { args: {
search: 1, search: 1,
} }
}; };
let result = await app.models.Route.filter(ctx); const result = await app.models.Route.filter(ctx);
expect(result.length).toEqual(1); expect(result.length).toEqual(1);
expect(result[0].id).toEqual(1); expect(result[0].id).toEqual(1);
@ -28,7 +28,6 @@ describe('Route filter()', () => {
const to = new Date(); const to = new Date();
to.setHours(23, 59, 59, 999); to.setHours(23, 59, 59, 999);
const ctx = { const ctx = {
args: { args: {
from: from, from: from,
@ -48,43 +47,43 @@ describe('Route filter()', () => {
}); });
it('should return the routes matching "m3"', async() => { it('should return the routes matching "m3"', async() => {
let ctx = { const ctx = {
args: { args: {
m3: 0.1, m3: 0.1,
} }
}; };
let result = await app.models.Route.filter(ctx); const result = await app.models.Route.filter(ctx);
expect(result.length).toEqual(1); expect(result.length).toEqual(1);
}); });
it('should return the routes matching "description"', async() => { it('should return the routes matching "description"', async() => {
let ctx = { const ctx = {
args: { args: {
description: 'third route', description: 'third route',
} }
}; };
let result = await app.models.Route.filter(ctx); const result = await app.models.Route.filter(ctx);
expect(result.length).toEqual(1); expect(result.length).toEqual(1);
}); });
it('should return the routes matching "workerFk"', async() => { it('should return the routes matching "workerFk"', async() => {
let ctx = { const ctx = {
args: { args: {
workerFk: 56, workerFk: 56,
} }
}; };
let result = await app.models.Route.filter(ctx); const result = await app.models.Route.filter(ctx);
expect(result.length).toEqual(5); expect(result.length).toEqual(5);
}); });
it('should return the routes matching "warehouseFk"', async() => { it('should return the routes matching "warehouseFk"', async() => {
let ctx = { const ctx = {
args: { args: {
warehouseFk: 1, warehouseFk: 1,
} }
@ -102,25 +101,25 @@ describe('Route filter()', () => {
}); });
it('should return the routes matching "vehicleFk"', async() => { it('should return the routes matching "vehicleFk"', async() => {
let ctx = { const ctx = {
args: { args: {
vehicleFk: 2, vehicleFk: 2,
} }
}; };
let result = await app.models.Route.filter(ctx); const result = await app.models.Route.filter(ctx);
expect(result.length).toEqual(1); expect(result.length).toEqual(1);
}); });
it('should return the routes matching "agencyModeFk"', async() => { it('should return the routes matching "agencyModeFk"', async() => {
let ctx = { const ctx = {
args: { args: {
agencyModeFk: 7, agencyModeFk: 7,
} }
}; };
let result = await app.models.Route.filter(ctx); const result = await app.models.Route.filter(ctx);
expect(result.length).toEqual(1); expect(result.length).toEqual(1);
}); });

View File

@ -1,30 +1,32 @@
const app = require('vn-loopback/server/server'); const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
describe('route getSuggestedTickets()', () => { describe('route getSuggestedTickets()', () => {
const routeID = 1; const routeID = 1;
const ticketId = 12; const ticketId = 12;
it('should return an array of suggested tickets', async() => { it('should return an array of suggested tickets', async() => {
const activeCtx = { const activeCtx = {
accessToken: {userId: 19}, accessToken: {userId: 19},
headers: {origin: 'http://localhost'} headers: {origin: 'http://localhost'}
}; };
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx active: activeCtx
}); });
const tx = await app.models.Ticket.beginTransaction({}); const tx = await models.Ticket.beginTransaction({});
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const ticketInRoute = await app.models.Ticket.findById(ticketId, null, options); const ticketInRoute = await models.Ticket.findById(ticketId, null, options);
await ticketInRoute.updateAttributes({ await ticketInRoute.updateAttributes({
routeFk: null, routeFk: null,
landed: new Date() landed: new Date()
}, options); }, options);
const result = await app.models.Route.getSuggestedTickets(routeID, options); const result = await models.Route.getSuggestedTickets(routeID, options);
const length = result.length; const length = result.length;
const anyResult = result[Math.floor(Math.random() * Math.floor(length))]; const anyResult = result[Math.floor(Math.random() * Math.floor(length))];

View File

@ -24,6 +24,8 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const date = new Date();
date.setHours(0, 0, 0, 0);
const query = ` const query = `
SELECT SELECT
s.id AS saleFk, s.id AS saleFk,
@ -39,11 +41,11 @@ module.exports = Self => {
INNER JOIN vn.sale s ON s.ticketFk = t.id INNER JOIN vn.sale s ON s.ticketFk = t.id
LEFT JOIN vn.claimBeginning cb ON cb.saleFk = s.id LEFT JOIN vn.claimBeginning cb ON cb.saleFk = s.id
WHERE (t.landed) >= TIMESTAMPADD(DAY, -7, CURDATE()) WHERE (t.landed) >= TIMESTAMPADD(DAY, -7, ?)
AND t.id = ? AND cb.id IS NULL AND t.id = ? AND cb.id IS NULL
ORDER BY t.landed DESC, t.id DESC`; ORDER BY t.landed DESC, t.id DESC`;
const claimableSales = await Self.rawSql(query, [ticketFk], myOptions); const claimableSales = await Self.rawSql(query, [date, ticketFk], myOptions);
return claimableSales; return claimableSales;
}; };

View File

@ -56,7 +56,7 @@ module.exports = Self => {
salesIds.push(null); salesIds.push(null);
const servicesIds = []; const servicesIds = [];
if (services) { if (services && services.length) {
for (let service of services) for (let service of services)
servicesIds.push(service.id); servicesIds.push(service.id);
} else } else

View File

@ -132,6 +132,8 @@ module.exports = Self => {
Self.filter = async(ctx, filter, options) => { Self.filter = async(ctx, filter, options) => {
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const conn = Self.dataSource.connector; const conn = Self.dataSource.connector;
const date = new Date();
date.setHours(0, 0, 0, 0);
const models = Self.app.models; const models = Self.app.models;
const args = ctx.args; const args = ctx.args;
@ -297,7 +299,8 @@ module.exports = Self => {
stmts.push(stmt); stmts.push(stmt);
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.sale_getProblems'); stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.sale_getProblems');
stmts.push(`
stmt = new ParameterizedSQL(`
CREATE TEMPORARY TABLE tmp.sale_getProblems CREATE TEMPORARY TABLE tmp.sale_getProblems
(INDEX (ticketFk)) (INDEX (ticketFk))
ENGINE = MEMORY ENGINE = MEMORY
@ -305,7 +308,9 @@ module.exports = Self => {
FROM tmp.filter f FROM tmp.filter f
LEFT JOIN alertLevel al ON al.id = f.alertLevel LEFT JOIN alertLevel al ON al.id = f.alertLevel
WHERE (al.code = 'FREE' OR f.alertLevel IS NULL) WHERE (al.code = 'FREE' OR f.alertLevel IS NULL)
AND f.shipped >= CURDATE()`); AND f.shipped >= ?`, [date]);
stmts.push(stmt);
stmts.push('CALL ticket_getProblems(FALSE)'); stmts.push('CALL ticket_getProblems(FALSE)');
stmt = new ParameterizedSQL(` stmt = new ParameterizedSQL(`

View File

@ -26,6 +26,8 @@ module.exports = function(Self) {
Self.makeInvoice = async(ctx, ticketsIds, options) => { Self.makeInvoice = async(ctx, ticketsIds, options) => {
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const models = Self.app.models; const models = Self.app.models;
const date = new Date();
date.setHours(0, 0, 0, 0);
const myOptions = {}; const myOptions = {};
let tx; let tx;
@ -81,7 +83,7 @@ module.exports = function(Self) {
WHERE id IN(?) AND refFk IS NULL WHERE id IN(?) AND refFk IS NULL
`, [ticketsIds], myOptions); `, [ticketsIds], myOptions);
await Self.rawSql('CALL invoiceOut_new(?, CURDATE(), null, @invoiceId)', [serial], myOptions); await Self.rawSql('CALL invoiceOut_new(?, ?, null, @invoiceId)', [serial, date], myOptions);
const [resultInvoice] = await Self.rawSql('SELECT @invoiceId id', [], myOptions); const [resultInvoice] = await Self.rawSql('SELECT @invoiceId id', [], myOptions);

View File

@ -142,6 +142,25 @@ module.exports = Self => {
const updatedTicket = await ticket.updateAttribute('isDeleted', true, myOptions); const updatedTicket = await ticket.updateAttribute('isDeleted', true, myOptions);
const [ticketCollection] = await models.TicketCollection.find({
fields: ['id'],
where: {
ticketFk: ticket.id
}
});
if (ticketCollection)
await models.TicketCollection.destroyById(ticketCollection.id, myOptions);
await Self.rawSql(`
DELETE sc
FROM vn.saleGroup sg
JOIN vn.sectorCollectionSaleGroup scsg ON scsg.saleGroupFk = sg.id
JOIN vn.sectorCollection sc ON sc.id = scsg.sectorCollectionFk
JOIN vn.saleGroupDetail sgd ON sgd.saleGroupFk = sg.id
JOIN vn.sale s ON s.id = sgd.saleFk
WHERE s.ticketFk = ?;`, [ticket.id], myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();
return updatedTicket; return updatedTicket;

View File

@ -56,7 +56,6 @@ describe('ticket canBeInvoiced()', () => {
it('should return falsy for a ticket shipping in future', async() => { it('should return falsy for a ticket shipping in future', async() => {
const tx = await models.Ticket.beginTransaction({}); const tx = await models.Ticket.beginTransaction({});
try { try {
const options = {transaction: tx}; const options = {transaction: tx};

View File

@ -213,7 +213,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(2); expect(result.length).toEqual(3);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -8,6 +8,12 @@ describe('ticket setDeleted()', () => {
accessToken: {userId: userId}, accessToken: {userId: userId},
}; };
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should throw an error if the given ticket has a claim', async() => { it('should throw an error if the given ticket has a claim', async() => {
const tx = await models.Ticket.beginTransaction({}); const tx = await models.Ticket.beginTransaction({});
@ -29,6 +35,70 @@ describe('ticket setDeleted()', () => {
expect(error.message).toEqual('You must delete the claim id %d first'); expect(error.message).toEqual('You must delete the claim id %d first');
}); });
it('should delete a sectorCollection row', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {
req: {
accessToken: {userId: 9},
headers: {origin: 'http://localhost:5000'},
}
};
ctx.req.__ = value => {
return value;
};
const ticketId = 23;
await models.Ticket.setDeleted(ctx, ticketId, options);
const [sectorCollection] = await models.Ticket.rawSql(
`SELECT COUNT(*) numberRows
FROM vn.sectorCollection`, [], options);
expect(sectorCollection.numberRows).toEqual(0);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should delete a ticketCollection row', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {
req: {
accessToken: {userId: 9},
headers: {origin: 'http://localhost:5000'},
}
};
ctx.req.__ = value => {
return value;
};
const ticketId = 23;
await models.Ticket.setDeleted(ctx, ticketId, options);
const [ticketCollection] = await models.Ticket.rawSql(
`SELECT COUNT(*) numberRows
FROM vn.ticketCollection`, [], options);
expect(ticketCollection.numberRows).toEqual(3);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should delete ticket, remove stowaway and itemshelving then change stowaway state to "FIXING" ', async() => { it('should delete ticket, remove stowaway and itemshelving then change stowaway state to "FIXING" ', async() => {
pending('test excluded by task #3693'); pending('test excluded by task #3693');
const tx = await models.Ticket.beginTransaction({}); const tx = await models.Ticket.beginTransaction({});

View File

@ -44,6 +44,9 @@
"Ticket": { "Ticket": {
"dataSource": "vn" "dataSource": "vn"
}, },
"TicketCollection": {
"dataSource": "vn"
},
"TicketDms": { "TicketDms": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -0,0 +1,22 @@
{
"name": "TicketCollection",
"base": "VnModel",
"options": {
"mysql": {
"table": "ticketCollection"
}
},
"properties": {
"id": {
"id": true,
"type": "number"
}
},
"relations": {
"ticket": {
"type": "belongsTo",
"model": "Ticket",
"foreignKey": "ticketFk"
}
}
}

View File

@ -19,6 +19,7 @@
disabled="!$ctrl.clientId" disabled="!$ctrl.clientId"
url="{{ $ctrl.clientId ? 'Clients/'+ $ctrl.clientId +'/addresses' : null }}" url="{{ $ctrl.clientId ? 'Clients/'+ $ctrl.clientId +'/addresses' : null }}"
fields="['nickname', 'street', 'city']" fields="['nickname', 'street', 'city']"
where="{isActive: true}"
ng-model="$ctrl.addressId" ng-model="$ctrl.addressId"
show-field="nickname" show-field="nickname"
value-field="id" value-field="id"

View File

@ -4,7 +4,7 @@
filter="::$ctrl.filter" filter="::$ctrl.filter"
limit="20" limit="20"
data="weeklies" data="weeklies"
order="ticketFk" order="weekDay, ticketFk"
primary-key="ticketFk" primary-key="ticketFk"
auto-load="true"> auto-load="true">
</vn-crud-model> </vn-crud-model>

View File

@ -40,6 +40,7 @@ module.exports = Self => {
const models = Self.app.models; const models = Self.app.models;
let tx; let tx;
const myOptions = {}; const myOptions = {};
const date = new Date();
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
@ -57,8 +58,8 @@ module.exports = Self => {
await Self.rawSql(` await Self.rawSql(`
INSERT INTO travelThermograph(thermographFk, warehouseFk, temperatureFk, created) INSERT INTO travelThermograph(thermographFk, warehouseFk, temperatureFk, created)
VALUES (?, ?, ?, NOW()) VALUES (?, ?, ?, ?)
`, [thermograph.id, warehouseId, temperatureFk], myOptions); `, [thermograph.id, warehouseId, temperatureFk, date], myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();

View File

@ -1,8 +1,8 @@
const app = require('vn-loopback/server/server'); const models = require('vn-loopback/server/server').models;
describe('workerTimeControl filter()', () => { describe('workerTimeControl filter()', () => {
it('should return 1 result filtering by id', async() => { it('should return 1 result filtering by id', async() => {
let ctx = {req: {accessToken: {userId: 1106}}, args: {workerFk: 1106}}; const ctx = {req: {accessToken: {userId: 1106}}, args: {workerFk: 1106}};
const firstHour = new Date(); const firstHour = new Date();
firstHour.setHours(7, 0, 0, 0); firstHour.setHours(7, 0, 0, 0);
const lastHour = new Date(); const lastHour = new Date();
@ -14,13 +14,13 @@ describe('workerTimeControl filter()', () => {
timed: {between: [firstHour, lastHour]} timed: {between: [firstHour, lastHour]}
} }
}; };
let result = await app.models.WorkerTimeControl.filter(ctx, filter); const result = await models.WorkerTimeControl.filter(ctx, filter);
expect(result.length).toEqual(4); expect(result.length).toEqual(4);
}); });
it('should return a privilege error for a non subordinate worker', async() => { it('should return a privilege error for a non subordinate worker', async() => {
let ctx = {req: {accessToken: {userId: 1107}}, args: {workerFk: 1106}}; const ctx = {req: {accessToken: {userId: 1107}}, args: {workerFk: 1106}};
const firstHour = new Date(); const firstHour = new Date();
firstHour.setHours(7, 0, 0, 0); firstHour.setHours(7, 0, 0, 0);
const lastHour = new Date(); const lastHour = new Date();
@ -34,7 +34,7 @@ describe('workerTimeControl filter()', () => {
}; };
let error; let error;
await app.models.WorkerTimeControl.filter(ctx, filter).catch(e => { await models.WorkerTimeControl.filter(ctx, filter).catch(e => {
error = e; error = e;
}).finally(() => { }).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`); expect(error.message).toEqual(`You don't have enough privileges`);

View File

@ -8,10 +8,10 @@ describe('workerTimeControl add/delete timeEntry()', () => {
const employeeId = 1; const employeeId = 1;
const salesPersonId = 1106; const salesPersonId = 1106;
const salesBossId = 19; const salesBossId = 19;
let activeCtx = { const activeCtx = {
accessToken: {userId: 50}, accessToken: {userId: 50},
}; };
let ctx = {req: activeCtx}; const ctx = {req: activeCtx};
beforeAll(() => { beforeAll(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
@ -82,7 +82,7 @@ describe('workerTimeControl add/delete timeEntry()', () => {
const workerId = salesPersonId; const workerId = salesPersonId;
let error; let error;
let calendar = await app.models.Calendar.findById(3); const calendar = await app.models.Calendar.findById(3);
try { try {
ctx.args = {timed: new Date(calendar.dated), direction: 'in'}; ctx.args = {timed: new Date(calendar.dated), direction: 'in'};

View File

@ -80,6 +80,9 @@ module.exports = Self => {
if (hasHoursRecorded && isNotHalfAbsence) if (hasHoursRecorded && isNotHalfAbsence)
throw new UserError(`The worker has hours recorded that day`); throw new UserError(`The worker has hours recorded that day`);
const date = new Date();
const now = new Date();
date.setHours(0, 0, 0, 0);
const [result] = await Self.rawSql( const [result] = await Self.rawSql(
`SELECT COUNT(*) halfHolidayCounter `SELECT COUNT(*) halfHolidayCounter
FROM vn.calendar c FROM vn.calendar c
@ -88,8 +91,8 @@ module.exports = Self => {
JOIN vn.person pe ON pe.id = p.person_id JOIN vn.person pe ON pe.id = p.person_id
WHERE c.dayOffTypeFk = 6 WHERE c.dayOffTypeFk = 6
AND pe.workerFk = ? AND pe.workerFk = ?
AND c.dated BETWEEN util.firstDayOfYear(CURDATE()) AND c.dated BETWEEN util.firstDayOfYear(?)
AND LAST_DAY(DATE_ADD(NOW(), INTERVAL 12-MONTH(NOW()) MONTH))`, [id]); AND LAST_DAY(DATE_ADD(?, INTERVAL 12 - MONTH(?) MONTH))`, [id, date, now, now]);
const hasHalfHoliday = result.halfHolidayCounter > 0; const hasHalfHoliday = result.halfHolidayCounter > 0;
const isHalfHoliday = absenceType.code === 'halfHoliday'; const isHalfHoliday = absenceType.code === 'halfHoliday';

View File

@ -57,25 +57,9 @@ module.exports = Self => {
ended.setDate(0); ended.setDate(0);
ended.setHours(23, 59, 59, 59); ended.setHours(23, 59, 59, 59);
const filter = { const filter = {where: {businessFk: args.businessFk}};
where: { const contract = await models.WorkerLabour.findOne(filter, myOptions);
and: [ const payedHolidays = contract.payedHolidays;
{workerFk: id},
{
or: [
{started: {between: [started, ended]}},
{ended: {between: [started, ended]}},
{and: [{started: {lt: started}}, {ended: {gt: ended}}]},
{and: [{started: {lt: started}}, {ended: null}]}
]
}
],
}
};
const contracts = await models.WorkerLabour.find(filter, myOptions);
let [firstContract] = contracts;
const payedHolidays = firstContract.payedHolidays;
let queryIndex; let queryIndex;
const year = started.getFullYear(); const year = started.getFullYear();

View File

@ -27,4 +27,15 @@ describe('Worker holidays()', () => {
expect(result.totalHolidays).toEqual(27.5); expect(result.totalHolidays).toEqual(27.5);
expect(result.holidaysEnjoyed).toEqual(5); expect(result.holidaysEnjoyed).toEqual(5);
}); });
it('should now get the payed holidays calendar for a worker', async() => {
const now = new Date();
const year = now.getFullYear();
ctx.args = {businessFk: businessId, year: year};
const result = await app.models.Worker.holidays(ctx, workerId);
expect(result.payedHolidays).toEqual(8);
});
}); });

View File

@ -8,7 +8,6 @@ describe('zone getZoneClosing()', () => {
const options = {transaction: tx}; const options = {transaction: tx};
const date = new Date(); const date = new Date();
const today = date.toISOString().split('T')[0]; const today = date.toISOString().split('T')[0];
const result = await models.Zone.getZoneClosing([1, 2, 3], today, options); const result = await models.Zone.getZoneClosing([1, 2, 3], today, options);
expect(result.length).toEqual(3); expect(result.length).toEqual(3);

View File

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

View File

@ -167,7 +167,8 @@ module.exports = {
const body = `No se ha podido enviar el albarán <strong>${ticket.id}</strong> const body = `No se ha podido enviar el albarán <strong>${ticket.id}</strong>
al cliente <strong>${ticket.clientFk} - ${ticket.clientName}</strong> al cliente <strong>${ticket.clientFk} - ${ticket.clientName}</strong>
porque la dirección de email <strong>"${ticket.recipient}"</strong> no es correcta o no está disponible.<br/><br/> porque la dirección de email <strong>"${ticket.recipient}"</strong> no es correcta
o no está disponible.<br/><br/>
Para evitar que se repita este error, se ha eliminado la dirección de email de la ficha del cliente. Para evitar que se repita este error, se ha eliminado la dirección de email de la ficha del cliente.
Actualiza la dirección de email con una correcta.`; Actualiza la dirección de email con una correcta.`;

View File

@ -93,11 +93,12 @@ module.exports = async function(request, response, next) {
await email.send(mailOptions); await email.send(mailOptions);
} }
// Update queue status // Update queue status
const date = new Date();
sql = `UPDATE invoiceOut_queue sql = `UPDATE invoiceOut_queue
SET status = "printed", SET status = "printed",
printed = NOW() printed = ?
WHERE invoiceFk = ?`; WHERE invoiceFk = ?`;
connection.query(sql, [invoiceOut.id]); connection.query(sql, [date, invoiceOut.id]);
connection.query('COMMIT'); connection.query('COMMIT');
} catch (error) { } catch (error) {
connection.query('ROLLBACK'); connection.query('ROLLBACK');