refs #4815 feat:DeviceLog Model #1705

Merged
sergiodt merged 5 commits from 4815-deviLogModel into dev 2023-09-01 10:45:19 +00:00
115 changed files with 1419 additions and 537 deletions
Showing only changes of commit 00201197cc - Show all commits

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).
## [2334.01] - 2023-08-24
## [2338.01] - 2023-09-21
### Added
@ -14,6 +14,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### 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
@ -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
### Fixed
## [2328.01] - 2023-07-13
### Added

View File

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

View File

@ -65,7 +65,7 @@ module.exports = Self => {
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({
overrideAttachments: true,

View File

@ -16,19 +16,9 @@ describe('docuware download()', () => {
it('should return the document data', async() => {
const docuwareId = 1;
const response = {
Items: [
{
Id: docuwareId,
Fields: [
{
FieldName: 'ESTADO',
Item: 'Firmado'
}
]
}
]
};
const response = [{
'Document ID': docuwareId
}];
spyOn(docuwareModel, 'get').and.returnValue((new Promise(resolve => resolve(response))));
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');
// delete old
const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet, false);
const docuwareFile = await models.Docuware.checkFile(id, fileCabinet, false);
if (docuwareFile) {
const deleteJson = {
'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 => {
require('../methods/osticket/osTicketReportEmail')(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
COPY changes ./changes
COPY dump/fixtures.sql ./
ARG STAMP=unknown
RUN gosu mysql docker-fixtures.sh
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
*/
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...');
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.');
let dockerArgs;

File diff suppressed because one or more lines are too long

View File

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

View File

@ -7,7 +7,6 @@ SCHEMAS=(
edi
hedera
pbx
postgresql
sage
salix
stock
@ -23,7 +22,6 @@ IGNORETABLES=(
--ignore-table=bs.productionIndicators
--ignore-table=bs.VentasPorCliente
--ignore-table=bs.v_ventas
--ignore-table=postgresql.currentWorkersStats
--ignore-table=vn.accounting__
--ignore-table=vn.agencyModeZone
--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() => {
// dayBreak to 9h in postgresql.professional_category
const workerId = 1110;
const tabletId = 1;
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() => {
// dayBreak to 9h in postgresql.professional_category
const workerId = 1110;
const tabletId = 1;
let stmts = [];
@ -239,12 +237,6 @@ xdescribe('worker workerTimeControl_check()', () => {
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)
VALUES
(?,TIMESTAMPADD(HOUR,-24,NOW()),0,"in"),

View File

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

View File

@ -187,7 +187,7 @@ export default {
country: 'vn-client-create vn-autocomplete[ng-model="$ctrl.client.countryFk"]',
userName: 'vn-client-create vn-textfield[ng-model="$ctrl.client.userName"]',
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',
saveNewCityButton: '#saveCity',
saveNewPoscode: '#savePostcode',
@ -199,7 +199,7 @@ export default {
email: 'vn-client-basic-data vn-textfield[ng-model="$ctrl.client.email"]',
phone: 'vn-client-basic-data vn-textfield[ng-model="$ctrl.client.phone"]',
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"]',
transferor: 'vn-client-basic-data vn-autocomplete[ng-model="$ctrl.client.transferorFk"]',
businessType: 'vn-client-basic-data vn-autocomplete[ng-model="$ctrl.client.businessTypeFk"]',
@ -735,7 +735,7 @@ export default {
},
createStateView: {
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]`
},
claimsIndex: {
@ -781,12 +781,12 @@ export default {
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"]',
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"]',
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"]',
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"]',
saveDevelopmentButton: 'button[type=submit]'
},
@ -854,7 +854,7 @@ export default {
},
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"]',
vehicleAuto: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.vehicleFk"]',
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"]',
user: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.name"]',
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"]',
iban: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.iban"]',
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
.waitToGetProperty(selectors.createStateView.worker, 'value');
expect(result).toEqual('salesPersonNick');
expect(result).toEqual('salesPerson');
});
it(`should succesfully create a valid state`, async() => {

View File

@ -18,7 +18,7 @@
<label>
<span translate>{{$ctrl.label}}</span>
<span class="required">*</span>
</label>
</label>
</div>
<div class="icons pre">
<vn-icon
@ -50,4 +50,4 @@
on-select="$ctrl.onDropDownSelect(item)"
on-data-ready="$ctrl.onDataReady()"
on-close-start="$ctrl.onDropDownClose()">
</vn-drop-down>
</vn-drop-down>

View File

@ -17,10 +17,9 @@ import './style.scss';
* @event change Thrown when value is changed
*/
export default class Autocomplete extends Field {
constructor($element, $, $compile, $transclude) {
super($element, $, $compile);
constructor($element, $, $transclude) {
super($element, $, $transclude);
this.$transclude = $transclude;
this.$compile = $compile;
this._selection = null;
this.input = this.element.querySelector('input');
}
@ -153,7 +152,14 @@ export default class Autocomplete extends Field {
filter.include = this.include;
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),
() => this.onSelectionRequest()
);
@ -282,7 +288,7 @@ export default class Autocomplete extends Field {
this.refreshSelection();
}
}
Autocomplete.$inject = ['$element', '$scope', '$compile', '$transclude'];
Autocomplete.$inject = ['$element', '$scope', '$transclude'];
ngModule.vnComponent('vnAutocomplete', {
template: require('./index.html'),

View File

@ -13,12 +13,12 @@
class="dropdown"
ng-click="$ctrl.onContainerClick($event)">
</ul>
<div
ng-if="$ctrl.statusText"
<div
ng-if="$ctrl.statusText"
ng-click="$ctrl.onLoadMoreClick($event)"
class="status"
translate>
{{$ctrl.statusText}}
</div>
</div>
</default>
</default>

View File

@ -3,8 +3,8 @@ import FormInput from '../form-input';
import './style.scss';
export default class Field extends FormInput {
constructor($element, $scope) {
super($element, $scope);
constructor($element, $scope, $transclude) {
super($element, $scope, $transclude);
this.prefix = 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', {
template: require('./index.html'),

View File

@ -51,7 +51,9 @@ import './textarea';
import './th';
import './treeview';
import './wday-picker';
import './worker-autocomplete';
import './datalist';
import './contextmenu';
import './rating';
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);
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)
element.classList.add(shape.type);
@ -95,7 +107,7 @@ export default class Controller extends Component {
clearTimeout(shape.hideTimeout);
shape.hideTimeout = setTimeout(
() => this.hide(shape), shape.timeout || 3000);
() => this.hide(shape), shape.timeout || 5000);
this.lastShape = shape;
}

View File

@ -20,11 +20,15 @@ vn-snackbar .shape {
margin-bottom: 15px;
color: white;
padding: 12px 25px 12px 12px;
display: flex ;
flex-direction: row;
justify-content: center;
align-items: center;
& > .text {
text-align: center;
vn-chip {
vn-chip {
position: absolute;
left: -16px;
top: -16px;
@ -64,4 +68,12 @@ vn-snackbar .shape {
top: 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

@ -13,4 +13,5 @@ Finalize: Finalize
Previous: Back
Load more: Load more
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
Undo changes: Deshacer cambios
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);
}
showError(message) {
showError(message, additionalData) {
if (this.logger)
this.logger.showError(message);
this.logger.showError(message, additionalData);
}
pushLoader() {

View File

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

View File

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

View File

@ -148,7 +148,13 @@ function $exceptionHandler(vnApp, $window, $state, $injector) {
if (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);

View File

@ -1,21 +1,45 @@
const validateIban = require('../validateIban');
describe('IBAN validation', () => {
it('should return false for non-IBAN input', () => {
let isValid = validateIban('Pepinillos');
it('should return false for invalid Spanish IBAN format', () => {
let isValid = validateIban('ES00 9999 0000 9999 0000 9999', 'ES');
expect(isValid).toBeFalsy();
});
it('should return false for invalid spanish IBAN input', () => {
let isValid = validateIban('ES00 9999 0000 9999 0000 9999');
expect(isValid).toBeFalsy();
});
it('should return true for valid spanish IBAN', () => {
let isValid = validateIban('ES91 2100 0418 4502 0005 1332');
it('should return true for valid Spanish IBAN', () => {
let isValid = validateIban('ES91 2100 0418 4502 0005 1332', 'ES');
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 (typeof iban != 'string') return false;
if (countryCode?.toLowerCase() != 'es') return true;
iban = iban.toUpperCase();
iban = trim(iban);

View File

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

View File

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

View File

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

View File

@ -90,16 +90,17 @@ module.exports = Self => {
});
async function ibanNeedsValidation(err, done) {
const filter = {
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')
if (!this.bankEntityFk)
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();
done();
}

View File

@ -14,13 +14,13 @@
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
vn-one
label="Comercial Name"
vn-one
label="Comercial Name"
ng-model="$ctrl.client.name"
rule
vn-focus>
</vn-textfield>
<vn-autocomplete
<vn-autocomplete
vn-one
ng-model="$ctrl.client.businessTypeFk"
url="BusinessTypes"
@ -37,8 +37,8 @@
rule>
</vn-textfield>
<vn-textfield
vn-one
label="Email"
vn-one
label="Email"
ng-model="$ctrl.client.email"
rule
info="You can save multiple emails">
@ -59,18 +59,15 @@
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
<vn-worker-autocomplete
vn-one
ng-model="$ctrl.client.salesPersonFk"
url="Workers/activeWithInheritedRole"
departments="['VT']"
show-field="nickname"
search-function="{firstName: $search}"
value-field="id"
where="{role: 'salesPerson'}"
label="Salesperson"
vn-acl="salesAssistant">
</vn-autocomplete>
<vn-autocomplete
</vn-worker-autocomplete>
<vn-autocomplete
vn-one
ng-model="$ctrl.client.contactChannelFk"
data="contactChannels"

View File

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

View File

@ -15,18 +15,15 @@
rule
vn-focus>
</vn-textfield>
<vn-autocomplete
<vn-worker-autocomplete
label="Salesperson"
ng-model="$ctrl.client.salesPersonFk"
url="Workers/activeWithInheritedRole"
search-function="{firstName: $search}"
show-field="firstName"
where="{role: 'salesPerson'}">
<tpl-item>{{firstName}} {{lastName}}</tpl-item>
</vn-autocomplete>
departments="['VT']"
show-field="nickname">
</vn-worker-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
<vn-autocomplete
vn-id="businessTypeFk"
ng-model="$ctrl.client.businessTypeFk"
url="BusinessTypes"
@ -68,7 +65,7 @@
show-field="code"
rule>
<tpl-item>
{{code}} - {{town.name}} ({{town.province.name}},
{{code}} - {{town.name}} ({{town.province.name}},
{{town.province.country.country}})
</tpl-item>
<append>
@ -82,7 +79,7 @@
</append>
</vn-datalist>
<vn-datalist
vn-id="town"
vn-id="town"
label="City"
ng-model="$ctrl.client.city"
selection="$ctrl.town"
@ -90,7 +87,7 @@
fields="['id', 'name', 'provinceFk']"
value-field="name">
<tpl-item>
{{name}}, {{province.name}}
{{name}}, {{province.name}}
({{province.country.country}})
</tpl-item>
</vn-datalist>
@ -150,4 +147,4 @@
<vn-geo-postcode
vn-id="postcode"
on-response="$ctrl.onResponse($response)">
</vn-geo-postcode>
</vn-geo-postcode>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -32,7 +32,7 @@
<vn-thead>
<vn-tr>
<vn-th shrink>
<vn-multi-check
<vn-multi-check
model="model">
</vn-multi-check>
</vn-th>
@ -46,7 +46,7 @@
ui-sref="order.card.summary({id: {{::order.id}}})" target="_blank">
<vn-tr>
<vn-td>
<vn-check
<vn-check
ng-model="order.checked"
vn-click-stop>
</vn-check>
@ -98,7 +98,7 @@
scroll-offset="100">
</vn-pagination>
</vn-card>
<vn-worker-descriptor-popover
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<vn-client-descriptor-popover
@ -112,22 +112,22 @@
ng-click="contextmenu.filterBySelection()">
Filter by selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.excludeSelection()">
Exclude selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.removeFilter()">
Remove filter
</vn-item>
<vn-item translate
<vn-item translate
ng-click="contextmenu.removeAllFilters()">
Remove all filters
</vn-item>
<vn-item translate
ng-if="contextmenu.isActionAllowed()"
<vn-item translate
ng-if="contextmenu.isActionAllowed()"
ng-click="contextmenu.copyValue()">
Copy value
</vn-item>
@ -138,4 +138,4 @@
on-accept="$ctrl.onDelete()"
question="All the selected elements will be deleted. Are you sure you want to continue?"
message="Delete selected elements">
</vn-confirm>
</vn-confirm>

View File

@ -1,7 +1,7 @@
<div class="search-panel">
<vn-crud-model
auto-load="true"
url="Warehouses"
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form id="manifold-form" ng-submit="$ctrl.onSearch()">
@ -43,16 +43,12 @@
label="Nickname"
ng-model="filter.nickname">
</vn-textfield>
<vn-autocomplete
<vn-worker-autocomplete
vn-one
ng-model="filter.salesPersonFk"
url="Workers/activeWithInheritedRole"
search-function="{firstName: $search}"
value-field="id"
where="{role: 'employee'}"
departments="['VT']"
label="Sales person">
<tpl-item>{{firstName}} {{name}}</tpl-item>
</vn-autocomplete>
</vn-worker-autocomplete>
<vn-textfield
vn-one
label="Invoice"

View File

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

View File

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

View File

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

View File

@ -24,7 +24,14 @@ module.exports = Self => {
if (typeof options == 'object')
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({
where: {
@ -35,11 +42,13 @@ module.exports = Self => {
const zoneIds = [];
for (let zoneAgencyMode of zoneAgencyModes)
zoneIds.push(zoneAgencyMode.zoneFk);
const minDate = new Date(route.created);
minDate.setHours(0, 0, 0, 0);
const maxDate = new Date(route.created);
maxDate.setHours(23, 59, 59, 59);
let tickets = await Self.app.models.Ticket.find({
where: {
zoneFk: {inq: zoneIds},
@ -80,6 +89,12 @@ module.exports = Self => {
]
}, 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">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-autocomplete
<vn-worker-autocomplete
ng-model="$ctrl.route.workerFk"
url="Workers/activeWithInheritedRole"
show-field="nickname"
search-function="{firstName: $search}"
value-field="id"
where="{role: 'employee'}"
label="Worker"
vn-name="worker">
<tpl-item>
<div>{{::nickname}}</div>
<div class="text-secondary text-caption">{{::name}}</div>
</tpl-item>
</vn-autocomplete>
</vn-worker-autocomplete>
<vn-autocomplete
label="Vehicle"
ng-model="$ctrl.route.vehicleFk"

View File

@ -8,16 +8,12 @@
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-autocomplete
label="Worker"
<vn-worker-autocomplete
ng-model="$ctrl.route.workerFk"
url="Workers/activeWithInheritedRole"
show-field="nickname"
search-function="{firstName: $search}"
where="{role: 'employee'}">
</vn-autocomplete>
show-field="nickname">
</vn-worker-autocomplete>
<vn-date-picker
label="Created"
label="Created"
ng-model="$ctrl.route.created">
</vn-date-picker>
</vn-horizontal>

View File

@ -1,7 +1,7 @@
<div class="search-panel">
<vn-crud-model
auto-load="true"
url="Warehouses"
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form id="manifold-form" ng-submit="$ctrl.onSearch()">
@ -15,16 +15,11 @@
</vn-textfield>
</vn-horizontal>
<vn-horizontal class="vn-px-lg">
<vn-autocomplete
<vn-worker-autocomplete
vn-one
ng-model="filter.workerFk"
url="Workers/activeWithInheritedRole"
show-field="nickname"
search-function="{firstName: $search}"
value-field="id"
where="{role: 'employee'}"
label="Worker">
</vn-autocomplete>
show-field="nickname">
</vn-worker-autocomplete>
<vn-autocomplete
vn-one
label="Agency"

View File

@ -19,17 +19,17 @@
<vn-thead>
<vn-tr>
<vn-th shrink>
<vn-multi-check
<vn-multi-check
model="model">
</vn-multi-check>
</vn-th>
<vn-th number>Ticket</vn-th>
<vn-th>Client</vn-th>
<vn-th shrink>Province</vn-th>
<vn-th shrink>
<vn-th shrink>
<span translate>
Population
</span>
Population
</span>
</vn-th>
<vn-th expand>PC</vn-th>
<vn-th>Address</vn-th>
@ -39,7 +39,7 @@
<vn-tbody>
<vn-tr ng-repeat="ticket in $ctrl.possibleTickets">
<vn-td shrink>
<vn-check
<vn-check
ng-model="ticket.checked">
</vn-check>
</vn-td>
@ -62,7 +62,7 @@
<vn-icon-button
icon="link_off"
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)">
</vn-icon-button>
</vn-td>

View File

@ -58,7 +58,7 @@
<vn-th field="street" expand>Street</vn-th>
<vn-th field="city">City</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="packages" shrink>Packages</vn-th>
<vn-th field="volume" shrink></vn-th>
@ -100,9 +100,9 @@
</field>
</vn-td-editable>
<vn-td expand title="{{::ticket.street}}">{{::ticket.street}}</vn-td>
<vn-td
expand
ng-click="$ctrl.goToBuscaman(ticket)"
<vn-td
expand
ng-click="$ctrl.goToBuscaman(ticket)"
class="link"
vn-tooltip="Open buscaman"
tooltip-position="up">

View File

@ -7,18 +7,18 @@ module.exports = Self => {
});
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 = {
fields: ['code'],
where: {id: supplier.countryFk}
where: {id: bankEntity.countryFk}
};
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();
done();
}

View File

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

View File

@ -15,29 +15,25 @@
rule
vn-focus>
</vn-textfield>
<vn-autocomplete
<vn-worker-autocomplete
vn-one
ng-model="$ctrl.supplier.workerFk"
url="Workers/activeWithInheritedRole"
search-function="{firstName: $search}"
show-field="nickname"
value-field="id"
where="{role: 'employee'}"
label="Responsible"
info="Responsible for approving invoices">
</vn-autocomplete>
</vn-worker-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-check
label="Verified"
label="Verified"
ng-model="$ctrl.supplier.isSerious">
</vn-check>
<vn-check
label="Active"
label="Active"
ng-model="$ctrl.supplier.isActive">
</vn-check>
<vn-check
label="PayMethodChecked"
label="PayMethodChecked"
ng-model="$ctrl.supplier.isPayMethodChecked"
vn-acl="financial">
</vn-check>
@ -45,7 +41,7 @@
<vn-horizontal>
<vn-textarea
vn-one
label="Notes"
label="Notes"
ng-model="$ctrl.supplier.note"
rule>
</vn-textarea>
@ -63,4 +59,4 @@
ng-click="watcher.loadOriginalData()">
</vn-button>
</vn-button-bar>
</form>
</form>

View File

@ -17,12 +17,11 @@
<vn-autocomplete
vn-one
ng-model="filter.buyerId"
url="Workers/activeWithRole"
url="TicketRequests/getItemTypeWorker"
search-function="{firstName: $search}"
show-field="nickname"
value-field="id"
where="{role: {inq: ['logistic', 'buyer']}}"
label="Buyer">
<tpl-item>{{nickname}}</tpl-item>
</vn-autocomplete>
</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;
describe('ticket listSaleTracking()', () => {
describe('sale-tracking listSaleTracking()', () => {
it('should call the listSaleTracking method and return the response', async() => {
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 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 [firstTicketId] = ticketsIds;
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;
if (typeof options == 'object')
Object.assign(myOptions, options);
const query =
`SELECT DISTINCT u.id, u.nickname
FROM itemType it

View File

@ -32,6 +32,14 @@ module.exports = Self => {
});
Self.docuwareDownload = async id => {
const models = Self.app.models;
const docuwareInfo = await models.Docuware.findOne({
where: {
code: 'deliveryNote',
action: 'find'
}
});
const filter = {
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 => {
Self.remoteMethodCtx('priceDifference', {
description: 'Returns sales with price difference if the ticket is editable',

View File

@ -20,6 +20,9 @@
"ExpeditionState": {
"dataSource": "vn"
},
"ExpeditionStateType": {
"dataSource": "vn"
},
"Packaging": {
"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) {
require('../methods/expedition-state/filter')(Self);
require('../methods/expedition-state/addExpeditionState')(Self);
};

View File

@ -1,3 +1,6 @@
module.exports = Self => {
require('../methods/sale-tracking/filter')(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 => {
require('../methods/sale/getClaimableFromTicket')(Self);
require('../methods/sale/salePreparingList')(Self);
require('../methods/sale/reserve')(Self);
require('../methods/sale/deleteSales')(Self);
require('../methods/sale/updatePrice')(Self);

View File

@ -42,4 +42,5 @@ module.exports = function(Self) {
require('../methods/ticket/expeditionPalletLabel')(Self);
require('../methods/ticket/saveSign')(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';
class Controller extends Section {
constructor($element, $, vnReport, vnEmail) {
constructor($element, $, vnReport, vnEmail, vnFile) {
super($element, $);
this.vnReport = vnReport;
this.vnEmail = vnEmail;
this.vnFile = vnFile;
}
get ticketId() {
@ -322,7 +323,7 @@ class Controller extends Section {
}
docuwareDownload() {
this.vnFile.download(`api/Ticket/${this.ticket.id}/docuwareDownload`);
this.vnFile.download(`api/Tickets/${this.ticket.id}/docuwareDownload`);
}
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', {
template: require('./index.html'),

View File

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

View File

@ -3,62 +3,6 @@ import Section from 'salix/components/section';
import './style.scss';
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) {
this.quicklinks = {
btnThree: {
@ -75,20 +19,145 @@ class Controller extends Section {
}
showSaleTracking(sale) {
this.saleId = sale.id;
this.saleId = sale.saleFk;
this.$.saleTracking.show();
}
showItemShelvingSale(sale) {
this.saleId = sale.id;
this.saleId = sale.saleFk;
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', {
template: require('./index.html'),
controller: Controller,
bindings: {
ticket: '<'
ticket: '<',
model: '<?'
}
});

View File

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

View File

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

View File

@ -1,7 +1,7 @@
<div class="search-panel">
<vn-crud-model
auto-load="true"
url="Warehouses"
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form id="manifold-form" ng-submit="$ctrl.onSearch()">
@ -62,16 +62,12 @@
label="Nickname"
ng-model="filter.nickname">
</vn-textfield>
<vn-autocomplete
<vn-worker-autocomplete
vn-one
ng-model="filter.salesPersonFk"
url="Workers/activeWithInheritedRole"
search-function="{firstName: $search}"
value-field="id"
where="{role: 'employee'}"
departments="['VT']"
label="Sales person">
<tpl-item>{{firstName}} {{name}}</tpl-item>
</vn-autocomplete>
</vn-worker-autocomplete>
<vn-textfield
vn-one
label="Invoice"

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