Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5941-send-to-support

This commit is contained in:
Alex Moreno 2023-08-11 11:55:02 +02:00
commit 02dddaeffe
111 changed files with 1740 additions and 612 deletions

View File

@ -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
## [2334.01] - 2023-08-24
### Added
@ -14,6 +15,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
## [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,7 +41,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
- (Clientes -> Razón social) Permite crear clientes con la misma razón social según el país
### Fixed

View File

@ -0,0 +1,45 @@
const axios = require('axios');
const {DOMParser} = require('xmldom');
module.exports = Self => {
Self.remoteMethod('internationalExpedition', {
description: 'Create an expedition and return a label',
accessType: 'WRITE',
accepts: [{
arg: 'expeditionFk',
type: 'number',
required: true
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/internationalExpedition`,
verb: 'POST'
}
});
Self.internationalExpedition = async expeditionFk => {
const models = Self.app.models;
const viaexpressConfig = await models.ViaexpressConfig.findOne({
fields: ['url']
});
const renderedXml = await models.ViaexpressConfig.renderer(expeditionFk);
const response = await axios.post(`${viaexpressConfig.url}ServicioVxClientes.asmx`, renderedXml, {
headers: {
'Content-Type': 'application/soap+xml; charset=utf-8'
}
});
const xmlString = response.data;
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
const referenciaVxElement = xmlDoc.getElementsByTagName('ReferenciaVx')[0];
const referenciaVx = referenciaVxElement.textContent;
return referenciaVx;
};
};

View File

@ -0,0 +1,126 @@
const fs = require('fs');
const ejs = require('ejs');
module.exports = Self => {
Self.remoteMethod('renderer', {
description: 'Renders the data from an XML',
accessType: 'READ',
accepts: [{
arg: 'expeditionFk',
type: 'number',
required: true
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/renderer`,
verb: 'GET'
}
});
Self.renderer = async expeditionFk => {
const models = Self.app.models;
const viaexpressConfig = await models.ViaexpressConfig.findOne({
fields: ['client', 'user', 'password', 'defaultWeight', 'deliveryType']
});
const expedition = await models.Expedition.findOne({
fields: ['id', 'ticketFk'],
where: {id: expeditionFk},
include: [
{
relation: 'ticket',
scope: {
fields: ['shipped', 'addressFk', 'clientFk', 'companyFk'],
include: [
{
relation: 'client',
scope: {
fields: ['mobile', 'phone', 'email']
}
},
{
relation: 'address',
scope: {
fields: [
'nickname',
'street',
'postalCode',
'city',
'mobile',
'phone',
'provinceFk'
],
include: {
relation: 'province',
scope: {
fields: ['name', 'countryFk'],
include: {
relation: 'country',
scope: {
fields: ['code'],
}
}
}
}
}
},
{
relation: 'company',
scope: {
fields: ['clientFk'],
include: {
relation: 'client',
scope: {
fields: ['socialName', 'mobile', 'phone', 'email', 'defaultAddressFk'],
include: {
relation: 'defaultAddress',
scope: {
fields: [
'street',
'postalCode',
'city',
'mobile',
'phone',
'provinceFk'
],
include: {
relation: 'province',
scope: {
fields: ['name']
}
}
}
}
}
}
}
}
]
}
}
]
});
const ticket = expedition.ticket();
const sender = ticket.company().client();
const shipped = ticket.shipped.toISOString();
const data = {
viaexpressConfig,
sender,
senderAddress: sender.defaultAddress(),
client: ticket.client(),
address: ticket.address(),
shipped
};
const template = fs.readFileSync(__dirname + '/template.ejs', 'utf-8');
const renderedXml = ejs.render(template, data);
return renderedXml;
};
};

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<PutExpedicionInternacional xmlns="http://82.223.6.71:82">
<ObjetoEnvio>
<Peso><%= viaexpressConfig.defaultWeight %></Peso>
<Bultos>1</Bultos>
<Reembolso>0</Reembolso>
<Fecha><%= shipped %></Fecha>
<ConRetorno>0</ConRetorno>
<Tipo><%= viaexpressConfig.deliveryType %></Tipo>
<Debidos>0</Debidos>
<Asegurado>0</Asegurado>
<Imprimir>0</Imprimir>
<ConDevolucionAlbaran>0</ConDevolucionAlbaran>
<Intradia>0</Intradia>
<Observaciones></Observaciones>
<AlbaranRemitente></AlbaranRemitente>
<Modo>0</Modo>
<TextoAgencia></TextoAgencia>
<Terminal></Terminal>
<ObjetoRemitente>
<RazonSocial><%= sender.socialName %></RazonSocial>
<Domicilio><%= senderAddress.street %></Domicilio>
<Cpostal><%= senderAddress.postalCode %></Cpostal>
<Poblacion><%= senderAddress.city %></Poblacion>
<Provincia><%= senderAddress.province().name %></Provincia>
<Contacto></Contacto>
<Telefono><%= senderAddress.mobile || senderAddress.phone || sender.mobile || sender.phone %></Telefono>
<Email><%= sender.email %></Email>
</ObjetoRemitente>
<ObjetoDestinatario>
<RazonSocial><%= address.nickname %></RazonSocial>
<Domicilio><%= address.street %></Domicilio>
<Cpostal><%= address.postalCode %></Cpostal>
<Poblacion><%= address.city %></Poblacion>
<Municipio></Municipio>
<Provincia><%= address.province().name %></Provincia>
<Contacto></Contacto>
<Telefono><%= address.mobile || address.phone || client.mobile || client.phone %></Telefono>
<Email><%= client.email %></Email>
<Pais><%= address.province().country().code %></Pais>
</ObjetoDestinatario>
<ObjetoLogin>
<IdCliente><%= viaexpressConfig.client %></IdCliente>
<Usuario><%= viaexpressConfig.user %></Usuario>
<Password><%= viaexpressConfig.password %></Password>
</ObjetoLogin>
</ObjetoEnvio>
</PutExpedicionInternacional>
</soap12:Body>
</soap12:Envelope>

View File

@ -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: {

View File

@ -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;

View File

@ -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;
}
};

View File

@ -150,6 +150,9 @@
},
"PrintConfig": {
"dataSource": "vn"
},
"ViaexpressConfig": {
"dataSource": "vn"
}
}

View File

@ -27,5 +27,12 @@
"where" :{
"expired": null
}
},
"relations": {
"client": {
"type": "belongsTo",
"model": "Client",
"foreignKey": "clientFk"
}
}
}

View File

@ -0,0 +1,4 @@
module.exports = Self => {
require('../methods/viaexpress-config/internationalExpedition')(Self);
require('../methods/viaexpress-config/renderer')(Self);
};

View File

@ -0,0 +1,34 @@
{
"name": "ViaexpressConfig",
"base": "VnModel",
"options": {
"mysql": {
"table": "viaexpressConfig"
}
},
"properties": {
"id": {
"type": "number",
"required": true
},
"url": {
"type": "string",
"required": true
},
"client": {
"type": "string"
},
"user": {
"type": "string"
},
"password": {
"type": "string"
},
"defaultWeight": {
"type": "number"
},
"deliveryType": {
"type": "string"
}
}
}

View File

@ -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};
};

View File

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

View File

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

View File

@ -0,0 +1,10 @@
CREATE TABLE `vn`.`viaexpressConfig` (
id int auto_increment NOT NULL,
url varchar(100) NOT NULL,
client varchar(100) NOT NULL,
user varchar(100) NOT NULL,
password varchar(100) NOT NULL,
defaultWeight decimal(10,2) NOT NULL,
deliveryType varchar(5) NOT NULL,
CONSTRAINT viaexpressConfig_PK PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;

View File

@ -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 ;

View File

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

View File

View File

@ -0,0 +1,6 @@
UPDATE `salix`.`ACL`
SET principalId='salesPerson'
WHERE
model='Ticket'
AND property='setDeleted'
AND accessType='WRITE';

View File

@ -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
@ -579,13 +579,13 @@ INSERT INTO `vn`.`supplierAccount`(`id`, `supplierFk`, `iban`, `bankEntityFk`)
VALUES
(241, 442, 'ES111122333344111122221111', 128);
INSERT INTO `vn`.`company`(`id`, `code`, `supplierAccountFk`, `workerManagerFk`, `companyCode`, `sage200Company`, `expired`, `companyGroupFk`, `phytosanitary`)
INSERT INTO `vn`.`company`(`id`, `code`, `supplierAccountFk`, `workerManagerFk`, `companyCode`, `sage200Company`, `expired`, `companyGroupFk`, `phytosanitary` , `clientFk`)
VALUES
(69 , 'CCs', NULL, 30, NULL, 0, NULL, 1, NULL),
(442 , 'VNL', 241, 30, 2 , 1, NULL, 2, 'VNL Company - Plant passport'),
(567 , 'VNH', NULL, 30, NULL, 4, NULL, 1, 'VNH Company - Plant passport'),
(791 , 'FTH', NULL, 30, NULL, 3, '2015-11-30', 1, NULL),
(1381, 'ORN', NULL, 30, NULL, 7, NULL, 1, 'ORN Company - Plant passport');
(69 , 'CCs', NULL, 30, NULL, 0, NULL, 1, NULL , NULL),
(442 , 'VNL', 241, 30, 2 , 1, NULL, 2, 'VNL Company - Plant passport' , 1101),
(567 , 'VNH', NULL, 30, NULL, 4, NULL, 1, 'VNH Company - Plant passport' , NULL),
(791 , 'FTH', NULL, 30, NULL, 3, '2015-11-30', 1, NULL , NULL),
(1381, 'ORN', NULL, 30, NULL, 7, NULL, 1, 'ORN Company - Plant passport' , NULL);
INSERT INTO `vn`.`taxArea` (`code`, `claveOperacionFactura`, `CodigoTransaccion`)
VALUES
@ -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');

View File

@ -875,7 +875,7 @@ export default {
},
routeTickets: {
firstTicketPriority: 'vn-route-tickets vn-tr:nth-child(1) vn-input-number[ng-model="ticket.priority"]',
firstTicketPriority: 'vn-route-tickets vn-tr:nth-child(1) vn-td-editable',
firstTicketCheckbox: 'vn-route-tickets vn-tr:nth-child(1) vn-check',
buscamanButton: 'vn-route-tickets vn-button[icon="icon-buscaman"]',
firstTicketDeleteButton: 'vn-route-tickets vn-tr:nth-child(1) vn-icon[icon="delete"]',

View File

@ -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);

View File

@ -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');

View File

@ -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');
});
});

View File

@ -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() => {

View File

@ -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');

View File

@ -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() => {

View File

@ -18,8 +18,7 @@ describe('Route tickets path', () => {
});
it('should modify the first ticket priority', async() => {
await page.clearInput(selectors.routeTickets.firstTicketPriority);
await page.type(selectors.routeTickets.firstTicketPriority, '9');
await page.writeOnEditableTD(selectors.routeTickets.firstTicketPriority, '9');
await page.keyboard.press('Enter');
const message = await page.waitForSnackbar();

View File

@ -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)

View File

@ -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');

View File

@ -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 = '';

View File

@ -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>'
})

View File

@ -178,5 +178,12 @@
"The renew period has not been exceeded": "The renew period has not been exceeded",
"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}}"
"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.",
"The ticket doesn't exist.": "The ticket doesn't exist."
}

View File

@ -305,7 +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"
"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.",
"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"
}

View File

@ -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);

View File

@ -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();

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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'
});

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,44 +0,0 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethod('activeBuyers', {
description: 'Returns a list of buyers for the given item type',
accepts: [{
arg: 'filter',
type: 'object',
description: `Filter defining where, order, offset, and limit - must be a JSON-encoded string`
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/activeBuyers`,
verb: 'GET'
}
});
Self.activeBuyers = async(filter, options) => {
const conn = Self.dataSource.connector;
const where = {isActive: true};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
filter = mergeFilters(filter, {where});
let stmt = new ParameterizedSQL(
`SELECT DISTINCT w.id workerFk, w.firstName, w.lastName, u.name, u.nickname
FROM worker w
JOIN itemType it ON it.workerFk = w.id
JOIN account.user u ON u.id = w.id
JOIN item i ON i.typeFk = it.id`,
null, myOptions);
stmt.merge(conn.makeSuffix(filter));
return conn.executeStmt(stmt);
};
};

View File

@ -1,24 +0,0 @@
const models = require('vn-loopback/server/server').models;
describe('Worker activeBuyers', () => {
it('should return the buyers in itemType as result', async() => {
const tx = await models.Item.beginTransaction({});
try {
const options = {transaction: tx};
const filter = {};
const result = await models.Item.activeBuyers(filter, options);
const firstWorker = result[0];
const secondWorker = result[1];
expect(result.length).toEqual(2);
expect(firstWorker.nickname).toEqual('logisticBossNick');
expect(secondWorker.nickname).toEqual('buyerNick');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -25,6 +25,9 @@
"type": "boolean"
},
"visible": {
"type": "number"
},
"userFk": {
"type": "number"
}
},

View File

@ -14,7 +14,6 @@ module.exports = Self => {
require('../methods/item/getWasteByWorker')(Self);
require('../methods/item/getWasteByItem')(Self);
require('../methods/item/createIntrastat')(Self);
require('../methods/item/activeBuyers')(Self);
require('../methods/item/buyerWasteEmail')(Self);
require('../methods/item/labelPdf')(Self);

View File

@ -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>

View File

@ -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%);
}
}
}
}
}
}

View File

@ -1,7 +1,7 @@
<div class="search-panel">
<vn-crud-model
auto-load="true"
url="Warehouses"
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form id="manifold-form" ng-submit="$ctrl.onSearch()">
@ -26,7 +26,7 @@
search-function="{firstName: $search}"
value-field="id"
where="{role: {inq: ['logistic', 'buyer']}}"
label="Atender">
label="Buyer">
<tpl-item>{{nickname}}</tpl-item>
</vn-autocomplete>
</vn-horizontal>
@ -57,7 +57,7 @@
<tpl-item>{{firstName}} {{lastName}}</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<section class="vn-px-md">
<vn-horizontal class="manifold-panel vn-pa-md">
<vn-date-picker
@ -89,9 +89,9 @@
</vn-horizontal>
</section>
<vn-horizontal class="vn-px-lg">
<vn-check vn-one
triple-state="true"
label="For me"
<vn-check vn-one
triple-state="true"
label="For me"
ng-model="filter.mine">
</vn-check>
<vn-autocomplete

View File

@ -38,13 +38,13 @@
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
<vn-autocomplete
vn-one
ng-model="filter.buyerFk"
url="Items/activeBuyers"
url="TicketRequests/getItemTypeWorker"
search-function="{firstName: $search}"
show-field="nickname"
search-function="{nickname: {like: '%'+ $search +'%'}}"
value-field="workerFk"
value-field="id"
label="Buyer">
</vn-autocomplete>
<vn-autocomplete
@ -88,7 +88,7 @@
label="Value"
ng-model="itemTag.value">
</vn-textfield>
<vn-autocomplete
<vn-autocomplete
vn-one
ng-show="tag.selection.isFree === false"
url="{{'Tags/' + itemTag.tagFk + '/filterValue'}}"
@ -167,7 +167,7 @@
</vn-icon-button>
</vn-horizontal>
<vn-horizontal>
<vn-check
<vn-check
vn-one
label="Floramondo"
ng-model="filter.isFloramondo"

View File

@ -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');
};

View File

@ -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,
sub.id 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];
};
};

View File

@ -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();
}
};
};

View File

@ -84,14 +84,21 @@
tabindex="-1">
</vn-icon-button>
</vn-td>
<vn-td>
<vn-input-number
on-change="$ctrl.setPriority(ticket.id, ticket.priority)"
ng-model="ticket.priority"
rule="Ticket"
class="dense">
</vn-input-number>
</vn-td>
<vn-td-editable number>
<text>
<strong>{{ticket.priority}}</strong>
</text>
<field>
<vn-input-number
rule="Ticket"
ng-model="ticket.priority"
on-change="$ctrl.setPriority(ticket.id, ticket.priority)"
step="1"
class="dense"
vn-focus>
</vn-input-number>
</field>
</vn-td-editable>
<vn-td expand title="{{::ticket.street}}">{{::ticket.street}}</vn-td>
<vn-td
expand

View File

@ -1,7 +1,9 @@
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.remoteMethodCtx('getItemTypeWorker', {
Self.remoteMethod('getItemTypeWorker', {
description: 'Returns the workers that appear in itemType',
accessType: 'READ',
accepts: [{
@ -20,38 +22,37 @@ module.exports = Self => {
}
});
Self.getItemTypeWorker = async(ctx, filter, options) => {
const myOptions = {};
Self.getItemTypeWorker = async filter => {
const conn = Self.dataSource.connector;
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
const query =
`SELECT DISTINCT u.id, u.nickname
FROM itemType it
JOIN worker w ON w.id = it.workerFk
JOIN account.user u ON u.id = w.id`;
const stmt = new ParameterizedSQL(query);
let stmt = new ParameterizedSQL(query);
if (filter.where) {
const value = filter.where.firstName;
const myFilter = {
where: {or: [
filter.where = buildFilter(filter.where, (param, value) => {
switch (param) {
case 'firstName':
return {or: [
{'w.firstName': {like: `%${value}%`}},
{'w.lastName': {like: `%${value}%`}},
{'u.name': {like: `%${value}%`}},
{'u.nickname': {like: `%${value}%`}}
]}
};
]};
case 'id':
return {'w.id': value};
}
});
stmt.merge(conn.makeSuffix(myFilter));
}
let myFilter = {
where: {'u.active': true}
};
if (tx) await tx.commit();
myFilter = mergeFilters(myFilter, filter);
stmt.merge(conn.makeSuffix(myFilter));
return conn.executeStmt(stmt);
};
};

View File

@ -1,12 +1,10 @@
const models = require('vn-loopback/server/server').models;
describe('ticket-request getItemTypeWorker()', () => {
const ctx = {req: {accessToken: {userId: 18}}};
it('should return the buyer as result', async() => {
const filter = {where: {firstName: 'buyer'}};
const result = await models.TicketRequest.getItemTypeWorker(ctx, filter);
const result = await models.TicketRequest.getItemTypeWorker(filter);
expect(result.length).toEqual(1);
});
@ -14,7 +12,7 @@ describe('ticket-request getItemTypeWorker()', () => {
it('should return the workers at itemType as result', async() => {
const filter = {};
const result = await models.TicketRequest.getItemTypeWorker(ctx, filter);
const result = await models.TicketRequest.getItemTypeWorker(filter);
expect(result.length).toBeGreaterThan(1);
});

View File

@ -1,4 +1,3 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
@ -47,9 +46,7 @@ module.exports = Self => {
}
try {
const isEditable = await models.Ticket.isEditable(ctx, id, myOptions);
if (!isEditable)
throw new UserError(`The sales of this ticket can't be modified`);
await models.Ticket.isEditableOrThrow(ctx, id, myOptions);
const item = await models.Item.findById(itemId, null, myOptions);
const ticket = await models.Ticket.findById(id, {

View File

@ -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`);
});

View File

@ -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
});
}
};

View File

@ -1,5 +1,5 @@
const UserError = require('vn-loopback/util/user-error');
const loggable = require('vn-loopback/util/log');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('componentUpdate', {
@ -113,13 +113,9 @@ module.exports = Self => {
}
try {
const userId = ctx.req.accessToken.userId;
const models = Self.app.models;
const $t = ctx.req.__; // $translate
const isEditable = await models.Ticket.isEditable(ctx, args.id, myOptions);
if (!isEditable)
throw new UserError(`The sales of this ticket can't be modified`);
await models.Ticket.isEditableOrThrow(ctx, args.id, myOptions);
const editZone = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'editZone', 'WRITE');
if (!editZone) {
@ -131,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) {

View File

@ -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)

View File

@ -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}
]
};

View File

@ -20,41 +20,12 @@ module.exports = Self => {
});
Self.isEditable = async(ctx, id, options) => {
const models = Self.app.models;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const state = await models.TicketState.findOne({
where: {ticketFk: id}
}, myOptions);
const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*');
const alertLevel = state ? state.alertLevel : null;
const ticket = await models.Ticket.findById(id, {
fields: ['clientFk'],
include: [{
relation: 'client',
scope: {
include: {
relation: 'type'
}
}
}]
}, myOptions);
const isLocked = await models.Ticket.isLocked(id, myOptions);
const isWeekly = await models.TicketWeekly.findOne({where: {ticketFk: id}}, myOptions);
const alertLevelGreaterThanZero = (alertLevel && alertLevel > 0);
const isNormalClient = ticket && ticket.client().type().code == 'normal';
const isEditable = !(alertLevelGreaterThanZero && isNormalClient);
if (ticket && (isEditable || isRoleAdvanced) && !isLocked && !isWeekly)
return true;
return false;
try {
await Self.isEditableOrThrow(ctx, id, options);
} catch (e) {
if (e.name === 'ForbiddenError') return false;
throw e;
}
return true;
};
};

View File

@ -0,0 +1,49 @@
const ForbiddenError = require('vn-loopback/util/forbiddenError');
module.exports = Self => {
Self.isEditableOrThrow = async(ctx, id, options) => {
const models = Self.app.models;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const state = await models.TicketState.findOne({
where: {ticketFk: id}
}, myOptions);
const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*');
const canEditWeeklyTicket = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'canEditWeekly', 'WRITE');
const alertLevel = state ? state.alertLevel : null;
const ticket = await models.Ticket.findById(id, {
fields: ['clientFk'],
include: [{
relation: 'client',
scope: {
include: {
relation: 'type'
}
}
}]
}, myOptions);
const isLocked = await models.Ticket.isLocked(id, myOptions);
const isWeekly = await models.TicketWeekly.findOne({where: {ticketFk: id}}, myOptions);
const alertLevelGreaterThanZero = (alertLevel && alertLevel > 0);
const isNormalClient = ticket && ticket.client().type().code == 'normal';
const isEditable = !(alertLevelGreaterThanZero && isNormalClient);
if (!ticket)
throw new ForbiddenError(`The ticket doesn't exist.`);
if (!isEditable && !isRoleAdvanced)
throw new ForbiddenError(`This ticket is not editable.`);
if (isLocked)
throw new ForbiddenError(`This ticket is locked.`);
if (isWeekly && !canEditWeeklyTicket)
throw new ForbiddenError(`You don't have enough privileges.`);
};
};

