Merge branch 'dev' into 6013-sendMail_fix
gitea/salix/pipeline/head There was a failure building this commit
Details
gitea/salix/pipeline/head There was a failure building this commit
Details
This commit is contained in:
commit
c7c17e6e4a
30
CHANGELOG.md
30
CHANGELOG.md
|
@ -5,7 +5,8 @@ All notable changes to this project will be documented in this file.
|
|||
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).
|
||||
|
||||
## [2330.01] - 2023-07-27
|
||||
|
||||
## [2336.01] - 2023-09-07
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -14,6 +15,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Fixed
|
||||
|
||||
|
||||
## [2334.01] - 2023-08-24
|
||||
|
||||
### Added
|
||||
- (General -> Errores) Botón para enviar cau con los datos del error
|
||||
|
||||
|
||||
## [2332.01] - 2023-08-10
|
||||
|
||||
### Added
|
||||
- (Trabajadores -> Gestión documental) Soporte para Docuware
|
||||
- (General -> Agencia) Soporte para Viaexpress
|
||||
- (Tickets -> SMS) Nueva sección en Lilium
|
||||
|
||||
### Changed
|
||||
- (General -> Tickets) Devuelve el motivo por el cual no es editable
|
||||
- (Desplegables -> Trabajadores) Mejorados
|
||||
- (General -> Clientes) Razón social y dirección en mayúsculas
|
||||
|
||||
### Fixed
|
||||
- (Clientes -> SMS) Al pasar el ratón por encima muestra el mensaje completo
|
||||
|
||||
|
||||
## [2330.01] - 2023-07-27
|
||||
|
||||
### Added
|
||||
|
@ -24,10 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Changed
|
||||
- (General -> Iconos) Añadidos nuevos iconos
|
||||
- (Clientes -> Razón social) Nuevas restricciones por pais
|
||||
|
||||
|
||||
### Fixed
|
||||
- (Clientes -> Razón social) Permite crear clientes con la misma razón social según el país
|
||||
|
||||
|
||||
## [2328.01] - 2023-07-13
|
||||
|
|
|
@ -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/>`;
|
||||
|
||||
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,
|
||||
replyTo: 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;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -47,7 +47,7 @@ module.exports = Self => {
|
|||
const user = await Self.findById(userId, {fields: ['hasGrant']}, myOptions);
|
||||
|
||||
const userToUpdate = await Self.findById(id, {
|
||||
fields: ['id', 'name', 'hasGrant', 'roleFk', 'password'],
|
||||
fields: ['id', 'name', 'hasGrant', 'roleFk', 'password', 'email'],
|
||||
include: {
|
||||
relation: 'role',
|
||||
scope: {
|
||||
|
|
|
@ -7,6 +7,11 @@ module.exports = Self => {
|
|||
type: 'string',
|
||||
description: 'The user name or email',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'app',
|
||||
type: 'string',
|
||||
description: 'The directory for mail'
|
||||
}
|
||||
],
|
||||
http: {
|
||||
|
@ -15,7 +20,7 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.recoverPassword = async function(user) {
|
||||
Self.recoverPassword = async function(user, app) {
|
||||
const models = Self.app.models;
|
||||
|
||||
const usesEmail = user.indexOf('@') !== -1;
|
||||
|
@ -29,7 +34,7 @@ module.exports = Self => {
|
|||
}
|
||||
|
||||
try {
|
||||
await Self.resetPassword({email: user, emailTemplate: 'recover-password'});
|
||||
await Self.resetPassword({email: user, emailTemplate: 'recover-password', app});
|
||||
} catch (err) {
|
||||
if (err.code === 'EMAIL_NOT_FOUND')
|
||||
return;
|
||||
|
|
|
@ -53,19 +53,13 @@ module.exports = Self => {
|
|||
return Self.validateLogin(user, password);
|
||||
};
|
||||
|
||||
Self.passExpired = async(vnUser, myOptions) => {
|
||||
Self.passExpired = async vnUser => {
|
||||
const today = Date.vnNew();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
if (vnUser.passExpired && vnUser.passExpired.getTime() <= today.getTime()) {
|
||||
const $ = Self.app.models;
|
||||
const changePasswordToken = await $.AccessToken.create({
|
||||
scopes: ['changePassword'],
|
||||
userId: vnUser.id
|
||||
}, myOptions);
|
||||
const err = new UserError('Pass expired', 'passExpired');
|
||||
changePasswordToken.twoFactor = vnUser.twoFactor ? true : false;
|
||||
err.details = {token: changePasswordToken};
|
||||
err.details = {userId: vnUser.id, twoFactor: vnUser.twoFactor ? true : false};
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/osticket/osTicketReportEmail')(Self);
|
||||
require('../methods/osticket/closeTicket')(Self);
|
||||
require('../methods/osticket/sendToSupport')(Self);
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ module.exports = function(Self) {
|
|||
Self.validatesFormatOf('email', {
|
||||
message: 'Invalid email',
|
||||
allowNull: true,
|
||||
allowBlank: true,
|
||||
allowBlank: false,
|
||||
with: /^[\w|.|-]+@[\w|-]+(\.[\w|-]+)*(,[\w|.|-]+@[\w|-]+(\.[\w|-]+)*)*$/
|
||||
});
|
||||
|
||||
|
@ -96,11 +96,21 @@ module.exports = function(Self) {
|
|||
const headers = httpRequest.headers;
|
||||
const origin = headers.origin;
|
||||
|
||||
const defaultHash = '/reset-password?access_token=$token$';
|
||||
const recoverHashes = {
|
||||
hedera: 'verificationToken=$token$'
|
||||
};
|
||||
|
||||
const app = info.options?.app;
|
||||
let recoverHash = app ? recoverHashes[app] : defaultHash;
|
||||
recoverHash = recoverHash.replace('$token$', info.accessToken.id);
|
||||
|
||||
const user = await Self.app.models.VnUser.findById(info.user.id);
|
||||
|
||||
const params = {
|
||||
recipient: info.email,
|
||||
lang: user.lang,
|
||||
url: `${origin}/#!/reset-password?access_token=${info.accessToken.id}`
|
||||
url: origin + '/#!' + recoverHash
|
||||
};
|
||||
|
||||
const options = Object.assign({}, info.options);
|
||||
|
@ -115,6 +125,14 @@ module.exports = function(Self) {
|
|||
Self.validateLogin = async function(user, password) {
|
||||
let loginInfo = Object.assign({password}, Self.userUses(user));
|
||||
token = await Self.login(loginInfo, 'user');
|
||||
|
||||
const userToken = await token.user.get();
|
||||
try {
|
||||
await Self.app.models.Account.sync(userToken.name, password);
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
}
|
||||
|
||||
return {token: token.id, ttl: token.ttl};
|
||||
};
|
||||
|
||||
|
|
|
@ -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,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');
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
DELIMITER $$
|
||||
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`client_create`(
|
||||
vFirstname VARCHAR(50),
|
||||
vSurnames VARCHAR(50),
|
||||
vFi VARCHAR(9),
|
||||
vAddress TEXT,
|
||||
vPostcode CHAR(5),
|
||||
vCity VARCHAR(25),
|
||||
vProvinceFk SMALLINT(5),
|
||||
vCompanyFk SMALLINT(5),
|
||||
vPhone VARCHAR(11),
|
||||
vEmail VARCHAR(255),
|
||||
vUserFk INT
|
||||
)
|
||||
BEGIN
|
||||
/**
|
||||
* Create new client
|
||||
*
|
||||
* @params vFirstname firstName
|
||||
* @params vSurnames surnames
|
||||
* @params vFi company code from accounting transactions
|
||||
* @params vAddress address
|
||||
* @params vPostcode postCode
|
||||
* @params vCity city
|
||||
* @params vProvinceFk province
|
||||
* @params vCompanyFk company in which he has become a client
|
||||
* @params vPhone telephone number
|
||||
* @params vEmail email address
|
||||
* @params vUserFk user id
|
||||
*/
|
||||
DECLARE vPayMethodFk INT;
|
||||
DECLARE vDueDay INT;
|
||||
DECLARE vDefaultCredit DECIMAL(10, 2);
|
||||
DECLARE vIsTaxDataChecked TINYINT(1);
|
||||
DECLARE vHasCoreVnl BOOLEAN;
|
||||
DECLARE vMandateTypeFk INT;
|
||||
|
||||
SELECT defaultPayMethodFk,
|
||||
defaultDueDay,
|
||||
defaultCredit,
|
||||
defaultIsTaxDataChecked,
|
||||
defaultHasCoreVnl,
|
||||
defaultMandateTypeFk
|
||||
INTO vPayMethodFk,
|
||||
vDueDay,
|
||||
vDefaultCredit,
|
||||
vIsTaxDataChecked,
|
||||
vHasCoreVnl,
|
||||
vMandateTypeFk
|
||||
FROM clientConfig;
|
||||
|
||||
INSERT INTO `client`
|
||||
SET id = vUserFk,
|
||||
name = CONCAT(vFirstname, ' ', vSurnames),
|
||||
street = vAddress,
|
||||
fi = TRIM(vFi),
|
||||
phone = vPhone,
|
||||
email = vEmail,
|
||||
provinceFk = vProvinceFk,
|
||||
city = vCity,
|
||||
postcode = vPostcode,
|
||||
socialName = UPPER(CONCAT(vSurnames, ' ', vFirstname)),
|
||||
payMethodFk = vPayMethodFk,
|
||||
dueDay = vDueDay,
|
||||
credit = vDefaultCredit,
|
||||
isTaxDataChecked = vIsTaxDataChecked,
|
||||
hasCoreVnl = vHasCoreVnl,
|
||||
isEqualizated = FALSE
|
||||
ON duplicate KEY UPDATE
|
||||
payMethodFk = vPayMethodFk,
|
||||
dueDay = vDueDay,
|
||||
credit = vDefaultCredit,
|
||||
isTaxDataChecked = vIsTaxDataChecked,
|
||||
hasCoreVnl = vHasCoreVnl,
|
||||
isActive = TRUE;
|
||||
|
||||
INSERT INTO mandate (clientFk, companyFk, mandateTypeFk)
|
||||
SELECT vUserFk, vCompanyFk, vMandateTypeFk
|
||||
WHERE NOT EXISTS (
|
||||
SELECT id
|
||||
FROM mandate
|
||||
WHERE clientFk = vUserFk
|
||||
AND companyFk = vCompanyFk
|
||||
AND mandateTypeFk = vMandateTypeFk
|
||||
);
|
||||
END$$
|
||||
DELIMITER ;
|
|
@ -0,0 +1,3 @@
|
|||
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||
VALUES
|
||||
('TicketSms', 'find', 'READ', 'ALLOW', 'ROLE', 'salesPerson');
|
|
@ -0,0 +1,6 @@
|
|||
UPDATE `salix`.`ACL`
|
||||
SET principalId='salesPerson'
|
||||
WHERE
|
||||
model='Ticket'
|
||||
AND property='setDeleted'
|
||||
AND accessType='WRITE';
|
|
@ -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
|
@ -360,18 +360,18 @@ INSERT INTO `vn`.`contactChannel`(`id`, `name`)
|
|||
|
||||
INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city`,`postcode`,`phone`,`mobile`,`isRelevant`,`email`,`iban`,`dueDay`,`accountingAccount`,`isEqualizated`,`provinceFk`,`hasToInvoice`,`credit`,`countryFk`,`isActive`,`gestdocFk`,`quality`,`payMethodFk`,`created`,`isToBeMailed`,`contactChannelFk`,`hasSepaVnl`,`hasCoreVnl`,`hasCoreVnh`,`riskCalculated`,`clientTypeFk`, `hasToInvoiceByAddress`,`isTaxDataChecked`,`isFreezed`,`creditInsurance`,`isCreatedAsServed`,`hasInvoiceSimplified`,`salesPersonFk`,`isVies`,`eypbc`, `businessTypeFk`)
|
||||
VALUES
|
||||
(1101, 'Bruce Wayne', '84612325V', 'Batman', 'Alfred', '1007 Mountain Drive, Gotham', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceWayne@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'),
|
||||
(1102, 'Petter Parker', '87945234L', 'Spider man', 'Aunt May', '20 Ingram Street, Queens, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'PetterParker@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'),
|
||||
(1103, 'Clark Kent', '06815934E', 'Super man', 'lois lane', '344 Clinton Street, Apartament 3-D', 'Gotham', 46460, 1111111111, 222222222, 1, 'ClarkKent@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 0, 19, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'),
|
||||
(1104, 'Tony Stark', '06089160W', 'Iron man', 'Pepper Potts', '10880 Malibu Point, 90265', 'Gotham', 46460, 1111111111, 222222222, 1, 'TonyStark@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'),
|
||||
(1105, 'Max Eisenhardt', '251628698', 'Magneto', 'Rogue', 'Unknown Whereabouts', 'Gotham', 46460, 1111111111, 222222222, 1, 'MaxEisenhardt@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 300, 8, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 18, 0, 1, 'florist'),
|
||||
(1106, 'DavidCharlesHaller', '53136686Q', 'Legion', 'Charles Xavier', 'City of New York, New York, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'DavidCharlesHaller@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 0, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 19, 0, 1, 'florist'),
|
||||
(1107, 'Hank Pym', '09854837G', 'Ant man', 'Hawk', 'Anthill, San Francisco, California', 'Gotham', 46460, 1111111111, 222222222, 1, 'HankPym@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1, 'florist'),
|
||||
(1108, 'Charles Xavier', '22641921P', 'Professor X', 'Beast', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1, 'florist'),
|
||||
(1109, 'Bruce Banner', '16104829E', 'Hulk', 'Black widow', 'Somewhere in New York', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 9, 0, 1, 'florist'),
|
||||
(1110, 'Jessica Jones', '58282869H', 'Jessica Jones', 'Luke Cage', 'NYCC 2015 Poster', 'Gotham', 46460, 1111111111, 222222222, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, NULL, 0, 1, 'florist'),
|
||||
(1111, 'Missing', NULL, 'Missing man', 'Anton', 'The space, Universe far away', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, 0, 1, 0, NULL, 1, 0, NULL, 0, 1, 'others'),
|
||||
(1112, 'Trash', NULL, 'Garbage man', 'Unknown name', 'New York city, Underground', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, 0, 1, 0, NULL, 1, 0, NULL, 0, 1, 'others');
|
||||
(1101, 'Bruce Wayne', '84612325V', 'BATMAN', 'Alfred', '1007 MOUNTAIN DRIVE, GOTHAM', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceWayne@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'),
|
||||
(1102, 'Petter Parker', '87945234L', 'SPIDER MAN', 'Aunt May', '20 INGRAM STREET, QUEENS, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'PetterParker@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'),
|
||||
(1103, 'Clark Kent', '06815934E', 'SUPER MAN', 'lois lane', '344 CLINTON STREET, APARTAMENT 3-D', 'Gotham', 46460, 1111111111, 222222222, 1, 'ClarkKent@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 0, 19, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'),
|
||||
(1104, 'Tony Stark', '06089160W', 'IRON MAN', 'Pepper Potts', '10880 MALIBU POINT, 90265', 'Gotham', 46460, 1111111111, 222222222, 1, 'TonyStark@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'),
|
||||
(1105, 'Max Eisenhardt', '251628698', 'MAGNETO', 'Rogue', 'UNKNOWN WHEREABOUTS', 'Gotham', 46460, 1111111111, 222222222, 1, 'MaxEisenhardt@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 300, 8, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 18, 0, 1, 'florist'),
|
||||
(1106, 'DavidCharlesHaller', '53136686Q', 'LEGION', 'Charles Xavier', 'CITY OF NEW YORK, NEW YORK, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'DavidCharlesHaller@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 0, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 19, 0, 1, 'florist'),
|
||||
(1107, 'Hank Pym', '09854837G', 'ANT MAN', 'Hawk', 'ANTHILL, SAN FRANCISCO, CALIFORNIA', 'Gotham', 46460, 1111111111, 222222222, 1, 'HankPym@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1, 'florist'),
|
||||
(1108, 'Charles Xavier', '22641921P', 'PROFESSOR X', 'Beast', '3800 VICTORY PKWY, CINCINNATI, OH 45207, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1, 'florist'),
|
||||
(1109, 'Bruce Banner', '16104829E', 'HULK', 'Black widow', 'SOMEWHERE IN NEW YORK', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 9, 0, 1, 'florist'),
|
||||
(1110, 'Jessica Jones', '58282869H', 'JESSICA JONES', 'Luke Cage', 'NYCC 2015 POSTER', 'Gotham', 46460, 1111111111, 222222222, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, NULL, 0, 1, 'florist'),
|
||||
(1111, 'Missing', NULL, 'MISSING MAN', 'Anton', 'THE SPACE, UNIVERSE FAR AWAY', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, 0, 1, 0, NULL, 1, 0, NULL, 0, 1, 'others'),
|
||||
(1112, 'Trash', NULL, 'GARBAGE MAN', 'Unknown name', 'NEW YORK CITY, UNDERGROUND', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, 0, 1, 0, NULL, 1, 0, NULL, 0, 1, 'others');
|
||||
|
||||
INSERT INTO `vn`.`client`(`id`, `name`, `fi`, `socialName`, `contact`, `street`, `city`, `postcode`, `isRelevant`, `email`, `iban`,`dueDay`,`accountingAccount`, `isEqualizated`, `provinceFk`, `hasToInvoice`, `credit`, `countryFk`, `isActive`, `gestdocFk`, `quality`, `payMethodFk`,`created`, `isTaxDataChecked`)
|
||||
SELECT id, name, CONCAT(RPAD(CONCAT(id,9),8,id),'A'), CONCAT(name, 'Social'), CONCAT(name, 'Contact'), CONCAT(name, 'Street'), 'GOTHAM', 46460, 1, CONCAT(name,'@mydomain.com'), NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1,NULL, 10, 5, util.VN_CURDATE(), 1
|
||||
|
@ -2955,6 +2955,6 @@ INSERT INTO `vn`.`invoiceInSerial` (`code`, `description`, `cplusTerIdNifFk`, `t
|
|||
('W', 'Vanaheim', 1, 'WORLD');
|
||||
|
||||
|
||||
INSERT INTO `hedera`.`imageConfig` (`id`, `maxSize`, `useXsendfile`, `url`)
|
||||
INSERT INTO `hedera`.`imageConfig` (`id`, `maxSize`, `useXsendfile`, `url`)
|
||||
VALUES
|
||||
(1, 0, 0, 'marvel.com');
|
||||
|
|
|
@ -65,7 +65,6 @@ TABLES=(
|
|||
sample
|
||||
state
|
||||
ticketUpdateAction
|
||||
time
|
||||
volumeConfig
|
||||
workCenter
|
||||
companyI18n
|
||||
|
|
|
@ -73,8 +73,8 @@ describe('Client create path', () => {
|
|||
|
||||
it(`should attempt to create a new user with all it's data but wrong email`, async() => {
|
||||
await page.write(selectors.createClientView.name, 'Carol Danvers');
|
||||
await page.write(selectors.createClientView.socialName, 'AVG tax');
|
||||
await page.write(selectors.createClientView.street, 'Many places');
|
||||
await page.write(selectors.createClientView.socialName, 'AVG TAX');
|
||||
await page.write(selectors.createClientView.street, 'MANY PLACES');
|
||||
await page.clearInput(selectors.createClientView.email);
|
||||
await page.write(selectors.createClientView.email, 'incorrect email format');
|
||||
await page.waitToClick(selectors.createClientView.createButton);
|
||||
|
|
|
@ -61,7 +61,7 @@ describe('Client Edit fiscalData path', () => {
|
|||
await page.clearInput(selectors.clientFiscalData.fiscalId);
|
||||
await page.write(selectors.clientFiscalData.fiscalId, 'INVALID!');
|
||||
await page.clearInput(selectors.clientFiscalData.address);
|
||||
await page.write(selectors.clientFiscalData.address, 'Somewhere edited');
|
||||
await page.write(selectors.clientFiscalData.address, 'SOMEWHERE EDITED');
|
||||
await page.autocompleteSearch(selectors.clientFiscalData.country, 'España');
|
||||
await page.autocompleteSearch(selectors.clientFiscalData.province, 'Province one');
|
||||
await page.clearInput(selectors.clientFiscalData.city);
|
||||
|
@ -190,7 +190,7 @@ describe('Client Edit fiscalData path', () => {
|
|||
const verifiedData = await page.checkboxState(selectors.clientFiscalData.verifiedDataCheckbox);
|
||||
|
||||
expect(fiscalId).toEqual('94980061C');
|
||||
expect(address).toEqual('Somewhere edited');
|
||||
expect(address).toEqual('SOMEWHERE EDITED');
|
||||
expect(postcode).toContain('46000');
|
||||
expect(sageTax).toEqual('Operaciones no sujetas');
|
||||
expect(sageTransaction).toEqual('Regularización de inversiones');
|
||||
|
|
|
@ -28,7 +28,7 @@ describe('Client lock verified data path', () => {
|
|||
it('should edit the social name', async() => {
|
||||
await page.waitForSelector(selectors.clientFiscalData.socialName);
|
||||
await page.clearInput(selectors.clientFiscalData.socialName);
|
||||
await page.write(selectors.clientFiscalData.socialName, 'Captain America Civil War');
|
||||
await page.write(selectors.clientFiscalData.socialName, 'CAPTAIN AMERICA CIVIL WAR');
|
||||
await page.waitToClick(selectors.clientFiscalData.saveButton);
|
||||
const message = await page.waitForSnackbar();
|
||||
|
||||
|
@ -39,7 +39,7 @@ describe('Client lock verified data path', () => {
|
|||
await page.reloadSection('client.card.fiscalData');
|
||||
const result = await page.waitToGetProperty(selectors.clientFiscalData.socialName, 'value');
|
||||
|
||||
expect(result).toEqual('Captain America Civil War');
|
||||
expect(result).toEqual('CAPTAIN AMERICA CIVIL WAR');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -88,7 +88,7 @@ describe('Client lock verified data path', () => {
|
|||
await page.reloadSection('client.card.fiscalData');
|
||||
const result = await page.waitToGetProperty(selectors.clientFiscalData.socialName, 'value');
|
||||
|
||||
expect(result).toEqual('Ant man and the Wasp');
|
||||
expect(result).toEqual('ANT MAN AND THE WASP');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -142,7 +142,7 @@ describe('Client lock verified data path', () => {
|
|||
await page.reloadSection('client.card.fiscalData');
|
||||
const result = await page.waitToGetProperty(selectors.clientFiscalData.socialName, 'value');
|
||||
|
||||
expect(result).toEqual('new social name edition');
|
||||
expect(result).toEqual('NEW SOCIAL NAME EDITION');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ describe('Client summary path', () => {
|
|||
it('should display fiscal address details', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.clientSummary.street, 'innerText');
|
||||
|
||||
expect(result).toContain('20 Ingram Street');
|
||||
expect(result).toContain('20 INGRAM STREET');
|
||||
});
|
||||
|
||||
it('should display some fiscal data', async() => {
|
||||
|
|
|
@ -23,7 +23,7 @@ describe('Worker create path', () => {
|
|||
await page.write(selectors.workerCreate.fi, '78457139E');
|
||||
await page.write(selectors.workerCreate.phone, '12356789');
|
||||
await page.write(selectors.workerCreate.postcode, '46680');
|
||||
await page.write(selectors.workerCreate.street, 'S/ Doomstadt');
|
||||
await page.write(selectors.workerCreate.street, 'S/ DOOMSTADT');
|
||||
await page.write(selectors.workerCreate.email, 'doctorDoom@marvel.com');
|
||||
await page.write(selectors.workerCreate.iban, 'ES9121000418450200051332');
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ describe('Ticket create path', () => {
|
|||
beforeAll(async() => {
|
||||
browser = await getBrowser();
|
||||
page = browser.page;
|
||||
await page.loginAndModule('employee', 'ticket');
|
||||
await page.loginAndModule('salesPerson', 'ticket');
|
||||
});
|
||||
|
||||
afterAll(async() => {
|
||||
|
|
|
@ -55,3 +55,4 @@ import './datalist';
|
|||
import './contextmenu';
|
||||
import './rating';
|
||||
import './smart-table';
|
||||
import './support-dialog';
|
||||
|
|
|
@ -339,8 +339,9 @@ export default class SmartTable extends Component {
|
|||
if (!header) return;
|
||||
|
||||
const tbody = this.element.querySelector('tbody');
|
||||
const columns = header.querySelectorAll('th');
|
||||
if (!tbody) return;
|
||||
|
||||
const columns = header.querySelectorAll('th');
|
||||
const hasSearchRow = tbody.querySelector('tr#searchRow');
|
||||
if (hasSearchRow) {
|
||||
if (this.$inputsScope)
|
||||
|
|
|
@ -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.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,27 @@
|
|||
import ngModule from '../../module';
|
||||
import Dialog from '../dialog';
|
||||
|
||||
export default class Controller extends Dialog {
|
||||
constructor($element, $, $transclude) {
|
||||
super($element, $, $transclude);
|
||||
}
|
||||
|
||||
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: '<?'
|
||||
}
|
||||
});
|
|
@ -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() {
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,9 +15,6 @@ export default class Controller {
|
|||
}
|
||||
|
||||
$onInit() {
|
||||
if (!this.$state.params.id)
|
||||
this.$state.go('login');
|
||||
|
||||
this.$http.get('UserPasswords/findOne')
|
||||
.then(res => {
|
||||
this.passRequirements = res.data;
|
||||
|
@ -25,7 +22,7 @@ export default class Controller {
|
|||
}
|
||||
|
||||
submit() {
|
||||
const userId = this.$state.params.userId;
|
||||
const userId = parseInt(this.$state.params.userId);
|
||||
const oldPassword = this.oldPassword;
|
||||
const newPassword = this.newPassword;
|
||||
const repeatPassword = this.repeatPassword;
|
||||
|
@ -36,18 +33,13 @@ export default class Controller {
|
|||
if (newPassword != this.repeatPassword)
|
||||
throw new UserError(`Passwords don't match`);
|
||||
|
||||
const headers = {
|
||||
Authorization: this.$state.params.id
|
||||
};
|
||||
|
||||
this.$http.patch('Accounts/change-password',
|
||||
{
|
||||
id: userId,
|
||||
userId,
|
||||
oldPassword,
|
||||
newPassword,
|
||||
code
|
||||
},
|
||||
{headers}
|
||||
}
|
||||
).then(() => {
|
||||
this.vnApp.showSuccess(this.$translate.instant('Password updated!'));
|
||||
this.$state.go('login');
|
||||
|
|
|
@ -36,7 +36,7 @@ export default class Controller {
|
|||
|
||||
const err = req.data?.error;
|
||||
if (err?.code == 'passExpired')
|
||||
this.$state.go('change-password', err.details.token);
|
||||
this.$state.go('change-password', err.details);
|
||||
|
||||
this.loading = false;
|
||||
this.password = '';
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -45,7 +45,7 @@ function config($stateProvider, $urlRouterProvider) {
|
|||
})
|
||||
.state('change-password', {
|
||||
parent: 'outLayout',
|
||||
url: '/change-password?id&userId&twoFactor',
|
||||
url: '/change-password?userId&twoFactor',
|
||||
description: 'Change password',
|
||||
template: '<vn-change-password></vn-change-password>'
|
||||
})
|
||||
|
|
|
@ -179,6 +179,9 @@
|
|||
"You can not use the same password": "You can not use the same password",
|
||||
"Valid priorities": "Valid priorities: %d",
|
||||
"Negative basis of tickets": "Negative basis of tickets: {{ticketsIds}}",
|
||||
"This ticket cannot be left empty.": "This ticket cannot be left empty. %s",
|
||||
"Social name should be uppercase": "Social name should be uppercase",
|
||||
"Street should be uppercase": "Street should be uppercase",
|
||||
"You don't have enough privileges.": "You don't have enough privileges.",
|
||||
"This ticket is locked.": "This ticket is locked.",
|
||||
"This ticket is not editable.": "This ticket is not editable.",
|
||||
|
|
|
@ -305,11 +305,15 @@
|
|||
"The renew period has not been exceeded": "El periodo de renovación no ha sido superado",
|
||||
"Valid priorities": "Prioridades válidas: %d",
|
||||
"Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}",
|
||||
"The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias",
|
||||
"You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado",
|
||||
"This ticket cannot be left empty.": "Este ticket no se puede dejar vacío. %s",
|
||||
"The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias",
|
||||
"You cannot assign/remove an alias that you are not assigned to": "No puede asignar/eliminar un alias que no tenga asignado",
|
||||
"This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado",
|
||||
"You don't have enough privileges.": "No tienes suficientes permisos.",
|
||||
"This ticket is locked.": "Este ticket está bloqueado.",
|
||||
"This ticket is not editable.": "Este ticket no es editable.",
|
||||
"The ticket doesn't exist.": "No existe el ticket."
|
||||
}
|
||||
"The ticket doesn't exist.": "No existe el ticket.",
|
||||
"Social name should be uppercase": "La razón social debe ir en mayúscula",
|
||||
"Street should be uppercase": "La dirección fiscal debe ir en mayúscula"
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('changePassword', {
|
||||
Self.remoteMethod('changePassword', {
|
||||
description: 'Changes the user password',
|
||||
accessType: 'WRITE',
|
||||
accessScopes: ['changePassword'],
|
||||
accepts: [
|
||||
{
|
||||
arg: 'userId',
|
||||
type: 'integer',
|
||||
description: 'The user id',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'oldPassword',
|
||||
type: 'string',
|
||||
description: 'The old password',
|
||||
|
@ -28,9 +31,7 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.changePassword = async function(ctx, oldPassword, newPassword, code, options) {
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
|
||||
Self.changePassword = async function(userId, oldPassword, newPassword, code, options) {
|
||||
const myOptions = {};
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const {models} = require('vn-loopback/server/server');
|
||||
|
||||
describe('account changePassword()', () => {
|
||||
const ctx = {req: {accessToken: {userId: 70}}};
|
||||
const userId = 70;
|
||||
const unauthCtx = {
|
||||
req: {
|
||||
headers: {},
|
||||
|
@ -20,7 +20,7 @@ describe('account changePassword()', () => {
|
|||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await models.Account.changePassword(ctx, 'wrongPassword', 'nightmare.9999', null, options);
|
||||
await models.Account.changePassword(userId, 'wrongPassword', 'nightmare.9999', null, options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
|
@ -37,8 +37,8 @@ describe('account changePassword()', () => {
|
|||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', null, options);
|
||||
await models.Account.changePassword(ctx, 'nightmare.9999', 'nightmare.9999', null, options);
|
||||
await models.Account.changePassword(userId, 'nightmare', 'nightmare.9999', null, options);
|
||||
await models.Account.changePassword(userId, 'nightmare.9999', 'nightmare.9999', null, options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
|
@ -54,7 +54,7 @@ describe('account changePassword()', () => {
|
|||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', null, options);
|
||||
await models.Account.changePassword(userId, 'nightmare', 'nightmare.9999', null, options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
|
@ -86,8 +86,8 @@ describe('account changePassword()', () => {
|
|||
}
|
||||
|
||||
try {
|
||||
const authCode = await models.AuthCode.findOne({where: {userFk: 70}}, options);
|
||||
await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', authCode.code, options);
|
||||
const authCode = await models.AuthCode.findOne({where: {userFk: userId}}, options);
|
||||
await models.Account.changePassword(userId, 'nightmare', 'nightmare.9999', authCode.code, options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
|
|
|
@ -7,8 +7,8 @@ describe('Client Create', () => {
|
|||
email: 'Deadpool@marvel.com',
|
||||
fi: '16195279J',
|
||||
name: 'Wade',
|
||||
socialName: 'Deadpool Marvel',
|
||||
street: 'Wall Street',
|
||||
socialName: 'DEADPOOL MARVEL',
|
||||
street: 'WALL STREET',
|
||||
city: 'New York',
|
||||
businessTypeFk: 'florist',
|
||||
provinceFk: 1
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('Client last active tickets', () => {
|
||||
it('should receive an array of last active tickets of Bruce Wayne', async() => {
|
||||
it('should receive an array of last active tickets of BRUCE WAYNE', async() => {
|
||||
const tx = await models.Client.beginTransaction({});
|
||||
|
||||
try {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('Client transactions', () => {
|
||||
it('should call transactions() method to receive a list of Web Payments from Bruce Wayne', async() => {
|
||||
it('should call transactions() method to receive a list of Web Payments from BRUCE WAYNE', async() => {
|
||||
const tx = await models.Client.beginTransaction({});
|
||||
|
||||
try {
|
||||
|
|
|
@ -36,6 +36,20 @@ module.exports = Self => {
|
|||
min: 3, max: 10
|
||||
});
|
||||
|
||||
Self.validatesFormatOf('street', {
|
||||
message: 'Street should be uppercase',
|
||||
allowNull: false,
|
||||
allowBlank: false,
|
||||
with: /^[^a-z]*$/
|
||||
});
|
||||
|
||||
Self.validatesFormatOf('socialName', {
|
||||
message: 'Social name should be uppercase',
|
||||
allowNull: false,
|
||||
allowBlank: false,
|
||||
with: /^[^a-z]*$/
|
||||
});
|
||||
|
||||
Self.validateAsync('socialName', socialNameIsUnique, {
|
||||
message: 'The company name must be unique'
|
||||
});
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
ng-model="$ctrl.client.socialName"
|
||||
info="Only letters, numbers and spaces can be used"
|
||||
required="true"
|
||||
ng-keyup="$ctrl.client.socialName = $ctrl.client.socialName.toUpperCase()"
|
||||
rule>
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
|
@ -46,6 +47,7 @@
|
|||
vn-two
|
||||
label="Street"
|
||||
ng-model="$ctrl.client.street"
|
||||
ng-keyup="$ctrl.client.street = $ctrl.client.street.toUpperCase()"
|
||||
rule>
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
auto-load="true">
|
||||
</vn-crud-model>
|
||||
<vn-data-viewer model="model">
|
||||
<vn-card class="vn-w-md">
|
||||
<vn-card class="vn-w-lg">
|
||||
<vn-table model="model" auto-load="false">
|
||||
<vn-thead>
|
||||
<vn-tr>
|
||||
|
@ -27,7 +27,7 @@
|
|||
</span>
|
||||
</vn-td>
|
||||
<vn-td number expand>{{::clientSms.sms.destination}}</vn-td>
|
||||
<vn-td>{{::clientSms.sms.message}}</vn-td>
|
||||
<vn-td expand vn-tooltip="{{::clientSms.sms.message}}">{{::clientSms.sms.message}}</vn-td>
|
||||
<vn-td>{{::clientSms.sms.status}}</vn-td>
|
||||
<vn-td shrink-datetime>{{::clientSms.sms.created | date:'dd/MM/yyyy HH:mm'}}</vn-td>
|
||||
</vn-tr>
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
label="Recovery email"
|
||||
ng-model="$ctrl.account.email"
|
||||
info="This email is used for user to regain access their account."
|
||||
rule="VnUser.name">
|
||||
rule="VnUser.email">
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
</vn-card>
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
name: invoice in
|
||||
columns:
|
||||
id: id
|
||||
serialNumber: serial number
|
||||
serial: serial
|
||||
supplierFk: supplier
|
||||
issued: issued
|
||||
supplierRef: supplierRef
|
||||
isBooked: is booked
|
||||
currencyFk: currency
|
||||
created: created
|
||||
companyFk: company
|
||||
docFk: document
|
||||
booked: booked
|
||||
operated: operated
|
||||
bookEntried: book entried
|
||||
isVatDeductible: is VAT deductible
|
||||
withholdingSageFk: withholding
|
||||
expenceFkDeductible: expence deductible
|
||||
editorFk: editor
|
|
@ -0,0 +1,20 @@
|
|||
name: factura recibida
|
||||
columns:
|
||||
id: id
|
||||
serialNumber: número de serie
|
||||
serial: serie
|
||||
supplierFk: proveedor
|
||||
issued: fecha emisión
|
||||
supplierRef: referéncia proveedor
|
||||
isBooked: facturado
|
||||
currencyFk: moneda
|
||||
created: creado
|
||||
companyFk: empresa
|
||||
docFk: documento
|
||||
booked: fecha contabilización
|
||||
operated: fecha entrega
|
||||
bookEntried: fecha asiento
|
||||
isVatDeductible: impuesto deducible
|
||||
withholdingSageFk: código de retención
|
||||
expenceFkDeductible: gasto deducible
|
||||
editorFk: editor
|
|
@ -0,0 +1,9 @@
|
|||
name: invoice in due day
|
||||
columns:
|
||||
id: id
|
||||
invoiceInFk: invoice in
|
||||
dueDated: due date
|
||||
bankFk: bank
|
||||
amount: amount
|
||||
foreignValue : foreign amount
|
||||
created: created
|
|
@ -0,0 +1,9 @@
|
|||
name: vencimientos factura recibida
|
||||
columns:
|
||||
id: id
|
||||
invoiceInFk: factura
|
||||
dueDated: fecha vto.
|
||||
bankFk: banco
|
||||
amount: importe
|
||||
foreignValue : importe divisa
|
||||
created: creado
|
|
@ -0,0 +1,12 @@
|
|||
name: invoice in tax
|
||||
columns:
|
||||
id: id
|
||||
invoiceInFk: invoice in
|
||||
taxCodeFk: tax
|
||||
taxableBase: taxable base
|
||||
expenceFk: expence
|
||||
foreignValue: foreign amount
|
||||
taxTypeSageFk: tax type
|
||||
transactionTypeSageFk: transaction type
|
||||
created: created
|
||||
editorFk: editor
|
|
@ -0,0 +1,12 @@
|
|||
name: factura recibida impuesto
|
||||
columns:
|
||||
id: id
|
||||
invoiceInFk: factura recibida
|
||||
taxCodeFk: código IVA
|
||||
taxableBase: base imponible
|
||||
expenceFk: código gasto
|
||||
foreignValue: importe divisa
|
||||
taxTypeSageFk: código impuesto
|
||||
transactionTypeSageFk: código transacción
|
||||
created: creado
|
||||
editorFk: editor
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
<vn-horizontal>
|
||||
<vn-auto>
|
||||
<section
|
||||
<vn-auto>
|
||||
<section
|
||||
class="inline-tag ellipsize"
|
||||
ng-class="::{empty: !$ctrl.item.value5}"
|
||||
ng-class="::{empty: !$ctrl.item.tag5}"
|
||||
title="{{::$ctrl.item.tag5}}: {{::$ctrl.item.value5}}">
|
||||
{{::$ctrl.item.value5}}
|
||||
</section>
|
||||
<section
|
||||
<section
|
||||
class="inline-tag ellipsize"
|
||||
ng-class="::{empty: !$ctrl.item.value6}"
|
||||
ng-class="::{empty: !$ctrl.item.tag6}"
|
||||
title="{{::$ctrl.item.tag6}}: {{::$ctrl.item.value6}}">
|
||||
{{::$ctrl.item.value6}}
|
||||
</section>
|
||||
<section
|
||||
<section
|
||||
class="inline-tag ellipsize"
|
||||
ng-class="::{empty: !$ctrl.item.value7}"
|
||||
ng-class="::{empty: !$ctrl.item.tag7}"
|
||||
title="{{::$ctrl.item.tag7}}: {{::$ctrl.item.value7}}">
|
||||
{{::$ctrl.item.value7}}
|
||||
</section>
|
||||
<section
|
||||
<section
|
||||
class="inline-tag ellipsize"
|
||||
ng-class="::{empty: !$ctrl.item.value8}"
|
||||
ng-class="::{empty: !$ctrl.item.tag8}"
|
||||
title="{{::$ctrl.item.tag8}}: {{::$ctrl.item.value8}}">
|
||||
{{::$ctrl.item.value8}}
|
||||
</section>
|
||||
<section
|
||||
<section
|
||||
class="inline-tag ellipsize"
|
||||
ng-class="::{empty: !$ctrl.item.value9}"
|
||||
ng-class="::{empty: !$ctrl.item.tag9}"
|
||||
title="{{::$ctrl.item.tag9}}: {{::$ctrl.item.value9}}">
|
||||
{{::$ctrl.item.value9}}
|
||||
</section>
|
||||
<section
|
||||
<section
|
||||
class="inline-tag ellipsize"
|
||||
ng-class="::{empty: !$ctrl.item.value10}"
|
||||
ng-class="::{empty: !$ctrl.item.tag10}"
|
||||
title="{{::$ctrl.item.tag10}}: {{::$ctrl.item.value10}}">
|
||||
{{::$ctrl.item.value10}}
|
||||
</section>
|
||||
</vn-auto>
|
||||
</vn-horizontal>
|
||||
</vn-horizontal>
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
vn-fetched-tags {
|
||||
& > vn-horizontal {
|
||||
align-items: center;
|
||||
|
||||
max-width: 210px;
|
||||
& > vn-auto {
|
||||
flex-wrap: wrap;
|
||||
|
||||
|
@ -43,19 +43,19 @@ vn-fetched-tags {
|
|||
& > .inline-tag {
|
||||
color: $color-font-secondary;
|
||||
text-align: center;
|
||||
font-size: .75rem;
|
||||
height: 12px;
|
||||
font-size: .8rem;
|
||||
height: 13px;
|
||||
padding: 1px;
|
||||
width: 64px;
|
||||
min-width: 64px;
|
||||
max-width: 64px;
|
||||
flex: 1;
|
||||
border: 1px solid $color-spacer;
|
||||
|
||||
border: 1px solid $color-font-secondary;
|
||||
|
||||
&.empty {
|
||||
border: 1px solid $color-spacer-light;
|
||||
border: 1px solid darken($color-font-secondary, 30%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('cmr', {
|
||||
description: 'Returns the cmr',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: 'The cmr id',
|
||||
http: {source: 'path'}
|
||||
}
|
||||
],
|
||||
returns: [
|
||||
{
|
||||
arg: 'body',
|
||||
type: 'file',
|
||||
root: true
|
||||
}, {
|
||||
arg: 'Content-Type',
|
||||
type: 'String',
|
||||
http: {target: 'header'}
|
||||
}, {
|
||||
arg: 'Content-Disposition',
|
||||
type: 'String',
|
||||
http: {target: 'header'}
|
||||
}
|
||||
],
|
||||
http: {
|
||||
path: '/:id/cmr',
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.cmr = (ctx, id) => Self.printReport(ctx, id, 'cmr');
|
||||
};
|
|
@ -0,0 +1,133 @@
|
|||
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
||||
const buildFilter = require('vn-loopback/util/filter').buildFilter;
|
||||
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('getExternalCmrs', {
|
||||
description: 'Returns an array of external cmrs',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'filter',
|
||||
type: 'object',
|
||||
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
|
||||
},
|
||||
{
|
||||
arg: 'cmrFk',
|
||||
type: 'integer',
|
||||
description: 'Searchs the route by id',
|
||||
},
|
||||
{
|
||||
arg: 'ticketFk',
|
||||
type: 'integer',
|
||||
description: 'The worker id',
|
||||
},
|
||||
{
|
||||
arg: 'country',
|
||||
type: 'string',
|
||||
description: 'The agencyMode id',
|
||||
},
|
||||
{
|
||||
arg: 'clientFk',
|
||||
type: 'integer',
|
||||
description: 'The vehicle id',
|
||||
},
|
||||
{
|
||||
arg: 'hasCmrDms',
|
||||
type: 'boolean',
|
||||
description: 'The vehicle id',
|
||||
},
|
||||
{
|
||||
arg: 'shipped',
|
||||
type: 'date',
|
||||
description: 'The to date filter',
|
||||
},
|
||||
],
|
||||
returns: {
|
||||
type: ['object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/getExternalCmrs`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.getExternalCmrs = async(
|
||||
filter,
|
||||
cmrFk,
|
||||
ticketFk,
|
||||
country,
|
||||
clientFk,
|
||||
hasCmrDms,
|
||||
shipped,
|
||||
options
|
||||
) => {
|
||||
const params = {
|
||||
cmrFk,
|
||||
ticketFk,
|
||||
country,
|
||||
clientFk,
|
||||
hasCmrDms,
|
||||
shipped,
|
||||
};
|
||||
const conn = Self.dataSource.connector;
|
||||
|
||||
let where = buildFilter(params, (param, value) => {return {[param]: value}});
|
||||
filter = mergeFilters(filter, {where});
|
||||
|
||||
if (!filter.where) {
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
filter.where = {'shipped': yesterday.toISOString().split('T')[0]}
|
||||
}
|
||||
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
let stmts = [];
|
||||
const stmt = new ParameterizedSQL(`
|
||||
SELECT *
|
||||
FROM (
|
||||
SELECT t.cmrFk,
|
||||
t.id ticketFk,
|
||||
co.country,
|
||||
t.clientFk,
|
||||
IF(sub.id, TRUE, FALSE) hasCmrDms,
|
||||
DATE(t.shipped) shipped
|
||||
FROM ticket t
|
||||
JOIN ticketState ts ON ts.ticketFk = t.id
|
||||
JOIN state s ON s.id = ts.stateFk
|
||||
JOIN alertLevel al ON al.id = s.alertLevel
|
||||
JOIN client c ON c.id = t.clientFk
|
||||
JOIN address a ON a.id = t.addressFk
|
||||
JOIN province p ON p.id = a.provinceFk
|
||||
JOIN country co ON co.id = p.countryFk
|
||||
JOIN agencyMode am ON am.id = t.agencyModeFk
|
||||
JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk
|
||||
JOIN warehouse w ON w.id = t.warehouseFk
|
||||
LEFT JOIN (
|
||||
SELECT td.ticketFk, d.id
|
||||
FROM ticketDms td
|
||||
JOIN dms d ON d.id = td.dmsFk
|
||||
JOIN dmsType dt ON dt.id = d.dmsTypeFk
|
||||
WHERE dt.name = 'cmr'
|
||||
) sub ON sub.ticketFk = t.id
|
||||
WHERE co.code <> 'ES'
|
||||
AND am.name <> 'ABONO'
|
||||
AND w.code = 'ALG'
|
||||
AND dm.code = 'DELIVERY'
|
||||
AND t.cmrFk
|
||||
) sub
|
||||
`);
|
||||
|
||||
stmt.merge(conn.makeSuffix(filter));
|
||||
const itemsIndex = stmts.push(stmt) - 1;
|
||||
|
||||
const sql = ParameterizedSQL.join(stmts, ';');
|
||||
const result = await conn.executeStmt(sql);
|
||||
return itemsIndex === 0 ? result : result[itemsIndex];
|
||||
};
|
||||
};
|
|
@ -14,6 +14,8 @@ module.exports = Self => {
|
|||
require('../methods/route/driverRouteEmail')(Self);
|
||||
require('../methods/route/sendSms')(Self);
|
||||
require('../methods/route/downloadZip')(Self);
|
||||
require('../methods/route/cmr')(Self);
|
||||
require('../methods/route/getExternalCmrs')(Self);
|
||||
|
||||
Self.validate('kmStart', validateDistance, {
|
||||
message: 'Distance must be lesser than 1000'
|
||||
|
@ -28,5 +30,5 @@ module.exports = Self => {
|
|||
const routeMaxKm = 1000;
|
||||
if (routeTotalKm > routeMaxKm || this.kmStart > this.kmEnd)
|
||||
err();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -46,8 +46,6 @@ class Controller extends Section {
|
|||
}
|
||||
|
||||
deleteRoadmaps() {
|
||||
console.log(this.checked);
|
||||
|
||||
for (const roadmap of this.checked) {
|
||||
this.$http.delete(`Roadmaps/${roadmap.id}`)
|
||||
.then(() => this.$.model.refresh())
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
|
@ -67,7 +67,7 @@ module.exports = function(Self) {
|
|||
throw new UserError(`This ticket is already invoiced`);
|
||||
|
||||
const priceZero = ticket.totalWithVat == 0;
|
||||
if (priceZero)
|
||||
if (ticketsIds.length == 1 && priceZero)
|
||||
throw new UserError(`A ticket with an amount of zero can't be invoiced`);
|
||||
});
|
||||
|
||||
|
|
|
@ -5,177 +5,177 @@ const config = require('vn-print/core/config');
|
|||
const storage = require('vn-print/core/storage');
|
||||
|
||||
module.exports = async function(ctx, Self, tickets, reqArgs = {}) {
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
if (tickets.length == 0) return;
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
if (tickets.length == 0) return;
|
||||
|
||||
const failedtickets = [];
|
||||
for (const ticket of tickets) {
|
||||
try {
|
||||
await Self.rawSql(`CALL vn.ticket_closeByTicket(?)`, [ticket.id], {userId});
|
||||
const failedtickets = [];
|
||||
for (const ticket of tickets) {
|
||||
try {
|
||||
await Self.rawSql(`CALL vn.ticket_closeByTicket(?)`, [ticket.id], {userId});
|
||||
|
||||
const [invoiceOut] = await Self.rawSql(`
|
||||
SELECT io.id, io.ref, io.serial, cny.code companyCode, io.issued
|
||||
FROM ticket t
|
||||
JOIN invoiceOut io ON io.ref = t.refFk
|
||||
JOIN company cny ON cny.id = io.companyFk
|
||||
WHERE t.id = ?
|
||||
`, [ticket.id]);
|
||||
const [invoiceOut] = await Self.rawSql(`
|
||||
SELECT io.id, io.ref, io.serial, cny.code companyCode, io.issued
|
||||
FROM ticket t
|
||||
JOIN invoiceOut io ON io.ref = t.refFk
|
||||
JOIN company cny ON cny.id = io.companyFk
|
||||
WHERE t.id = ?
|
||||
`, [ticket.id]);
|
||||
|
||||
const mailOptions = {
|
||||
overrideAttachments: true,
|
||||
attachments: []
|
||||
};
|
||||
const mailOptions = {
|
||||
overrideAttachments: true,
|
||||
attachments: []
|
||||
};
|
||||
|
||||
const isToBeMailed = ticket.recipient && ticket.salesPersonFk && ticket.isToBeMailed;
|
||||
const isToBeMailed = ticket.recipient && ticket.salesPersonFk && ticket.isToBeMailed;
|
||||
|
||||
if (invoiceOut) {
|
||||
const args = {
|
||||
reference: invoiceOut.ref,
|
||||
recipientId: ticket.clientFk,
|
||||
recipient: ticket.recipient,
|
||||
replyTo: ticket.salesPersonEmail
|
||||
};
|
||||
if (invoiceOut) {
|
||||
const args = {
|
||||
reference: invoiceOut.ref,
|
||||
recipientId: ticket.clientFk,
|
||||
recipient: ticket.recipient,
|
||||
replyTo: ticket.salesPersonEmail
|
||||
};
|
||||
|
||||
const invoiceReport = new Report('invoice', args);
|
||||
const stream = await invoiceReport.toPdfStream();
|
||||
const invoiceReport = new Report('invoice', args);
|
||||
const stream = await invoiceReport.toPdfStream();
|
||||
|
||||
const issued = invoiceOut.issued;
|
||||
const year = issued.getFullYear().toString();
|
||||
const month = (issued.getMonth() + 1).toString();
|
||||
const day = issued.getDate().toString();
|
||||
const issued = invoiceOut.issued;
|
||||
const year = issued.getFullYear().toString();
|
||||
const month = (issued.getMonth() + 1).toString();
|
||||
const day = issued.getDate().toString();
|
||||
|
||||
const fileName = `${year}${invoiceOut.ref}.pdf`;
|
||||
const fileName = `${year}${invoiceOut.ref}.pdf`;
|
||||
|
||||
// Store invoice
|
||||
await storage.write(stream, {
|
||||
type: 'invoice',
|
||||
path: `${year}/${month}/${day}`,
|
||||
fileName: fileName
|
||||
});
|
||||
// Store invoice
|
||||
await storage.write(stream, {
|
||||
type: 'invoice',
|
||||
path: `${year}/${month}/${day}`,
|
||||
fileName: fileName
|
||||
});
|
||||
|
||||
await Self.rawSql('UPDATE invoiceOut SET hasPdf = true WHERE id = ?', [invoiceOut.id], {userId});
|
||||
await Self.rawSql('UPDATE invoiceOut SET hasPdf = true WHERE id = ?', [invoiceOut.id], {userId});
|
||||
|
||||
if (isToBeMailed) {
|
||||
const invoiceAttachment = {
|
||||
filename: fileName,
|
||||
content: stream
|
||||
};
|
||||
if (isToBeMailed) {
|
||||
const invoiceAttachment = {
|
||||
filename: fileName,
|
||||
content: stream
|
||||
};
|
||||
|
||||
if (invoiceOut.serial == 'E' && invoiceOut.companyCode == 'VNL') {
|
||||
const exportation = new Report('exportation', args);
|
||||
const stream = await exportation.toPdfStream();
|
||||
const fileName = `CITES-${invoiceOut.ref}.pdf`;
|
||||
if (invoiceOut.serial == 'E' && invoiceOut.companyCode == 'VNL') {
|
||||
const exportation = new Report('exportation', args);
|
||||
const stream = await exportation.toPdfStream();
|
||||
const fileName = `CITES-${invoiceOut.ref}.pdf`;
|
||||
|
||||
mailOptions.attachments.push({
|
||||
filename: fileName,
|
||||
content: stream
|
||||
});
|
||||
}
|
||||
mailOptions.attachments.push({
|
||||
filename: fileName,
|
||||
content: stream
|
||||
});
|
||||
}
|
||||
|
||||
mailOptions.attachments.push(invoiceAttachment);
|
||||
mailOptions.attachments.push(invoiceAttachment);
|
||||
|
||||
const email = new Email('invoice', args);
|
||||
await email.send(mailOptions);
|
||||
}
|
||||
} else if (isToBeMailed) {
|
||||
const args = {
|
||||
id: ticket.id,
|
||||
recipientId: ticket.clientFk,
|
||||
recipient: ticket.recipient,
|
||||
replyTo: ticket.salesPersonEmail
|
||||
};
|
||||
const email = new Email('invoice', args);
|
||||
await email.send(mailOptions);
|
||||
}
|
||||
} else if (isToBeMailed) {
|
||||
const args = {
|
||||
id: ticket.id,
|
||||
recipientId: ticket.clientFk,
|
||||
recipient: ticket.recipient,
|
||||
replyTo: ticket.salesPersonEmail
|
||||
};
|
||||
|
||||
const email = new Email('delivery-note-link', args);
|
||||
await email.send();
|
||||
}
|
||||
const email = new Email('delivery-note-link', args);
|
||||
await email.send();
|
||||
}
|
||||
|
||||
// Incoterms authorization
|
||||
const [{firstOrder}] = await Self.rawSql(`
|
||||
SELECT COUNT(*) as firstOrder
|
||||
FROM ticket t
|
||||
JOIN client c ON c.id = t.clientFk
|
||||
WHERE t.clientFk = ?
|
||||
AND NOT t.isDeleted
|
||||
AND c.isVies
|
||||
`, [ticket.clientFk]);
|
||||
// Incoterms authorization
|
||||
const [{firstOrder}] = await Self.rawSql(`
|
||||
SELECT COUNT(*) as firstOrder
|
||||
FROM ticket t
|
||||
JOIN client c ON c.id = t.clientFk
|
||||
WHERE t.clientFk = ?
|
||||
AND NOT t.isDeleted
|
||||
AND c.isVies
|
||||
`, [ticket.clientFk]);
|
||||
|
||||
if (firstOrder == 1) {
|
||||
const args = {
|
||||
id: ticket.clientFk,
|
||||
companyId: ticket.companyFk,
|
||||
recipientId: ticket.clientFk,
|
||||
recipient: ticket.recipient,
|
||||
replyTo: ticket.salesPersonEmail
|
||||
};
|
||||
if (firstOrder == 1) {
|
||||
const args = {
|
||||
id: ticket.clientFk,
|
||||
companyId: ticket.companyFk,
|
||||
recipientId: ticket.clientFk,
|
||||
recipient: ticket.recipient,
|
||||
replyTo: ticket.salesPersonEmail
|
||||
};
|
||||
|
||||
const email = new Email('incoterms-authorization', args);
|
||||
await email.send();
|
||||
const email = new Email('incoterms-authorization', args);
|
||||
await email.send();
|
||||
|
||||
const [sample] = await Self.rawSql(
|
||||
`SELECT id
|
||||
FROM sample
|
||||
WHERE code = 'incoterms-authorization'
|
||||
`);
|
||||
const [sample] = await Self.rawSql(
|
||||
`SELECT id
|
||||
FROM sample
|
||||
WHERE code = 'incoterms-authorization'
|
||||
`);
|
||||
|
||||
await Self.rawSql(`
|
||||
INSERT INTO clientSample (clientFk, typeFk, companyFk) VALUES(?, ?, ?)
|
||||
`, [ticket.clientFk, sample.id, ticket.companyFk], {userId});
|
||||
}
|
||||
} catch (error) {
|
||||
// Domain not found
|
||||
if (error.responseCode == 450)
|
||||
return invalidEmail(ticket);
|
||||
await Self.rawSql(`
|
||||
INSERT INTO clientSample (clientFk, typeFk, companyFk) VALUES(?, ?, ?)
|
||||
`, [ticket.clientFk, sample.id, ticket.companyFk], {userId});
|
||||
};
|
||||
} catch (error) {
|
||||
// Domain not found
|
||||
if (error.responseCode == 450)
|
||||
return invalidEmail(ticket);
|
||||
|
||||
// Save tickets on a list of failed ids
|
||||
failedtickets.push({
|
||||
id: ticket.id,
|
||||
stacktrace: error
|
||||
});
|
||||
}
|
||||
}
|
||||
// Save tickets on a list of failed ids
|
||||
failedtickets.push({
|
||||
id: ticket.id,
|
||||
stacktrace: error
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Send email with failed tickets
|
||||
if (failedtickets.length > 0) {
|
||||
let body = 'This following tickets have failed:<br/><br/>';
|
||||
// Send email with failed tickets
|
||||
if (failedtickets.length > 0) {
|
||||
let body = 'This following tickets have failed:<br/><br/>';
|
||||
|
||||
for (const ticket of failedtickets) {
|
||||
body += `Ticket: <strong>${ticket.id}</strong>
|
||||
<br/> <strong>${ticket.stacktrace}</strong><br/><br/>`;
|
||||
}
|
||||
for (const ticket of failedtickets) {
|
||||
body += `Ticket: <strong>${ticket.id}</strong>
|
||||
<br/> <strong>${ticket.stacktrace}</strong><br/><br/>`;
|
||||
}
|
||||
|
||||
smtp.send({
|
||||
to: config.app.reportEmail,
|
||||
subject: '[API] Nightly ticket closure report',
|
||||
html: body
|
||||
});
|
||||
}
|
||||
smtp.send({
|
||||
to: config.app.reportEmail,
|
||||
subject: '[API] Nightly ticket closure report',
|
||||
html: body
|
||||
});
|
||||
}
|
||||
|
||||
async function invalidEmail(ticket) {
|
||||
await Self.rawSql(`UPDATE client SET email = NULL WHERE id = ?`, [
|
||||
ticket.clientFk
|
||||
], {userId});
|
||||
async function invalidEmail(ticket) {
|
||||
await Self.rawSql(`UPDATE client SET email = NULL WHERE id = ?`, [
|
||||
ticket.clientFk
|
||||
], {userId});
|
||||
|
||||
const oldInstance = `{"email": "${ticket.recipient}"}`;
|
||||
const newInstance = `{"email": ""}`;
|
||||
await Self.rawSql(`
|
||||
INSERT INTO clientLog (originFk, userFk, action, changedModel, oldInstance, newInstance)
|
||||
VALUES (?, NULL, 'UPDATE', 'Client', ?, ?)`, [
|
||||
ticket.clientFk,
|
||||
oldInstance,
|
||||
newInstance
|
||||
], {userId});
|
||||
const oldInstance = `{"email": "${ticket.recipient}"}`;
|
||||
const newInstance = `{"email": ""}`;
|
||||
await Self.rawSql(`
|
||||
INSERT INTO clientLog (originFk, userFk, action, changedModel, oldInstance, newInstance)
|
||||
VALUES (?, NULL, 'UPDATE', 'Client', ?, ?)`, [
|
||||
ticket.clientFk,
|
||||
oldInstance,
|
||||
newInstance
|
||||
], {userId});
|
||||
|
||||
const body = `No se ha podido enviar el albarán <strong>${ticket.id}</strong>
|
||||
al cliente <strong>${ticket.clientFk} - ${ticket.clientName}</strong>
|
||||
porque la dirección de email <strong>"${ticket.recipient}"</strong> no es correcta
|
||||
o no está disponible.<br/><br/>
|
||||
Para evitar que se repita este error, se ha eliminado la dirección de email de la ficha del cliente.
|
||||
Actualiza la dirección de email con una correcta.`;
|
||||
const body = `No se ha podido enviar el albarán <strong>${ticket.id}</strong>
|
||||
al cliente <strong>${ticket.clientFk} - ${ticket.clientName}</strong>
|
||||
porque la dirección de email <strong>"${ticket.recipient}"</strong> no es correcta
|
||||
o no está disponible.<br/><br/>
|
||||
Para evitar que se repita este error, se ha eliminado la dirección de email de la ficha del cliente.
|
||||
Actualiza la dirección de email con una correcta.`;
|
||||
|
||||
smtp.send({
|
||||
to: ticket.salesPersonEmail,
|
||||
subject: 'No se ha podido enviar el albarán',
|
||||
html: body
|
||||
});
|
||||
}
|
||||
smtp.send({
|
||||
to: ticket.salesPersonEmail,
|
||||
subject: 'No se ha podido enviar el albarán',
|
||||
html: body
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const loggable = require('vn-loopback/util/log');
|
||||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('componentUpdate', {
|
||||
|
@ -112,7 +113,6 @@ module.exports = Self => {
|
|||
}
|
||||
|
||||
try {
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
const models = Self.app.models;
|
||||
const $t = ctx.req.__; // $translate
|
||||
await models.Ticket.isEditableOrThrow(ctx, args.id, myOptions);
|
||||
|
@ -127,11 +127,8 @@ module.exports = Self => {
|
|||
args.warehouseFk,
|
||||
myOptions);
|
||||
|
||||
if (!zoneShipped || zoneShipped.zoneFk != args.zoneFk) {
|
||||
const error = `You don't have privileges to change the zone`;
|
||||
|
||||
throw new UserError(error);
|
||||
}
|
||||
if (!zoneShipped || zoneShipped.zoneFk != args.zoneFk)
|
||||
throw new UserError(`You don't have privileges to change the zone`);
|
||||
}
|
||||
|
||||
if (args.isWithoutNegatives) {
|
||||
|
|
|
@ -248,6 +248,7 @@ module.exports = Self => {
|
|||
am.name AS agencyMode,
|
||||
am.id AS agencyModeFk,
|
||||
st.name AS state,
|
||||
st.classColor,
|
||||
wk.lastName AS salesPerson,
|
||||
ts.stateFk AS stateFk,
|
||||
ts.alertLevel AS alertLevel,
|
||||
|
@ -339,7 +340,8 @@ module.exports = Self => {
|
|||
{'tp.isFreezed': hasProblem},
|
||||
{'tp.risk': hasProblem},
|
||||
{'tp.hasTicketRequest': hasProblem},
|
||||
{'tp.itemShortage': range}
|
||||
{'tp.itemShortage': range},
|
||||
{'tp.hasRounding': hasProblem}
|
||||
]};
|
||||
|
||||
if (hasWhere)
|
||||
|
|
|
@ -194,7 +194,8 @@ module.exports = Self => {
|
|||
{'tp.hasTicketRequest': hasProblem},
|
||||
{'tp.itemShortage': range},
|
||||
{'tp.hasComponentLack': hasProblem},
|
||||
{'tp.isTooLittle': hasProblem}
|
||||
{'tp.isTooLittle': hasProblem},
|
||||
{'tp.hasRounding': hasProblem}
|
||||
]
|
||||
};
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@ describe('sale transferSales()', () => {
|
|||
const userId = 1101;
|
||||
const activeCtx = {
|
||||
accessToken: {userId: userId},
|
||||
headers: {origin: ''},
|
||||
__: value => value
|
||||
};
|
||||
const ctx = {req: activeCtx};
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ module.exports = Self => {
|
|||
const userId = ctx.req.accessToken.userId;
|
||||
const models = Self.app.models;
|
||||
const myOptions = {userId};
|
||||
const $t = ctx.req.__; // $translate
|
||||
let tx;
|
||||
|
||||
if (typeof options == 'object')
|
||||
|
@ -95,9 +96,18 @@ module.exports = Self => {
|
|||
|
||||
const isTicketEmpty = await models.Ticket.isEmpty(id, myOptions);
|
||||
if (isTicketEmpty) {
|
||||
await originalTicket.updateAttributes({
|
||||
isDeleted: true
|
||||
}, myOptions);
|
||||
try {
|
||||
await models.Ticket.setDeleted(ctx, id, myOptions);
|
||||
} catch (e) {
|
||||
if (e.statusCode === 400) {
|
||||
throw new UserError(
|
||||
`This ticket cannot be left empty.`,
|
||||
'TRANSFER_SET_DELETED',
|
||||
$t(e.message, ...e.translateArgs)
|
||||
);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (tx) await tx.commit();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -27,6 +27,9 @@
|
|||
"code": {
|
||||
"type": "string",
|
||||
"required": false
|
||||
},
|
||||
"classColor": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,6 +123,12 @@
|
|||
class="bright"
|
||||
icon="icon-components">
|
||||
</vn-icon>
|
||||
<vn-icon
|
||||
ng-show="::ticket.hasRounding"
|
||||
translate-attr="{title: 'Rounding'}"
|
||||
class="bright"
|
||||
icon="sync_problem">
|
||||
</vn-icon>
|
||||
</td>
|
||||
<td><span
|
||||
ng-click="ticketDescriptor.show($event, ticket.id)"
|
||||
|
|
|
@ -36,3 +36,4 @@ import './future';
|
|||
import './future-search-panel';
|
||||
import './advance';
|
||||
import './advance-search-panel';
|
||||
import './sms';
|
||||
|
|
|
@ -75,6 +75,12 @@
|
|||
class="bright"
|
||||
icon="icon-components">
|
||||
</vn-icon>
|
||||
<vn-icon
|
||||
ng-show="::ticket.hasRounding"
|
||||
translate-attr="{title: 'Rounding'}"
|
||||
class="bright"
|
||||
icon="sync_problem">
|
||||
</vn-icon>
|
||||
</vn-td>
|
||||
<vn-td shrink>{{::ticket.id}}</vn-td>
|
||||
<vn-td class="expendable">
|
||||
|
|
|
@ -26,7 +26,8 @@
|
|||
{"state": "ticket.card.components", "icon": "icon-components"},
|
||||
{"state": "ticket.card.saleTracking", "icon": "assignment"},
|
||||
{"state": "ticket.card.dms.index", "icon": "cloud_download"},
|
||||
{"state": "ticket.card.boxing", "icon": "science"}
|
||||
{"state": "ticket.card.boxing", "icon": "science"},
|
||||
{"state": "ticket.card.sms", "icon": "sms"}
|
||||
]
|
||||
},
|
||||
"keybindings": [
|
||||
|
@ -287,6 +288,15 @@
|
|||
"state": "ticket.advance",
|
||||
"component": "vn-ticket-advance",
|
||||
"description": "Advance tickets"
|
||||
},
|
||||
{
|
||||
"url": "/sms",
|
||||
"state": "ticket.card.sms",
|
||||
"component": "vn-ticket-sms",
|
||||
"description": "Sms",
|
||||
"params": {
|
||||
"ticket": "$ctrl.ticket"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
<vn-card>
|
||||
</vn-card>
|
|
@ -0,0 +1,21 @@
|
|||
import ngModule from '../module';
|
||||
import Section from 'salix/components/section';
|
||||
|
||||
class Controller extends Section {
|
||||
constructor($element, $) {
|
||||
super($element, $);
|
||||
}
|
||||
|
||||
async $onInit() {
|
||||
this.$state.go('ticket.card.summary', {id: this.$params.id});
|
||||
window.location.href = await this.vnApp.getUrl(`ticket/${this.$params.id}/sms`);
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.vnComponent('vnTicketSms', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller,
|
||||
bindings: {
|
||||
ticket: '<'
|
||||
}
|
||||
});
|
|
@ -71,8 +71,9 @@ module.exports = Self => {
|
|||
'Stored on': 'created',
|
||||
'Document ID': 'id'
|
||||
};
|
||||
|
||||
workerDocuware =
|
||||
await models.Docuware.getById('hr', worker.lastName + worker.firstName, docuwareParse) ?? [];
|
||||
await models.Docuware.getById('hr', worker.lastName + ' ' + worker.firstName, docuwareParse) ?? [];
|
||||
for (document of workerDocuware) {
|
||||
const defaultData = {
|
||||
file: 'dw' + document.id + '.png',
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue