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

This commit is contained in:
Sergio De la torre 2023-09-01 05:24:55 +00:00
commit 00201197cc
115 changed files with 1419 additions and 537 deletions

View File

@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2334.01] - 2023-08-24 ## [2338.01] - 2023-09-21
### Added ### Added
@ -14,6 +14,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
## [2336.01] - 2023-09-07
### Added
### Changed
### Fixed
## [2334.01] - 2023-08-24
### Added
- (General -> Errores) Botón para enviar cau con los datos del error
## [2332.01] - 2023-08-10 ## [2332.01] - 2023-08-10
@ -44,9 +58,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- (Clientes -> Razón social) Permite crear clientes con la misma razón social según el país - (Clientes -> Razón social) Permite crear clientes con la misma razón social según el país
### Fixed
## [2328.01] - 2023-07-13 ## [2328.01] - 2023-07-13
### Added ### Added

View File

@ -71,11 +71,10 @@ module.exports = Self => {
} }
try { try {
const response = await Self.get(fileCabinet, filter); const [response] = await Self.get(fileCabinet, filter);
const [documents] = response.Items; if (!response) return false;
if (!documents) return false;
return {id: documents.Id}; return {id: response['Document ID']};
} catch (error) { } catch (error) {
return false; return false;
} }

View File