View File

@ -1,5 +1,3 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('priceDifference', {
description: 'Returns sales with price difference if the ticket is editable',
@ -72,10 +70,7 @@ module.exports = Self => {
}
try {
const isEditable = await Self.isEditable(ctx, args.id, myOptions);
if (!isEditable)
throw new UserError(`The sales of this ticket can't be modified`);
await Self.isEditableOrThrow(ctx, args.id, myOptions);
const editZone = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'editZone', 'WRITE');
if (!editZone) {

View File

@ -1,4 +1,3 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('recalculateComponents', {
description: 'Calculates the price of a sale and its components',
@ -33,10 +32,7 @@ module.exports = Self => {
}
try {
const isEditable = await Self.isEditable(ctx, id, myOptions);
if (!isEditable)
throw new UserError(`The current ticket can't be modified`);
await Self.isEditableOrThrow(ctx, id, myOptions);
const recalculation = await Self.rawSql('CALL vn.ticket_recalcComponents(?, NULL)', [id], myOptions);

View File

@ -39,10 +39,7 @@ module.exports = Self => {
const ticketToDelete = await models.Ticket.findById(id, {fields: ['isDeleted']}, myOptions);
if (ticketToDelete.isDeleted) return false;
const isEditable = await Self.isEditable(ctx, id, myOptions);
if (!isEditable)
throw new UserError(`The sales of this ticket can't be modified`);
await Self.isEditableOrThrow(ctx, id, myOptions);
// Check if ticket has refunds
const ticketRefunds = await models.TicketRefund.find({

View File

@ -97,6 +97,6 @@ describe('ticket addSale()', () => {
error = e;
}
expect(error.message).toEqual(`The sales of this ticket can't be modified`);
expect(error.message).toEqual(`This ticket is locked.`);
});
});

View File

@ -1,156 +1,37 @@
const models = require('vn-loopback/server/server').models;
describe('ticket isEditable()', () => {
it('should return false if the given ticket does not exist', async() => {
describe('isEditable()', () => {
it('should return false if It is able to edit', async() => {
const tx = await models.Ticket.beginTransaction({});
let result;
try {
const options = {transaction: tx};
const ctx = {
req: {accessToken: {userId: 9}}
};
result = await models.Ticket.isEditable(ctx, 9999, options);
const ctx = {req: {accessToken: {userId: 35}}};
result = await models.Ticket.isEditable(ctx, 5, options);
await tx.rollback();
} catch (e) {
} catch (error) {
await tx.rollback();
throw e;
}
expect(result).toEqual(false);
expect(result).toBeFalse();
});
it(`should return false if the given ticket isn't invoiced but isDeleted`, async() => {
it('should return true if It is able to edit', async() => {
const tx = await models.Ticket.beginTransaction({});
let result;
try {
const options = {transaction: tx};
const deletedTicket = await models.Ticket.findOne({
where: {
invoiceOut: null,
isDeleted: true
},
fields: ['id']
});
const ctx = {
req: {accessToken: {userId: 9}}
};
result = await models.Ticket.isEditable(ctx, deletedTicket.id, options);
const ctx = {req: {accessToken: {userId: 35}}};
result = await models.Ticket.isEditable(ctx, 15, options);
await tx.rollback();
} catch (e) {
} catch (error) {
await tx.rollback();
throw e;
}
expect(result).toEqual(false);
});
it('should return true if the given ticket is editable', async() => {
const tx = await models.Ticket.beginTransaction({});
let result;
try {
const options = {transaction: tx};
const ctx = {
req: {accessToken: {userId: 9}}
};
result = await models.Ticket.isEditable(ctx, 16, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(result).toEqual(true);
});
it('should not be able to edit a deleted or invoiced ticket even for salesAssistant', async() => {
const tx = await models.Ticket.beginTransaction({});
let result;
try {
const options = {transaction: tx};
const ctx = {
req: {accessToken: {userId: 21}}
};
result = await models.Ticket.isEditable(ctx, 19, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(result).toEqual(false);
});
it('should not be able to edit a deleted or invoiced ticket even for productionBoss', async() => {
const tx = await models.Ticket.beginTransaction({});
let result;
try {
const options = {transaction: tx};
const ctx = {
req: {accessToken: {userId: 50}}
};
result = await models.Ticket.isEditable(ctx, 19, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(result).toEqual(false);
});
it('should not be able to edit a deleted or invoiced ticket even for salesPerson', async() => {
const tx = await models.Ticket.beginTransaction({});
let result;
try {
const options = {transaction: tx};
const ctx = {
req: {accessToken: {userId: 18}}
};
result = await models.Ticket.isEditable(ctx, 19, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(result).toEqual(false);
});
it('should not be able to edit if is a ticket weekly', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}};
const result = await models.Ticket.isEditable(ctx, 15, options);
expect(result).toEqual(false);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(result).toBeTrue();
});
});

View File

@ -0,0 +1,96 @@
const models = require('vn-loopback/server/server').models;
describe('ticket isEditableOrThrow()', () => {
it('should throw an error as the ticket does not exist', async() => {
const tx = await models.Ticket.beginTransaction({});
let error;
try {
const options = {transaction: tx};
const ctx = {
req: {accessToken: {userId: 9}}
};
await models.Ticket.isEditableOrThrow(ctx, 9999, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error.message).toEqual(`The ticket doesn't exist.`);
});
it('should throw an error as this ticket is not editable', async() => {
const tx = await models.Ticket.beginTransaction({});
let error;
try {
const options = {transaction: tx};
const ctx = {
req: {accessToken: {userId: 1}}
};
await models.Ticket.isEditableOrThrow(ctx, 8, options);
await tx.rollback();
} catch (e) {
error = e;
await tx.rollback();
}
expect(error.message).toEqual(`This ticket is not editable.`);
});
it('should throw an error as this ticket is locked.', async() => {
const tx = await models.Ticket.beginTransaction({});
let error;
try {
const options = {transaction: tx};
const ctx = {
req: {accessToken: {userId: 18}}
};
await models.Ticket.isEditableOrThrow(ctx, 19, options);
await tx.rollback();
} catch (e) {
error = e;
await tx.rollback();
}
expect(error.message).toEqual(`This ticket is locked.`);
});
it('should throw an error as you do not have enough privileges.', async() => {
const tx = await models.Ticket.beginTransaction({});
let error;
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}};
await models.Ticket.isEditableOrThrow(ctx, 15, options);
await tx.rollback();
} catch (e) {
error = e;
await tx.rollback();
}
expect(error.message).toEqual(`You don't have enough privileges.`);
});
it('should return undefined if It can be edited', async() => {
const tx = await models.Ticket.beginTransaction({});
let result;
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 35}}};
result = await models.Ticket.isEditableOrThrow(ctx, 15, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(result).toBeUndefined();
});
});

View File

@ -1,5 +1,6 @@
const models = require('vn-loopback/server/server').models;
const UserError = require('vn-loopback/util/user-error');
const ForbiddenError = require('vn-loopback/util/forbiddenError');
describe('sale priceDifference()', () => {
it('should return ticket price differences', async() => {
@ -59,7 +60,7 @@ describe('sale priceDifference()', () => {
await tx.rollback();
}
expect(error).toEqual(new UserError(`The sales of this ticket can't be modified`));
expect(error).toEqual(new ForbiddenError(`This ticket is not editable.`));
});
it('should return ticket movable', async() => {

View File

@ -1,4 +1,5 @@
const models = require('vn-loopback/server/server').models;
const ForbiddenError = require('vn-loopback/util/forbiddenError');
describe('ticket recalculateComponents()', () => {
const ticketId = 11;
@ -38,6 +39,6 @@ describe('ticket recalculateComponents()', () => {
error = e;
}
expect(error).toEqual(new Error(`The current ticket can't be modified`));
expect(error).toEqual(new ForbiddenError(`This ticket is locked.`));
});
});

View File

@ -23,7 +23,7 @@ describe('Ticket transferClient()', () => {
error = e;
}
expect(error.message).toEqual(`The current ticket can't be modified`);
expect(error.message).toEqual(`This ticket is locked.`);
});
it('should be assigned a different clientFk', async() => {

View File

@ -5,6 +5,8 @@ describe('sale transferSales()', () => {
const userId = 1101;
const activeCtx = {
accessToken: {userId: userId},
headers: {origin: ''},
__: value => value
};
const ctx = {req: activeCtx};
@ -33,7 +35,7 @@ describe('sale transferSales()', () => {
error = e;
}
expect(error.message).toEqual(`The sales of this ticket can't be modified`);
expect(error.message).toEqual(`This ticket is not editable.`);
});
it('should throw an error if the receiving ticket is not editable', async() => {

View File

@ -1,4 +1,3 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('transferClient', {
description: 'Transfering ticket to another client',
@ -29,10 +28,7 @@ module.exports = Self => {
if (typeof options == 'object')
Object.assign(myOptions, options);
const isEditable = await Self.isEditable(ctx, id, myOptions);
if (!isEditable)
throw new UserError(`The current ticket can't be modified`);
await Self.isEditableOrThrow(ctx, id, myOptions);
const ticket = await models.Ticket.findById(
id,

View File

@ -1,4 +1,4 @@
let UserError = require('vn-loopback/util/user-error');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('transferSales', {
@ -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')
@ -48,9 +49,7 @@ module.exports = Self => {
}
try {
const isEditable = await models.Ticket.isEditable(ctx, id, myOptions);
if (!isEditable)
throw new UserError(`The sales of this ticket can't be modified`);
await models.Ticket.isEditableOrThrow(ctx, id, myOptions);
if (ticketId) {
const isReceiverEditable = await models.Ticket.isEditable(ctx, ticketId, myOptions);
@ -97,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();

View File

@ -27,6 +27,9 @@
"code": {
"type": "string",
"required": false
},
"classColor": {
"type": "string"
}
}
}

View File

@ -6,6 +6,7 @@ module.exports = function(Self) {
require('../methods/ticket/componentUpdate')(Self);
require('../methods/ticket/new')(Self);
require('../methods/ticket/isEditable')(Self);
require('../methods/ticket/isEditableOrThrow')(Self);
require('../methods/ticket/setDeleted')(Self);
require('../methods/ticket/restore')(Self);
require('../methods/ticket/getSales')(Self);

View File

@ -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)"

View File

@ -36,3 +36,4 @@ import './future';
import './future-search-panel';
import './advance';
import './advance-search-panel';
import './sms';

View File

@ -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">

View File

@ -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"
}
}
]
}

View File

@ -0,0 +1,2 @@
<vn-card>
</vn-card>

View File

@ -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: '<'
}
});

View File

@ -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',

View File

@ -36,9 +36,9 @@ module.exports = Self => {
if (isSubordinate === false)
throw new UserError(`You don't have enough privileges`);
const subordinate = await Worker.findById(ctx.args.workerFk);
const subordinate = await Worker.findById(ctx.args.workerFk, {fields: ['id']});
filter = mergeFilters(filter, {where: {
userFk: subordinate.userFk
userFk: subordinate.id
}});
return Self.find(filter);

View File

@ -20,11 +20,11 @@ describe('Worker new', () => {
const employeeId = 1;
const defaultWorker = {
fi: '78457139E',
name: 'defaultWorker',
firstName: 'default',
lastNames: 'worker',
name: 'DEFAULTERWORKER',
firstName: 'DEFAULT',
lastNames: 'WORKER',
email: 'defaultWorker@mydomain.com',
street: 'S/ defaultWorkerStreet',
street: 'S/ DEFAULTWORKERSTREET',
city: 'defaultWorkerCity',
provinceFk: 1,
companyFk: 442,

View File

@ -25,8 +25,7 @@
"type" : "string"
},
"userFk": {
"type" : "number",
"required": true
"type" : "number"
},
"bossFk": {
"type" : "number"

View File

@ -36,7 +36,7 @@ class Controller extends ModuleCard {
this.$http.get(`Workers/${this.$params.id}`, {filter})
.then(res => this.worker = res.data),
this.$http.get(`Workers/${this.$params.id}/activeContract`)
.then(res => this.hasWorkCenter = res.data.workCenterFk)
.then(res => this.hasWorkCenter = res.data?.workCenterFk)
]);
}
}

View File

@ -32,6 +32,28 @@ class Controller extends Descriptor {
this.vnApp.showSuccess(this.$t('Department deleted.'));
});
}
loadData() {
const filter = {
fields: ['id', 'name', 'code', 'workerFk', 'isProduction', 'chatName',
'isTeleworking', 'notificationEmail', 'hasToRefill', 'hasToSendMail', 'hasToMistake', 'clientFk'],
include: [
{relation: 'client',
scope: {
fields: ['id', 'name']
}},
{
relation: 'worker',
scope: {
fields: ['id', 'firstName', 'lastName']
}
}
]
};
return this.getData(`Departments/${this.id}`, {filter})
.then(res => this.entity = res.data);
}
}
Controller.$inject = ['$element', '$scope', '$rootScope'];

View File

@ -4,7 +4,7 @@
filter="::$ctrl.filter"
data="$ctrl.hours">
</vn-crud-model>
<div ng-if="$ctrl.card.hasWorkCenter">
<div>
<vn-card class="vn-pa-lg vn-w-lg">
<vn-table model="model" auto-load="false">
<vn-thead>
@ -106,12 +106,6 @@
</vn-button>
</vn-button-bar>
</div>
<div
ng-if="!$ctrl.card.hasWorkCenter"
class="bg-title"
translate>
Autonomous worker
</div>
<vn-side-menu side="right">
<div class="vn-pa-md">

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "salix-back",
"version": "23.32.01",
"version": "23.32.02",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@ -1,6 +1,6 @@
{
"name": "salix-back",
"version": "23.32.01",
"version": "23.34.01",
"author": "Verdnatura Levante SL",
"description": "Salix backend",
"license": "GPL-3.0",

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