refs #4815 feat:DeviceLog Model #1705
19
CHANGELOG.md
19
CHANGELOG.md
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'}]
|
||||
|
|
|
@ -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, ' ').split('\n').join('<br>');
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,4 +1,5 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/osticket/osTicketReportEmail')(Self);
|
||||
require('../methods/osticket/closeTicket')(Self);
|
||||
require('../methods/osticket/sendToSupport')(Self);
|
||||
};
|
||||
|
|
|
@ -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" \
|
||||
|
|
|
@ -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.
|
||||
*/
|
|
@ -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');
|
|
@ -0,0 +1,3 @@
|
|||
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||
VALUES
|
||||
('Worker', 'search', 'READ', 'ALLOW', 'ROLE', 'employee');
|
|
@ -0,0 +1,2 @@
|
|||
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalId`)
|
||||
VALUES ('ExpeditionState','addExpeditionState','WRITE','ALLOW','ROLE','delivery');
|
|
@ -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');
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
UPDATE `vn`.`department`
|
||||
SET code='VN'
|
||||
WHERE name='VERDNATURA';
|
|
@ -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');
|
||||
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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"]',
|
||||
|
|
|
@ -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() => {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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: '<?'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
<tpl-item>
|
||||
<div>
|
||||
{{name}}
|
||||
</div>
|
||||
<div class="text-caption text-secondary">
|
||||
{{nickname}}, {{code}}
|
||||
</div>
|
||||
</tpl-item>
|
|
@ -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: '<?'
|
||||
},
|
||||
});
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -41,11 +41,6 @@
|
|||
"type": "belongsTo",
|
||||
"model": "VnUser",
|
||||
"foreignKey": "userFk"
|
||||
},
|
||||
"shelving": {
|
||||
"type": "belongsTo",
|
||||
"model": "Shelving",
|
||||
"foreignKey": "shelvingFk"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -25,10 +25,6 @@ class Controller extends SearchPanel {
|
|||
this.filter.values.push({});
|
||||
setTimeout(() => this.parentPopover.relocate());
|
||||
}
|
||||
|
||||
changeTag() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.vnComponent('vnOrderCatalogSearchPanel', {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
};
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>m³</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">
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}.`);
|
||||
}
|
||||
};
|
||||
};
|
|
@ -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.');
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -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];
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
});
|
|
@ -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({});
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
"ExpeditionState": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"ExpeditionStateType": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Packaging": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
module.exports = function(Self) {
|
||||
require('../methods/expedition-state/filter')(Self);
|
||||
require('../methods/expedition-state/addExpeditionState')(Self);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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: '<?'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue