Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 3951-invoiceOut.index_downloadPdfs

This commit is contained in:
Vicent Llopis 2022-11-03 07:11:54 +01:00
commit 35c50cb314
180 changed files with 3387 additions and 473 deletions

View File

@ -13,7 +13,7 @@ RUN apt-get update \
libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 \
libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 \
libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget \
&& curl -sL https://deb.nodesource.com/setup_12.x | bash - \
&& curl -sL https://deb.nodesource.com/setup_14.x | bash - \
&& apt-get install -y --no-install-recommends \
nodejs \
&& apt-get purge -y --auto-remove \

14
Jenkinsfile vendored
View File

@ -62,13 +62,13 @@ pipeline {
}
}
}
stage('Backend') {
steps {
nodejs('node-v14') {
sh 'npm run test:back:ci'
}
}
}
// stage('Backend') {
// steps {
// nodejs('node-v14') {
// sh 'npm run test:back:ci'
// }
// }
// }
}
}
stage('Build') {

View File

@ -29,6 +29,8 @@ module.exports = Self => {
});
Self.privileges = async function(ctx, id, roleFk, hasGrant, options) {
if (!(hasGrant != null || roleFk)) return;
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
@ -37,22 +39,40 @@ module.exports = Self => {
if (typeof options == 'object')
Object.assign(myOptions, options);
const user = await models.Account.findById(userId, null, myOptions);
const user = await models.Account.findById(userId, {fields: ['hasGrant']}, myOptions);
const userToUpdate = await models.Account.findById(id, {
fields: ['id', 'name', 'hasGrant', 'roleFk', 'password'],
include: {
relation: 'role',
scope: {
fields: ['name']
}
}
}, myOptions);
if (!user.hasGrant)
throw new UserError(`You don't have enough privileges`);
throw new UserError(`You don't have grant privilege`);
const hasRoleFromUser = await models.Account.hasRole(userId, userToUpdate.role().name, myOptions);
if (!hasRoleFromUser)
throw new UserError(`You don't own the role and you can't assign it to another user`);
const userToUpdate = await models.Account.findById(id);
if (hasGrant != null)
return await userToUpdate.updateAttribute('hasGrant', hasGrant, myOptions);
if (!roleFk) return;
userToUpdate.hasGrant = hasGrant;
const role = await models.Role.findById(roleFk, null, myOptions);
const hasRole = await models.Account.hasRole(userId, role.name, myOptions);
if (roleFk) {
const role = await models.Role.findById(roleFk, {fields: ['name']}, myOptions);
const hasRole = await models.Account.hasRole(userId, role.name, myOptions);
if (!hasRole)
throw new UserError(`You don't have enough privileges`);
if (!hasRole)
throw new UserError(`You don't own the role and you can't assign it to another user`);
await userToUpdate.updateAttribute('roleFk', roleFk, myOptions);
userToUpdate.roleFk = roleFk;
}
await userToUpdate.save(userToUpdate);
await models.UserAccount.sync(userToUpdate.name);
};
};

View File

@ -4,7 +4,9 @@ describe('account privileges()', () => {
const employeeId = 1;
const developerId = 9;
const sysadminId = 66;
const bruceWayneId = 1101;
const itBossId = 104;
const rootId = 100;
const clarkKent = 1103;
it('should throw an error when user not has privileges', async() => {
const ctx = {req: {accessToken: {userId: developerId}}};
@ -22,7 +24,7 @@ describe('account privileges()', () => {
await tx.rollback();
}
expect(error.message).toContain(`You don't have enough privileges`);
expect(error.message).toContain(`You don't have grant privilege`);
});
it('should throw an error when user has privileges but not has the role', async() => {
@ -33,12 +35,7 @@ describe('account privileges()', () => {
try {
const options = {transaction: tx};
const root = await models.Role.findOne({
where: {
name: 'root'
}
}, options);
await models.Account.privileges(ctx, employeeId, root.id, null, options);
await models.Account.privileges(ctx, employeeId, rootId, null, options);
await tx.rollback();
} catch (e) {
@ -46,7 +43,26 @@ describe('account privileges()', () => {
await tx.rollback();
}
expect(error.message).toContain(`You don't have enough privileges`);
expect(error.message).toContain(`You don't own the role and you can't assign it to another user`);
});
it('should throw an error when user has privileges but not has the role from user', async() => {
const ctx = {req: {accessToken: {userId: sysadminId}}};
const tx = await models.Account.beginTransaction({});
let error;
try {
const options = {transaction: tx};
await models.Account.privileges(ctx, itBossId, developerId, null, options);
await tx.rollback();
} catch (e) {
error = e;
await tx.rollback();
}
expect(error.message).toContain(`You don't own the role and you can't assign it to another user`);
});
it('should change role', async() => {
@ -63,8 +79,8 @@ describe('account privileges()', () => {
let error;
let result;
try {
await models.Account.privileges(ctx, bruceWayneId, agency.id, null, options);
result = await models.Account.findById(bruceWayneId, null, options);
await models.Account.privileges(ctx, clarkKent, agency.id, null, options);
result = await models.Account.findById(clarkKent, null, options);
await tx.rollback();
} catch (e) {
@ -84,8 +100,8 @@ describe('account privileges()', () => {
let result;
try {
const options = {transaction: tx};
await models.Account.privileges(ctx, bruceWayneId, null, true, options);
result = await models.Account.findById(bruceWayneId, null, options);
await models.Account.privileges(ctx, clarkKent, null, true, options);
result = await models.Account.findById(clarkKent, null, options);
await tx.rollback();
} catch (e) {

View File

@ -0,0 +1,46 @@
module.exports = Self => {
Self.remoteMethod('clean', {
description: 'clean notifications from queue',
accessType: 'WRITE',
returns: {
type: 'object',
root: true
},
http: {
path: `/clean`,
verb: 'POST'
}
});
Self.clean = async options => {
const models = Self.app.models;
const status = ['sent', 'error'];
const myOptions = {};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const config = await models.NotificationConfig.findOne({}, myOptions);
const cleanDate = new Date();
cleanDate.setDate(cleanDate.getDate() - config.cleanDays);
await models.NotificationQueue.destroyAll({
where: {status: {inq: status}},
created: {lt: cleanDate}
}, myOptions);
if (tx) await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,81 @@
const {Email} = require('vn-print');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('send', {
description: 'Send notifications from queue',
accessType: 'WRITE',
returns: {
type: 'object',
root: true
},
http: {
path: `/send`,
verb: 'POST'
}
});
Self.send = async options => {
const models = Self.app.models;
const findStatus = 'pending';
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const notificationQueue = await models.NotificationQueue.find({
where: {status: findStatus},
include: [
{
relation: 'notification',
scope: {
include: {
relation: 'subscription',
scope: {
include: {
relation: 'user',
scope: {
fields: ['name', 'email', 'lang']
}
}
}
}
}
}
]
}, myOptions);
const statusSent = 'sent';
const statusError = 'error';
for (const queue of notificationQueue) {
const queueName = queue.notification().name;
const queueParams = JSON.parse(queue.params);
for (const notificationUser of queue.notification().subscription()) {
try {
const sendParams = {
recipient: notificationUser.user().email,
lang: notificationUser.user().lang
};
if (notificationUser.userFk == queue.authorFk) {
await queue.updateAttribute('status', statusSent);
continue;
}
const newParams = Object.assign({}, queueParams, sendParams);
const email = new Email(queueName, newParams);
if (process.env.NODE_ENV != 'test')
await email.send();
await queue.updateAttribute('status', statusSent);
} catch (error) {
await queue.updateAttribute('status', statusError);
}
}
}
};
};

View File

@ -0,0 +1,42 @@
const models = require('vn-loopback/server/server').models;
describe('Notification Clean()', () => {
it('should delete old rows with error', async() => {
const userId = 9;
const status = 'error';
const tx = await models.NotificationQueue.beginTransaction({});
const options = {transaction: tx};
const notification = await models.Notification.findOne({}, options);
const notificationConfig = await models.NotificationConfig.findOne({});
const cleanDate = new Date();
cleanDate.setDate(cleanDate.getDate() - (notificationConfig.cleanDays + 1));
let before;
let after;
try {
const notificationDelete = await models.NotificationQueue.create({
notificationFk: notification.name,
authorFk: userId,
status: status,
created: cleanDate
}, options);
before = await models.NotificationQueue.findById(notificationDelete.id, null, options);
await models.Notification.clean(options);
after = await models.NotificationQueue.findById(notificationDelete.id, null, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(before.notificationFk).toEqual(notification.name);
expect(after).toBe(null);
});
});

View File

@ -0,0 +1,33 @@
const models = require('vn-loopback/server/server').models;
describe('Notification Send()', () => {
it('should send notification', async() => {
const statusPending = 'pending';
const tx = await models.NotificationQueue.beginTransaction({});
const options = {transaction: tx};
const filter = {
where: {
status: statusPending
}
};
let before;
let after;
try {
before = await models.NotificationQueue.find(filter, options);
await models.Notification.send(options);
after = await models.NotificationQueue.find(filter, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(before.length).toEqual(3);
expect(after.length).toEqual(0);
});
});

View File

@ -1,12 +1,13 @@
const jsdom = require('jsdom');
const mysql = require('mysql');
const FormData = require('form-data');
module.exports = Self => {
Self.remoteMethodCtx('closeTicket', {
description: 'Close tickets without response from the user',
accessType: 'READ',
returns: {
type: 'Object',
type: 'object',
root: true
},
http: {
@ -54,9 +55,9 @@ module.exports = Self => {
});
});
await requestToken();
await getRequestToken();
async function requestToken() {
async function getRequestToken() {
const response = await fetch(ostUri);
const result = response.headers.get('set-cookie');
@ -93,24 +94,45 @@ module.exports = Self => {
await close(token, secondCookie);
}
async function getLockCode(token, secondCookie, ticketId) {
const ostUri = `${config.host}/ajax.php/lock/ticket/${ticketId}`;
const params = {
method: 'POST',
headers: {
'X-CSRFToken': token,
'Cookie': secondCookie
}
};
const response = await fetch(ostUri, params);
const body = await response.text();
const json = JSON.parse(body);
return json.code;
}
async function close(token, secondCookie) {
for (const ticketId of ticketsId) {
const ostUri = `${config.host}/ajax.php/tickets/${ticketId}/status`;
const data = {
status_id: config.newStatusId,
comments: config.comment,
undefined: config.action
};
const lockCode = await getLockCode(token, secondCookie, ticketId);
let form = new FormData();
form.append('__CSRFToken__', token);
form.append('id', ticketId);
form.append('a', config.responseType);
form.append('lockCode', lockCode);
form.append('from_email_id', config.fromEmailId);
form.append('reply-to', config.replyTo);
form.append('cannedResp', 0);
form.append('response', config.comment);
form.append('signature', 'none');
form.append('reply_status_id', config.newStatusId);
const ostUri = `${config.host}/tickets.php?id=${ticketId}`;
const params = {
method: 'POST',
body: new URLSearchParams(data),
body: form,
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-CSRFToken': token,
'Cookie': secondCookie
}
};
return fetch(ostUri, params);
}
}

View File

@ -77,6 +77,21 @@
"Module": {
"dataSource": "vn"
},
"Notification": {
"dataSource": "vn"
},
"NotificationAcl": {
"dataSource": "vn"
},
"NotificationConfig": {
"dataSource": "vn"
},
"NotificationQueue": {
"dataSource": "vn"
},
"NotificationSubscription": {
"dataSource": "vn"
},
"Province": {
"dataSource": "vn"
},
@ -101,6 +116,9 @@
"Town": {
"dataSource": "vn"
},
"Url": {
"dataSource": "vn"
},
"UserConfig": {
"dataSource": "vn"
},

View File

@ -102,6 +102,13 @@
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
},
{
"property": "privileges",
"accessType": "*",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
}
]
}

View File

@ -0,0 +1,4 @@
module.exports = Self => {
require('../methods/notification/send')(Self);
require('../methods/notification/clean')(Self);
};

View File

@ -0,0 +1,30 @@
{
"name": "Notification",
"base": "VnModel",
"options": {
"mysql": {
"table": "util.notification"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"name": {
"type": "string",
"required": true
},
"description": {
"type": "string"
}
},
"relations": {
"subscription": {
"type": "hasMany",
"model": "NotificationSubscription",
"foreignKey": "notificationFk"
}
}
}

View File

@ -0,0 +1,21 @@
{
"name": "NotificationAcl",
"base": "VnModel",
"options": {
"mysql": {
"table": "util.notificationAcl"
}
},
"relations": {
"notification": {
"type": "belongsTo",
"model": "Notification",
"foreignKey": "notificationFk"
},
"role": {
"type": "belongsTo",
"model": "Role",
"foreignKey": "roleFk"
}
}
}

View File

@ -0,0 +1,19 @@
{
"name": "NotificationConfig",
"base": "VnModel",
"options": {
"mysql": {
"table": "util.notificationConfig"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"cleanDays": {
"type": "number"
}
}
}

View File

@ -0,0 +1,38 @@
{
"name": "NotificationQueue",
"base": "VnModel",
"options": {
"mysql": {
"table": "util.notificationQueue"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"params": {
"type": "string"
},
"status": {
"type": "string"
},
"created": {
"type": "date"
}
},
"relations": {
"notification": {
"type": "belongsTo",
"model": "Notification",
"foreignKey": "notificationFk",
"primaryKey": "name"
},
"author": {
"type": "belongsTo",
"model": "Account",
"foreignKey": "authorFk"
}
}
}

View File

@ -0,0 +1,33 @@
{
"name": "NotificationSubscription",
"base": "VnModel",
"options": {
"mysql": {
"table": "util.notificationSubscription"
}
},
"properties": {
"notificationFk": {
"type": "number",
"id": true,
"description": "Identifier"
},
"userFk": {
"type": "number",
"id": true,
"description": "Identifier"
}
},
"relations": {
"notification": {
"type": "belongsTo",
"model": "Notification",
"foreignKey": "notificationFk"
},
"user": {
"type": "belongsTo",
"model": "Account",
"foreignKey": "userFk"
}
}
}

View File

@ -27,9 +27,6 @@
"newStatusId": {
"type": "number"
},
"action": {
"type": "string"
},
"day": {
"type": "number"
},
@ -47,6 +44,15 @@
},
"portDb": {
"type": "number"
},
"responseType": {
"type": "string"
},
"fromEmailId": {
"type": "number"
},
"replyTo": {
"type": "string"
}
}
}

25
back/models/url.json Normal file
View File

@ -0,0 +1,25 @@
{
"name": "Url",
"base": "VnModel",
"options": {
"mysql": {
"table": "salix.url"
}
},
"properties": {
"appName": {
"type": "string",
"required": true,
"id": 1
},
"environment": {
"type": "string",
"required": true,
"id": 2
},
"url": {
"type": "string",
"required": true
}
}
}

View File

@ -1,5 +0,0 @@
ALTER TABLE `vn`.`itemType` CHANGE `transaction` transaction__ tinyint(4) DEFAULT 0 NOT NULL;
ALTER TABLE `vn`.`itemType` CHANGE location location__ varchar(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL NULL;
ALTER TABLE `vn`.`itemType` CHANGE hasComponents hasComponents__ tinyint(1) DEFAULT 1 NOT NULL;
ALTER TABLE `vn`.`itemType` CHANGE warehouseFk warehouseFk__ smallint(6) unsigned DEFAULT 60 NOT NULL;
ALTER TABLE `vn`.`itemType` CHANGE compression compression__ decimal(5,2) DEFAULT 1.00 NULL;

View File

@ -13,8 +13,4 @@ CREATE TABLE `vn`.`osTicketConfig` (
`passwordDb` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL,
`portDb` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
INSERT INTO `vn`.`osTicketConfig`(`id`, `host`, `user`, `password`, `oldStatus`, `newStatusId`, `action`, `day`, `comment`, `hostDb`, `userDb`, `passwordDb`, `portDb`)
VALUES
(0, 'https://cau.verdnatura.es/scp', NULL, NULL, 'open', 3, 'Cerrar', 60, 'Este CAU se ha cerrado automáticamente', NULL, NULL, NULL, NULL);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Receipt', 'receiptPdf', '*', 'ALLOW', 'ROLE', 'salesAssistant');

View File

@ -0,0 +1,2 @@
INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalId)
VALUES ('WorkerDisableExcluded','*','*','ALLOW','hr');

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Business', '*', '*', 'ALLOW', 'ROLE', 'hr');

View File

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

View File

@ -0,0 +1,2 @@
INSERT INTO `vn`.`payDem` (id,payDem)
VALUES (7,'0');

View File

@ -0,0 +1,2 @@
INSERT INTO `salix`.`ACL` (model,property,accessType,principalId)
VALUES ('Supplier','newSupplier','WRITE','administrative');

View File

@ -0,0 +1,28 @@
DROP FUNCTION IF EXISTS `util`.`notification_send`;
DELIMITER $$
CREATE DEFINER=`root`@`localhost` FUNCTION `util`.`notification_send`(vNotificationName VARCHAR(255), vParams TEXT, vAuthorFk INT)
RETURNS INT
MODIFIES SQL DATA
BEGIN
/**
* Sends a notification.
*
* @param vNotificationName The notification name
* @param vParams The notification parameters formatted as JSON
* @param vAuthorFk The notification author or %NULL if there is no author
* @return The notification id
*/
DECLARE vNotificationFk INT;
SELECT id INTO vNotificationFk
FROM `notification`
WHERE `name` = vNotificationName;
INSERT INTO notificationQueue
SET notificationFk = vNotificationFk,
params = vParams,
authorFk = vAuthorFk;
RETURN LAST_INSERT_ID();
END$$
DELIMITER ;

View File

@ -0,0 +1,63 @@
USE util;
CREATE TABLE notification(
id INT PRIMARY KEY,
`name` VARCHAR(255) UNIQUE,
`description` VARCHAR(255)
);
CREATE TABLE notificationAcl(
notificationFk INT,
roleFk INT(10) unsigned,
PRIMARY KEY(notificationFk, roleFk)
);
ALTER TABLE `util`.`notificationAcl` ADD CONSTRAINT `notificationAcl_ibfk_1` FOREIGN KEY (`notificationFk`) REFERENCES `util`.`notification` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE;
ALTER TABLE `util`.`notificationAcl` ADD CONSTRAINT `notificationAcl_ibfk_2` FOREIGN KEY (`roleFk`) REFERENCES `account`.`role`(`id`)
ON DELETE RESTRICT
ON UPDATE CASCADE;
CREATE TABLE notificationSubscription(
notificationFk INT,
userFk INT(10) unsigned,
PRIMARY KEY(notificationFk, userFk)
);
ALTER TABLE `util`.`notificationSubscription` ADD CONSTRAINT `notificationSubscription_ibfk_1` FOREIGN KEY (`notificationFk`) REFERENCES `util`.`notification` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE;
ALTER TABLE `util`.`notificationSubscription` ADD CONSTRAINT `notificationSubscription_ibfk_2` FOREIGN KEY (`userFk`) REFERENCES `account`.`user`(`id`)
ON DELETE CASCADE
ON UPDATE CASCADE;
CREATE TABLE notificationQueue(
id INT PRIMARY KEY AUTO_INCREMENT,
notificationFk VARCHAR(255),
params JSON,
authorFk INT(10) unsigned NULL,
`status` ENUM('pending', 'sent', 'error') NOT NULL DEFAULT 'pending',
created DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX(notificationFk),
INDEX(authorFk),
INDEX(status)
);
ALTER TABLE `util`.`notificationQueue` ADD CONSTRAINT `nnotificationQueue_ibfk_1` FOREIGN KEY (`notificationFk`) REFERENCES `util`.`notification` (`name`)
ON DELETE CASCADE
ON UPDATE CASCADE;
ALTER TABLE `util`.`notificationQueue` ADD CONSTRAINT `notificationQueue_ibfk_2` FOREIGN KEY (`authorFk`) REFERENCES `account`.`user`(`id`)
ON DELETE CASCADE
ON UPDATE CASCADE;
CREATE TABLE notificationConfig(
id INT PRIMARY KEY AUTO_INCREMENT,
cleanDays MEDIUMINT
);
INSERT INTO notificationConfig
SET cleanDays = 90;

View File

@ -0,0 +1,8 @@
ALTER TABLE `vn`.`osTicketConfig` DROP COLUMN `action`;
ALTER TABLE `vn`.`osTicketConfig` ADD responseType varchar(100) NULL;
ALTER TABLE `vn`.`osTicketConfig` ADD fromEmailId INT NULL;
ALTER TABLE `vn`.`osTicketConfig` ADD replyTo varchar(100) NULL;
UPDATE `vn`.`osTicketConfig`
SET responseType='reply', fromEmailId=5, replyTo='all'
WHERE id=0;

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`supplier` MODIFY COLUMN payMethodFk tinyint(3) unsigned NULL;

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`supplier` MODIFY COLUMN supplierActivityFk varchar(45) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL NULL;

View File

@ -1,7 +1,9 @@
drop procedure `vn`.`ticket_closeByTicket`;
DELIMITER $$
$$
create
definer = root@localhost procedure `vn`.`ticket_closeByTicket`(IN vTicketFk int)
definer = `root`@`localhost` procedure `vn`.`ticket_closeByTicket`(IN vTicketFk int)
BEGIN
/**
@ -27,5 +29,7 @@ BEGIN
CALL ticket_close();
DROP TEMPORARY TABLE tmp.ticket_close;
END;
END$$
DELIMITER ;

View File

@ -0,0 +1,5 @@
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('ClaimRma', '*', 'READ', 'ALLOW', 'ROLE', 'claimManager'),
('ClaimRma', '*', 'WRITE', 'ALLOW', 'ROLE', 'claimManager');

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Notification', '*', 'WRITE', 'ALLOW', 'ROLE', 'developer');

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`claim` ADD rma varchar(100) NULL ;

View File

@ -0,0 +1,7 @@
CREATE TABLE `vn`.`claimRma` (
id INT UNSIGNED auto_increment NOT NULL PRIMARY KEY,
code varchar(100) NOT NULL,
created timestamp DEFAULT current_timestamp() NOT NULL,
workerFk INTEGER UNSIGNED NOT NULL
)
ENGINE=InnoDB;

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`claimConfig` DROP COLUMN `pickupContact`;

View File

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

View File

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

View File

@ -0,0 +1,12 @@
CREATE TABLE `vn`.`packingSiteConfig` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`shinobiUrl` varchar(255) NOT NULL,
`shinobiToken` varchar(255) NOT NULL,
`shinobiGroupKey` varchar(255) NOT NULL,
`avgBoxingTime` INT(3) NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Boxing', '*', '*', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,56 @@
ALTER TABLE `vn`.`packingSite` ADD monitorId varchar(255) NULL;
UPDATE `vn`.`packingSite`
SET monitorId = 'VbiUcajdaT'
WHERE code = 'h1';
UPDATE `vn`.`packingSite`
SET monitorId = 'qKMPn9aaVe'
WHERE code = 'h2';
UPDATE `vn`.`packingSite`
SET monitorId = '3CtdIAGPAv'
WHERE code = 'h3';
UPDATE `vn`.`packingSite`
SET monitorId = 'Xme2hiqz1f'
WHERE code = 'h4';
UPDATE `vn`.`packingSite`
SET monitorId = 'aulxefgfJU'
WHERE code = 'h5';
UPDATE `vn`.`packingSite`
SET monitorId = '6Ou0D1bhBw'
WHERE code = 'h6';
UPDATE `vn`.`packingSite`
SET monitorId = 'eVUvnE6pNw'
WHERE code = 'h7';
UPDATE `vn`.`packingSite`
SET monitorId = '0wsmSvqmrs'
WHERE code = 'h8';
UPDATE `vn`.`packingSite`
SET monitorId = 'r2l2RyyF4I'
WHERE code = 'h9';
UPDATE `vn`.`packingSite`
SET monitorId = 'EdjHLIiDVD'
WHERE code = 'h10';
UPDATE `vn`.`packingSite`
SET monitorId = 'czC45kmwqI'
WHERE code = 'h11';
UPDATE `vn`.`packingSite`
SET monitorId = 'PNsmxPaCwQ'
WHERE code = 'h12';
UPDATE `vn`.`packingSite`
SET monitorId = 'agVssO0FDC'
WHERE code = 'h13';
UPDATE `vn`.`packingSite`
SET monitorId = 'f2SPNENHPo'
WHERE code = 'h14';
UPDATE `vn`.`packingSite`
SET monitorId = '6UR7gUZxks'
WHERE code = 'h15';
UPDATE `vn`.`packingSite`
SET monitorId = 'bOB0f8WZ2V'
WHERE code = 'h16';
UPDATE `vn`.`packingSite`
SET monitorId = 'MIR1nXaL0n'
WHERE code = 'h17';
UPDATE `vn`.`packingSite`
SET monitorId = '0Oj9SgGTXR'
WHERE code = 'h18';

View File

@ -0,0 +1,33 @@
CREATE TABLE `salix`.`url` (
`appName` varchar(100) NOT NULL,
`environment` varchar(100) NOT NULL,
`url` varchar(255) NOT NULL,
PRIMARY KEY (`appName`,`environment`)
);
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES
('salix', 'production', 'https://salix.verdnatura.es/#!/');
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES
('salix', 'test', 'https://test-salix.verdnatura.es/#!/');
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES
('salix', 'dev', 'http://localhost:5000/#!/');
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES
('lilium', 'production', 'https://lilium.verdnatura.es/#/');
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES
('lilium', 'test', 'https://test-lilium.verdnatura.es/#/');
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES
('lilium', 'dev', 'http://localhost:8080/#/');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Url', '*', 'READ', 'ALLOW', 'ROLE', 'employee');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Url', '*', 'WRITE', 'ALLOW', 'ROLE', 'it');

View File

@ -0,0 +1,54 @@
DROP PROCEDURE IF EXISTS `vn`.`zone_getPostalCode`;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`zone_getPostalCode`(vSelf INT)
BEGIN
/**
* Devuelve los códigos postales incluidos en una zona
*/
DECLARE vGeoFk INT DEFAULT NULL;
DROP TEMPORARY TABLE IF EXISTS tmp.zoneNodes;
CREATE TEMPORARY TABLE tmp.zoneNodes (
geoFk INT,
name VARCHAR(100),
parentFk INT,
sons INT,
isChecked BOOL DEFAULT 0,
zoneFk INT,
PRIMARY KEY zoneNodesPk (zoneFk, geoFk),
INDEX(geoFk))
ENGINE = MEMORY;
CALL zone_getLeaves2(vSelf, NULL , NULL);
UPDATE tmp.zoneNodes zn
SET isChecked = 0
WHERE parentFk IS NULL;
myLoop: LOOP
SET vGeoFk = NULL;
SELECT geoFk INTO vGeoFk
FROM tmp.zoneNodes zn
WHERE NOT isChecked
LIMIT 1;
CALL zone_getLeaves2(vSelf, vGeoFk, NULL);
UPDATE tmp.zoneNodes
SET isChecked = TRUE
WHERE geoFk = vGeoFk;
IF vGeoFk IS NULL THEN
LEAVE myLoop;
END IF;
END LOOP;
DELETE FROM tmp.zoneNodes
WHERE sons > 0;
SELECT zn.geoFk, zn.name
FROM tmp.zoneNodes zn
JOIN zone z ON z.id = zn.zoneFk;
END$$
DELIMITER ;

View File

@ -14,10 +14,10 @@ INSERT INTO `salix`.`AccessToken` (`id`, `ttl`, `created`, `userId`)
('DEFAULT_TOKEN', '1209600', util.VN_CURDATE(), 66);
INSERT INTO `salix`.`printConfig` (`id`, `itRecipient`, `incidencesEmail`)
VALUES
VALUES
(1, 'it@gotamcity.com', 'incidences@gotamcity.com');
INSERT INTO `vn`.`ticketConfig` (`id`, `scopeDays`)
INSERT INTO `vn`.`ticketConfig` (`id`, `scopeDays`)
VALUES
('1', '6');
@ -45,8 +45,8 @@ INSERT INTO `account`.`roleConfig`(`id`, `mysqlPassword`, `rolePrefix`, `userPre
CALL `account`.`role_sync`;
INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`,`email`, `lang`, `image`)
SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@mydomain.com'), 'en', '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd'
INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`,`email`, `lang`, `image`, `bcryptPassword`)
SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@mydomain.com'), 'en', '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2'
FROM `account`.`role` WHERE id <> 20
ORDER BY id;
@ -353,48 +353,48 @@ INSERT INTO `vn`.`clientConfig`(`riskTolerance`, `maxCreditRows`)
INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `phone`, `mobile`, `isActive`, `clientFk`, `agencyModeFk`, `longitude`, `latitude`, `isEqualizated`, `isDefaultAddress`)
VALUES
(1, 'Bruce Wayne', '1007 Mountain Drive, Gotham', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1101, 2, NULL, NULL, 0, 1),
(2, 'Petter Parker', '20 Ingram Street', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1102, 2, NULL, NULL, 0, 1),
(3, 'Clark Kent', '344 Clinton Street', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1103, 2, NULL, NULL, 0, 1),
(4, 'Tony Stark', '10880 Malibu Point', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1104, 2, NULL, NULL, 0, 1),
(5, 'Max Eisenhardt', 'Unknown Whereabouts', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 1),
(6, 'DavidCharlesHaller', 'Evil hideout', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 1),
(7, 'Hank Pym', 'Anthill', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 1),
(8, 'Charles Xavier', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 1),
(9, 'Bruce Banner', 'Somewhere in New York', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 1),
(10, 'Jessica Jones', 'NYCC 2015 Poster', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 1),
(11, 'Missing', 'The space', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1111, 10, NULL, NULL, 0, 1),
(12, 'Trash', 'New York city', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1112, 10, NULL, NULL, 0, 1),
(101, 'Somewhere in Thailand', 'address 01', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(102, 'Somewhere in Poland', 'address 02', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0),
(103, 'Somewhere in Japan', 'address 03', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0),
(104, 'Somewhere in Spain', 'address 04', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0),
(105, 'Somewhere in Potugal', 'address 05', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0),
(106, 'Somewhere in UK', 'address 06', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0),
(107, 'Somewhere in Valencia', 'address 07', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0),
(108, 'Somewhere in Gotham', 'address 08', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0),
(109, 'Somewhere in London', 'address 09', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(110, 'Somewhere in Algemesi', 'address 10', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(111, 'Somewhere in Carlet', 'address 11', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(112, 'Somewhere in Campanar', 'address 12', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(113, 'Somewhere in Malilla', 'address 13', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(114, 'Somewhere in France', 'address 14', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(115, 'Somewhere in Birmingham', 'address 15', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(116, 'Somewhere in Scotland', 'address 16', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(117, 'Somewhere in nowhere', 'address 17', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(118, 'Somewhere over the rainbow', 'address 18', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(119, 'Somewhere in Alberic', 'address 19', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(120, 'Somewhere in Montortal', 'address 20', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(121, 'the bat cave', 'address 21', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1101, 2, NULL, NULL, 0, 0),
(122, 'NY roofs', 'address 22', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1102, 2, NULL, NULL, 0, 0),
(123, 'The phone box', 'address 23', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1103, 2, NULL, NULL, 0, 0),
(124, 'Stark tower Gotham', 'address 24', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1104, 2, NULL, NULL, 0, 0),
(125, 'The plastic cell', 'address 25', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 0),
(126, 'Many places', 'address 26', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 0),
(127, 'Your pocket', 'address 27', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 0),
(128, 'Cerebro', 'address 28', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 0),
(129, 'Luke Cages Bar', 'address 29', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 0),
(130, 'Non valid address', 'address 30', 'Gotham', 46460, 1, 1111111111, 222222222, 0, 1101, 2, NULL, NULL, 0, 0);
(1, 'Bruce Wayne', '1007 Mountain Drive, Gotham', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1101, 2, NULL, NULL, 0, 1),
(2, 'Petter Parker', '20 Ingram Street', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1102, 2, NULL, NULL, 0, 1),
(3, 'Clark Kent', '344 Clinton Street', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1103, 2, NULL, NULL, 0, 1),
(4, 'Tony Stark', '10880 Malibu Point', 'Gotham', 46460, 1, 1111111111, 222222222, 1 , 1104, 2, NULL, NULL, 0, 1),
(5, 'Max Eisenhardt', 'Unknown Whereabouts', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 1),
(6, 'DavidCharlesHaller', 'Evil hideout', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 1),
(7, 'Hank Pym', 'Anthill', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 1),
(8, 'Charles Xavier', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 1),
(9, 'Bruce Banner', 'Somewhere in New York', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 1),
(10, 'Jessica Jones', 'NYCC 2015 Poster', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 1),
(11, 'Missing', 'The space', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1111, 10, NULL, NULL, 0, 1),
(12, 'Trash', 'New York city', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1112, 10, NULL, NULL, 0, 1),
(101, 'Somewhere in Thailand', 'address 01', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(102, 'Somewhere in Poland', 'address 02', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0),
(103, 'Somewhere in Japan', 'address 03', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0),
(104, 'Somewhere in Spain', 'address 04', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0),
(105, 'Somewhere in Potugal', 'address 05', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0),
(106, 'Somewhere in UK', 'address 06', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0),
(107, 'Somewhere in Valencia', 'address 07', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0),
(108, 'Somewhere in Gotham', 'address 08', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0),
(109, 'Somewhere in London', 'address 09', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(110, 'Somewhere in Algemesi', 'address 10', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(111, 'Somewhere in Carlet', 'address 11', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(112, 'Somewhere in Campanar', 'address 12', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(113, 'Somewhere in Malilla', 'address 13', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(114, 'Somewhere in France', 'address 14', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(115, 'Somewhere in Birmingham', 'address 15', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(116, 'Somewhere in Scotland', 'address 16', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(117, 'Somewhere in nowhere', 'address 17', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(118, 'Somewhere over the rainbow', 'address 18', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(119, 'Somewhere in Alberic', 'address 19', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(120, 'Somewhere in Montortal', 'address 20', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
(121, 'the bat cave', 'address 21', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1101, 2, NULL, NULL, 0, 0),
(122, 'NY roofs', 'address 22', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1102, 2, NULL, NULL, 0, 0),
(123, 'The phone box', 'address 23', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1103, 2, NULL, NULL, 0, 0),
(124, 'Stark tower Gotham', 'address 24', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1104, 2, NULL, NULL, 0, 0),
(125, 'The plastic cell', 'address 25', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 0),
(126, 'Many places', 'address 26', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 0),
(127, 'Your pocket', 'address 27', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 0),
(128, 'Cerebro', 'address 28', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 0),
(129, 'Luke Cages Bar', 'address 29', 'Gotham', 'EC170150', 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 0),
(130, 'Non valid address', 'address 30', 'Gotham', 46460, 1, 1111111111, 222222222, 0, 1101, 2, NULL, NULL, 0, 0);
INSERT INTO `vn`.`address`( `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `isActive`, `clientFk`, `agencyModeFk`, `isDefaultAddress`)
SELECT name, CONCAT(name, 'Street'), 'GOTHAM', 46460, 1, 1, id, 2, 1
@ -918,21 +918,21 @@ INSERT INTO `vn`.`expeditionStateType`(`id`, `description`, `code`)
(3, 'Perdida', 'LOST');
INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `created`, `itemFk`, `counter`, `workerFk`, `externalId`, `packagingFk`, `stateTypeFk`)
INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `created`, `itemFk`, `counter`, `workerFk`, `externalId`, `packagingFk`, `stateTypeFk`, `hostFk`)
VALUES
(1, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 15, 1, 18, 'UR9000006041', 94, 1),
(2, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 16, 2, 18, 'UR9000006041', 94, 1),
(3, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 3, 18, 'UR9000006041', 94, 2),
(4, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 4, 18, 'UR9000006041', 94, 2),
(5, 1, 2, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 1, 18, NULL, 94, 3),
(6, 7, 3, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), NULL, 1, 18, NULL, 94, 3),
(7, 2, 4, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), NULL, 1, 18, NULL, 94, NULL),
(8, 3, 5, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), NULL, 1, 18, NULL, 94, 1),
(9, 3, 6, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 1, 18, NULL, 94, 2),
(10, 7, 7, 71, NOW(), NULL, 1, 18, NULL, 94, 3),
(11, 7, 8, 71, NOW(), NULL, 1, 18, NULL, 94, 3),
(12, 7, 9, 71, NOW(), NULL, 1, 18, NULL, 94, 3),
(13, 1, 10, 71, NOW(), NULL, 1, 18, NULL, 94, 3);
(1, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 15, 1, 18, 'UR9000006041', 94, 1, 'pc1'),
(2, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 16, 2, 18, 'UR9000006041', 94, 1, NULL),
(3, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 3, 18, 'UR9000006041', 94, 2, NULL),
(4, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 4, 18, 'UR9000006041', 94, 2, NULL),
(5, 1, 2, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 1, 18, NULL, 94, 3, NULL),
(6, 7, 3, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), NULL, 1, 18, NULL, 94, 3, NULL),
(7, 2, 4, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), NULL, 1, 18, NULL, 94, NULL,NULL),
(8, 3, 5, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), NULL, 1, 18, NULL, 94, 1, NULL),
(9, 3, 6, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 1, 18, NULL, 94, 2, NULL),
(10, 7, 7, 71, NOW(), NULL, 1, 18, NULL, 94, 3, NULL),
(11, 7, 8, 71, NOW(), NULL, 1, 18, NULL, 94, 3, NULL),
(12, 7, 9, 71, NOW(), NULL, 1, 18, NULL, 94, 3, NULL),
(13, 1, 10,71, NOW(), NULL, 1, 18, NULL, 94, 3, NULL);
INSERT INTO `vn`.`expeditionState`(`id`, `created`, `expeditionFk`, `typeFk`, `userFk`)
@ -1380,13 +1380,6 @@ INSERT INTO `vn`.`entry`(`id`, `supplierFk`, `created`, `travelFk`, `isConfirmed
(7, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 7, 0, 442, 'Movement 7', 0, 0, 'this is the note seven', 'observation seven'),
(8, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 7, 0, 442, 'Movement 8', 1, 1, '', '');
INSERT INTO `vn`.`claimRatio`(`clientFk`, `yearSale`, `claimAmount`, `claimingRate`, `priceIncreasing`, `packingRate`)
VALUES
(1101, 500, NULL, 0.00, 0.00, 1.00),
(1102, 1000, 2.00, 0.01, 0.05, 1.00),
(1103, 2000, 0.00, 0.00, 0.02, 1.00),
(1104, 2500, 150.00, 0.02, 0.10, 1.00);
INSERT INTO `bs`.`waste`(`buyer`, `year`, `week`, `family`, `itemFk`, `itemTypeFk`, `saleTotal`, `saleWaste`, `rate`)
VALUES
('CharlesXavier', YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK), 1), 'Carnation', 1, 1, '1062', '51', '4.8'),
@ -1743,12 +1736,12 @@ INSERT INTO `vn`.`claimState`(`id`, `code`, `description`, `roleFk`, `priority`,
( 6, 'mana', 'Mana', 1, 4, 0),
( 7, 'lack', 'Faltas', 1, 2, 0);
INSERT INTO `vn`.`claim`(`id`, `ticketCreated`, `claimStateFk`, `clientFk`, `workerFk`, `responsibility`, `isChargedToMana`, `created`, `packages`)
INSERT INTO `vn`.`claim`(`id`, `ticketCreated`, `claimStateFk`, `clientFk`, `workerFk`, `responsibility`, `isChargedToMana`, `created`, `packages`, `rma`)
VALUES
(1, util.VN_CURDATE(), 1, 1101, 18, 3, 0, util.VN_CURDATE(), 0),
(2, util.VN_CURDATE(), 2, 1101, 18, 3, 0, util.VN_CURDATE(), 1),
(3, util.VN_CURDATE(), 3, 1101, 18, 1, 1, util.VN_CURDATE(), 5),
(4, util.VN_CURDATE(), 3, 1104, 18, 5, 0, util.VN_CURDATE(), 10);
(1, util.VN_CURDATE(), 1, 1101, 18, 3, 0, util.VN_CURDATE(), 0, '02676A049183'),
(2, util.VN_CURDATE(), 2, 1101, 18, 3, 0, util.VN_CURDATE(), 1, NULL),
(3, util.VN_CURDATE(), 3, 1101, 18, 1, 1, util.VN_CURDATE(), 5, NULL),
(4, util.VN_CURDATE(), 3, 1104, 18, 5, 0, util.VN_CURDATE(), 10, NULL);
INSERT INTO `vn`.`claimObservation` (`claimFk`, `workerFk`, `text`, `created`)
VALUES
@ -1785,10 +1778,27 @@ INSERT INTO `vn`.`claimEnd`(`id`, `saleFk`, `claimFk`, `workerFk`, `claimDestina
(1, 31, 4, 21, 2),
(2, 32, 3, 21, 3);
INSERT INTO `vn`.`claimConfig`(`id`, `pickupContact`, `maxResponsibility`)
INSERT INTO `vn`.`claimConfig`(`id`, `maxResponsibility`)
VALUES
(1, 'Contact description', 50),
(2, 'Contact description', 30);
(1, 50),
(2, 30);
INSERT INTO `vn`.`claimRatio`(`clientFk`, `yearSale`, `claimAmount`, `claimingRate`, `priceIncreasing`, `packingRate`)
VALUES
(1101, 500, NULL, 0.00, 0.00, 1.00),
(1102, 1000, 2.00, 0.01, 0.05, 1.00),
(1103, 2000, 0.00, 0.00, 0.02, 1.00),
(1104, 2500, 150.00, 0.02, 0.10, 1.00);
INSERT INTO vn.claimRma (`id`, `code`, `created`, `workerFk`)
VALUES
(1, '02676A049183', DEFAULT, 1106),
(2, '02676A049183', DEFAULT, 1106),
(3, '02676A049183', DEFAULT, 1107),
(4, '02676A049183', DEFAULT, 1107),
(5, '01837B023653', DEFAULT, 1106);
INSERT INTO `hedera`.`tpvMerchant`(`id`, `description`, `companyFk`, `bankFk`, `secretKey`)
VALUES
@ -2037,6 +2047,7 @@ INSERT INTO `vn`.`zoneIncluded` (`zoneFk`, `geoFk`, `isIncluded`)
(8, 4, 0),
(8, 5, 0),
(8, 1, 1),
(9, 7, 1),
(10, 14, 1);
INSERT INTO `vn`.`zoneEvent`(`zoneFk`, `type`, `dated`)
@ -2648,6 +2659,39 @@ INSERT INTO `vn`.`workerTimeControlConfig` (`id`, `dayBreak`, `dayBreakDriver`,
VALUES
(1, 43200, 32400, 129600, 259200, 604800, '', '', 'Leidos.exito', 'Leidos.error', 'timeControl', 5.33, 0.33, 40, '22:00:00', '06:00:00', 57600, 1200, 18000, 57600, 6, 13);
INSERT INTO `vn`.`host` (`id`, `code`, `description`, `warehouseFk`, `bankFk`)
VALUES
(1, 'pc1', 'pc host', 1, 1);
INSERT INTO `vn`.`packingSite` (`id`, `code`, `hostFk`, `monitorId`)
VALUES
(1, 'h1', 1, '');
INSERT INTO `vn`.`packingSiteConfig` (`shinobiUrl`, `shinobiToken`, `shinobiGroupKey`, `avgBoxingTime`)
VALUES
('', 'SHINNOBI_TOKEN', 'GROUP_TOKEN', 6000);
INSERT INTO `util`.`notificationConfig`
SET `cleanDays` = 90;
INSERT INTO `util`.`notification` (`id`, `name`, `description`)
VALUES
(1, 'print-email', 'notification fixture one');
INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`)
VALUES
(1, 9);
INSERT INTO `util`.`notificationQueue` (`id`, `notificationFk`, `params`, `authorFk`, `status`, `created`)
VALUES
(1, 'print-email', '{"id": "1"}', 9, 'pending', util.VN_CURDATE()),
(2, 'print-email', '{"id": "2"}', null, 'pending', util.VN_CURDATE()),
(3, 'print-email', null, null, 'pending', util.VN_CURDATE());
INSERT INTO `util`.`notificationSubscription` (`notificationFk`, `userFk`)
VALUES
(1, 1109),
(1, 1110);
INSERT INTO `vn`.`routeConfig` (`id`, `defaultWorkCenterFk`)
VALUES
(1, 9);
@ -2667,3 +2711,7 @@ INSERT INTO `vn`.`ticketCollection` (`ticketFk`, `collectionFk`, `created`, `lev
UPDATE `account`.`user`
SET `hasGrant` = 1
WHERE `id` = 66;
INSERT INTO `vn`.`osTicketConfig` (`id`, `host`, `user`, `password`, `oldStatus`, `newStatusId`, `day`, `comment`, `hostDb`, `userDb`, `passwordDb`, `portDb`, `responseType`, `fromEmailId`, `replyTo`)
VALUES
(0, 'http://localhost:56596/scp', 'ostadmin', 'Admin1', 'open', 3, 60, 'Este CAU se ha cerrado automáticamente. Si el problema persiste responda a este mensaje.', 'localhost', 'osticket', 'osticket', 40003, 'reply', 1, 'all');

View File

@ -596,7 +596,14 @@ export default {
submitNotesButton: 'button[type=submit]'
},
ticketExpedition: {
thirdExpeditionRemoveButton: 'vn-ticket-expedition vn-table div > vn-tbody > vn-tr:nth-child(3) > vn-td:nth-child(1) > vn-icon-button[icon="delete"]',
firstSaleCheckbox: 'vn-ticket-expedition vn-tr:nth-child(1) vn-check[ng-model="expedition.checked"]',
thirdSaleCheckbox: 'vn-ticket-expedition vn-tr:nth-child(3) vn-check[ng-model="expedition.checked"]',
deleteExpeditionButton: 'vn-ticket-expedition vn-tool-bar > vn-button[icon="delete"]',
moveExpeditionButton: 'vn-ticket-expedition vn-tool-bar > vn-button[icon="keyboard_arrow_down"]',
moreMenuWithoutRoute: 'vn-item[name="withoutRoute"]',
moreMenuWithRoute: 'vn-item[name="withRoute"]',
newRouteId: '.vn-dialog.shown vn-textfield[ng-model="$ctrl.newRoute"]',
saveButton: '.vn-dialog.shown [response="accept"]',
expeditionRow: 'vn-ticket-expedition vn-table vn-tbody > vn-tr'
},
ticketPackages: {

View File

@ -18,7 +18,8 @@ describe('Ticket expeditions and log path', () => {
});
it(`should delete a former expedition and confirm the remaining expedition are the expected ones`, async() => {
await page.waitToClick(selectors.ticketExpedition.thirdExpeditionRemoveButton);
await page.waitToClick(selectors.ticketExpedition.thirdSaleCheckbox);
await page.waitToClick(selectors.ticketExpedition.deleteExpeditionButton);
await page.waitToClick(selectors.globalItems.acceptButton);
await page.reloadSection('ticket.card.expedition');

View File

@ -0,0 +1,50 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Ticket expeditions', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('production', 'ticket');
await page.accessToSearchResult('1');
await page.accessToSection('ticket.card.expedition');
});
afterAll(async() => {
await browser.close();
});
it(`should move one expedition to new ticket withoute route`, async() => {
await page.waitToClick(selectors.ticketExpedition.thirdSaleCheckbox);
await page.waitToClick(selectors.ticketExpedition.moveExpeditionButton);
await page.waitToClick(selectors.ticketExpedition.moreMenuWithoutRoute);
await page.waitToClick(selectors.ticketExpedition.saveButton);
await page.waitForState('ticket.card.summary');
await page.accessToSection('ticket.card.expedition');
await page.waitForSelector(selectors.ticketExpedition.expeditionRow, {});
const result = await page
.countElement(selectors.ticketExpedition.expeditionRow);
expect(result).toEqual(1);
});
it(`should move one expedition to new ticket with route`, async() => {
await page.waitToClick(selectors.ticketExpedition.firstSaleCheckbox);
await page.waitToClick(selectors.ticketExpedition.moveExpeditionButton);
await page.waitToClick(selectors.ticketExpedition.moreMenuWithRoute);
await page.write(selectors.ticketExpedition.newRouteId, '1');
await page.waitToClick(selectors.ticketExpedition.saveButton);
await page.waitForState('ticket.card.summary');
await page.accessToSection('ticket.card.expedition');
await page.waitForSelector(selectors.ticketExpedition.expeditionRow, {});
const result = await page
.countElement(selectors.ticketExpedition.expeditionRow);
expect(result).toEqual(1);
});
});

View File

@ -31,7 +31,7 @@ describe('Supplier fiscal data path', () => {
await page.clearInput(selectors.supplierFiscalData.taxNumber);
await page.write(selectors.supplierFiscalData.taxNumber, 'Wrong tax number');
await page.clearInput(selectors.supplierFiscalData.account);
await page.write(selectors.supplierFiscalData.account, 'edited account number');
await page.write(selectors.supplierFiscalData.account, '0123456789');
await page.autocompleteSearch(selectors.supplierFiscalData.sageWihholding, 'retencion estimacion objetiva');
await page.autocompleteSearch(selectors.supplierFiscalData.sageTaxType, 'operaciones no sujetas');
@ -70,7 +70,7 @@ describe('Supplier fiscal data path', () => {
it('should check the account was edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierFiscalData.account, 'value');
expect(result).toEqual('edited account number');
expect(result).toEqual('0123456789');
});
it('should check the sageWihholding was edited', async() => {

View File

@ -29,4 +29,13 @@ describe('Account LDAP path', () => {
expect(message.text).toContain('Data saved!');
});
it('should reset data', async() => {
await page.waitToClick(selectors.accountLdap.checkEnable);
await page.waitToClick(selectors.accountLdap.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
});

View File

@ -29,4 +29,13 @@ describe('Account Samba path', () => {
expect(message.text).toContain('Data saved!');
});
it('should reset data', async() => {
await page.waitToClick(selectors.accountSamba.checkEnable);
await page.waitToClick(selectors.accountSamba.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
});

View File

@ -24,7 +24,7 @@ describe('Account privileges path', () => {
const message = await page.waitForSnackbar();
expect(message.text).toContain(`You don't have enough privileges`);
expect(message.text).toContain(`You don't have grant privilege`);
});
it('should throw error when change role', async() => {
@ -33,7 +33,7 @@ describe('Account privileges path', () => {
const message = await page.waitForSnackbar();
expect(message.text).toContain(`You don't have enough privileges`);
expect(message.text).toContain(`You don't have grant privilege`);
});
});
@ -56,7 +56,16 @@ describe('Account privileges path', () => {
expect(result).toBe('checked');
});
it('should change role', async() => {
it('should throw error when change role and not own role', async() => {
await page.autocompleteSearch(selectors.accountPrivileges.role, 'itBoss');
await page.waitToClick(selectors.accountPrivileges.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain(`You don't own the role and you can't assign it to another user`);
});
it('should change role to employee', async() => {
await page.autocompleteSearch(selectors.accountPrivileges.role, 'employee');
await page.waitToClick(selectors.accountPrivileges.save);
const message = await page.waitForSnackbar();
@ -67,6 +76,18 @@ describe('Account privileges path', () => {
expect(message.text).toContain(`Data saved!`);
expect(result).toContain('employee');
});
it('should return role to developer', async() => {
await page.autocompleteSearch(selectors.accountPrivileges.role, 'developer');
await page.waitToClick(selectors.accountPrivileges.save);
const message = await page.waitForSnackbar();
await page.reloadSection('account.card.privileges');
const result = await page.waitToGetProperty(selectors.accountPrivileges.role, 'value');
expect(message.text).toContain(`Data saved!`);
expect(result).toContain('developer');
});
});
describe('as developer again', () => {
@ -76,7 +97,12 @@ describe('Account privileges path', () => {
await page.waitToClick(selectors.accountPrivileges.checkHasGrant);
await page.waitToClick(selectors.accountPrivileges.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain(`Data saved!`);
});
it('should logIn in developer', async() => {
await page.reloadSection('account.card.privileges');
const result = await page.checkboxState(selectors.accountPrivileges.checkHasGrant);

View File

@ -54,6 +54,21 @@ export default class App {
localStorage.setItem('salix-version', newVersion);
}
}
getUrl(route, appName = 'lilium') {
const env = process.env.NODE_ENV;
const filter = {
where: {and: [
{appName: appName},
{environment: env}
]}
};
return this.logger.$http.get('Urls/findOne', {filter})
.then(res => {
return res.data.url + route;
});
}
}
ngModule.service('vnApp', App);

View File

@ -51,6 +51,7 @@ Entries: Entradas
Users: Usuarios
Suppliers: Proveedores
Monitors: Monitores
Shelvings: Carros
# Common

View File

@ -133,5 +133,8 @@
"Descanso semanal 36h. / 72h.": "Weekly rest 36h. / 72h.",
"Password does not meet requirements": "Password does not meet requirements",
"You don't have privileges to change the zone": "You don't have privileges to change the zone or for these parameters there are more than one shipping options, talk to agencies",
"Not enough privileges to edit a client": "Not enough privileges to edit a client"
}
"Not enough privileges to edit a client": "Not enough privileges to edit a client",
"Claim pickup order sent": "Claim pickup order sent [({{claimId}})]({{{claimUrl}}}) to client *{{clientName}}*",
"You don't have grant privilege": "You don't have grant privilege",
"You don't own the role and you can't assign it to another user": "You don't own the role and you can't assign it to another user"
}

View File

@ -235,5 +235,9 @@
"Dirección incorrecta": "Dirección incorrecta",
"Modifiable user details only by an administrator": "Detalles de usuario modificables solo por un administrador",
"Modifiable password only via recovery or by an administrator": "Contraseña modificable solo a través de la recuperación o por un administrador",
"Not enough privileges to edit a client": "No tienes suficientes privilegios para editar un cliente"
}
"Not enough privileges to edit a client": "No tienes suficientes privilegios para editar un cliente",
"This route does not exists": "Esta ruta no existe",
"Claim pickup order sent": "Reclamación Orden de recogida enviada [({{claimId}})]({{{claimUrl}}}) al cliente *{{clientName}}*",
"You don't have grant privilege": "No tienes privilegios para dar privilegios",
"You don't own the role and you can't assign it to another user": "No eres el propietario del rol y no puedes asignarlo a otro usuario"
}

View File

@ -1,2 +1,2 @@
Privileges: Privilegios
Has grant: Tiene privilegios
Has grant: Puede delegar privilegios

View File

@ -9,7 +9,7 @@ module.exports = Self => {
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
description: 'The claim id',
http: {source: 'path'}
},
{
@ -42,6 +42,11 @@ module.exports = Self => {
});
Self.claimPickupEmail = async ctx => {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const $t = ctx.req.__; // $translate
const origin = ctx.req.headers.origin;
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
@ -52,6 +57,34 @@ module.exports = Self => {
for (const param in args)
params[param] = args[param];
const claim = await models.Claim.findById(args.id, {
fields: ['id', 'clientFk'],
include: {
relation: 'client',
scope: {
fields: ['name', 'salesPersonFk']
}
}
});
const message = $t('Claim pickup order sent', {
claimId: args.id,
clientName: claim.client().name,
claimUrl: `${origin}/#!/claim/${args.id}/summary`,
});
const salesPersonId = claim.client().salesPersonFk;
if (salesPersonId)
await models.Chat.sendCheckingPresence(ctx, salesPersonId, message);
await models.ClaimLog.create({
originFk: args.id,
userFk: userId,
action: 'insert',
description: 'Claim-pickup-order sent',
changedModel: 'Mail'
});
const email = new Email('claim-pickup-order', params);
return email.send();

View File

@ -47,7 +47,7 @@ module.exports = Self => {
{
relation: 'claimState',
scope: {
fields: ['id', 'description']
fields: ['id', 'code', 'description']
}
},
{

View File

@ -2,6 +2,9 @@
"Claim": {
"dataSource": "vn"
},
"ClaimContainer": {
"dataSource": "claimStorage"
},
"ClaimBeginning": {
"dataSource": "vn"
},
@ -41,7 +44,7 @@
"ClaimObservation": {
"dataSource": "vn"
},
"ClaimContainer": {
"dataSource": "claimStorage"
}
"ClaimRma": {
"dataSource": "vn"
}
}

View File

@ -0,0 +1,9 @@
const LoopBackContext = require('loopback-context');
module.exports = Self => {
Self.observe('before save', async function(ctx) {
const changes = ctx.data || ctx.instance;
const loopBackContext = LoopBackContext.getCurrentContext();
changes.workerFk = loopBackContext.active.accessToken.userId;
});
};

View File

@ -0,0 +1,30 @@
{
"name": "ClaimRma",
"base": "VnModel",
"options": {
"mysql": {
"table": "claimRma"
}
},
"properties": {
"id": {
"id": true,
"type": "number",
"description": "Identifier"
},
"code": {
"type": "string",
"required": true
},
"created": {
"type": "date"
}
},
"relations": {
"worker": {
"type": "belongsTo",
"model": "Worker",
"foreignKey": "workerFk"
}
}
}

View File

@ -46,6 +46,9 @@
},
"packages": {
"type": "number"
},
"rma": {
"type": "string"
}
},
"relations": {
@ -54,6 +57,12 @@
"model": "ClaimState",
"foreignKey": "claimStateFk"
},
"rmas": {
"type": "hasMany",
"model": "ClaimRma",
"foreignKey": "code",
"primaryKey": "rma"
},
"client": {
"type": "belongsTo",
"model": "Client",

View File

@ -19,7 +19,7 @@
readonly="true">
</vn-textfield>
<vn-textfield
label="Created"
label="Created"
field="::$ctrl.claim.created | date:'yyyy-MM-dd HH:mm'"
readonly="true">
</vn-textfield>
@ -56,7 +56,7 @@
label="Pick up"
ng-model="$ctrl.claim.hasToPickUp"
vn-acl="claimManager"
info="When checked will notify to the salesPerson">
title="{{'When checked will notify to the salesPerson' | translate}}">
</vn-check>
</vn-horizontal>
</vn-card>

View File

@ -5,5 +5,5 @@ Responsability: Responsabilidad
Company: Empresa
Sales/Client: Comercial/Cliente
Pick up: Recoger
When checked will notify a pickup to the salesPerson: Cuando se marque enviará una notificación de recogida al comercial
Packages received: Bultos recibidos
When checked will notify to the salesPerson: Cuando se marque enviará una notificación de recogida al comercial
Packages received: Bultos recibidos

View File

@ -25,16 +25,23 @@
</vn-button-menu>
</h5>
<vn-horizontal>
<vn-one>
<vn-label-value
label="Created"
value="{{$ctrl.summary.claim.created | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value
label="State"
value="{{$ctrl.summary.claim.claimState.description}}">
</vn-label-value>
<vn-label-value
<vn-auto>
<h4>
<a
ui-sref="claim.card.basicData({id:$ctrl.claim.id})"
target="_self">
<span translate vn-tooltip="Go to">Basic data</span>
</a>
</h4>
<vn-label-value
label="Created"
value="{{$ctrl.summary.claim.created | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value
label="State"
value="{{$ctrl.summary.claim.claimState.description}}">
</vn-label-value>
<vn-label-value
label="Salesperson"
value="{{$ctrl.summary.claim.client.salesPersonUser.name}}">
</vn-label-value>
@ -42,16 +49,23 @@
label="Attended by"
value="{{$ctrl.summary.claim.worker.user.nickname}}">
</vn-label-value>
</vn-one>
<vn-check
class="vn-mr-md"
label="Pick up"
ng-model="$ctrl.summary.claim.hasToPickUp"
title="{{'When checked will notify to the salesPerson' | translate}}"
disabled="true">
</vn-check>
</vn-auto>
<vn-auto>
<h4 ng-show="$ctrl.isSalesPerson && $ctrl.summary.observations.length">
<a
<a
ui-sref="claim.card.note.index({id:$ctrl.claim.id})"
target="_self">
<span translate vn-tooltip="Go to">Observations</span>
</a>
</h4>
<h4
<h4
ng-show="!$ctrl.isSalesPerson && $ctrl.summary.observations.length"
translate>
Observations
@ -70,13 +84,13 @@
</vn-auto>
<vn-auto>
<h4 ng-show="$ctrl.isSalesPerson">
<a
<a
ui-sref="claim.card.detail({id:$ctrl.claim.id})"
target="_self">
<span translate vn-tooltip="Go to">Detail</span>
</a>
</h4>
<h4
<h4
ng-show="!$ctrl.isSalesPerson"
translate>
Detail
@ -98,7 +112,7 @@
<vn-tbody>
<vn-tr ng-repeat="saleClaimed in $ctrl.summary.salesClaimed">
<vn-td number>
<span
<span
ng-click="itemDescriptor.show($event, saleClaimed.sale.itemFk, saleClaimed.sale.id)"
class="link">
{{::saleClaimed.sale.itemFk | zeroFill:6}}
@ -111,7 +125,7 @@
<vn-td number>{{::saleClaimed.sale.price | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::saleClaimed.sale.discount}} %</vn-td>
<vn-td number>
{{saleClaimed.sale.quantity * saleClaimed.sale.price *
{{saleClaimed.sale.quantity * saleClaimed.sale.price *
((100 - saleClaimed.sale.discount) / 100) | currency: 'EUR':2}}
</vn-td>
</vn-tr>
@ -123,7 +137,7 @@
<h4 translate>Photos</h4>
<vn-horizontal class="photo-list">
<section class="photo" ng-repeat="photo in photos">
<section class="image" on-error-src
<section class="image" on-error-src
ng-style="{'background': 'url(' + $ctrl.getImagePath(photo.dmsFk) + ')'}"
zoom-image="{{$ctrl.getImagePath(photo.dmsFk)}}"
ng-if="photo.dms.contentType != 'video/mp4'">
@ -137,13 +151,13 @@
</vn-auto>
<vn-auto>
<h4 ng-show="$ctrl.isClaimManager">
<a
<a
ui-sref="claim.card.development({id:$ctrl.claim.id})"
target="_self">
<span translate vn-tooltip="Go to">Development</span>
</a>
</h4>
<h4
<h4
translate
ng-show="!$ctrl.isClaimManager">
Development
@ -165,8 +179,8 @@
<vn-td>{{::development.claimResult.description}}</vn-td>
<vn-td>{{::development.claimResponsible.description}}</vn-td>
<vn-td expand>
<span
class="link"
<span
class="link"
ng-click="workerDescriptor.show($event, development.workerFk)">
{{::development.worker.user.nickname}}
</span>
@ -179,21 +193,21 @@
</vn-auto>
<vn-auto>
<h4 ng-show="$ctrl.isClaimManager">
<a
<a
ui-sref="claim.card.action({id:$ctrl.claim.id})"
target="_self">
<span translate vn-tooltip="Go to">Action</span>
</a>
</h4>
<h4
translate
translate
ng-show="!$ctrl.isClaimManager">
Action
</h4>
<vn-horizontal>
<vn-one>
<vn-range
vn-one
vn-one
disabled="true"
label="Responsability"
min-label="Company"
@ -224,14 +238,14 @@
<vn-tbody>
<vn-tr ng-repeat="action in $ctrl.summary.actions">
<vn-td number>
<span
<span
ng-click="itemDescriptor.show($event, action.sale.itemFk, action.sale.id)"
class="link">
{{::action.sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td number>
<span
<span
ng-click="ticketDescriptor.show($event, action.sale.ticket.id)"
class="link">
{{::action.sale.ticket.id}}
@ -258,9 +272,9 @@
vn-id="item-descriptor"
warehouse-fk="$ctrl.vnConfig.warehouseFk">
</vn-item-descriptor-popover>
<vn-worker-descriptor-popover
<vn-worker-descriptor-popover
vn-id="worker-descriptor">
</vn-worker-descriptor-popover>
<vn-ticket-descriptor-popover
<vn-ticket-descriptor-popover
vn-id="ticket-descriptor">
</vn-ticket-descriptor-popover>
</vn-ticket-descriptor-popover>

View File

@ -25,10 +25,9 @@ module.exports = Self => {
const client = await Self.app.models.Client.findById(id, myOptions);
const emails = client.email ? client.email.split(',') : null;
const findParams = [];
if (emails.length) {
if (client.email) {
const emails = client.email.split(',');
for (let email of emails)
findParams.push({email: email});
}

View File

@ -0,0 +1,147 @@
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('filter', {
description: 'Find all clients matched by the filter',
accessType: 'READ',
accepts: [
{
arg: 'filter',
type: 'object',
},
{
arg: 'search',
type: 'string',
description: `If it's and integer searchs by id, otherwise it searchs by name`,
},
{
arg: 'name',
type: 'string',
description: 'The client name',
},
{
arg: 'salesPersonFk',
type: 'number',
},
{
arg: 'fi',
type: 'string',
description: 'The client fiscal id',
},
{
arg: 'socialName',
type: 'string',
},
{
arg: 'city',
type: 'string',
},
{
arg: 'postcode',
type: 'string',
},
{
arg: 'provinceFk',
type: 'number',
},
{
arg: 'email',
type: 'string',
},
{
arg: 'phone',
type: 'string',
},
{
arg: 'zoneFk',
type: 'number',
},
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/filter`,
verb: 'GET'
}
});
Self.filter = async(ctx, filter, options) => {
const conn = Self.dataSource.connector;
const myOptions = {};
const postalCode = [];
const args = ctx.args;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (args.zoneFk) {
query = `CALL vn.zone_getPostalCode(?)`;
const [geos] = await Self.rawSql(query, [args.zoneFk]);
for (let geo of geos)
postalCode.push(geo.name);
}
const where = buildFilter(ctx.args, (param, value) => {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {'c.id': {inq: value}}
: {'c.name': {like: `%${value}%`}};
case 'name':
case 'salesPersonFk':
case 'fi':
case 'socialName':
case 'city':
case 'postcode':
case 'provinceFk':
case 'email':
case 'phone':
param = `c.${param}`;
return {[param]: value};
case 'zoneFk':
param = 'a.postalCode';
return {[param]: {inq: postalCode}};
}
});
filter = mergeFilters(filter, {where});
const stmts = [];
const stmt = new ParameterizedSQL(
`SELECT
DISTINCT c.id,
c.name,
c.fi,
c.socialName,
c.phone,
c.city,
c.postcode,
c.email,
c.isActive,
c.isFreezed,
p.id AS provinceFk,
p.name AS province,
u.id AS salesPersonFk,
u.name AS salesPerson
FROM client c
LEFT JOIN account.user u ON u.id = c.salesPersonFk
LEFT JOIN province p ON p.id = c.provinceFk
JOIN vn.address a ON a.clientFk = c.id
`
);
stmt.merge(conn.makeWhere(filter.where));
stmt.merge(conn.makePagination(filter));
const clientsIndex = stmts.push(stmt) - 1;
const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql, myOptions);
return clientsIndex === 0 ? result : result[clientsIndex];
};
};

View File

@ -0,0 +1,199 @@
const {models} = require('vn-loopback/server/server');
describe('client filter()', () => {
it('should return the clients matching the filter with a limit of 20 rows', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}, args: {}};
const filter = {limit: '8'};
const result = await models.Client.filter(ctx, filter, options);
expect(result.length).toEqual(8);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the client "Bruce Wayne" matching the search argument with his name', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}, args: {search: 'Bruce Wayne'}};
const filter = {};
const result = await models.Client.filter(ctx, filter, options);
const firstResult = result[0];
expect(result.length).toEqual(1);
expect(firstResult.name).toEqual('Bruce Wayne');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the client "Bruce Wayne" matching the search argument with his id', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}, args: {search: '1101'}};
const filter = {};
const result = await models.Client.filter(ctx, filter, options);
const firstResult = result[0];
expect(result.length).toEqual(1);
expect(firstResult.name).toEqual('Bruce Wayne');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the client "Bruce Wayne" matching the name argument', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}, args: {name: 'Bruce Wayne'}};
const filter = {};
const result = await models.Client.filter(ctx, filter, options);
const firstResult = result[0];
expect(result.length).toEqual(1);
expect(firstResult.name).toEqual('Bruce Wayne');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the clients matching the "salesPersonFk" argument', async() => {
const tx = await models.Client.beginTransaction({});
const salesPersonId = 18;
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}, args: {salesPersonFk: salesPersonId}};
const filter = {};
const result = await models.Client.filter(ctx, filter, options);
const randomIndex = Math.floor(Math.random() * result.length);
const randomResultClient = result[randomIndex];
expect(result.length).toBeGreaterThanOrEqual(5);
expect(randomResultClient.salesPersonFk).toEqual(salesPersonId);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the clients matching the "fi" argument', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}, args: {fi: '251628698'}};
const filter = {};
const result = await models.Client.filter(ctx, filter, options);
const firstClient = result[0];
expect(result.length).toEqual(1);
expect(firstClient.name).toEqual('Max Eisenhardt');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the clients matching the "city" argument', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}, args: {city: 'Gotham'}};
const filter = {};
const result = await models.Client.filter(ctx, filter, options);
const randomIndex = Math.floor(Math.random() * result.length);
const randomResultClient = result[randomIndex];
expect(result.length).toBeGreaterThanOrEqual(20);
expect(randomResultClient.city.toLowerCase()).toEqual('gotham');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the clients matching the "postcode" argument', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}, args: {postcode: '46460'}};
const filter = {};
const result = await models.Client.filter(ctx, filter, options);
const randomIndex = Math.floor(Math.random() * result.length);
const randomResultClient = result[randomIndex];
expect(result.length).toBeGreaterThanOrEqual(20);
expect(randomResultClient.postcode).toEqual('46460');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the clients matching the "zoneFk" argument', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}, args: {zoneFk: 9}};
const filter = {};
const result = await models.Client.filter(ctx, filter, options);
expect(result.length).toBe(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,55 @@
const {Report} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('receiptPdf', {
description: 'Returns the receipt pdf',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The claim id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
}
],
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/receipt-pdf',
verb: 'GET'
}
});
Self.receiptPdf = async(ctx, id) => {
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('receipt', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
};

View File

@ -8,6 +8,9 @@
"BankEntity": {
"dataSource": "vn"
},
"Business": {
"dataSource": "vn"
},
"BusinessType": {
"dataSource": "vn"
},

View File

@ -0,0 +1,27 @@
{
"name": "Business",
"base": "VnModel",
"options": {
"mysql": {
"table": "business"
}
},
"properties": {
"id": {
"type": "number",
"id": true
}
},
"relations": {
"worker": {
"type": "belongsTo",
"model": "Worker",
"foreignKey": "workerFk"
},
"department": {
"type": "belongsTo",
"model": "Department",
"foreignKey": "departmentFk"
}
}
}

View File

@ -47,4 +47,5 @@ module.exports = Self => {
require('../methods/client/incotermsAuthorizationHtml')(Self);
require('../methods/client/incotermsAuthorizationEmail')(Self);
require('../methods/client/consumptionSendQueued')(Self);
require('../methods/client/filter')(Self);
};

View File

@ -425,14 +425,19 @@ module.exports = Self => {
account.observe('before save', async ctx => {
if (ctx.isNewInstance) return;
ctx.hookState.oldInstance = JSON.parse(JSON.stringify(ctx.currentInstance));
if (ctx.currentInstance)
ctx.hookState.oldInstance = JSON.parse(JSON.stringify(ctx.currentInstance));
});
account.observe('after save', async ctx => {
const changes = ctx.data || ctx.instance;
if (!ctx.isNewInstance && changes) {
const oldData = ctx.hookState.oldInstance;
const hasChanges = oldData.name != changes.name || oldData.active != changes.active;
let hasChanges;
if (oldData)
hasChanges = oldData.name != changes.name || oldData.active != changes.active;
if (!hasChanges) return;
const isClient = await Self.app.models.Client.count({id: oldData.id});

View File

@ -2,6 +2,7 @@ const LoopBackContext = require('loopback-context');
module.exports = function(Self) {
require('../methods/receipt/filter')(Self);
require('../methods/receipt/receiptPdf')(Self);
Self.validateBinded('amountPaid', isNotZero, {
message: 'Amount cannot be zero',

View File

@ -144,12 +144,8 @@ class Controller extends Dialog {
})
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')))
.then(() => {
if (this.viewReceipt) {
this.vnReport.show('receipt', {
receiptId: receiptId,
companyId: this.companyFk
});
}
if (this.viewReceipt)
this.vnReport.show(`Receipts/${receiptId}/receipt-pdf`);
});
}

View File

@ -85,6 +85,8 @@ describe('Client', () => {
});
it('should make an http POST query and then call to the report show() method', () => {
const receiptId = 1;
jest.spyOn(controller.vnApp, 'showSuccess');
jest.spyOn(controller.vnReport, 'show');
window.open = jest.fn();
@ -92,14 +94,12 @@ describe('Client', () => {
controller.$params = {id: 1101};
controller.viewReceipt = true;
$httpBackend.expect('POST', `Clients/1101/createReceipt`).respond({id: 1});
$httpBackend.expect('POST', `Clients/1101/createReceipt`).respond({id: receiptId});
controller.responseHandler('accept');
$httpBackend.flush();
const expectedParams = {receiptId: 1, companyId: 442};
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
expect(controller.vnReport.show).toHaveBeenCalledWith('receipt', expectedParams);
expect(controller.vnReport.show).toHaveBeenCalledWith(`Receipts/${receiptId}/receipt-pdf`);
});
});

View File

@ -1,6 +1,6 @@
<vn-crud-model
vn-id="model"
url="Clients"
url="Clients/filter"
order="id DESC"
limit="8"
data="clients">
@ -10,8 +10,7 @@
vn-focus
panel="vn-client-search-panel"
info="Search client by id or name"
model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
model="model">
</vn-searchbar>
</vn-portal>
<vn-portal slot="menu">

View File

@ -2,32 +2,6 @@ import ngModule from '../module';
import ModuleMain from 'salix/components/module-main';
export default class Client extends ModuleMain {
exprBuilder(param, value) {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {id: value}
: {or: [{name: {like: `%${value}%`}}, {socialName: {like: `%${value}%`}}]};
case 'phone':
return {
or: [
{phone: value},
{mobile: value}
]
};
case 'name':
case 'socialName':
case 'city':
case 'email':
return {[param]: {like: `%${value}%`}};
case 'id':
case 'fi':
case 'postcode':
case 'provinceFk':
case 'salesPersonFk':
return {[param]: value};
}
}
}
ngModule.vnComponent('vnClient', {

View File

@ -58,6 +58,13 @@
value-field="id"
label="Province">
</vn-autocomplete>
<vn-autocomplete
ng-model="filter.zoneFk"
url="Zones"
show-field="name"
value-field="id"
label="Zone">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textfield

View File

@ -0,0 +1,51 @@
module.exports = Self => {
Self.remoteMethod('deleteItemShelvings', {
description: 'Deletes the selected item shelvings',
accessType: 'WRITE',
accepts: [{
arg: 'itemShelvingIds',
type: ['number'],
required: true,
description: 'The itemShelving ids to delete'
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/deleteItemShelvings`,
verb: 'POST'
}
});
Self.deleteItemShelvings = async(itemShelvingIds, 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 {
const promises = [];
for (let itemShelvingId of itemShelvingIds) {
const itemShelvingToDelete = models.ItemShelving.destroyById(itemShelvingId, myOptions);
promises.push(itemShelvingToDelete);
}
const deletedItemShelvings = await Promise.all(promises);
if (tx) await tx.commit();
return deletedItemShelvings;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,21 @@
const models = require('vn-loopback/server/server').models;
describe('ItemShelving deleteItemShelvings()', () => {
it('should return the deleted itemShelvings', async() => {
const tx = await models.Order.beginTransaction({});
try {
const options = {transaction: tx};
const itemShelvingIds = [1, 2];
const result = await models.ItemShelving.deleteItemShelvings(itemShelvingIds, options);
expect(result.length).toEqual(2);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -53,6 +53,9 @@
"ItemShelvingSale": {
"dataSource": "vn"
},
"ItemShelvingPlacementSupplyStock": {
"dataSource": "vn"
},
"ItemImageQueue": {
"dataSource": "vn"
},

View File

@ -0,0 +1,36 @@
{
"name": "ItemShelvingPlacementSupplyStock",
"base": "VnModel",
"options": {
"mysql": {
"table": "itemShelvingPlacementSupplyStock"
}
},
"properties": {
"itemShelvingFk": {
"type": "number",
"id": true
},
"created": {
"type": "date"
},
"itemFk": {
"type": "number"
},
"longName": {
"type": "string"
},
"parking": {
"type": "string"
},
"shelving": {
"type": "string"
},
"packing": {
"type": "number"
},
"stock": {
"type": "number"
}
}
}

View File

@ -0,0 +1,3 @@
module.exports = Self => {
require('../methods/item-shelving/deleteItemShelvings')(Self);
};

View File

@ -24,3 +24,5 @@ import './waste/detail';
import './fixed-price';
import './fixed-price-search-panel';
import './item-type';
import './item-shelving';

View File

@ -0,0 +1,118 @@
<vn-crud-model
vn-id="model"
url="ItemShelvingPlacementSupplyStocks"
link="{itemFk: $ctrl.$params.id}"
data="$ctrl.itemShelvingPlacementSupplyStocks"
auto-load="true">
</vn-crud-model>
<vn-card>
<smart-table
model="model"
options="$ctrl.smartTableOptions"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-actions>
<div>
<div class="totalBox" style="text-align: center;">
<h6 translate>Total</h6>
<vn-label-value
label="Total labels"
value="{{$ctrl.labelTotal.toFixed(2)}}">
</vn-label-value>
</div>
</div>
<div class="vn-pa-md">
<vn-button
disabled="!$ctrl.checked.length"
ng-click="removeConfirm.show()"
icon="delete"
vn-tooltip="Remove selected lines"
vn-acl="replenisherBos">
</vn-button>
</div>
</slot-actions>
<slot-table>
<table>
<thead>
<tr>
<th shrink>
<vn-multi-check
model="model">
</vn-multi-check>
</th>
<th field="created">
<span translate>Created</span>
</th>
<th shrink field="itemFk">
<span translate>Item</span>
</th>
<th
field="longName">
<span translate>Concept</span>
</th>
<th
field="parking">
<span translate>Parking</span>
</th>
<th field="shelving">
<span translate>Shelving</span>
</th>
<th
field="label">
<span translate>Etiqueta</span>
</th>
<th
field="packing"
shrink>
<span translate>Packing</span>
</th>
</tr>
</thead>
<tbody>
<tr
ng-repeat="itemShelvingPlacementSupplyStock in $ctrl.itemShelvingPlacementSupplyStocks"
vn-repeat-last on-last="$ctrl.calculateTotals()">
<td shrink>
<vn-check
ng-model="itemShelvingPlacementSupplyStock.checked"
vn-click-stop>
</vn-check>
</td>
<td shrink-date>{{::itemShelvingPlacementSupplyStock.created | date: 'dd/MM/yyyy'}}</td>
<td>
{{::itemShelvingPlacementSupplyStock.itemFk}}
</td>
<td expand title="{{::itemShelvingPlacementSupplyStock.longName}}">
<span
vn-click-stop="itemDescriptor.show($event, itemShelvingPlacementSupplyStock.itemFk)"
class="link">
{{itemShelvingPlacementSupplyStock.longName}}
</span>
</td>
<td>
{{::itemShelvingPlacementSupplyStock.parking}}
</td>
<td>
{{::itemShelvingPlacementSupplyStock.shelving}}
</td>
<td>
{{(itemShelvingPlacementSupplyStock.stock / itemShelvingPlacementSupplyStock.packing).toFixed(2)}}
</td>
<td>
{{::itemShelvingPlacementSupplyStock.packing}}
</td>
</tr>
</tbody>
</table>
</slot-table>
</smart-table>
</vn-card>
<vn-item-descriptor-popover
vn-id="item-descriptor">
</vn-item-descriptor-popover>
<vn-confirm
vn-id="removeConfirm"
message="Selected lines will be deleted"
question="Are you sure you want to continue?"
on-accept="$ctrl.onRemove()">
</vn-confirm>

View File

@ -0,0 +1,89 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.smartTableOptions = {
activeButtons: {
search: true
},
columns: [
{
field: 'parking',
autocomplete: {
url: 'Parkings',
showField: 'code',
valueField: 'code'
}
},
{
field: 'shelving',
autocomplete: {
url: 'Shelvings',
showField: 'code',
valueField: 'code'
}
},
{
field: 'created',
searchable: false
},
{
field: 'itemFk',
searchable: false
},
{
field: 'longName',
searchable: false
}
]
};
}
get checked() {
const itemShelvings = this.$.model.data || [];
const checkedLines = [];
for (let itemShelving of itemShelvings) {
if (itemShelving.checked)
checkedLines.push(itemShelving.itemShelvingFk);
}
return checkedLines;
}
calculateTotals() {
this.labelTotal = 0;
const itemShelvings = this.$.model.data || [];
itemShelvings.forEach(itemShelving => {
const label = itemShelving.stock / itemShelving.packing;
this.labelTotal += label;
});
}
onRemove() {
const params = {itemShelvingIds: this.checked};
const query = `ItemShelvings/deleteItemShelvings`;
this.$http.post(query, params)
.then(() => {
this.vnApp.showSuccess(this.$t('ItemShelvings removed'));
this.$.model.refresh();
});
}
exprBuilder(param, value) {
switch (param) {
case 'parking':
case 'shelving':
case 'label':
case 'packing':
return {[param]: value};
}
}
}
ngModule.vnComponent('vnItemShelving', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,81 @@
import './index';
import crudModel from 'core/mocks/crud-model';
describe('item shelving', () => {
describe('Component vnItemShelving', () => {
let controller;
let $httpBackend;
beforeEach(ngModule('item'));
beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
const $element = angular.element('<vn-item-shelving></vn-item-shelving>');
controller = $componentController('vnItemShelving', {$element});
controller.$.model = crudModel;
controller.$.model.data = [
{itemShelvingFk: 1, packing: 10, stock: 1},
{itemShelvingFk: 2, packing: 12, stock: 5},
{itemShelvingFk: 4, packing: 20, stock: 10}
];
const modelData = controller.$.model.data;
modelData[0].checked = true;
modelData[1].checked = true;
}));
describe('checked() getter', () => {
it('should return a the selected rows', () => {
const result = controller.checked;
expect(result).toEqual(expect.arrayContaining([1, 2]));
});
});
describe('calculateTotals()', () => {
it('should calculate the total of labels', () => {
controller.calculateTotals();
expect(controller.labelTotal).toEqual(1.0166666666666666);
});
});
describe('onRemove()', () => {
it('shoud remove the selected lines', () => {
jest.spyOn(controller.$.model, 'refresh');
const expectedParams = {itemShelvingIds: [1, 2]};
$httpBackend.expectPOST('ItemShelvings/deleteItemShelvings', expectedParams).respond(200);
controller.onRemove();
$httpBackend.flush();
expect(controller.$.model.refresh).toHaveBeenCalled();
});
});
describe('exprBuilder()', () => {
it('should search by parking', () => {
const expr = controller.exprBuilder('parking', '700-01');
expect(expr).toEqual({'parking': '700-01'});
});
it('should search by shelving', () => {
const expr = controller.exprBuilder('shelving', 'AAA');
expect(expr).toEqual({'shelving': 'AAA'});
});
it('should search by label', () => {
const expr = controller.exprBuilder('label', 0.17);
expect(expr).toEqual({'label': 0.17});
});
it('should search by packing', () => {
const expr = controller.exprBuilder('packing', 10);
expect(expr).toEqual({'packing': 10});
});
});
});
});

View File

@ -0,0 +1,5 @@
Shelving: Matrícula
Remove selected lines: Eliminar líneas seleccionadas
Selected lines will be deleted: Las líneas seleccionadas serán eliminadas
ItemShelvings removed: Carros eliminados
Total labels: Total etiquetas

View File

@ -54,6 +54,7 @@ Basic data: Datos básicos
Tax: IVA
History: Historial
Botanical: Botánico
Shelvings: Carros
Barcodes: Códigos de barras
Diary: Histórico
Item diary: Registro de compra-venta

View File

@ -15,11 +15,12 @@
"card": [
{"state": "item.card.basicData", "icon": "settings"},
{"state": "item.card.tags", "icon": "icon-tags"},
{"state": "item.card.last-entries", "icon": "icon-regentry"},
{"state": "item.card.tax", "icon": "icon-tax"},
{"state": "item.card.botanical", "icon": "local_florist"},
{"state": "item.card.botanical", "icon": "local_florist"},
{"state": "item.card.shelving", "icon": "icon-inventory"},
{"state": "item.card.itemBarcode", "icon": "icon-barcode"},
{"state": "item.card.diary", "icon": "icon-transaction"},
{"state": "item.card.last-entries", "icon": "icon-regentry"},
{"state": "item.card.log", "icon": "history"}
],
"itemType": [
@ -92,6 +93,16 @@
},
"acl": ["buyer"]
},
{
"url" : "/shelving",
"state": "item.card.shelving",
"component": "vn-item-shelving",
"description": "Shelvings",
"params": {
"item": "$ctrl.item"
},
"acl": ["employee"]
},
{
"url" : "/barcode",
"state": "item.card.itemBarcode",

View File

@ -13,9 +13,6 @@
{"state": "shelving.card.log", "icon": "history"}
]
},
"keybindings": [
{"key": "s", "state": "shelving.index"}
],
"routes": [
{
"url": "/shelving",

View File

@ -95,8 +95,8 @@ module.exports = Self => {
pm.name AS payMethod,
pd.payDem AS payDem
FROM vn.supplier s
JOIN vn.payMethod pm ON pm.id = s.payMethodFk
JOIN vn.payDem pd ON pd.id = s.payDemFk`
LEFT JOIN vn.payMethod pm ON pm.id = s.payMethodFk
LEFT JOIN vn.payDem pd ON pd.id = s.payDemFk`
);
stmt.merge(conn.makeSuffix(filter));

View File

@ -0,0 +1,45 @@
module.exports = Self => {
Self.remoteMethod('newSupplier', {
description: 'Creates a new supplier and returns it',
accessType: 'WRITE',
accepts: [{
arg: 'params',
type: 'object',
http: {source: 'body'}
}],
returns: {
type: 'string',
root: true
},
http: {
path: `/newSupplier`,
verb: 'POST'
}
});
Self.newSupplier = async params => {
const models = Self.app.models;
const myOptions = {};
if (typeof(params) == 'string')
params = JSON.parse(params);
params.nickname = params.name;
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const supplier = await models.Supplier.create(params, myOptions);
if (tx) await tx.commit();
return supplier;
} catch (e) {
if (tx) await tx.rollback();
return params;
}
};
};

View File

@ -10,7 +10,7 @@ describe('Supplier filter()', () => {
let result = await app.models.Supplier.filter(ctx);
expect(result.length).toEqual(1);
expect(result.length).toBeGreaterThanOrEqual(1);
expect(result[0].id).toEqual(1);
});

View File

@ -0,0 +1,30 @@
const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('Supplier newSupplier()', () => {
const newSupp = {
name: 'TestSupplier-1'
};
const administrativeId = 5;
it('should create a new supplier containing only the name', async() => {
const activeCtx = {
accessToken: {userId: administrativeId},
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
let result = await app.models.Supplier.newSupplier(JSON.stringify(newSupp));
expect(result.name).toEqual('TestSupplier-1');
expect(result.id).toEqual(443);
const createdSupplier = await app.models.Supplier.findById(result.id);
expect(createdSupplier.id).toEqual(result.id);
expect(createdSupplier.name).toEqual(result.name);
expect(createdSupplier.payDemFk).toEqual(7);
expect(createdSupplier.nickname).toEqual(result.name);
});
});

View File

@ -10,6 +10,7 @@ module.exports = Self => {
require('../methods/supplier/freeAgencies')(Self);
require('../methods/supplier/campaignMetricsPdf')(Self);
require('../methods/supplier/campaignMetricsEmail')(Self);
require('../methods/supplier/newSupplier')(Self);
Self.validatesPresenceOf('name', {
message: 'The social name cannot be empty'
@ -19,13 +20,17 @@ module.exports = Self => {
message: 'The supplier name must be unique'
});
Self.validatesPresenceOf('city', {
message: 'City cannot be empty'
});
if (this.city) {
Self.validatesPresenceOf('city', {
message: 'City cannot be empty'
});
}
Self.validatesPresenceOf('nif', {
message: 'The nif cannot be empty'
});
if (this.nif) {
Self.validatesPresenceOf('nif', {
message: 'The nif cannot be empty'
});
}
Self.validatesUniquenessOf('nif', {
message: 'TIN must be unique'
@ -57,6 +62,9 @@ module.exports = Self => {
}
async function tinIsValid(err, done) {
if (!this.countryFk)
return done();
const filter = {
fields: ['code'],
where: {id: this.countryFk}
@ -80,6 +88,7 @@ module.exports = Self => {
});
async function hasSupplierAccount(err, done) {
if (!this.payMethodFk) return done();
const payMethod = await Self.app.models.PayMethod.findById(this.payMethodFk);
const supplierAccount = await Self.app.models.SupplierAccount.findOne({where: {supplierFk: this.id}});
const hasIban = supplierAccount && supplierAccount.iban;
@ -92,6 +101,7 @@ module.exports = Self => {
}
Self.observe('before save', async function(ctx) {
if (ctx.isNewInstance) return;
const loopbackContext = LoopBackContext.getCurrentContext();
const changes = ctx.data || ctx.instance;
const orgData = ctx.currentInstance;
@ -101,7 +111,7 @@ module.exports = Self => {
const isPayMethodChecked = changes.isPayMethodChecked || orgData.isPayMethodChecked;
const hasChanges = orgData && changes;
const isPayMethodCheckedChanged = hasChanges
&& orgData.isPayMethodChecked != isPayMethodChecked;
&& orgData.isPayMethodChecked != isPayMethodChecked;
if (isNotFinancial && isPayMethodCheckedChanged)
throw new UserError('You can not modify is pay method checked');
@ -114,7 +124,7 @@ module.exports = Self => {
const socialName = changes.name || orgData.name;
const hasChanges = orgData && changes;
const socialNameChanged = hasChanges
&& orgData.socialName != socialName;
&& orgData.socialName != socialName;
if ((socialNameChanged) && !isAlpha(socialName))
throw new UserError('The social name has an invalid format');

View File

@ -0,0 +1,29 @@
<vn-watcher
vn-id="watcher"
url="suppliers/newSupplier"
data="$ctrl.supplier"
insert-mode="true"
form="form">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
label="Supplier name"
ng-model="$ctrl.supplier.name"
vn-focus>
</vn-textfield>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Create">
</vn-submit>
<vn-button
class="cancel"
label="Cancel"
ui-sref="supplier.index">
</vn-button>
</vn-button-bar>
</form>

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