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 => {
Self.remoteMethod('setPassword', {
description: 'Sets the user password',
accepts: [
{
arg: 'id',
type: 'Number',
type: 'number',
description: 'The user id',
http: {source: 'path'}
}, {
arg: 'newPassword',
type: 'String',
type: 'string',
description: 'The new password',
required: true
}

View File

@ -200,6 +200,31 @@ module.exports = Self => {
const toTable = table.toTable;
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({});
try {

View File

@ -31,11 +31,13 @@ COPY \
import-changes.sh \
config.ini \
dump/mysqlPlugins.sql \
dump/mockDate.sql \
dump/structure.sql \
dump/dumpedFixtures.sql \
./
RUN gosu mysql docker-init.sh \
&& docker-dump.sh mysqlPlugins \
&& docker-dump.sh mockDate \
&& docker-dump.sh structure \
&& docker-dump.sh dumpedFixtures \
&& 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),
PRIMARY KEY(`name`)
);
CREATE TABLE `vn`.`mdbVersion` (
CREATE TABLE IF NOT EXISTS `vn`.`mdbVersion` (
`app` VARCHAR(255) NOT NULL,
`branchFk` VARCHAR(255) NOT NULL,
`version` INT,
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`)
VALUES('MdbVersion', '*', '*', 'ALLOW', 'ROLE', 'developer');
INSERT IGNORE INTO `salix`.`ACL` (`id`, `model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
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 \
${SCHEMAS[@]} \
${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' \
> dump/structure.sql

View File

@ -5,8 +5,9 @@ describe('zone zone_getLanded()', () => {
it(`should return data for a shipped in the past`, async() => {
let stmts = [];
let stmt;
stmts.push('START TRANSACTION');
const date = new Date();
date.setHours(0, 0, 0, 0);
let params = {
addressFk: 121,
@ -14,7 +15,8 @@ describe('zone zone_getLanded()', () => {
warehouseFk: 1,
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.agencyModeFk,
params.warehouseFk,
@ -38,6 +40,8 @@ describe('zone zone_getLanded()', () => {
it(`should return data for a shipped tomorrow`, async() => {
let stmts = [];
let stmt;
const date = new Date();
date.setHours(0, 0, 0, 0);
stmts.push('START TRANSACTION');
@ -47,7 +51,8 @@ describe('zone zone_getLanded()', () => {
warehouseFk: 1,
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.agencyModeFk,
params.warehouseFk,

View File

@ -55,6 +55,7 @@ export default {
setPassword: '.vn-menu [name="setPassword"]',
activateAccount: '.vn-menu [name="enableAccount"]',
activateUser: '.vn-menu [name="activateUser"]',
deactivateUser: '.vn-menu [name="deactivateUser"]',
newPassword: 'vn-textfield[ng-model="$ctrl.newPassword"]',
repeatPassword: 'vn-textfield[ng-model="$ctrl.repeatPassword"]',
newRole: 'vn-autocomplete[ng-model="$ctrl.newRole"]',
@ -275,6 +276,7 @@ export default {
clientWebAccess: {
enableWebAccessCheckbox: 'vn-check[label="Enable web access"]',
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]'
},
clientNotes: {

View File

@ -1,3 +1,4 @@
/* eslint max-len: ["error", { "code": 150 }]*/
import selectors from '../../helpers/selectors';
import getBrowser from '../../helpers/puppeteer';
@ -8,7 +9,7 @@ describe('Client Edit web access path', () => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('employee', 'client');
await page.accessToSearchResult('Bruce Banner');
await page.accessToSearchResult('max');
await page.accessToSection('client.card.webAccess');
});
@ -26,7 +27,16 @@ describe('Client Edit web access path', () => {
it(`should update the name`, async() => {
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);
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() => {
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() => {
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
.waitToGetProperty(selectors.clientLog.lastModificationPreviousValue, 'innerText');
let lastModificationCurrentValue = await page
.waitToGetProperty(selectors.clientLog.lastModificationCurrentValue, 'innerText');
expect(lastModificationPreviousValue).toEqual('name BruceBanner active false');
expect(lastModificationCurrentValue).toEqual('name Hulk active false');
expect(lastModificationPreviousValue).toEqual('name MaxEisenhardt 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
.waitToGetProperty(selectors.clientLog.penultimateModificationPreviousValue, 'innerText');
let penultimateModificationCurrentValue = await page
.waitToGetProperty(selectors.clientLog.penultimateModificationCurrentValue, 'innerText');
expect(penultimateModificationPreviousValue).toEqual('name BruceBanner active true');
expect(penultimateModificationCurrentValue).toEqual('name BruceBanner active false');
expect(penultimateModificationPreviousValue).toEqual('name MaxEisenhardt active true');
expect(penultimateModificationCurrentValue).toEqual('name MaxEisenhardt active false');
});
});

View File

@ -28,12 +28,12 @@ describe('Client defaulter path', () => {
const salesPersonName =
await page.waitToGetProperty(selectors.clientDefaulter.firstSalesPersonName, 'innerText');
expect(clientName).toEqual('Ororo Munroe');
expect(salesPersonName).toEqual('salesPersonNick');
expect(clientName).toEqual('Bruce Banner');
expect(salesPersonName).toEqual('developer');
});
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');
expect(result).toContain(expectedObservation);

View File

@ -36,8 +36,7 @@ describe('Account create and basic data path', () => {
await page.waitForState('account.card.basicData');
});
it('should reload the section and check the name is as expected', async() => {
await page.reloadSection('account.card.basicData');
it('should check the name is as expected', async() => {
const result = await page.waitToGetProperty(selectors.accountBasicData.name, 'value');
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('activate user', () => {
// it(`should check the inactive user icon is present in the descriptor`, async() => {
// await page.waitForSelector(selectors.accountDescriptor.activeUserIcon, {visible: true});
// });
describe('deactivate user', () => {
it(`should check the inactive user icon isn't present in the descriptor just yet`, async() => {
await page.waitForNumberOfElements(selectors.accountDescriptor.activeUserIcon, 0);
});
// 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();
it('should deactivate the user using the descriptor menu', async() => {
await page.waitToClick(selectors.accountDescriptor.menuButton);
await page.waitToClick(selectors.accountDescriptor.deactivateUser);
await page.waitToClick(selectors.accountDescriptor.acceptButton);
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() => {
// await page.waitForNumberOfElements(selectors.accountDescriptor.activeUserIcon, 0);
// });
// });
it('should check the inactive user icon is now present', async() => {
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', () => {
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');
});
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.accessToSection('account.alias.card.users');
const rolesCount = await page.countElement(selectors.accountAliasUsers.anyResult);

View File

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

View File

@ -10,6 +10,7 @@ export default class App {
constructor() {
this.loaderStatus = 0;
this.loading = false;
this.versionInterval = setInterval(this.getVersion.bind(this), 300000);
}
showMessage(message) {
@ -38,6 +39,21 @@ export default class App {
if (this.loaderStatus === 0)
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);

View File

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

View File

@ -19,6 +19,14 @@
</div>
<vn-slot name="topbar"></vn-slot>
<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
id="apps"
icon="apps"
@ -39,7 +47,6 @@
translate-attr="{title: 'Account'}"
on-error-src/>
</button>
</div>
<vn-menu vn-id="apps-menu">
<vn-list class="modules-menu">

View File

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

View File

@ -60,6 +60,9 @@ vn-layout {
font-size: 1.05rem;
padding: 0;
}
.vn-icon-button.refresh {
color: $color-alert;
}
}
& > vn-side-menu > .menu {
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
What is new: Novedades de la versión
Settings: Ajustes
There is a new version, click here to reload: Hay una nueva versión, pulse aquí para recargar
# Actions

View File

@ -123,5 +123,6 @@
"The worker has hours recorded that day": "The worker has hours recorded that day",
"isWithoutNegatives": "isWithoutNegatives",
"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",
"This ticket is already a refund": "Este ticket ya es un abono",
"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": {}
},
"auth:after": {
"./middleware/current-user": {}
"./middleware/current-user": {},
"./middleware/salix-version": {}
},
"parse": {
"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) => {
const models = Self.app.models;
const args = ctx.args;
const date = new Date();
date.setHours(0, 0, 0, 0);
let tx;
const myOptions = {};
@ -92,8 +95,9 @@ module.exports = function(Self) {
throw new UserError('Invalid account');
await Self.rawSql(
`CALL vn.ledger_doCompensation(CURDATE(), ?, ?, ?, ?, ?, ?)`,
`CALL vn.ledger_doCompensation(?, ?, ?, ?, ?, ?, ?)`,
[
date,
args.compensationAccount,
args.bankFk,
accountingType.receiptDescription + originalClient.accountingAccount,
@ -106,9 +110,10 @@ module.exports = function(Self) {
} else if (accountingType.isAutoConciliated == true) {
const description = `${originalClient.id} : ${originalClient.socialName} - ${accountingType.receiptDescription}`;
const [xdiarioNew] = await Self.rawSql(
`SELECT xdiario_new(?, CURDATE(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ledger;`,
`SELECT xdiario_new(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ledger;`,
[
null,
date,
bank.account,
originalClient.accountingAccount,
description,
@ -126,9 +131,10 @@ module.exports = function(Self) {
);
await Self.rawSql(
`SELECT xdiario_new(?, CURDATE(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`,
`SELECT xdiario_new(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`,
[
xdiarioNew.ledger,
date,
originalClient.accountingAccount,
bank.account,
description,

View File

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

View File

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

View File

@ -32,6 +32,8 @@ module.exports = Self => {
if (typeof options == 'object')
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 query = `
SELECT
@ -50,12 +52,12 @@ module.exports = Self => {
JOIN vn.warehouse w ON t.warehouseFk = w.id
JOIN vn.address ad ON t.addressFk = ad.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 = ?
ORDER BY t.shipped
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);
expect(result.recovery.id).toEqual(3);
await tx.rollback();
} catch (e) {
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);
}
async function getRecoveries(recoveryModel, clientId, options) {
const filter = {
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 date = new Date();
date.setHours(0, 0, 0, 0);
const stmt = new ParameterizedSQL(
`SELECT *
FROM (
@ -58,12 +60,12 @@ module.exports = Self => {
DISTINCT c.id clientFk,
c.name clientName,
c.salesPersonFk,
u.nickname salesPersonName,
u.name salesPersonName,
d.amount,
co.created,
co.text observation,
uw.id workerFk,
uw.nickname workerName,
uw.name workerName,
c.creditInsurance,
d.defaulterSinced
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 uw ON uw.id = co.workerFk
WHERE
d.created = CURDATE()
d.created = ?
AND d.amount > 0
ORDER BY co.created DESC) d`
);
, [date]);
stmt.merge(conn.makeWhere(filter.where));
stmt.merge(`GROUP BY d.clientFk`);

View File

@ -27,13 +27,15 @@ module.exports = Self => {
if (typeof options == 'object')
Object.assign(myOptions, options);
const date = new Date();
date.setHours(0, 0, 0, 0);
const query = `
SELECT count(*) AS hasActiveRecovery
FROM vn.recovery
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;
};

View File

@ -8,30 +8,32 @@ const LoopBackContext = require('loopback-context');
module.exports = Self => {
// 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/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/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/confirmTransaction')(Self);
require('../methods/client/canBeInvoiced')(Self);
require('../methods/client/uploadFile')(Self);
require('../methods/client/hasCustomerRole')(Self);
require('../methods/client/isValidClient')(Self);
require('../methods/client/lastActiveTickets')(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/consumption')(Self);
require('../methods/client/createReceipt')(Self);
require('../methods/client/updateFiscalData')(Self);
require('../methods/client/updatePortfolio')(Self);
require('../methods/client/checkDuplicated')(Self);
require('../methods/client/extendedListFilter')(Self);
require('../methods/client/updateUser')(Self);
require('../methods/client/uploadFile')(Self);
// Validations
@ -446,7 +448,7 @@ module.exports = Self => {
const app = require('vn-loopback/server/server');
app.on('started', function() {
let account = app.models.Account;
const account = app.models.Account;
account.observe('before save', async ctx => {
if (ctx.isNewInstance) return;
@ -454,22 +456,26 @@ module.exports = Self => {
});
account.observe('after save', async ctx => {
let changes = ctx.data || ctx.instance;
const changes = ctx.data || ctx.instance;
if (!ctx.isNewInstance && changes) {
let oldData = ctx.hookState.oldInstance;
let hasChanges = oldData.name != changes.name || oldData.active != changes.active;
const oldData = ctx.hookState.oldInstance;
const hasChanges = oldData.name != changes.name || oldData.active != changes.active;
if (!hasChanges) return;
let userId = ctx.options.accessToken.userId;
let logRecord = {
originFk: oldData.id,
userFk: userId,
action: 'update',
changedModel: 'Account',
oldInstance: {name: oldData.name, active: oldData.active},
newInstance: {name: changes.name, active: changes.active}
};
await Self.app.models.ClientLog.create(logRecord);
const isClient = await Self.app.models.Client.count({id: oldData.id});
if (isClient) {
const loopBackContext = LoopBackContext.getCurrentContext();
const userId = loopBackContext.active.accessToken.userId;
const logRecord = {
originFk: oldData.id,
userFk: userId,
action: 'update',
changedModel: 'Account',
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"
filter="::$ctrl.filter"
limit="20"
order="amount DESC"
data="defaulters"
auto-load="true">
</vn-crud-model>
@ -70,13 +71,13 @@
</th>
<th
vn-tooltip="Last observation date"
field="created"
shrink-datetime>
<span translate>Last observation D.</span>
field="created">
<span translate>L. O. Date</span>
</th>
<th
vn-tooltip="Credit insurance"
field="creditInsurance" >
field="creditInsurance"
shrink>
<span translate>Credit I.</span>
</th>
<th field="defaulterSinced">
@ -124,13 +125,13 @@
ng-model="defaulter.observation">
</vn-textarea>
</td>
<td shrink-datetime>
<td shrink-date>
<span class="chip {{::$ctrl.chipColor(defaulter.created)}}">
{{::defaulter.created | date: 'dd/MM/yyyy'}}
</span>
</td>
<td>{{::defaulter.creditInsurance | currency: 'EUR': 2}}</td>
<td>{{::defaulter.defaulterSinced | date: 'dd/MM/yyyy'}}</td>
<td shrink>{{::defaulter.creditInsurance | currency: 'EUR': 2}}</td>
<td shrink-date>{{::defaulter.defaulterSinced | date: 'dd/MM/yyyy'}}</td>
</tr>
</tbody>
</table>

View File

@ -26,7 +26,7 @@ export default class Controller extends Section {
url: 'Workers/activeWithInheritedRole',
where: `{role: 'salesPerson'}`,
searchFunction: '{firstName: $search}',
showField: 'nickname',
showField: 'name',
valueField: 'id',
}
},
@ -35,7 +35,7 @@ export default class Controller extends Section {
autocomplete: {
url: 'Workers/activeWithInheritedRole',
searchFunction: '{firstName: $search}',
showField: 'nickname',
showField: 'name',
valueField: 'id',
}
},
@ -53,16 +53,8 @@ export default class Controller extends Section {
}
]
};
}
get balanceDueTotal() {
let balanceDueTotal = 0;
const defaulters = this.$.model.data || [];
for (let defaulter of defaulters)
balanceDueTotal += defaulter.amount;
return balanceDueTotal;
this.getBalanceDueTotal();
}
get checked() {
@ -76,6 +68,18 @@ export default class Controller extends Section {
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) {
const day = 24 * 60 * 60 * 1000;
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()', () => {
it('should return undefined when the date is the present', () => {
let today = new Date();
@ -93,6 +82,7 @@ describe('client defaulter', () => {
const params = [{text: controller.defaulter.observation, clientFk: data[1].clientFk}];
jest.spyOn(controller.vnApp, 'showMessage');
$httpBackend.expect('GET', `Defaulters/filter`).respond(200);
$httpBackend.expect('POST', `ClientObservations`, params).respond(200, params);
controller.onResponse();
@ -115,5 +105,17 @@ describe('client defaulter', () => {
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.
Credit I.: Crédito A.
Last observation: Última observación
Last observation D.: Fecha última O.
L. O. Date: Fecha Ú. O.
Last observation date: Fecha última observación
Search client: Buscar clientes
Worker who made the last observation: Trabajador que ha realizado la última observación

View File

@ -5,7 +5,12 @@
data="$ctrl.account"
form="form">
</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-horizontal>
<vn-check
@ -28,6 +33,17 @@
rule>
</vn-textfield>
</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-button-bar>
<vn-submit
@ -55,6 +71,7 @@
<vn-textfield
type="password"
label="New password"
info="{{'Password requirements' | translate:$ctrl.passRequirements[0]}}"
ng-model="$ctrl.newPassword">
</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`);
if (this.newPassword != this.repeatPassword)
throw new Error(`Passwords don't match`);
let account = {
password: this.newPassword
const data = {
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!'));
});
} catch (e) {
@ -59,6 +59,18 @@ export default class Controller extends Section {
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'];

View File

@ -52,7 +52,7 @@ describe('Component VnClientWebAccess', () => {
});
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'};
expect(controller.canEnableCheckBox).toBeTruthy();
@ -82,7 +82,9 @@ describe('Component VnClientWebAccess', () => {
controller.newPassword = 'm24x8';
controller.repeatPassword = 'm24x8';
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();
$httpBackend.flush();
});

View File

@ -5,3 +5,5 @@ Repeat password: Repetir contraseña
Change password: Cambiar contraseña
Passwords don't match: Las contraseñas no coinciden
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) => {
const models = Self.app.models;
const myOptions = {};
if (typeof options == 'object')
@ -143,8 +144,12 @@ module.exports = Self => {
const stmts = [];
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(`
SELECT
i.image,
@ -202,9 +207,9 @@ module.exports = Self => {
LEFT JOIN itemType t ON t.id = i.typeFk
LEFT JOIN intrastat intr ON intr.id = i.intrastatFk
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`
);
, [date]);
if (ctx.args.tags) {
let i = 1;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -74,6 +74,8 @@ module.exports = Self => {
filter = mergeFilters(filter, {where});
const date = new Date();
date.setHours(0, 0, 0, 0);
const stmts = [];
const stmt = new ParameterizedSQL(
`SELECT *
@ -101,10 +103,10 @@ module.exports = Self => {
LEFT JOIN vn.ticket t ON t.routeFk = r.id
LEFT JOIN vn.supplierAgencyTerm sat ON sat.agencyFk = a.id
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
) a`
);
, [date]);
stmt.merge(conn.makeWhere(filter.where));
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;
describe('Route filter()', () => {
let today = new Date();
const today = new Date();
today.setHours(2, 0, 0, 0);
it('should return the routes matching "search"', async() => {
let ctx = {
const ctx = {
args: {
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[0].id).toEqual(1);
@ -28,7 +28,6 @@ describe('Route filter()', () => {
const to = new Date();
to.setHours(23, 59, 59, 999);
const ctx = {
args: {
from: from,
@ -48,43 +47,43 @@ describe('Route filter()', () => {
});
it('should return the routes matching "m3"', async() => {
let ctx = {
const ctx = {
args: {
m3: 0.1,
}
};
let result = await app.models.Route.filter(ctx);
const result = await app.models.Route.filter(ctx);
expect(result.length).toEqual(1);
});
it('should return the routes matching "description"', async() => {
let ctx = {
const ctx = {
args: {
description: 'third route',
}
};
let result = await app.models.Route.filter(ctx);
const result = await app.models.Route.filter(ctx);
expect(result.length).toEqual(1);
});
it('should return the routes matching "workerFk"', async() => {
let ctx = {
const ctx = {
args: {
workerFk: 56,
}
};
let result = await app.models.Route.filter(ctx);
const result = await app.models.Route.filter(ctx);
expect(result.length).toEqual(5);
});
it('should return the routes matching "warehouseFk"', async() => {
let ctx = {
const ctx = {
args: {
warehouseFk: 1,
}
@ -102,25 +101,25 @@ describe('Route filter()', () => {
});
it('should return the routes matching "vehicleFk"', async() => {
let ctx = {
const ctx = {
args: {
vehicleFk: 2,
}
};
let result = await app.models.Route.filter(ctx);
const result = await app.models.Route.filter(ctx);
expect(result.length).toEqual(1);
});
it('should return the routes matching "agencyModeFk"', async() => {
let ctx = {
const ctx = {
args: {
agencyModeFk: 7,
}
};
let result = await app.models.Route.filter(ctx);
const result = await app.models.Route.filter(ctx);
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');
describe('route getSuggestedTickets()', () => {
const routeID = 1;
const ticketId = 12;
it('should return an array of suggested tickets', async() => {
const activeCtx = {
accessToken: {userId: 19},
headers: {origin: 'http://localhost'}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
const tx = await app.models.Ticket.beginTransaction({});
const tx = await models.Ticket.beginTransaction({});
try {
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({
routeFk: null,
landed: new Date()
}, options);
const result = await app.models.Route.getSuggestedTickets(routeID, options);
const result = await models.Route.getSuggestedTickets(routeID, options);
const length = result.length;
const anyResult = result[Math.floor(Math.random() * Math.floor(length))];

View File

@ -24,6 +24,8 @@ module.exports = Self => {
if (typeof options == 'object')
Object.assign(myOptions, options);
const date = new Date();
date.setHours(0, 0, 0, 0);
const query = `
SELECT
s.id AS saleFk,
@ -39,11 +41,11 @@ module.exports = Self => {
INNER JOIN vn.sale s ON s.ticketFk = t.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
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;
};

View File

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

View File

@ -132,6 +132,8 @@ module.exports = Self => {
Self.filter = async(ctx, filter, options) => {
const userId = ctx.req.accessToken.userId;
const conn = Self.dataSource.connector;
const date = new Date();
date.setHours(0, 0, 0, 0);
const models = Self.app.models;
const args = ctx.args;
@ -297,7 +299,8 @@ module.exports = Self => {
stmts.push(stmt);
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.sale_getProblems');
stmts.push(`
stmt = new ParameterizedSQL(`
CREATE TEMPORARY TABLE tmp.sale_getProblems
(INDEX (ticketFk))
ENGINE = MEMORY
@ -305,7 +308,9 @@ module.exports = Self => {
FROM tmp.filter f
LEFT JOIN alertLevel al ON al.id = f.alertLevel
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)');
stmt = new ParameterizedSQL(`

View File

@ -26,6 +26,8 @@ module.exports = function(Self) {
Self.makeInvoice = async(ctx, ticketsIds, options) => {
const userId = ctx.req.accessToken.userId;
const models = Self.app.models;
const date = new Date();
date.setHours(0, 0, 0, 0);
const myOptions = {};
let tx;
@ -81,7 +83,7 @@ module.exports = function(Self) {
WHERE id IN(?) AND refFk IS NULL
`, [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);

View File

@ -142,6 +142,25 @@ module.exports = Self => {
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();
return updatedTicket;

View File

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

View File

@ -213,7 +213,7 @@ describe('ticket filter()', () => {
const filter = {};
const result = await models.Ticket.filter(ctx, filter, options);
expect(result.length).toEqual(2);
expect(result.length).toEqual(3);
await tx.rollback();
} catch (e) {

View File

@ -8,6 +8,12 @@ describe('ticket setDeleted()', () => {
accessToken: {userId: userId},
};
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should throw an error if the given ticket has a claim', async() => {
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');
});
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() => {
pending('test excluded by task #3693');
const tx = await models.Ticket.beginTransaction({});

View File

@ -44,6 +44,9 @@
"Ticket": {
"dataSource": "vn"
},
"TicketCollection": {
"dataSource": "vn"
},
"TicketDms": {
"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"
url="{{ $ctrl.clientId ? 'Clients/'+ $ctrl.clientId +'/addresses' : null }}"
fields="['nickname', 'street', 'city']"
where="{isActive: true}"
ng-model="$ctrl.addressId"
show-field="nickname"
value-field="id"

View File

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

View File

@ -40,6 +40,7 @@ module.exports = Self => {
const models = Self.app.models;
let tx;
const myOptions = {};
const date = new Date();
if (typeof options == 'object')
Object.assign(myOptions, options);
@ -57,8 +58,8 @@ module.exports = Self => {
await Self.rawSql(`
INSERT INTO travelThermograph(thermographFk, warehouseFk, temperatureFk, created)
VALUES (?, ?, ?, NOW())
`, [thermograph.id, warehouseId, temperatureFk], myOptions);
VALUES (?, ?, ?, ?)
`, [thermograph.id, warehouseId, temperatureFk, date], myOptions);
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()', () => {
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();
firstHour.setHours(7, 0, 0, 0);
const lastHour = new Date();
@ -14,13 +14,13 @@ describe('workerTimeControl filter()', () => {
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);
});
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();
firstHour.setHours(7, 0, 0, 0);
const lastHour = new Date();
@ -34,7 +34,7 @@ describe('workerTimeControl filter()', () => {
};
let error;
await app.models.WorkerTimeControl.filter(ctx, filter).catch(e => {
await models.WorkerTimeControl.filter(ctx, filter).catch(e => {
error = e;
}).finally(() => {
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 salesPersonId = 1106;
const salesBossId = 19;
let activeCtx = {
const activeCtx = {
accessToken: {userId: 50},
};
let ctx = {req: activeCtx};
const ctx = {req: activeCtx};
beforeAll(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
@ -82,7 +82,7 @@ describe('workerTimeControl add/delete timeEntry()', () => {
const workerId = salesPersonId;
let error;
let calendar = await app.models.Calendar.findById(3);
const calendar = await app.models.Calendar.findById(3);
try {
ctx.args = {timed: new Date(calendar.dated), direction: 'in'};

View File

@ -80,6 +80,9 @@ module.exports = Self => {
if (hasHoursRecorded && isNotHalfAbsence)
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(
`SELECT COUNT(*) halfHolidayCounter
FROM vn.calendar c
@ -88,8 +91,8 @@ module.exports = Self => {
JOIN vn.person pe ON pe.id = p.person_id
WHERE c.dayOffTypeFk = 6
AND pe.workerFk = ?
AND c.dated BETWEEN util.firstDayOfYear(CURDATE())
AND LAST_DAY(DATE_ADD(NOW(), INTERVAL 12-MONTH(NOW()) MONTH))`, [id]);
AND c.dated BETWEEN util.firstDayOfYear(?)
AND LAST_DAY(DATE_ADD(?, INTERVAL 12 - MONTH(?) MONTH))`, [id, date, now, now]);
const hasHalfHoliday = result.halfHolidayCounter > 0;
const isHalfHoliday = absenceType.code === 'halfHoliday';

View File

@ -57,25 +57,9 @@ module.exports = Self => {
ended.setDate(0);
ended.setHours(23, 59, 59, 59);
const filter = {
where: {
and: [
{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;
const filter = {where: {businessFk: args.businessFk}};
const contract = await models.WorkerLabour.findOne(filter, myOptions);
const payedHolidays = contract.payedHolidays;
let queryIndex;
const year = started.getFullYear();

View File

@ -27,4 +27,15 @@ describe('Worker holidays()', () => {
expect(result.totalHolidays).toEqual(27.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 date = new Date();
const today = date.toISOString().split('T')[0];
const result = await models.Zone.getZoneClosing([1, 2, 3], today, options);
expect(result.length).toEqual(3);

View File

@ -1,6 +1,6 @@
{
"name": "salix-back",
"version": "1.0.0",
"version": "6.8.0",
"author": "Verdnatura Levante SL",
"description": "Salix backend",
"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>
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.
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);
}
// Update queue status
const date = new Date();
sql = `UPDATE invoiceOut_queue
SET status = "printed",
printed = NOW()
printed = ?
WHERE invoiceFk = ?`;
connection.query(sql, [invoiceOut.id]);
connection.query(sql, [date, invoiceOut.id]);
connection.query('COMMIT');
} catch (error) {
connection.query('ROLLBACK');