@ -65,7 +65,7 @@ module.exports = Self => {
const email = new Email('delivery-note', params); const email = new Email('delivery-note', params);
const docuwareFile = await models.Docuware.download(ctx, id, 'deliveryNote'); const docuwareFile = await models.Docuware.download(id, 'deliveryNote');
return email.send({ return email.send({
overrideAttachments: true, overrideAttachments: true,

View File

@ -16,19 +16,9 @@ describe('docuware download()', () => {
it('should return the document data', async() => { it('should return the document data', async() => {
const docuwareId = 1; const docuwareId = 1;
const response = { const response = [{
Items: [ 'Document ID': docuwareId
{ }];
Id: docuwareId,
Fields: [
{
FieldName: 'ESTADO',
Item: 'Firmado'
}
]
}
]
};
spyOn(docuwareModel, 'get').and.returnValue((new Promise(resolve => resolve(response)))); spyOn(docuwareModel, 'get').and.returnValue((new Promise(resolve => resolve(response))));
const result = await models.Docuware.checkFile(ticketId, fileCabinetName, null, true); const result = await models.Docuware.checkFile(ticketId, fileCabinetName, null, true);

View File

@ -111,7 +111,7 @@ module.exports = Self => {
throw new UserError('Action not allowed on the test environment'); throw new UserError('Action not allowed on the test environment');
// delete old // delete old
const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet, false); const docuwareFile = await models.Docuware.checkFile(id, fileCabinet, false);
if (docuwareFile) { if (docuwareFile) {
const deleteJson = { const deleteJson = {
'Field': [{'FieldName': 'ESTADO', 'Item': 'Pendiente eliminar', 'ItemElementName': 'String'}] 'Field': [{'FieldName': 'ESTADO', 'Item': 'Pendiente eliminar', 'ItemElementName': 'String'}]

View File

@ -0,0 +1,63 @@
const smtp = require('vn-print/core/smtp');
const config = require('vn-print/core/config');
module.exports = Self => {
Self.remoteMethodCtx('sendToSupport', {
description: 'Send mail to support',
accessType: 'WRITE',
accepts: [
{
arg: 'reason',
type: 'string',
description: 'The reason'
},
{
arg: 'additionalData',
type: 'object',
required: true,
description: 'The additional data'
}
],
returns: {
type: 'object',
root: true
},
http: {
path: `/send-to-support`,
verb: 'POST'
}
});
Self.sendToSupport = async(ctx, reason, additionalData) => {
const emailUser =
await Self.app.models.EmailUser.findById(ctx.req.accessToken.userId, {fields: ['email']});
let html = `<strong>Motivo</strong>:<br/>${reason}<br/>`;
html += `<strong>Usuario</strong>:<br/>${ctx.req.accessToken.userId} ${emailUser.email}<br/>`;
for (const data in additionalData)
html += `<strong>${data}</strong>:<br/>${tryParse(additionalData[data])}<br/>`;
const subjectReason = JSON.parse(additionalData?.httpRequest)?.data?.error;
smtp.send({
to: `${config.app.reportEmail}, ${emailUser.email}`,
subject:
'[Support-Salix] ' +
additionalData?.frontPath + ' ' +
subjectReason?.name + ':' +
subjectReason?.message,
html
});
};
function tryParse(value) {
try {
try {
value = JSON.parse(value);
} catch {}
return JSON.stringify(value, null, '&nbsp;').split('\n').join('<br>');
} catch {
return value;
}
}
};

View File

@ -1,4 +1,5 @@
module.exports = Self => { module.exports = Self => {
require('../methods/osticket/osTicketReportEmail')(Self); require('../methods/osticket/osTicketReportEmail')(Self);
require('../methods/osticket/closeTicket')(Self); require('../methods/osticket/closeTicket')(Self);
require('../methods/osticket/sendToSupport')(Self);
}; };

View File

@ -31,7 +31,6 @@ RUN sed -i -e 's/@mockDate/'"$MOCKDATE"'/g' mockDate.sql \
&& gosu mysql docker-structure.sh && gosu mysql docker-structure.sh
COPY changes ./changes COPY changes ./changes
COPY dump/fixtures.sql ./ COPY dump/fixtures.sql ./
ARG STAMP=unknown
RUN gosu mysql docker-fixtures.sh RUN gosu mysql docker-fixtures.sh
RUN echo "[INFO] -> Import finished" \ RUN echo "[INFO] -> Import finished" \

View File

@ -1,4 +0,0 @@
/**
* Hay una versión en salix que machacará toda esta función/procedimiento avisa
* a ___ de los cambios que quieres hacer.
*/

View File

@ -0,0 +1,4 @@
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('SaleTracking', 'deleteSaleGroupDetail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('SaleTracking', 'replaceOrCreate', 'WRITE', 'ALLOW', 'ROLE', 'employee');

View File

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('Worker', 'search', 'READ', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,2 @@
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalId`)
VALUES ('ExpeditionState','addExpeditionState','WRITE','ALLOW','ROLE','delivery');

View File

@ -0,0 +1,32 @@
INSERT INTO `account`.`role` (`id`, `name`, `description`, `hasLogin`)
VALUES ('claimViewer','Trabajadores que consulta las reclamaciones ',1);
INSERT INTO `account`.`roleInherit` (`role`,`inheritsFrom`)
SELECT `r`.`id`, `r2`.`id`
FROM `account`.`role` `r`
JOIN `account`.`role` `r2` ON `r2`.`name` = 'claimViewer'
WHERE `r`.`name` IN (
'salesPerson',
'buyer',
'deliveryBoss',
'handmadeBoss'
)
DELETE FROM `salix`.`ACL`
WHERE `model`= 'claim'
AND `property` IN (
'filter',
'find',
'findById',
'getSummary'
);
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`)
VALUES ('Claim','filter','READ','ALLOW','ROLE','claimViewer');
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`)
VALUES ('Claim','find','READ','ALLOW','ROLE','claimViewer');
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`)
VALUES ('Claim','findById','READ','ALLOW','ROLE','claimViewer');
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`)
VALUES ('Claim','getSummary','READ','ALLOW','ROLE','claimViewer');

View File

@ -0,0 +1,3 @@
UPDATE `vn`.`department`
SET code='VN'
WHERE name='VERDNATURA';

View File

@ -0,0 +1,7 @@
DELETE FROM `vn`.`saleGroupDetail` WHERE id IN (468106,468104,468107,468105,495210,495208,495207,495209,462879,462880,447186,450623,450622,455606,455605,455827,455829,455828,459067,460689,460691,460690,460692,462408,463403,463405,463404,463129,463127,463126,463128,468098,468096,468099,468097,468310,468314,468313,475654,468325,473248,474803,474739,475042,475052,475047,475041,475051,475046,475040,475050,475045,475039,475049,475044,475038,475048,475043,474888,474892,474890,474887,474891,474889,481109,481107,481105,481108,481106,481110,479008,490787,490792,490791,485295,485294,485293,485528,490796,487853,487959,491303,490789,490914,490913,492305,492310,492307,492304,492309,492306,492303,492308,494111,494110,494480,494482,494481,494483,495202,495200,495199,495201,497209,499765,499763,499767,499764,499768,499766,502014,502013,508820,508819,508818,463133,463131,463130,463132,468102,468100,468103,468101,468311,468316,468315,468327,474894,474898,474896,474893,474897,474895,495206,495204,495203,495205,499771,499769,499773,499770,499774,499772);
ALTER TABLE `vn`.`saleGroupDetail` ADD CONSTRAINT saleGroupDetail_UN UNIQUE KEY (saleFk);
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`)
VALUES
('SaleGroupDetail','deleteById','WRITE','ALLOW','employee');

View File

View File

@ -22,12 +22,8 @@ module.exports = class Docker {
* @param {String} networkName Name of the container network * @param {String} networkName Name of the container network
*/ */
async run(ci, networkName = 'jenkins') { async run(ci, networkName = 'jenkins') {
let d = new Date();
let pad = v => v < 10 ? '0' + v : v;
let stamp = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
log('Building container image...'); log('Building container image...');
await this.execP(`docker build --build-arg STAMP=${stamp} -t salix-db ./db`); await this.execP(`docker build -t salix-db ./db`);
log('Image built.'); log('Image built.');
let dockerArgs; let dockerArgs;

File diff suppressed because one or more lines are too long

View File

@ -65,7 +65,6 @@ TABLES=(
sample sample
state state
ticketUpdateAction ticketUpdateAction
time
volumeConfig volumeConfig
workCenter workCenter
companyI18n companyI18n
@ -98,13 +97,6 @@ TABLES=(
) )
dump_tables ${TABLES[@]} dump_tables ${TABLES[@]}
TABLES=(
postgresql
labour_agreement
media_type
)
dump_tables ${TABLES[@]}
TABLES=( TABLES=(
sage sage
TiposIva TiposIva

View File

@ -7,7 +7,6 @@ SCHEMAS=(
edi edi
hedera hedera
pbx pbx
postgresql
sage sage
salix salix
stock stock
@ -23,7 +22,6 @@ IGNORETABLES=(
--ignore-table=bs.productionIndicators --ignore-table=bs.productionIndicators
--ignore-table=bs.VentasPorCliente --ignore-table=bs.VentasPorCliente
--ignore-table=bs.v_ventas --ignore-table=bs.v_ventas
--ignore-table=postgresql.currentWorkersStats
--ignore-table=vn.accounting__ --ignore-table=vn.accounting__
--ignore-table=vn.agencyModeZone --ignore-table=vn.agencyModeZone
--ignore-table=vn.agencyProvince --ignore-table=vn.agencyProvince

View File

@ -54,7 +54,6 @@ xdescribe('worker workerTimeControl_check()', () => {
}); });
it('should throw an error if the worker with a special category has not finished the 9h break', async() => { it('should throw an error if the worker with a special category has not finished the 9h break', async() => {
// dayBreak to 9h in postgresql.professional_category
const workerId = 1110; const workerId = 1110;
const tabletId = 1; const tabletId = 1;
let stmts = []; let stmts = [];
@ -91,7 +90,6 @@ xdescribe('worker workerTimeControl_check()', () => {
}); });
it('should check f the worker with a special category has finished the 9h break', async() => { it('should check f the worker with a special category has finished the 9h break', async() => {
// dayBreak to 9h in postgresql.professional_category
const workerId = 1110; const workerId = 1110;
const tabletId = 1; const tabletId = 1;
let stmts = []; let stmts = [];
@ -239,12 +237,6 @@ xdescribe('worker workerTimeControl_check()', () => {
stmts.push('START TRANSACTION'); stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO postgresql.calendar_employee(businessFk,calendar_state_id,date)
VALUES
(?,1,CURDATE())`, [
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction) stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES VALUES
(?,TIMESTAMPADD(HOUR,-24,NOW()),0,"in"), (?,TIMESTAMPADD(HOUR,-24,NOW()),0,"in"),

View File

@ -632,6 +632,7 @@ let actions = {
await this.write(selector, value.toString()); await this.write(selector, value.toString());
break; break;
case 'vn-autocomplete': case 'vn-autocomplete':
case 'vn-worker-autocomplete':
if (value) if (value)
await this.autocompleteSearch(selector, value.toString()); await this.autocompleteSearch(selector, value.toString());
else else
@ -667,6 +668,7 @@ let actions = {
switch (tagName) { switch (tagName) {
case 'vn-textfield': case 'vn-textfield':
case 'vn-autocomplete': case 'vn-autocomplete':
case 'vn-worker-autocomplete':
case 'vn-input-time': case 'vn-input-time':
case 'vn-datalist': case 'vn-datalist':
el = await input.$('input'); el = await input.$('input');

View File

@ -187,7 +187,7 @@ export default {
country: 'vn-client-create vn-autocomplete[ng-model="$ctrl.client.countryFk"]', country: 'vn-client-create vn-autocomplete[ng-model="$ctrl.client.countryFk"]',
userName: 'vn-client-create vn-textfield[ng-model="$ctrl.client.userName"]', userName: 'vn-client-create vn-textfield[ng-model="$ctrl.client.userName"]',
email: 'vn-client-create vn-textfield[ng-model="$ctrl.client.email"]', email: 'vn-client-create vn-textfield[ng-model="$ctrl.client.email"]',
salesPerson: 'vn-client-create vn-autocomplete[ng-model="$ctrl.client.salesPersonFk"]', salesPerson: 'vn-client-create vn-worker-autocomplete[ng-model="$ctrl.client.salesPersonFk"]',
saveNewProvicenButton: '#saveProvince', saveNewProvicenButton: '#saveProvince',
saveNewCityButton: '#saveCity', saveNewCityButton: '#saveCity',
saveNewPoscode: '#savePostcode', saveNewPoscode: '#savePostcode',
@ -199,7 +199,7 @@ export default {
email: 'vn-client-basic-data vn-textfield[ng-model="$ctrl.client.email"]', email: 'vn-client-basic-data vn-textfield[ng-model="$ctrl.client.email"]',
phone: 'vn-client-basic-data vn-textfield[ng-model="$ctrl.client.phone"]', phone: 'vn-client-basic-data vn-textfield[ng-model="$ctrl.client.phone"]',
mobile: 'vn-client-basic-data vn-textfield[ng-model="$ctrl.client.mobile"]', mobile: 'vn-client-basic-data vn-textfield[ng-model="$ctrl.client.mobile"]',
salesPerson: 'vn-client-basic-data vn-autocomplete[ng-model="$ctrl.client.salesPersonFk"]', salesPerson: 'vn-client-basic-data vn-worker-autocomplete[ng-model="$ctrl.client.salesPersonFk"]',
channel: 'vn-client-basic-data vn-autocomplete[ng-model="$ctrl.client.contactChannelFk"]', channel: 'vn-client-basic-data vn-autocomplete[ng-model="$ctrl.client.contactChannelFk"]',
transferor: 'vn-client-basic-data vn-autocomplete[ng-model="$ctrl.client.transferorFk"]', transferor: 'vn-client-basic-data vn-autocomplete[ng-model="$ctrl.client.transferorFk"]',
businessType: 'vn-client-basic-data vn-autocomplete[ng-model="$ctrl.client.businessTypeFk"]', businessType: 'vn-client-basic-data vn-autocomplete[ng-model="$ctrl.client.businessTypeFk"]',
@ -735,7 +735,7 @@ export default {
}, },
createStateView: { createStateView: {
state: 'vn-autocomplete[ng-model="$ctrl.stateFk"]', state: 'vn-autocomplete[ng-model="$ctrl.stateFk"]',
worker: 'vn-autocomplete[ng-model="$ctrl.workerFk"]', worker: 'vn-worker-autocomplete[ng-model="$ctrl.workerFk"]',
saveStateButton: `button[type=submit]` saveStateButton: `button[type=submit]`
}, },
claimsIndex: { claimsIndex: {
@ -781,12 +781,12 @@ export default {
firstClaimReason: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimReasonFk"]', firstClaimReason: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimReasonFk"]',
firstClaimResult: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimResultFk"]', firstClaimResult: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimResultFk"]',
firstClaimResponsible: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimResponsibleFk"]', firstClaimResponsible: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimResponsibleFk"]',
firstClaimWorker: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.workerFk"]', firstClaimWorker: 'vn-claim-development vn-horizontal:nth-child(1) vn-worker-autocomplete[ng-model="claimDevelopment.workerFk"]',
firstClaimRedelivery: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimRedeliveryFk"]', firstClaimRedelivery: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimRedeliveryFk"]',
secondClaimReason: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimReasonFk"]', secondClaimReason: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimReasonFk"]',
secondClaimResult: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimResultFk"]', secondClaimResult: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimResultFk"]',
secondClaimResponsible: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimResponsibleFk"]', secondClaimResponsible: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimResponsibleFk"]',
secondClaimWorker: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.workerFk"]', secondClaimWorker: 'vn-claim-development vn-horizontal:nth-child(2) vn-worker-autocomplete[ng-model="claimDevelopment.workerFk"]',
secondClaimRedelivery: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimRedeliveryFk"]', secondClaimRedelivery: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimRedeliveryFk"]',
saveDevelopmentButton: 'button[type=submit]' saveDevelopmentButton: 'button[type=submit]'
}, },
@ -854,7 +854,7 @@ export default {
}, },
createRouteView: { createRouteView: {
worker: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.workerFk"]', worker: 'vn-route-create vn-worker-autocomplete[ng-model="$ctrl.route.workerFk"]',
createdDatePicker: 'vn-route-create vn-date-picker[ng-model="$ctrl.route.created"]', createdDatePicker: 'vn-route-create vn-date-picker[ng-model="$ctrl.route.created"]',
vehicleAuto: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.vehicleFk"]', vehicleAuto: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.vehicleFk"]',
agency: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.agencyModeFk"]', agency: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.agencyModeFk"]',
@ -976,7 +976,7 @@ export default {
street: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.street"]', street: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.street"]',
user: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.name"]', user: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.name"]',
email: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.email"]', email: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.email"]',
boss: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.bossFk"]', boss: 'vn-worker-create vn-worker-autocomplete[ng-model="$ctrl.worker.bossFk"]',
role: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.roleFk"]', role: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.roleFk"]',
iban: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.iban"]', iban: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.iban"]',
createButton: 'vn-worker-create vn-submit[label="Create"]', createButton: 'vn-worker-create vn-submit[label="Create"]',

View File

@ -59,7 +59,7 @@ describe('Ticket Create new tracking state path', () => {
const result = await page const result = await page
.waitToGetProperty(selectors.createStateView.worker, 'value'); .waitToGetProperty(selectors.createStateView.worker, 'value');
expect(result).toEqual('salesPersonNick'); expect(result).toEqual('salesPerson');
}); });
it(`should succesfully create a valid state`, async() => { it(`should succesfully create a valid state`, async() => {

View File

@ -17,10 +17,9 @@ import './style.scss';
* @event change Thrown when value is changed * @event change Thrown when value is changed
*/ */
export default class Autocomplete extends Field { export default class Autocomplete extends Field {
constructor($element, $, $compile, $transclude) { constructor($element, $, $transclude) {
super($element, $, $compile); super($element, $, $transclude);
this.$transclude = $transclude; this.$transclude = $transclude;
this.$compile = $compile;
this._selection = null; this._selection = null;
this.input = this.element.querySelector('input'); this.input = this.element.querySelector('input');
} }
@ -153,7 +152,14 @@ export default class Autocomplete extends Field {
filter.include = this.include; filter.include = this.include;
let json = encodeURIComponent(JSON.stringify(filter)); let json = encodeURIComponent(JSON.stringify(filter));
this.$http.get(`${this.url}?filter=${json}`).then(
let url;
if (this.url.includes('?'))
url = `${this.url}&filter=${json}`;
else
url = `${this.url}?filter=${json}`;
this.$http.get(url).then(
json => this.onSelectionRequest(json.data), json => this.onSelectionRequest(json.data),
() => this.onSelectionRequest() () => this.onSelectionRequest()
); );
@ -282,7 +288,7 @@ export default class Autocomplete extends Field {
this.refreshSelection(); this.refreshSelection();
} }
} }
Autocomplete.$inject = ['$element', '$scope', '$compile', '$transclude']; Autocomplete.$inject = ['$element', '$scope', '$transclude'];
ngModule.vnComponent('vnAutocomplete', { ngModule.vnComponent('vnAutocomplete', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -3,8 +3,8 @@ import FormInput from '../form-input';
import './style.scss'; import './style.scss';
export default class Field extends FormInput { export default class Field extends FormInput {
constructor($element, $scope) { constructor($element, $scope, $transclude) {
super($element, $scope); super($element, $scope, $transclude);
this.prefix = null; this.prefix = null;
this.suffix = null; this.suffix = null;
@ -197,7 +197,7 @@ export default class Field extends FormInput {
}); });
} }
} }
Field.$inject = ['$element', '$scope']; Field.$inject = ['$element', '$scope', '$transclude'];
ngModule.vnComponent('vnField', { ngModule.vnComponent('vnField', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -51,7 +51,9 @@ import './textarea';
import './th'; import './th';
import './treeview'; import './treeview';
import './wday-picker'; import './wday-picker';
import './worker-autocomplete';
import './datalist'; import './datalist';
import './contextmenu'; import './contextmenu';
import './rating'; import './rating';
import './smart-table'; import './smart-table';
import './support-dialog';

View File

@ -1 +1,5 @@
<div id="shapes"></div> <div id="shapes"></div>
<vn-support-dialog
vn-id="support-dialog"
additional-data="$ctrl.additionalData">
</vn-support-dialog>

View File

@ -27,6 +27,18 @@ export default class Controller extends Component {
setTimeout(() => element.classList.add('shown'), 30); setTimeout(() => element.classList.add('shown'), 30);
shape.element = element; shape.element = element;
if (data.additionalData && this.vnToken.token) {
this.additionalData = data.additionalData;
let supportButton = document.createElement('i');
supportButton.setAttribute('class', 'material-icons clickable');
supportButton.addEventListener('click', () => this.$.supportDialog.show());
element.appendChild(supportButton);
let buttonIcon = 'support_agent';
buttonIcon = document.createTextNode(buttonIcon);
supportButton.appendChild(buttonIcon);
}
if (shape.type) if (shape.type)
element.classList.add(shape.type); element.classList.add(shape.type);
@ -95,7 +107,7 @@ export default class Controller extends Component {
clearTimeout(shape.hideTimeout); clearTimeout(shape.hideTimeout);
shape.hideTimeout = setTimeout( shape.hideTimeout = setTimeout(
() => this.hide(shape), shape.timeout || 3000); () => this.hide(shape), shape.timeout || 5000);
this.lastShape = shape; this.lastShape = shape;
} }

View File

@ -20,6 +20,10 @@ vn-snackbar .shape {
margin-bottom: 15px; margin-bottom: 15px;
color: white; color: white;
padding: 12px 25px 12px 12px; padding: 12px 25px 12px 12px;
display: flex ;
flex-direction: row;
justify-content: center;
align-items: center;
& > .text { & > .text {
text-align: center; text-align: center;
@ -64,4 +68,12 @@ vn-snackbar .shape {
top: 0; top: 0;
right: 0 right: 0
} }
.clickable{
background-color: $color-main;
padding: 6px;
border-radius: 50%;
cursor: pointer;
margin-right: 7px;
}
} }

View File

@ -0,0 +1,22 @@
<tpl-body>
<section>
<h5 class="vn-py-sm" translate>Send cau</h5>
<vn-horizontal>
<vn-textarea vn-one
label="ExplainReason"
ng-model="$ctrl.reason"
rows="2"
required="true">
</vn-textarea>
</vn-horizontal>
<vn-horizontal>
<span>
{{'By sending this ticket, all the data related to the error, the section, the user, etc., are already sent.' | translate}}
</span>
</vn-horizontal>
</section>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Send</button>
</tpl-buttons>

View File

@ -0,0 +1,30 @@
import ngModule from '../../module';
import Dialog from '../dialog';
export default class Controller extends Dialog {
constructor($element, $, $transclude) {
super($element, $, $transclude);
}
responseHandler(response) {
if (response !== 'accept')
return super.responseHandler(response);
this.$http.post('Ostickets/send-to-support', {
reason: this.reason,
additionalData: this.additionalData
})
.then(() => super.responseHandler(response))
.then(() => this.vnApp.showSuccess(this.$t('Email sended!')));
}
}
Controller.$inject = ['$element', '$scope', '$transclude'];
ngModule.vnComponent('vnSupportDialog', {
slotTemplate: require('./index.html'),
controller: Controller,
bindings: {
additionalData: '<?'
}
});

View File

@ -0,0 +1,8 @@
<tpl-item>
<div>
{{name}}
</div>
<div class="text-caption text-secondary">
{{nickname}}, {{code}}
</div>
</tpl-item>

View File

@ -0,0 +1,40 @@
import ngModule from '../../module';
import Autocomplete from '../autocomplete';
export default class WorkerAutocomplete extends Autocomplete {
constructor(...args) {
super(...args);
}
$onInit() {
super.$onInit();
let url = 'Workers/search';
if (this.departments) {
const parameter = encodeURIComponent(JSON.stringify(this.departments));
url = `Workers/search?departmentCodes=${parameter}`;
}
Object.assign(this, {
label: 'Worker',
url,
searchFunction: function({$search}) {
return {and: [
{'active': {neq: false}},
{or: [
{'name': $search},
{'nickname': {like: '%' + $search + '%'}},
{'code': {like: $search + '%'}}
]}
]};
},
});
}
}
ngModule.vnComponent('vnWorkerAutocomplete', {
slotTemplate: require('./index.html'),
controller: WorkerAutocomplete,
bindings: {
departments: '<?'
},
});

View File

@ -14,3 +14,4 @@ Previous: Back
Load more: Load more Load more: Load more
Auto-scroll interrupted, please adjust the search: Auto-scroll interrupted, please adjust the search Auto-scroll interrupted, please adjust the search: Auto-scroll interrupted, please adjust the search
General search: General search General search: General search
ExplainReason: Explain the reason why this error should not occur

View File

@ -64,3 +64,6 @@ No results found: Sin resultados
No data: Sin datos No data: Sin datos
Undo changes: Deshacer cambios Undo changes: Deshacer cambios
Load more results: Cargar más resultados Load more results: Cargar más resultados
Send cau: Enviar cau
By sending this ticket, all the data related to the error, the section, the user, etc., are already sent.: Al enviar este cau ya se envían todos los datos relacionados con el error, la sección, el usuario, etc
ExplainReason: Explique el motivo por el que no deberia aparecer este fallo

View File

@ -23,9 +23,9 @@ export default class App {
this.logger.showSuccess(message); this.logger.showSuccess(message);
} }
showError(message) { showError(message, additionalData) {
if (this.logger) if (this.logger)
this.logger.showError(message); this.logger.showError(message, additionalData);
} }
pushLoader() { pushLoader() {

View File

@ -60,7 +60,7 @@ export default class Token {
if (!this.token) return; if (!this.token) return;
const created = storage.getItem('vnTokenCreated'); const created = storage.getItem('vnTokenCreated');
this.created = created && new Date(created); this.created = created && new Date(created);
this.renewPeriod = storage.getItem('vnTokenRenewPeriod'); this.ttl = storage.getItem('vnTokenTtl');
} }
setStorage(storage, token, created, ttl) { setStorage(storage, token, created, ttl) {

View File

@ -25,15 +25,15 @@ export default class App extends Component {
} }
showMessage(message) { showMessage(message) {
this.$.snackbar.show({message: message}); this.$.snackbar.show({message});
} }
showSuccess(message) { showSuccess(message) {
this.$.snackbar.showSuccess({message: message}); this.$.snackbar.showSuccess({message});
} }
showError(message) { showError(message, additionalData) {
this.$.snackbar.showError({message: message}); this.$.snackbar.showError({message, additionalData});
} }
} }

View File

@ -148,7 +148,13 @@ function $exceptionHandler(vnApp, $window, $state, $injector) {
if (messageT) if (messageT)
message = $translate.instant(messageT); message = $translate.instant(messageT);
vnApp.showError(message);
const additonalData = {
frontPath: $state.current.name,
httpRequest: cause?.replace('Possibly unhandled rejection: ', ''),
backError: exception
};
vnApp.showError(message, additonalData);
}; };
} }
ngModule.factory('$exceptionHandler', $exceptionHandler); ngModule.factory('$exceptionHandler', $exceptionHandler);

View File

@ -1,21 +1,45 @@
const validateIban = require('../validateIban'); const validateIban = require('../validateIban');
describe('IBAN validation', () => { describe('IBAN validation', () => {
it('should return false for non-IBAN input', () => { it('should return false for invalid Spanish IBAN format', () => {
let isValid = validateIban('Pepinillos'); let isValid = validateIban('ES00 9999 0000 9999 0000 9999', 'ES');
expect(isValid).toBeFalsy(); expect(isValid).toBeFalsy();
}); });
it('should return false for invalid spanish IBAN input', () => { it('should return true for valid Spanish IBAN', () => {
let isValid = validateIban('ES00 9999 0000 9999 0000 9999'); let isValid = validateIban('ES91 2100 0418 4502 0005 1332', 'ES');
expect(isValid).toBeFalsy();
});
it('should return true for valid spanish IBAN', () => {
let isValid = validateIban('ES91 2100 0418 4502 0005 1332');
expect(isValid).toBeTruthy(); expect(isValid).toBeTruthy();
}); });
it('should return false for invalid Spanish IBAN with incorrect length', () => {
let isValid = validateIban('ES91210004184502000513', 'ES');
expect(isValid).toBeFalsy();
});
it('should return false for invalid Spanish IBAN with incorrect module97 result', () => {
let isValid = validateIban('ES9121000418450200051331', 'ES');
expect(isValid).toBeFalsy();
});
it('should return true for a non-Spanish countryCode', () => {
let isValid = validateIban('DE89370400440532013000', 'AT');
expect(isValid).toBeTruthy();
});
it('should return true for null IBAN', () => {
let isValid = validateIban(null, 'ES');
expect(isValid).toBeTruthy();
});
it('should return false for non-string IBAN', () => {
let isValid = validateIban(12345, 'ES');
expect(isValid).toBeFalsy();
});
}); });

View File

@ -1,6 +1,7 @@
module.exports = function(iban) { module.exports = function(iban, countryCode) {
if (iban == null) return true; if (iban == null) return true;
if (typeof iban != 'string') return false; if (typeof iban != 'string') return false;
if (countryCode?.toLowerCase() != 'es') return true;
iban = iban.toUpperCase(); iban = iban.toUpperCase();
iban = trim(iban); iban = trim(iban);

View File

@ -25,15 +25,13 @@
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete <vn-worker-autocomplete
disabled="false" disabled="false"
ng-model="$ctrl.claim.workerFk"
url="Workers/activeWithRole"
show-field="nickname" show-field="nickname"
search-function="{firstName: $search}" ng-model="$ctrl.claim.workerFk"
where="{role: 'salesPerson'}" departments="['VT']"
label="Attended by"> label="Attended by">
</vn-autocomplete> </vn-worker-autocomplete>
<vn-autocomplete <vn-autocomplete
ng-model="$ctrl.claim.claimStateFk" ng-model="$ctrl.claim.claimStateFk"
data="claimStates" data="claimStates"

View File

@ -36,7 +36,7 @@
data="claimDevelopments" data="claimDevelopments"
form="form"> form="form">
</vn-watcher> </vn-watcher>
<vn-vertical class="vn-w-md"> <vn-vertical class="vn-w-lg">
<vn-card class="vn-pa-lg"> <vn-card class="vn-pa-lg">
<vn-vertical> <vn-vertical>
<form name="form"> <form name="form">
@ -66,16 +66,11 @@
show-field="description" show-field="description"
rule> rule>
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete <vn-worker-autocomplete
ng-model="claimDevelopment.workerFk" ng-model="claimDevelopment.workerFk"
url="Workers/activeWithInheritedRole"
show-field="nickname" show-field="nickname"
search-function="{firstName: $search}"
value-field="id"
where="{role: 'employee'}"
label="Worker"
rule> rule>
</vn-autocomplete> </vn-worker-autocomplete>
<vn-autocomplete <vn-autocomplete
label="Redelivery" label="Redelivery"
ng-model="claimDevelopment.claimRedeliveryFk" ng-model="claimDevelopment.claimRedeliveryFk"

View File

@ -22,26 +22,18 @@
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete <vn-worker-autocomplete
vn-one vn-one
ng-model="filter.salesPersonFk" ng-model="filter.salesPersonFk"
url="Workers/activeWithRole" departments="['VT']"
search-function="{firstName: $search}"
value-field="id"
where="{role: {inq: ['salesTeamBoss', 'salesPerson', 'officeBoss']}}"
label="Salesperson"> label="Salesperson">
<tpl-item>{{firstName}} {{name}}</tpl-item> </vn-worker-autocomplete>
</vn-autocomplete> <vn-worker-autocomplete
<vn-autocomplete
vn-one vn-one
ng-model="filter.attenderFk" ng-model="filter.attenderFk"
url="Workers/activeWithRole" departments="['VT']"
search-function="{firstName: $search}"
value-field="id"
where="{role: {inq: ['salesTeamBoss', 'salesPerson']}}"
label="Attended by"> label="Attended by">
<tpl-item>{{firstName}} {{name}}</tpl-item> </vn-worker-autocomplete>
</vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete vn-one <vn-autocomplete vn-one

View File

@ -90,16 +90,17 @@ module.exports = Self => {
}); });
async function ibanNeedsValidation(err, done) { async function ibanNeedsValidation(err, done) {
const filter = { if (!this.bankEntityFk)
fields: ['code'],
where: {id: this.countryFk}
};
const country = await Self.app.models.Country.findOne(filter);
const code = country ? country.code.toLowerCase() : null;
if (code != 'es')
return done(); return done();
if (!validateIban(this.iban)) const bankEntity = await Self.app.models.BankEntity.findById(this.bankEntityFk);
const filter = {
fields: ['code'],
where: {id: bankEntity.countryFk}
};
const country = await Self.app.models.Country.findOne(filter);
if (!validateIban(this.iban, country?.code))
err(); err();
done(); done();
} }

View File

@ -59,17 +59,14 @@
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete <vn-worker-autocomplete
vn-one vn-one
ng-model="$ctrl.client.salesPersonFk" ng-model="$ctrl.client.salesPersonFk"
url="Workers/activeWithInheritedRole" departments="['VT']"
show-field="nickname" show-field="nickname"
search-function="{firstName: $search}"
value-field="id"
where="{role: 'salesPerson'}"
label="Salesperson" label="Salesperson"
vn-acl="salesAssistant"> vn-acl="salesAssistant">
</vn-autocomplete> </vn-worker-autocomplete>
<vn-autocomplete <vn-autocomplete
vn-one vn-one
ng-model="$ctrl.client.contactChannelFk" ng-model="$ctrl.client.contactChannelFk"

View File

@ -17,12 +17,11 @@
<vn-autocomplete <vn-autocomplete
vn-one vn-one
ng-model="filter.buyerId" ng-model="filter.buyerId"
url="Workers/activeWithRole" url="TicketRequests/getItemTypeWorker"
search-function="{firstName: $search}" search-function="{firstName: $search}"
show-field="nickname"
value-field="id" value-field="id"
where="{role: {inq: ['logistic', 'buyer']}}"
label="Buyer"> label="Buyer">
<tpl-item>{{nickname}}</tpl-item>
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>

View File

@ -15,15 +15,12 @@
rule rule
vn-focus> vn-focus>
</vn-textfield> </vn-textfield>
<vn-autocomplete <vn-worker-autocomplete
label="Salesperson" label="Salesperson"
ng-model="$ctrl.client.salesPersonFk" ng-model="$ctrl.client.salesPersonFk"
url="Workers/activeWithInheritedRole" departments="['VT']"
search-function="{firstName: $search}" show-field="nickname">
show-field="firstName" </vn-worker-autocomplete>
where="{role: 'salesPerson'}">
<tpl-item>{{firstName}} {{lastName}}</tpl-item>
</vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete <vn-autocomplete

View File

@ -14,17 +14,12 @@
vn-one label="Name" vn-one label="Name"
ng-model="filter.name"> ng-model="filter.name">
</vn-textfield> </vn-textfield>
<vn-autocomplete <vn-worker-autocomplete
vn-one vn-one
ng-model="filter.salesPersonFk" ng-model="filter.salesPersonFk"
url="Workers/activeWithInheritedRole" departments="['VT']"
search-function="{firstName: $search}"
show-field="firstName"
value-field="id"
where="{role: 'salesPerson'}"
label="Salesperson"> label="Salesperson">
<tpl-item>{{firstName}} {{name}}</tpl-item> </vn-worker-autocomplete>
</vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-textfield <vn-textfield

View File

@ -54,11 +54,10 @@
vn-id="salesPerson" vn-id="salesPerson"
disabled="false" disabled="false"
ng-model="$ctrl.filter.salesPersonFk" ng-model="$ctrl.filter.salesPersonFk"
url="Workers/activeWithRole" url="TicketRequests/getItemTypeWorker"
show-field="nickname" show-field="nickname"
search-function="{firstName: $search}" search-function="{firstName: $search}"
value-field="id" value-field="id"
where="{role: {inq: ['logistic', 'buyer']}}"
label="Buyer" label="Buyer"
on-change="$ctrl.addFilters()"> on-change="$ctrl.addFilters()">
</vn-autocomplete> </vn-autocomplete>

View File

@ -28,11 +28,15 @@ module.exports = Self => {
Object.assign(myOptions, options); Object.assign(myOptions, options);
const stmt = new ParameterizedSQL(` const stmt = new ParameterizedSQL(`
SELECT iss.created, SELECT
iss.id,
iss.created,
iss.saleFk, iss.saleFk,
iss.quantity, iss.quantity,
iss.userFk, iss.userFk,
ish.id itemShelvingFk,
ish.shelvingFk, ish.shelvingFk,
s.parkingFk,
p.code, p.code,
u.name u.name
FROM itemShelvingSale iss FROM itemShelvingSale iss

View File

@ -41,11 +41,6 @@
"type": "belongsTo", "type": "belongsTo",
"model": "VnUser", "model": "VnUser",
"foreignKey": "userFk" "foreignKey": "userFk"
},
"shelving": {
"type": "belongsTo",
"model": "Shelving",
"foreignKey": "shelvingFk"
} }
} }
} }

View File

@ -54,11 +54,10 @@
vn-id="buyer" vn-id="buyer"
disabled="false" disabled="false"
ng-model="$ctrl.filter.buyerFk" ng-model="$ctrl.filter.buyerFk"
url="Workers/activeWithRole" url="TicketRequests/getItemTypeWorker"
show-field="nickname" show-field="nickname"
search-function="{firstName: $search}" search-function="{firstName: $search}"
value-field="id" value-field="id"
where="{role: {inq: ['logistic', 'buyer']}}"
label="Buyer" label="Buyer"
on-change="$ctrl.addFilters()"> on-change="$ctrl.addFilters()">
</vn-autocomplete> </vn-autocomplete>

View File

@ -44,8 +44,7 @@ class Controller extends Section {
{ {
field: 'buyerFk', field: 'buyerFk',
autocomplete: { autocomplete: {
url: 'Workers/activeWithRole', url: 'TicketRequests/getItemTypeWorker',
where: `{role: {inq: ['logistic', 'buyer']}}`,
searchFunction: '{firstName: $search}', searchFunction: '{firstName: $search}',
showField: 'nickname', showField: 'nickname',
valueField: 'id', valueField: 'id',

View File

@ -22,12 +22,11 @@
<vn-autocomplete <vn-autocomplete
vn-one vn-one
ng-model="filter.attenderFk" ng-model="filter.attenderFk"
url="Workers/activeWithRole" url="TicketRequests/getItemTypeWorker"
search-function="{firstName: $search}" search-function="{firstName: $search}"
show-field="nickname"
value-field="id" value-field="id"
where="{role: {inq: ['logistic', 'buyer']}}" label="Atender">
label="Buyer">
<tpl-item>{{nickname}}</tpl-item>
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal class="vn-px-lg"> <vn-horizontal class="vn-px-lg">
@ -46,18 +45,13 @@
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal class="vn-px-lg"> <vn-horizontal class="vn-px-lg">
<vn-autocomplete <vn-worker-autocomplete
vn-one vn-one
ng-model="filter.requesterFk" ng-model="filter.requesterFk"
url="Workers/activeWithInheritedRole" departments="['VT']"
search-function="{firstName: $search}" label="Salesperson">
value-field="id" </vn-worker-autocomplete>
where="{role: 'salesPerson'}"
label="Comercial">
<tpl-item>{{firstName}} {{lastName}}</tpl-item>
</vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<section class="vn-px-md"> <section class="vn-px-md">
<vn-horizontal class="manifold-panel vn-pa-md"> <vn-horizontal class="manifold-panel vn-pa-md">
<vn-date-picker <vn-date-picker

View File

@ -43,16 +43,12 @@
label="Nickname" label="Nickname"
ng-model="filter.nickname"> ng-model="filter.nickname">
</vn-textfield> </vn-textfield>
<vn-autocomplete <vn-worker-autocomplete
vn-one vn-one
ng-model="filter.salesPersonFk" ng-model="filter.salesPersonFk"
url="Workers/activeWithInheritedRole" departments="['VT']"
search-function="{firstName: $search}"
value-field="id"
where="{role: 'employee'}"
label="Sales person"> label="Sales person">
<tpl-item>{{firstName}} {{name}}</tpl-item> </vn-worker-autocomplete>
</vn-autocomplete>
<vn-textfield <vn-textfield
vn-one vn-one
label="Invoice" label="Invoice"

View File

@ -25,10 +25,6 @@ class Controller extends SearchPanel {
this.filter.values.push({}); this.filter.values.push({});
setTimeout(() => this.parentPopover.relocate()); setTimeout(() => this.parentPopover.relocate());
} }
changeTag() {
}
} }
ngModule.vnComponent('vnOrderCatalogSearchPanel', { ngModule.vnComponent('vnOrderCatalogSearchPanel', {

View File

@ -25,16 +25,13 @@
show-field="name" show-field="name"
value-field="id"> value-field="id">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete <vn-worker-autocomplete
vn-one vn-one
ng-model="filter.workerFk" ng-model="filter.workerFk"
url="Workers/activeWithInheritedRole" departments="['VT']"
search-function="{firstName: $search}"
show-field="nickname" show-field="nickname"
value-field="id"
where="{role: 'salesPerson'}"
label="Sales person"> label="Sales person">
</vn-autocomplete> </vn-worker-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-date-picker <vn-date-picker

View File

@ -95,7 +95,7 @@ module.exports = Self => {
t.id ticketFk, t.id ticketFk,
co.country, co.country,
t.clientFk, t.clientFk,
sub.id hasCmrDms, IF(sub.id, TRUE, FALSE) hasCmrDms,
DATE(t.shipped) shipped DATE(t.shipped) shipped
FROM ticket t FROM ticket t
JOIN ticketState ts ON ts.ticketFk = t.id JOIN ticketState ts ON ts.ticketFk = t.id

View File

@ -24,7 +24,14 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const route = await Self.app.models.Route.findById(id, null, myOptions); const route = await Self.app.models.Route.findById(id, {
include: {
relation: 'agencyMode',
scope: {
fields: ['name']
}
}
}, myOptions);
const zoneAgencyModes = await Self.app.models.ZoneAgencyMode.find({ const zoneAgencyModes = await Self.app.models.ZoneAgencyMode.find({
where: { where: {
@ -35,11 +42,13 @@ module.exports = Self => {
const zoneIds = []; const zoneIds = [];
for (let zoneAgencyMode of zoneAgencyModes) for (let zoneAgencyMode of zoneAgencyModes)
zoneIds.push(zoneAgencyMode.zoneFk); zoneIds.push(zoneAgencyMode.zoneFk);
const minDate = new Date(route.created); const minDate = new Date(route.created);
minDate.setHours(0, 0, 0, 0); minDate.setHours(0, 0, 0, 0);
const maxDate = new Date(route.created); const maxDate = new Date(route.created);
maxDate.setHours(23, 59, 59, 59); maxDate.setHours(23, 59, 59, 59);
let tickets = await Self.app.models.Ticket.find({ let tickets = await Self.app.models.Ticket.find({
where: { where: {
zoneFk: {inq: zoneIds}, zoneFk: {inq: zoneIds},
@ -80,6 +89,12 @@ module.exports = Self => {
] ]
}, myOptions); }, myOptions);
return tickets; return tickets.map(ticket => {
const simpleTicket = ticket.toJSON();
return {
...simpleTicket,
agencyName: route.agencyMode().name
};
});
}; };
}; };

View File

@ -8,20 +8,11 @@
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md"> <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-autocomplete <vn-worker-autocomplete
ng-model="$ctrl.route.workerFk" ng-model="$ctrl.route.workerFk"
url="Workers/activeWithInheritedRole"
show-field="nickname" show-field="nickname"
search-function="{firstName: $search}"
value-field="id"
where="{role: 'employee'}"
label="Worker"
vn-name="worker"> vn-name="worker">
<tpl-item> </vn-worker-autocomplete>
<div>{{::nickname}}</div>
<div class="text-secondary text-caption">{{::name}}</div>
</tpl-item>
</vn-autocomplete>
<vn-autocomplete <vn-autocomplete
label="Vehicle" label="Vehicle"
ng-model="$ctrl.route.vehicleFk" ng-model="$ctrl.route.vehicleFk"

View File

@ -8,14 +8,10 @@
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md"> <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-autocomplete <vn-worker-autocomplete
label="Worker"
ng-model="$ctrl.route.workerFk" ng-model="$ctrl.route.workerFk"
url="Workers/activeWithInheritedRole" show-field="nickname">
show-field="nickname" </vn-worker-autocomplete>
search-function="{firstName: $search}"
where="{role: 'employee'}">
</vn-autocomplete>
<vn-date-picker <vn-date-picker
label="Created" label="Created"
ng-model="$ctrl.route.created"> ng-model="$ctrl.route.created">

View File

@ -15,16 +15,11 @@
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal class="vn-px-lg"> <vn-horizontal class="vn-px-lg">
<vn-autocomplete <vn-worker-autocomplete
vn-one vn-one
ng-model="filter.workerFk" ng-model="filter.workerFk"
url="Workers/activeWithInheritedRole" show-field="nickname">
show-field="nickname" </vn-worker-autocomplete>
search-function="{firstName: $search}"
value-field="id"
where="{role: 'employee'}"
label="Worker">
</vn-autocomplete>
<vn-autocomplete <vn-autocomplete
vn-one vn-one
label="Agency" label="Agency"

View File

@ -62,7 +62,7 @@
<vn-icon-button <vn-icon-button
icon="link_off" icon="link_off"
class="pointer" class="pointer"
title="{{'Unlink zone' | translate: {zoneName: ticket.zone.name, agencyName: ticket.agencyMode.name} }}" title="{{'Unlink zone' | translate: {zoneName: ticket.zone.name, agencyName: ticket.agencyName} }}"
ng-click="unlinkZoneConfirmation.show(ticket)"> ng-click="unlinkZoneConfirmation.show(ticket)">
</vn-icon-button> </vn-icon-button>
</vn-td> </vn-td>

View File

@ -58,7 +58,7 @@
<vn-th field="street" expand>Street</vn-th> <vn-th field="street" expand>Street</vn-th>
<vn-th field="city">City</vn-th> <vn-th field="city">City</vn-th>
<vn-th field="postalCode" translate-attr="{title: 'Postcode'}" shrink>PC</vn-th> <vn-th field="postalCode" translate-attr="{title: 'Postcode'}" shrink>PC</vn-th>
<vn-th field="clientFk" expand>Client</vn-th> <vn-th field="nickname" expand>Client</vn-th>
<vn-th field="warehouse" expand>Warehouse</vn-th> <vn-th field="warehouse" expand>Warehouse</vn-th>
<vn-th field="packages" shrink>Packages</vn-th> <vn-th field="packages" shrink>Packages</vn-th>
<vn-th field="volume" shrink></vn-th> <vn-th field="volume" shrink></vn-th>

View File

@ -7,18 +7,18 @@ module.exports = Self => {
}); });
async function ibanValidation(err, done) { async function ibanValidation(err, done) {
const supplier = await Self.app.models.Supplier.findById(this.supplierFk); if (!this.bankEntityFk)
return done();
const bankEntity = await Self.app.models.BankEntity.findById(this.bankEntityFk);
const filter = { const filter = {
fields: ['code'], fields: ['code'],
where: {id: supplier.countryFk} where: {id: bankEntity.countryFk}
}; };
const country = await Self.app.models.Country.findOne(filter); const country = await Self.app.models.Country.findOne(filter);
const code = country ? country.code.toLowerCase() : null;
if (code != 'es')
return done();
if (!validateIban(this.iban)) if (!validateIban(this.iban, country?.code))
err(); err();
done(); done();
} }

View File

@ -142,12 +142,12 @@ module.exports = Self => {
const changes = ctx.data || ctx.instance; const changes = ctx.data || ctx.instance;
const orgData = ctx.currentInstance; const orgData = ctx.currentInstance;
const socialName = changes.name || orgData.name; const name = changes.name || orgData.name;
const hasChanges = orgData && changes; const hasChanges = orgData && changes;
const socialNameChanged = hasChanges const nameChanged = hasChanges
&& orgData.socialName != socialName; && orgData.name != name;
if ((socialNameChanged) && !isAlpha(socialName)) if ((nameChanged) && !isAlpha(name))
throw new UserError('The social name has an invalid format'); throw new UserError('The social name has an invalid format');
}); });
}; };

View File

@ -15,17 +15,13 @@
rule rule
vn-focus> vn-focus>
</vn-textfield> </vn-textfield>
<vn-autocomplete <vn-worker-autocomplete
vn-one vn-one
ng-model="$ctrl.supplier.workerFk" ng-model="$ctrl.supplier.workerFk"
url="Workers/activeWithInheritedRole"
search-function="{firstName: $search}"
show-field="nickname" show-field="nickname"
value-field="id"
where="{role: 'employee'}"
label="Responsible" label="Responsible"
info="Responsible for approving invoices"> info="Responsible for approving invoices">
</vn-autocomplete> </vn-worker-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-check <vn-check

View File

@ -17,12 +17,11 @@
<vn-autocomplete <vn-autocomplete
vn-one vn-one
ng-model="filter.buyerId" ng-model="filter.buyerId"
url="Workers/activeWithRole" url="TicketRequests/getItemTypeWorker"
search-function="{firstName: $search}" search-function="{firstName: $search}"
show-field="nickname"
value-field="id" value-field="id"
where="{role: {inq: ['logistic', 'buyer']}}"
label="Buyer"> label="Buyer">
<tpl-item>{{nickname}}</tpl-item>
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>

View File

@ -0,0 +1,66 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('addExpeditionState', {
description: 'Update an expedition state',
accessType: 'WRITE',
accepts: [
{
arg: 'expeditions',
type: ['object'],
required: true,
description: 'Array of objects containing expeditionFk and stateCode'
}
],
returns: {
type: 'boolean',
root: true
},
http: {
path: `/addExpeditionState`,
verb: 'post'
}
});
Self.addExpeditionState = async(expeditions, options) => {
const models = Self.app.models;
let tx;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
let expeditionId;
try {
for (const expedition of expeditions) {
const expeditionStateType = await models.ExpeditionStateType.findOne(
{
fields: ['id'],
where: {code: expedition.stateCode}
}, myOptions
);
if (!expeditionStateType)
throw new UserError(`Invalid state code: ${expedition.stateCode}.`);
const typeFk = expeditionStateType.id;
expeditionId = expedition.expeditionFk;
await models.ExpeditionState.create({
expeditionFk: expedition.expeditionFk,
typeFk,
}, myOptions);
}
if (tx) await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
if (e instanceof UserError)
throw e;
throw new UserError(`Invalid expedition id: ${expeditionId}.`);
}
};
};

View File

@ -0,0 +1,75 @@
const models = require('vn-loopback/server/server').models;
describe('expeditionState addExpeditionState()', () => {
it('should update the expedition states', async() => {
const tx = await models.ExpeditionState.beginTransaction({});
try {
const options = {transaction: tx};
const payload = [
{
expeditionFk: 8,
stateCode: 'ON DELIVERY'
},
];
await models.ExpeditionState.addExpeditionState(payload, options);
const expeditionState = await models.ExpeditionState.findOne({
where: {id: 5}
});
expect(expeditionState.typeFk).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should throw an error an error when an stateCode does not exist', async() => {
const tx = await models.ExpeditionState.beginTransaction({});
let error;
try {
const options = {transaction: tx};
const payload = [
{
expeditionFk: 2,
stateCode: 'DUMMY'
}
];
await models.ExpeditionState.addExpeditionState(payload, options);
await tx.rollback();
} catch (e) {
error = e;
await tx.rollback();
}
expect(error.message).toContain('Invalid state code: DUMMY.');
});
it('should throw an error when expeditionFk does not exist', async() => {
const tx = await models.ExpeditionState.beginTransaction({});
let error;
try {
const options = {transaction: tx};
const payload = [
{
expeditionFk: 50,
stateCode: 'LOST'
}
];
await models.ExpeditionState.addExpeditionState(payload, options);
await tx.rollback();
} catch (e) {
error = e;
await tx.rollback();
}
expect(error.message).toContain('Invalid expedition id: 50.');
});
});

View File

@ -0,0 +1,69 @@
module.exports = Self => {
Self.remoteMethod('delete', {
description: 'Delete sale trackings and item shelving sales',
accessType: 'READ',
accepts: [
{
arg: 'saleFk',
type: 'number',
description: 'The sale id'
},
{
arg: 'stateCode',
type: 'string'
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/delete`,
verb: 'POST'
}
});
Self.delete = async(saleFk, stateCode, options) => {
const models = Self.app.models;
const myOptions = {};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
if (stateCode === 'PREPARED') {
const itemShelvingSales = await models.ItemShelvingSale.find({where: {saleFk: saleFk}}, myOptions);
for (let itemShelvingSale of itemShelvingSales)
await itemShelvingSale.destroy(myOptions);
}
const state = await models.State.findOne({
where: {code: stateCode}
}, myOptions);
const filter = {
where: {
saleFk: saleFk,
stateFk: state.id
}
};
const saleTrackings = await models.SaleTracking.find(filter, myOptions);
for (let saleTracking of saleTrackings)
await saleTracking.destroy(myOptions);
if (tx) await tx.commit();
return true;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,94 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethod('filter', {
description: 'Returns a list with the lines of a ticket and its different states of preparation',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The ticket id',
http: {source: 'path'}
},
{
arg: 'filter',
type: 'object',
description: 'Filter defining where and paginated data'
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/:id/filter`,
verb: 'GET'
}
});
Self.filter = async(id, filter, options) => {
const conn = Self.dataSource.connector;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const stmts = [];
let stmt;
stmts.push('CALL cache.last_buy_refresh(FALSE)');
stmt = new ParameterizedSQL(
`SELECT t.clientFk,
t.shipped,
s.ticketFk,
s.itemFk,
s.quantity,
s.concept,
s.id saleFk,
i.image,
i.subName,
IF(stPrevious.saleFk,TRUE,FALSE) as isPreviousSelected,
stPrevious.isChecked as isPrevious,
stPrepared.isChecked as isPrepared,
stControled.isChecked as isControled,
sgd.id saleGroupDetailFk,
(MAX(sgd.id) IS NOT NULL) AS hasSaleGroupDetail,
p.code AS parkingCode,
i.value5,
i.value6,
i.value7,
i.value8,
i.value9,
i.value10
FROM vn.ticket t
JOIN vn.sale s ON s.ticketFk = t.id
JOIN vn.item i ON i.id = s.itemFk
LEFT JOIN cache.last_buy lb ON lb.item_id = i.id AND lb.warehouse_id = t.warehouseFk
LEFT JOIN vn.state st ON TRUE
LEFT JOIN vn.saleTracking stPrevious ON stPrevious.saleFk = s.id
AND stPrevious.stateFk = (SELECT id FROM vn.state WHERE code = 'PREVIOUS_PREPARATION')
LEFT JOIN vn.saleTracking stPrepared ON stPrepared.saleFk = s.id
AND stPrepared.stateFk = (SELECT id FROM vn.state WHERE code = 'PREPARED')
LEFT JOIN vn.saleTracking stControled ON stControled.saleFk = s.id
AND stControled.stateFk = (SELECT id FROM vn.state s2 WHERE code = 'CHECKED')
LEFT JOIN vn.saleGroupDetail sgd ON sgd.saleFk = s.id
LEFT JOIN vn.saleGroup sg ON sg.id = sgd.saleGroupFk
LEFT JOIN vn.parking p ON p.id = sg.parkingFk
WHERE t.id = ?
GROUP BY s.id`, [id]);
stmts.push(stmt);
stmt.merge(Self.makeSuffix(filter));
const index = stmts.push(stmt) - 1;
const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql, myOptions);
return result[index];
};
};

View File

@ -0,0 +1,90 @@
module.exports = Self => {
Self.remoteMethodCtx('new', {
description: `Replaces the record or creates it if it doesn't exist`,
accessType: 'READ',
accepts: [
{
arg: 'saleFk',
type: 'number',
description: 'The sale id'
},
{
arg: 'isChecked',
type: 'boolean'
},
{
arg: 'quantity',
type: 'number'
},
{
arg: 'stateCode',
type: 'string'
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/new`,
verb: 'POST'
}
});
Self.new = async(ctx, saleFk, isChecked, quantity, stateCode, options) => {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const myOptions = {};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const state = await models.State.findOne({
where: {code: stateCode}
}, myOptions);
const saleTracking = await models.SaleTracking.findOne({
where: {
saleFk: saleFk,
stateFk: state.id,
workerFk: userId
}
}, myOptions);
let newSaleTracking;
if (saleTracking) {
newSaleTracking = await saleTracking.updateAttributes({
saleFk: saleFk,
stateFk: state.id,
workerFk: userId,
isChecked: isChecked,
originalQuantity: quantity,
isScanned: null
}, myOptions);
} else {
newSaleTracking = await models.SaleTracking.create({
saleFk: saleFk,
stateFk: state.id,
workerFk: userId,
isChecked: isChecked,
originalQuantity: quantity,
isScanned: null
}, myOptions);
}
if (tx) await tx.commit();
return newSaleTracking;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,30 @@
const models = require('vn-loopback/server/server').models;
describe('sale-tracking delete()', () => {
it('should delete a row of saleTracking and itemShelvingSale', async() => {
const tx = await models.SaleTracking.beginTransaction({});
try {
const options = {transaction: tx};
const itemShelvingsBefore = await models.ItemShelvingSale.find(null, options);
const saleTrackingsBefore = await models.SaleTracking.find(null, options);
const saleFk = 1;
const stateCode = 'PREPARED';
const result = await models.SaleTracking.delete(saleFk, stateCode, options);
const itemShelvingsAfter = await models.ItemShelvingSale.find(null, options);
const saleTrackingsAfter = await models.SaleTracking.find(null, options);
expect(result).toEqual(true);
expect(saleTrackingsAfter.length).toBeLessThan(saleTrackingsBefore.length);
expect(itemShelvingsAfter.length).toBeLessThan(itemShelvingsBefore.length);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,23 @@
const app = require('vn-loopback/server/server');
describe('sale-tracking filter()', () => {
it('should return 1 result filtering by ticket id', async() => {
const tx = await app.models.Claim.beginTransaction({});
try {
const options = {transaction: tx};
const id = 1;
const filter = {order: ['concept ASC', 'quantity DESC']};
const result = await app.models.SaleTracking.filter(id, filter, options);
expect(result.length).toEqual(4);
expect(result[0].ticketFk).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,6 +1,6 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
describe('ticket listSaleTracking()', () => { describe('sale-tracking listSaleTracking()', () => {
it('should call the listSaleTracking method and return the response', async() => { it('should call the listSaleTracking method and return the response', async() => {
const tx = await models.SaleTracking.beginTransaction({}); const tx = await models.SaleTracking.beginTransaction({});

View File

@ -0,0 +1,49 @@
const models = require('vn-loopback/server/server').models;
describe('sale-tracking new()', () => {
it('should update a saleTracking', async() => {
const tx = await models.SaleTracking.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 55}}};
const saleFk = 1;
const isChecked = true;
const quantity = 20;
const stateCode = 'PREPARED';
const result = await models.SaleTracking.new(ctx, saleFk, isChecked, quantity, stateCode, options);
expect(result.isChecked).toBe(true);
expect(result.originalQuantity).toBe(20);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should create a saleTracking', async() => {
const tx = await models.SaleTracking.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}};
const saleFk = 1;
const isChecked = true;
const quantity = 20;
const stateCode = 'PREPARED';
const result = await models.SaleTracking.new(ctx, saleFk, isChecked, quantity, stateCode, options);
expect(result.isChecked).toBe(true);
expect(result.originalQuantity).toBe(20);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -67,8 +67,12 @@ module.exports = Self => {
const sales = await models.Sale.find(salesFilter, myOptions); const sales = await models.Sale.find(salesFilter, myOptions);
const ticketsIds = [...new Set(sales.map(sale => sale.ticketFk))]; const ticketsIds = [...new Set(sales.map(sale => sale.ticketFk))];
let [firstTicketId] = ticketsIds;
if (!firstTicketId) {
[ticketServices] = await models.TicketService.find({where: {id: {inq: servicesIds}}}, myOptions);
firstTicketId = ticketServices.ticketFk;
}
const now = Date.vnNew(); const now = Date.vnNew();
const [firstTicketId] = ticketsIds;
const refundTicket = await createTicketRefund(firstTicketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions); const refundTicket = await createTicketRefund(firstTicketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions);

View File

@ -1,33 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('salePreparingList', {
description: 'Returns a list with the lines of a ticket and its different states of preparation',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The ticket id',
http: {source: 'path'}
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/:id/salePreparingList`,
verb: 'GET'
}
});
Self.salePreparingList = async(ctx, id, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
query = `CALL vn.salePreparingList(?)`;
const [sales] = await Self.rawSql(query, [id], myOptions);
return sales;
};
};

View File

@ -22,9 +22,13 @@ module.exports = Self => {
} }
}); });
Self.getItemTypeWorker = async filter => { Self.getItemTypeWorker = async(filter, options) => {
const myOptions = {};
const conn = Self.dataSource.connector; const conn = Self.dataSource.connector;
if (typeof options == 'object')
Object.assign(myOptions, options);
const query = const query =
`SELECT DISTINCT u.id, u.nickname `SELECT DISTINCT u.id, u.nickname
FROM itemType it FROM itemType it

View File

@ -32,6 +32,14 @@ module.exports = Self => {
}); });
Self.docuwareDownload = async id => { Self.docuwareDownload = async id => {
const models = Self.app.models;
const docuwareInfo = await models.Docuware.findOne({
where: {
code: 'deliveryNote',
action: 'find'
}
});
const filter = { const filter = {
condition: [ condition: [
{ {
@ -50,6 +58,6 @@ module.exports = Self => {
} }
] ]
}; };
return Self.app.models.Docuware.download(id, 'deliveryNote', filter); return models.Docuware.download(id, 'deliveryNote', filter);
}; };
}; };

View File

@ -1,3 +1,5 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('priceDifference', { Self.remoteMethodCtx('priceDifference', {
description: 'Returns sales with price difference if the ticket is editable', description: 'Returns sales with price difference if the ticket is editable',

View File

@ -20,6 +20,9 @@
"ExpeditionState": { "ExpeditionState": {
"dataSource": "vn" "dataSource": "vn"
}, },
"ExpeditionStateType": {
"dataSource": "vn"
},
"Packaging": { "Packaging": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -0,0 +1,22 @@
{
"name": "ExpeditionStateType",
"base": "VnModel",
"options": {
"mysql": {
"table": "expeditionStateType"
}
},
"properties": {
"id": {
"id": true,
"type": "number",
"description": "Identifier"
},
"description": {
"type": "string"
},
"code": {
"type": "string"
}
}
}

View File

@ -1,3 +1,4 @@
module.exports = function(Self) { module.exports = function(Self) {
require('../methods/expedition-state/filter')(Self); require('../methods/expedition-state/filter')(Self);
require('../methods/expedition-state/addExpeditionState')(Self);
}; };

View File

@ -1,3 +1,6 @@
module.exports = Self => { module.exports = Self => {
require('../methods/sale-tracking/filter')(Self);
require('../methods/sale-tracking/listSaleTracking')(Self); require('../methods/sale-tracking/listSaleTracking')(Self);
require('../methods/sale-tracking/new')(Self);
require('../methods/sale-tracking/delete')(Self);
}; };

View File

@ -1,6 +1,5 @@
module.exports = Self => { module.exports = Self => {
require('../methods/sale/getClaimableFromTicket')(Self); require('../methods/sale/getClaimableFromTicket')(Self);
require('../methods/sale/salePreparingList')(Self);
require('../methods/sale/reserve')(Self); require('../methods/sale/reserve')(Self);
require('../methods/sale/deleteSales')(Self); require('../methods/sale/deleteSales')(Self);
require('../methods/sale/updatePrice')(Self); require('../methods/sale/updatePrice')(Self);

View File

@ -42,4 +42,5 @@ module.exports = function(Self) {
require('../methods/ticket/expeditionPalletLabel')(Self); require('../methods/ticket/expeditionPalletLabel')(Self);
require('../methods/ticket/saveSign')(Self); require('../methods/ticket/saveSign')(Self);
require('../methods/ticket/invoiceTickets')(Self); require('../methods/ticket/invoiceTickets')(Self);
require('../methods/ticket/docuwareDownload')(Self);
}; };

View File

@ -3,10 +3,11 @@ import Section from 'salix/components/section';
import './style.scss'; import './style.scss';
class Controller extends Section { class Controller extends Section {
constructor($element, $, vnReport, vnEmail) { constructor($element, $, vnReport, vnEmail, vnFile) {
super($element, $); super($element, $);
this.vnReport = vnReport; this.vnReport = vnReport;
this.vnEmail = vnEmail; this.vnEmail = vnEmail;
this.vnFile = vnFile;
} }
get ticketId() { get ticketId() {
@ -322,7 +323,7 @@ class Controller extends Section {
} }
docuwareDownload() { docuwareDownload() {
this.vnFile.download(`api/Ticket/${this.ticket.id}/docuwareDownload`); this.vnFile.download(`api/Tickets/${this.ticket.id}/docuwareDownload`);
} }
setTicketWeight(weight) { setTicketWeight(weight) {
@ -335,7 +336,7 @@ class Controller extends Section {
} }
} }
Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail']; Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail', 'vnFile'];
ngModule.vnComponent('vnTicketDescriptorMenu', { ngModule.vnComponent('vnTicketDescriptorMenu', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -1,11 +1,19 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="sales" url="SaleTrackings/{{$ctrl.$params.id}}/filter"
filter="::$ctrl.filter"
link="{ticketFk: $ctrl.$params.id}"
limit="20" limit="20"
data="$ctrl.sales" data="$ctrl.sales"
order="concept ASC" order="concept ASC, quantity DESC"
auto-load="true">
</vn-crud-model>
<vn-crud-model
url="Shelvings"
data="shelvings"
auto-load="true">
</vn-crud-model>
<vn-crud-model
url="Parkings"
data="parkings"
auto-load="true"> auto-load="true">
</vn-crud-model> </vn-crud-model>
<vn-data-viewer model="model"> <vn-data-viewer model="model">
@ -13,7 +21,7 @@
<vn-table model="model"> <vn-table model="model">
<vn-thead> <vn-thead>
<vn-tr> <vn-tr>
<vn-th field="isChecked" center>Is checked</vn-th> <vn-th field="isChecked" center expand>Is checked</vn-th>
<vn-th field="itemFk" number>Item</vn-th> <vn-th field="itemFk" number>Item</vn-th>
<vn-th field="concept">Description</vn-th> <vn-th field="concept">Description</vn-th>
<vn-th field="quantity" number>Quantity</vn-th> <vn-th field="quantity" number>Quantity</vn-th>
@ -23,76 +31,84 @@
</vn-thead> </vn-thead>
<vn-tbody> <vn-tbody>
<vn-tr ng-repeat="sale in $ctrl.sales"> <vn-tr ng-repeat="sale in $ctrl.sales">
<vn-td center> <vn-td center expand>
<vn-chip <vn-chip
ng-class="::{ ng-class="{
'pink': sale.preparingList.hasSaleGroupDetail, 'pink': sale.hasSaleGroupDetail,
'none': !sale.preparingList.hasSaleGroupDetail, 'none': !sale.hasSaleGroupDetail,
}" }"
class="circleState" class="circleState"
vn-tooltip="has saleGroupDetail" vn-tooltip="sale group detail"
> vn-click-stop="$ctrl.clickSaleGroupDetail($index)">
</vn-chip> </vn-chip>
<vn-chip ng-class="::{ <vn-chip
'notice': sale.preparingList.isPreviousSelected, ng-class="{
'none': !sale.preparingList.isPreviousSelected, 'notice': sale.isPreviousSelected,
'none': !sale.isPreviousSelected,
}" }"
class="circleState" class="circleState"
vn-tooltip="is previousSelected"> vn-tooltip="previous selected"
vn-click-stop="$ctrl.clickPreviousSelected($index)">
</vn-chip> </vn-chip>
<vn-chip ng-class="::{ <vn-chip
'dark-notice': sale.preparingList.isPrevious, ng-class="{
'none': !sale.preparingList.isPrevious, 'dark-notice': sale.isPrevious,
'none': !sale.isPrevious,
}" }"
class="circleState" class="circleState"
vn-tooltip="is previous"> vn-tooltip="previous"
vn-click-stop="$ctrl.clickPrevious($index)">
</vn-chip> </vn-chip>
<vn-chip ng-class="::{ <vn-chip
'warning': sale.preparingList.isPrepared, ng-class="{
'none': !sale.preparingList.isPrepared, 'warning': sale.isPrepared,
'none': !sale.isPrepared,
}" }"
class="circleState" class="circleState"
vn-tooltip="is prepared"> vn-tooltip="prepared"
vn-click-stop="$ctrl.clickPrepared($index)">
</vn-chip> </vn-chip>
<vn-chip ng-class="::{ <vn-chip
'yellow': sale.preparingList.isControled, ng-class="{
'none': !sale.preparingList.isControled, 'yellow': sale.isControled,
'none': !sale.isControled,
}" }"
class="circleState" class="circleState"
vn-tooltip="is controled"> vn-tooltip="checked"
vn-click-stop="$ctrl.clickControled($index)">
</vn-chip> </vn-chip>
</vn-td> </vn-td>
<vn-td number> <vn-td number>
<span <span
ng-click="itemDescriptor.show($event, sale.item.id)" ng-click="itemDescriptor.show($event, sale.itemFk)"
class="link"> class="link">
{{::sale.item.id}} {{::sale.itemFk}}
</span> </span>
</vn-td> </vn-td>
<vn-td vn-fetched-tags> <vn-td vn-fetched-tags>
<div> <div>
<vn-one title="{{::sale.item.name}}">{{::sale.item.name}}</vn-one> <vn-one title="{{::sale.concept}}">{{::sale.concept}}</vn-one>
<vn-one ng-if="::sale.item.subName"> <vn-one ng-if="::sale.subName">
<h3 title="{{::sale.item.subName}}">{{::sale.item.subName}}</h3> <h3 title="{{::sale.subName}}">{{::sale.subName}}</h3>
</vn-one> </vn-one>
</div> </div>
<vn-fetched-tags <vn-fetched-tags
max-length="6" max-length="6"
item="::sale.item" item="::sale"
tabindex="-1"> tabindex="-1">
</vn-fetched-tags> </vn-fetched-tags>
</vn-td> </vn-td>
<vn-td number>{{::sale.quantity}}</vn-td> <vn-td number>{{::sale.quantity}}</vn-td>
<vn-td center>{{::sale.saleGroupDetail.saleGroup.parking.code | dashIfEmpty}}</vn-td> <vn-td center>{{::sale.parkingCode | dashIfEmpty}}</vn-td>
<vn-td actions> <vn-td actions>
<vn-icon-button <vn-icon-button
vn-click-stop="$ctrl.showSaleTracking(sale)" vn-click-stop="$ctrl.showSaleTracking(sale)"
vn-tooltip="Sale tracking" vn-tooltip="Log states"
icon="history"> icon="history">
</vn-icon-button> </vn-icon-button>
<vn-icon-button <vn-icon-button
vn-click-stop="$ctrl.showItemShelvingSale(sale)" vn-click-stop="$ctrl.showItemShelvingSale(sale)"
vn-tooltip="ItemShelvings sale" vn-tooltip="Shelvings sale"
icon="icon-inventory"> icon="icon-inventory">
</vn-icon-button> </vn-icon-button>
</vn-td> </vn-td>
@ -154,28 +170,35 @@
<vn-popup vn-id="itemShelvingSale"> <vn-popup vn-id="itemShelvingSale">
<vn-crud-model <vn-crud-model
vn-id="modelSaleTracking" vn-id="modelItemShelvingSale"
url="ItemShelvingSales/filter" url="ItemShelvingSales/filter"
link="{saleFk: $ctrl.saleId}" link="{saleFk: $ctrl.saleId}"
limit="20" limit="20"
data="$ctrl.itemShelvingSales" data="$ctrl.itemShelvingSales"
auto-load="true"> auto-load="true">
</vn-crud-model> </vn-crud-model>
<vn-data-viewer model="modelSaleTracking"> <vn-data-viewer model="modelItemShelvingSale" class="vn-w-lg">
<vn-card class="vn-w-lg"> <vn-table>
<vn-table model="modelSaleTracking">
<vn-thead> <vn-thead>
<vn-tr> <vn-tr>
<vn-th field="quantity" number>Quantity</vn-th> <vn-th field="quantity" number>Quantity</vn-th>
<vn-th field="workerFk">Worker</vn-th> <vn-th field="workerFk">Worker</vn-th>
<vn-th field="shelving" shrink>Shelving</vn-th> <vn-th field="shelving" expand>Shelving</vn-th>
<vn-th field="parking" shrink>Parking</vn-th> <vn-th field="parking" expand>Parking</vn-th>
<vn-th field="created" expand>Created</vn-th> <vn-th field="created" expand>Created</vn-th>
</vn-tr> </vn-tr>
</vn-thead> </vn-thead>
<vn-tbody> <vn-tbody>
<vn-tr ng-repeat="itemShelvingSale in $ctrl.itemShelvingSales"> <vn-tr ng-repeat="itemShelvingSale in $ctrl.itemShelvingSales">
<vn-td number>{{::itemShelvingSale.quantity}}</vn-td> <vn-td-editable number shrink>
<text>{{itemShelvingSale.quantity}}</text>
<field>
<vn-input-number class="dense" vn-focus
ng-model="itemShelvingSale.quantity"
on-change="$ctrl.updateQuantity(itemShelvingSale)">
</vn-input-number>
</field>
</vn-td-editable>
<vn-td expand> <vn-td expand>
<span <span
class="link" class="link"
@ -183,8 +206,24 @@
{{::itemShelvingSale.name | dashIfEmpty}} {{::itemShelvingSale.name | dashIfEmpty}}
</span> </span>
</vn-td> </vn-td>
<vn-td shrink>{{::itemShelvingSale.shelvingFk}}</vn-td> <vn-td expand>
<vn-td shrink>{{::itemShelvingSale.code}}</vn-td> <vn-autocomplete
data="shelvings"
show-field="code"
value-field="code"
ng-model="itemShelvingSale.shelvingFk"
on-change="$ctrl.updateShelving(itemShelvingSale)">
</vn-autocomplete>
</vn-td>
<vn-td expand>
<vn-autocomplete
data="parkings"
show-field="code"
value-field="id"
ng-model="itemShelvingSale.parkingFk"
on-change="$ctrl.updateParking(itemShelvingSale)">
</vn-autocomplete>
</vn-td>
<vn-td expand>{{::itemShelvingSale.created | date: 'dd/MM/yyyy HH:mm'}}</vn-td> <vn-td expand>{{::itemShelvingSale.created | date: 'dd/MM/yyyy HH:mm'}}</vn-td>
</vn-tr> </vn-tr>
</vn-tbody> </vn-tbody>

View File

@ -3,62 +3,6 @@ import Section from 'salix/components/section';
import './style.scss'; import './style.scss';
class Controller extends Section { class Controller extends Section {
constructor($element, $) {
super($element, $);
this.filter = {
include: [
{
relation: 'item'
},
{
relation: 'saleTracking',
scope: {
fields: ['isChecked']
}
},
{
relation: 'saleGroupDetail',
scope: {
fields: ['saleGroupFk'],
include: {
relation: 'saleGroup',
scope: {
fields: ['parkingFk'],
include: {
relation: 'parking',
scope: {
fields: ['code']
}
}
}
}
}
}
]
};
}
get sales() {
return this._sales;
}
set sales(value) {
this._sales = value;
if (value) {
const query = `Sales/${this.$params.id}/salePreparingList`;
this.$http.get(query)
.then(res => {
this.salePreparingList = res.data;
for (const salePreparing of this.salePreparingList) {
for (const sale of this.sales) {
if (salePreparing.saleFk == sale.id)
sale.preparingList = salePreparing;
}
}
});
}
}
showItemDescriptor(event, sale) { showItemDescriptor(event, sale) {
this.quicklinks = { this.quicklinks = {
btnThree: { btnThree: {
@ -75,20 +19,145 @@ class Controller extends Section {
} }
showSaleTracking(sale) { showSaleTracking(sale) {
this.saleId = sale.id; this.saleId = sale.saleFk;
this.$.saleTracking.show(); this.$.saleTracking.show();
} }
showItemShelvingSale(sale) { showItemShelvingSale(sale) {
this.saleId = sale.id; this.saleId = sale.saleFk;
this.$.itemShelvingSale.show(); this.$.itemShelvingSale.show();
} }
clickSaleGroupDetail(index) {
const sale = this.sales[index];
if (!sale.saleGroupDetailFk) return;
return this.$http.delete(`SaleGroupDetails/${sale.saleGroupDetailFk}`)
.then(() => {
sale.hasSaleGroupDetail = false;
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
clickPreviousSelected(index) {
const sale = this.sales[index];
if (!sale.isPreviousSelected) {
this.saleTrackingNew(sale, 'PREVIOUS_PREPARATION', false);
sale.isPreviousSelected = true;
} else {
this.saleTrackingDel(sale, 'PREVIOUS_PREPARATION');
sale.isPreviousSelected = false;
sale.isPrevious = false;
}
}
clickPrevious(index) {
const sale = this.sales[index];
if (!sale.isPrevious) {
this.saleTrackingNew(sale, 'PREVIOUS_PREPARATION', true);
sale.isPrevious = true;
sale.isPreviousSelected = true;
} else {
this.saleTrackingNew(sale, 'PREVIOUS_PREPARATION', false);
sale.isPrevious = false;
}
}
clickPrepared(index) {
const sale = this.sales[index];
if (!sale.isPrepared) {
this.saleTrackingNew(sale, 'PREPARED', true);
sale.isPrepared = true;
} else {
this.saleTrackingDel(sale, 'PREPARED');
sale.isPrepared = false;
}
}
clickControled(index) {
const sale = this.sales[index];
if (!sale.isControled) {
this.saleTrackingNew(sale, 'CHECKED', true);
sale.isControled = true;
} else {
this.saleTrackingDel(sale, 'CHECKED');
sale.isControled = false;
}
}
saleTrackingNew(sale, stateCode, isChecked) {
const params = {
saleFk: sale.saleFk,
isChecked: isChecked,
quantity: sale.quantity,
stateCode: stateCode
};
this.$http.post(`SaleTrackings/new`, params).then(() => {
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
saleTrackingDel(sale, stateCode) {
const params = {
saleFk: sale.saleFk,
stateCode: stateCode
};
this.$http.post(`SaleTrackings/delete`, params).then(() => {
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
updateQuantity(itemShelvingSale) {
const params = {
quantity: itemShelvingSale.quantity
};
this.$http.patch(`ItemShelvingSales/${itemShelvingSale.id}`, params)
.then(() => {
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
async updateShelving(itemShelvingSale) {
const params = {
shelvingFk: itemShelvingSale.shelvingFk
};
const res = await this.$http.patch(`ItemShelvings/${itemShelvingSale.itemShelvingFk}`, params);
const filter = {
fields: ['parkingFk'],
where: {
code: res.data.shelvingFk
}
};
this.$http.get(`Shelvings/findOne`, {filter})
.then(res => {
itemShelvingSale.parkingFk = res.data.parkingFk;
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
async updateParking(itemShelvingSale) {
const filter = {
fields: ['id'],
where: {
code: itemShelvingSale.shelvingFk
}
};
const res = await this.$http.get(`Shelvings/findOne`, {filter});
const params = {
parkingFk: itemShelvingSale.parkingFk
};
this.$http.patch(`Shelvings/${res.data.id}`, params)
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')));
}
} }
ngModule.vnComponent('vnTicketSaleTracking', { ngModule.vnComponent('vnTicketSaleTracking', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller, controller: Controller,
bindings: { bindings: {
ticket: '<' ticket: '<',
model: '<?'
} }
}); });

View File

@ -1,6 +1,7 @@
ItemShelvings sale: Carros línea Shelvings sale: Carros línea
has saleGroupDetail: tiene detalle grupo lineas Log states: Historial estados
is previousSelected: es previa seleccionada sale group detail: detalle grupo lineas
is previous: es previa previous selected: previa seleccionado
is prepared: esta preparado previous: previa
is controled: esta controlado prepared: preparado
checked: revisado

View File

@ -1,14 +1,5 @@
@import "variables"; @import "variables";
vn-sale-tracking {
.chip {
display: inline-block;
min-width: 15px;
min-height: 25px;
}
}
.circleState { .circleState {
display: inline-block; display: inline-block;
justify-content: center; justify-content: center;

View File

@ -62,16 +62,12 @@
label="Nickname" label="Nickname"
ng-model="filter.nickname"> ng-model="filter.nickname">
</vn-textfield> </vn-textfield>
<vn-autocomplete <vn-worker-autocomplete
vn-one vn-one
ng-model="filter.salesPersonFk" ng-model="filter.salesPersonFk"
url="Workers/activeWithInheritedRole" departments="['VT']"
search-function="{firstName: $search}"
value-field="id"
where="{role: 'employee'}"
label="Sales person"> label="Sales person">
<tpl-item>{{firstName}} {{name}}</tpl-item> </vn-worker-autocomplete>
</vn-autocomplete>
<vn-textfield <vn-textfield
vn-one vn-one
label="Invoice" label="Invoice"

View File

@ -19,17 +19,10 @@
label="State" label="State"
vn-focus> vn-focus>
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete <vn-worker-autocomplete
vn-one vn-one
url="Workers/activeWithInheritedRole" ng-model="$ctrl.workerFk">
ng-if="$ctrl.isPickerDesignedState" </vn-worker-autocomplete>
ng-model="$ctrl.workerFk"
show-field="nickname"
search-function="{firstName: $search}"
value-field="id"
where="{role: 'employee'}"
label="Worker">
</vn-autocomplete>
</vn-horizontal> </vn-horizontal>
</vn-card> </vn-card>
<vn-button-bar> <vn-button-bar>

View File

@ -121,15 +121,18 @@ module.exports = Self => {
`, [started, ended]); `, [started, ended]);
stmts.push(stmt); stmts.push(stmt);
stmt = new ParameterizedSQL(`INSERT INTO mail (receiver, subject, body) stmt = new ParameterizedSQL(`
INSERT INTO mail (receiver, subject, body)
SELECT CONCAT(u.name, '@verdnatura.es'), SELECT CONCAT(u.name, '@verdnatura.es'),
CONCAT('Error registro de horas semana ', ?, ' año ', ?) , CONCAT('Error registro de horas semana ', ?, ' año ', ?) ,
CONCAT('No se ha podido enviar el registro de horas al empleado/s: ', GROUP_CONCAT(DISTINCT CONCAT('<br>', w.id, ' ', w.firstName, ' ', w.lastName))) CONCAT('No se ha podido enviar el registro de horas al empleado/s: ',
GROUP_CONCAT(DISTINCT CONCAT('<br>', w.id, ' ', w.firstName, ' ', w.lastName)))
FROM tmp.timeControlError tce FROM tmp.timeControlError tce
JOIN vn.workerTimeControl wtc ON wtc.id = tce.id JOIN vn.workerTimeControl wtc ON wtc.id = tce.id
JOIN worker w ON w.id = wtc.userFK JOIN worker w ON w.id = wtc.userFK
JOIN account.user u ON u.id = w.bossFk JOIN account.user u ON u.id = w.bossFk
GROUP BY w.bossFk`, [args.week, args.year]); GROUP BY w.bossFk
`, [args.week, args.year]);
stmts.push(stmt); stmts.push(stmt);
stmt = new ParameterizedSQL(` stmt = new ParameterizedSQL(`
@ -177,10 +180,8 @@ module.exports = Self => {
const workerTimeControlConfig = await models.WorkerTimeControlConfig.findOne(null, myOptions); const workerTimeControlConfig = await models.WorkerTimeControlConfig.findOne(null, myOptions);
for (let day of days[index]) { for (let day of days[index]) {
if (!myOptions.transaction) {
tx = await Self.beginTransaction({}); tx = await Self.beginTransaction({});
myOptions.transaction = tx; myOptions.transaction = tx;
}
try { try {
workerFk = day.workerFk; workerFk = day.workerFk;
if (day.timeWorkDecimal > 0 && day.timeWorkedDecimal == null if (day.timeWorkDecimal > 0 && day.timeWorkedDecimal == null
@ -365,13 +366,31 @@ module.exports = Self => {
previousReceiver = day.receiver; previousReceiver = day.receiver;
} }
if (tx) { if (tx) await tx.commit();
await tx.commit();
delete myOptions.transaction;
}
} catch (e) { } catch (e) {
const stmts = [];
let stmt;
stmt = new ParameterizedSQL(`
INSERT INTO mail (receiver, subject, body)
SELECT CONCAT(u.name, '@verdnatura.es'),
CONCAT('Error registro de horas semana ', ?, ' año ', ?) ,
CONCAT('No se ha podido enviar el registro de horas al empleado: ',
w.id, ' ', w.firstName, ' ', w.lastName, ' por el motivo: ', ?)
FROM worker w
JOIN account.user u ON u.id = w.bossFk
WHERE w.id = ?
`, [args.week, args.year, e.message, day.workerFk]);
stmts.push(stmt);
const sql = ParameterizedSQL.join(stmts, ';');
await conn.executeStmt(sql);
previousWorkerFk = day.workerFk;
previousReceiver = day.receiver;
if (tx) await tx.rollback(); if (tx) await tx.rollback();
throw e;
continue;
} }
} }

View File

@ -51,7 +51,7 @@ module.exports = Self => {
const salix = await models.Url.findOne({ const salix = await models.Url.findOne({
where: { where: {
appName: 'salix', appName: 'salix',
environment: process.env.NODE_ENV || 'dev' environment: process.env.NODE_ENV || 'development'
} }
}, myOptions); }, myOptions);
@ -61,7 +61,7 @@ module.exports = Self => {
const url = `${salix.url}worker/${args.workerId}/time-control?timestamp=${timestamp}`; const url = `${salix.url}worker/${args.workerId}/time-control?timestamp=${timestamp}`;
ctx.args.url = url; ctx.args.url = url;
Self.sendTemplate(ctx, 'weekly-hour-record'); await Self.sendTemplate(ctx, 'weekly-hour-record');
return models.WorkerTimeControl.updateWorkerTimeControlMail(ctx, myOptions); return models.WorkerTimeControl.updateWorkerTimeControlMail(ctx, myOptions);
}; };

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