Merge branch 'dev' into 4074-download-user-ACL
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Alexandre Riera 2023-05-04 12:39:55 +00:00
commit ede35db638
308 changed files with 7900 additions and 2443 deletions

View File

@ -8,7 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [2316.01] - 2023-05-04
### Added
-
- (Usuarios -> Histórico) Nueva sección
- (Roles -> Histórico) Nueva sección
### Changed
- (Artículo -> Precio fijado) Modificado el buscador superior por uno lateral

1
Jenkinsfile vendored
View File

@ -52,6 +52,7 @@ pipeline {
}}}
environment {
NODE_ENV = ""
TZ = 'Europe/Madrid'
}
parallel {
stage('Frontend') {

View File

@ -0,0 +1,17 @@
name: account
columns:
id: id
name: name
roleFk: role
nickname: nickname
lang: lang
password: password
bcryptPassword: bcrypt password
active: active
email: email
emailVerified: email verified
created: created
updated: updated
image: image
hasGrant: has grant
userFk: user

View File

@ -0,0 +1,17 @@
name: cuenta
columns:
id: id
name: nombre
roleFk: rol
nickname: apodo
lang: idioma
password: contraseña
bcryptPassword: contraseña bcrypt
active: activo
email: email
emailVerified: email verificado
created: creado
updated: actualizado
image: imagen
hasGrant: tiene permiso
userFk: usuario

View File

@ -40,8 +40,7 @@ module.exports = Self => {
try {
const sale = await models.Sale.findById(saleId, null, myOptions);
const saleUpdated = await sale.updateAttributes({
originalQuantity: sale.quantity,
quantity: quantity
quantity
}, myOptions);
if (tx) await tx.commit();

View File

@ -30,7 +30,7 @@ describe('setSaleQuantity()', () => {
await models.Collection.setSaleQuantity(saleId, newQuantity, options);
const updateSale = await models.Sale.findById(saleId, null, options);
expect(updateSale.originalQuantity).toEqual(originalSale.quantity);
expect(updateSale.quantity).not.toEqual(originalSale.quantity);
expect(updateSale.quantity).toEqual(newQuantity);
await tx.rollback();

130
back/methods/image/scrub.js Normal file
View File

@ -0,0 +1,130 @@
const fs = require('fs-extra');
const path = require('path');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('scrub', {
description: 'Deletes images without database reference',
accessType: 'WRITE',
accepts: [
{
arg: 'collection',
type: 'string',
description: 'The collection name',
required: true
}, {
arg: 'remove',
type: 'boolean',
description: 'Delete instead of move images to trash'
}, {
arg: 'limit',
type: 'integer',
description: 'Maximum number of images to clean'
}, {
arg: 'dryRun',
type: 'boolean',
description: 'Simulate actions'
}, {
arg: 'skipLock',
type: 'boolean',
description: 'Wether to skip exclusive lock'
}
],
returns: {
type: 'integer',
root: true
},
http: {
path: `/scrub`,
verb: 'POST'
}
});
Self.scrub = async function(collection, remove, limit, dryRun, skipLock) {
const $ = Self.app.models;
const env = process.env.NODE_ENV;
dryRun = dryRun || (env && env !== 'production');
const instance = await $.ImageCollection.findOne({
fields: ['id'],
where: {name: collection}
});
if (!instance)
throw new UserError('Collection does not exist');
const container = await $.ImageContainer.container(collection);
const rootPath = container.client.root;
let tx;
let opts;
const lockName = 'salix.Image.scrub';
if (!skipLock) {
tx = await Self.beginTransaction({timeout: null});
opts = {transaction: tx};
const [row] = await Self.rawSql(
`SELECT GET_LOCK(?, 10) hasLock`, [lockName], opts);
if (!row.hasLock)
throw new UserError('Cannot obtain exclusive lock');
}
try {
const now = Date.vnNew().toJSON();
const scrubDir = path.join(rootPath, '.scrub', now);
const collectionDir = path.join(rootPath, collection);
const sizes = await fs.readdir(collectionDir);
let cleanCount = 0;
mainLoop: for (const size of sizes) {
const sizeDir = path.join(collectionDir, size);
const scrubSizeDir = path.join(scrubDir, collection, size);
const images = await fs.readdir(sizeDir);
for (const image of images) {
const imageName = path.parse(image).name;
const count = await Self.count({
collectionFk: collection,
name: imageName
}, opts);
const exists = count > 0;
let scrubDirCreated = false;
if (!exists) {
const srcFile = path.join(sizeDir, image);
if (remove !== true) {
if (!scrubDirCreated) {
if (!dryRun)
await fs.mkdir(scrubSizeDir, {recursive: true});
scrubDirCreated = true;
}
const dstFile = path.join(scrubSizeDir, image);
if (!dryRun) await fs.rename(srcFile, dstFile);
} else {
try {
if (!dryRun) await fs.unlink(srcFile);
} catch (err) {
console.error(err.message);
}
}
cleanCount++;
if (limit && cleanCount == limit)
break mainLoop;
}
}
}
return cleanCount;
} finally {
if (!skipLock) {
try {
await Self.rawSql(`DO RELEASE_LOCK(?)`, [lockName], opts);
await tx.rollback();
} catch (err) {
console.error(err.message);
}
}
}
};
};

View File

@ -12,13 +12,13 @@ module.exports = Self => {
type: 'Number',
description: 'The entity id',
required: true
},
{
}, {
arg: 'collection',
type: 'string',
description: 'The collection name',
required: true
}],
}
],
returns: {
type: 'Object',
root: true

View File

@ -5,6 +5,7 @@ const gm = require('gm');
module.exports = Self => {
require('../methods/image/download')(Self);
require('../methods/image/upload')(Self);
require('../methods/image/scrub')(Self);
Self.resize = async function({collectionName, srcFile, fileName, entityId}) {
const models = Self.app.models;
@ -29,13 +30,14 @@ module.exports = Self => {
);
// Insert image row
const imageName = path.parse(fileName).name;
await models.Image.upsertWithWhere(
{
name: fileName,
name: imageName,
collectionFk: collectionName
},
{
name: fileName,
name: imageName,
collectionFk: collectionName,
updated: Date.vnNow() / 1000,
}
@ -49,7 +51,7 @@ module.exports = Self => {
if (entity) {
await entity.updateAttribute(
collection.property,
fileName
imageName
);
}

View File

@ -53,7 +53,7 @@ async function test() {
const JunitReporter = require('jasmine-reporters');
jasmine.addReporter(new JunitReporter.JUnitXmlReporter());
jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 90000;
jasmine.exitOnCompletion = true;
}

View File

@ -8,7 +8,7 @@ CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`invoiceOut_new`(
BEGIN
/**
* Creación de facturas emitidas.
* requiere previamente tabla ticketToInvoice(id).
* requiere previamente tabla tmp.ticketToInvoice(id).
*
* @param vSerial serie a la cual se hace la factura
* @param vInvoiceDate fecha de la factura
@ -36,13 +36,13 @@ BEGIN
SELECT t.clientFk, t.companyFk
INTO vClient, vCompany
FROM ticketToInvoice tt
FROM tmp.ticketToInvoice tt
JOIN ticket t ON t.id = tt.id
LIMIT 1;
-- Eliminem de ticketToInvoice els tickets que no han de ser facturats
-- Eliminem de tmp.ticketToInvoice els tickets que no han de ser facturats
DELETE ti.*
FROM ticketToInvoice ti
FROM tmp.ticketToInvoice ti
JOIN ticket t ON t.id = ti.id
JOIN sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
@ -57,7 +57,7 @@ BEGIN
SELECT SUM(s.quantity * s.price * (100 - s.discount)/100), ts.id
INTO vIsAnySaleToInvoice, vIsAnyServiceToInvoice
FROM ticketToInvoice t
FROM tmp.ticketToInvoice t
LEFT JOIN sale s ON s.ticketFk = t.id
LEFT JOIN ticketService ts ON ts.ticketFk = t.id;
@ -100,13 +100,13 @@ BEGIN
WHERE id = vNewInvoiceId;
UPDATE ticket t
JOIN ticketToInvoice ti ON ti.id = t.id
JOIN tmp.ticketToInvoice ti ON ti.id = t.id
SET t.refFk = vNewRef;
DROP TEMPORARY TABLE IF EXISTS tmp.updateInter;
CREATE TEMPORARY TABLE tmp.updateInter ENGINE = MEMORY
SELECT s.id,ti.id ticket_id,vWorker Id_Trabajador
FROM ticketToInvoice ti
FROM tmp.ticketToInvoice ti
LEFT JOIN ticketState ts ON ti.id = ts.ticket
JOIN state s
WHERE IFNULL(ts.alertLevel,0) < 3 and s.`code` = getAlert3State(ti.id);
@ -116,7 +116,7 @@ BEGIN
INSERT INTO ticketLog (action, userFk, originFk, description)
SELECT 'UPDATE', account.myUser_getId(), ti.id, CONCAT('Crea factura ', vNewRef)
FROM ticketToInvoice ti;
FROM tmp.ticketToInvoice ti;
CALL invoiceExpenceMake(vNewInvoiceId);
CALL invoiceTaxMake(vNewInvoiceId,vTaxArea);
@ -159,7 +159,7 @@ BEGIN
(KEY (ticketFk))
ENGINE = MEMORY
SELECT id ticketFk
FROM ticketToInvoice;
FROM tmp.ticketToInvoice;
CALL `ticket_getTax`('NATIONAL');
@ -220,6 +220,6 @@ BEGIN
END IF;
DROP TEMPORARY TABLE `ticketToInvoice`;
DROP TEMPORARY TABLE `tmp`.`ticketToInvoice`;
END$$
DELIMITER ;

View File

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

View File

@ -10,7 +10,7 @@ CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`invoiceOut_new`(
BEGIN
/**
* Creación de facturas emitidas.
* requiere previamente tabla ticketToInvoice(id).
* requiere previamente tabla tmp.ticketToInvoice(id).
*
* @param vSerial serie a la cual se hace la factura
* @param vInvoiceDate fecha de la factura
@ -49,7 +49,7 @@ BEGIN
vCompanyFk,
vMaxShipped,
vIsCorrectInvoiceDate
FROM ticketToInvoice tt
FROM tmp.ticketToInvoice tt
JOIN ticket t ON t.id = tt.id;
IF(vMaxShipped > vInvoiceDate) THEN
@ -60,9 +60,9 @@ BEGIN
CALL util.throw('Exists an invoice with a previous date');
END IF;
-- Eliminem de ticketToInvoice els tickets que no han de ser facturats
-- Eliminem de tmp.ticketToInvoice els tickets que no han de ser facturats
DELETE ti.*
FROM ticketToInvoice ti
FROM tmp.ticketToInvoice ti
JOIN ticket t ON t.id = ti.id
JOIN sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
@ -77,11 +77,11 @@ BEGIN
SELECT SUM(s.quantity * s.price * (100 - s.discount)/100) <> 0
INTO vIsAnySaleToInvoice
FROM ticketToInvoice t
FROM tmp.ticketToInvoice t
JOIN sale s ON s.ticketFk = t.id;
SELECT COUNT(*) > 0 INTO vIsAnyServiceToInvoice
FROM ticketToInvoice t
FROM tmp.ticketToInvoice t
JOIN ticketService ts ON ts.ticketFk = t.id;
IF (vIsAnySaleToInvoice OR vIsAnyServiceToInvoice)
@ -121,13 +121,13 @@ BEGIN
WHERE id = vNewInvoiceId;
UPDATE ticket t
JOIN ticketToInvoice ti ON ti.id = t.id
JOIN tmp.ticketToInvoice ti ON ti.id = t.id
SET t.refFk = vNewRef;
DROP TEMPORARY TABLE IF EXISTS tmp.updateInter;
CREATE TEMPORARY TABLE tmp.updateInter ENGINE = MEMORY
SELECT s.id,ti.id ticket_id,vWorker Id_Trabajador
FROM ticketToInvoice ti
FROM tmp.ticketToInvoice ti
LEFT JOIN ticketState ts ON ti.id = ts.ticket
JOIN state s
WHERE IFNULL(ts.alertLevel,0) < 3 and s.`code` = getAlert3State(ti.id);
@ -137,7 +137,7 @@ BEGIN
INSERT INTO ticketLog (action, userFk, originFk, description)
SELECT 'UPDATE', account.myUser_getId(), ti.id, CONCAT('Crea factura ', vNewRef)
FROM ticketToInvoice ti;
FROM tmp.ticketToInvoice ti;
CALL invoiceExpenceMake(vNewInvoiceId);
CALL invoiceTaxMake(vNewInvoiceId,vTaxArea);
@ -175,7 +175,7 @@ BEGIN
(KEY (ticketFk))
ENGINE = MEMORY
SELECT id ticketFk
FROM ticketToInvoice;
FROM tmp.ticketToInvoice;
CALL `ticket_getTax`('NATIONAL');
@ -253,6 +253,6 @@ BEGIN
DROP TEMPORARY TABLE tmp.ticketServiceTax;
END IF;
END IF;
DROP TEMPORARY TABLE `ticketToInvoice`;
DROP TEMPORARY TABLE tmp.`ticketToInvoice`;
END$$
DELIMITER ;

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`printQueueArgs` MODIFY COLUMN value varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NULL;

View File

@ -0,0 +1,5 @@
DROP TRIGGER `vn`.`deviceProduction_afterInsert`;
DROP TRIGGER `vn`.`deviceProduction_afterUpdate`;
DROP TRIGGER `vn`.`deviceProductionUser_afterDelete`;

View File

@ -1,12 +1,5 @@
DROP TABLE IF EXISTS `vn`.`dmsRecover`;
ALTER TABLE `vn`.`delivery` DROP COLUMN addressFk;
ALTER TABLE `vn`.`delivery` DROP CONSTRAINT delivery_ticketFk_FK;
ALTER TABLE `vn`.`delivery` DROP COLUMN ticketFk;
ALTER TABLE `vn`.`delivery` ADD ticketFk INT DEFAULT NULL;
ALTER TABLE `vn`.`delivery` ADD CONSTRAINT delivery_ticketFk_FK FOREIGN KEY (`ticketFk`) REFERENCES `vn`.`ticket`(`id`);
DROP PROCEDURE IF EXISTS vn.route_getTickets;
DROP PROCEDURE IF EXISTS `vn`.`route_getTickets`;
DELIMITER $$
$$
@ -17,54 +10,68 @@ BEGIN
* de sus tickets.
*
* @param vRouteFk
*
* @select Información de los tickets
*/
SELECT
t.id Id,
t.clientFk Client,
a.id Address,
t.packages Packages,
a.street AddressName,
a.postalCode PostalCode,
a.city City,
sub2.itemPackingTypeFk PackingType,
c.phone ClientPhone,
c.mobile ClientMobile,
a.phone AddressPhone,
a.mobile AddressMobile,
d.longitude Longitude,
d.latitude Latitude,
wm.mediaValue SalePersonPhone,
tob.Note Note,
t.isSigned Signed
FROM ticket t
JOIN client c ON t.clientFk = c.id
JOIN address a ON t.addressFk = a.id
LEFT JOIN delivery d ON t.id = d.ticketFk
LEFT JOIN workerMedia wm ON wm.workerFk = c.salesPersonFk
LEFT JOIN
(SELECT tob.description Note, t.id
FROM ticketObservation tob
JOIN ticket t ON tob.ticketFk = t.id
JOIN observationType ot ON ot.id = tob.observationTypeFk
WHERE t.routeFk = vRouteFk
AND ot.code = 'delivery'
)tob ON tob.id = t.id
LEFT JOIN
(SELECT sub.ticketFk,
CONCAT('(', GROUP_CONCAT(DISTINCT sub.itemPackingTypeFk ORDER BY sub.items DESC SEPARATOR ','), ') ') itemPackingTypeFk
FROM (SELECT s.ticketFk , i.itemPackingTypeFk, COUNT(*) items
FROM ticket t
JOIN sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
WHERE t.routeFk = vRouteFk
GROUP BY t.id,i.itemPackingTypeFk)sub
GROUP BY sub.ticketFk
) sub2 ON sub2.ticketFk = t.id
WHERE t.routeFk = vRouteFk
GROUP BY t.id
ORDER BY t.priority;
SELECT *
FROM (
SELECT t.id Id,
t.clientFk Client,
a.id Address,
a.nickname ClientName,
t.packages Packages,
a.street AddressName,
a.postalCode PostalCode,
a.city City,
sub2.itemPackingTypeFk PackingType,
c.phone ClientPhone,
c.mobile ClientMobile,
a.phone AddressPhone,
a.mobile AddressMobile,
d.longitude Longitude,
d.latitude Latitude,
wm.mediaValue SalePersonPhone,
tob.description Note,
t.isSigned Signed,
t.priority
FROM ticket t
JOIN client c ON t.clientFk = c.id
JOIN address a ON t.addressFk = a.id
LEFT JOIN delivery d ON d.ticketFk = t.id
LEFT JOIN workerMedia wm ON wm.workerFk = c.salesPersonFk
LEFT JOIN(
SELECT tob.description, t.id
FROM ticketObservation tob
JOIN ticket t ON tob.ticketFk = t.id
JOIN observationType ot ON ot.id = tob.observationTypeFk
WHERE t.routeFk = vRouteFk
AND ot.code = 'delivery'
)tob ON tob.id = t.id
LEFT JOIN(
SELECT sub.ticketFk,
CONCAT('(',
GROUP_CONCAT(DISTINCT sub.itemPackingTypeFk
ORDER BY sub.items DESC SEPARATOR ','),
') ') itemPackingTypeFk
FROM (
SELECT s.ticketFk, i.itemPackingTypeFk, COUNT(*) items
FROM ticket t
JOIN sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
WHERE t.routeFk = vRouteFk
GROUP BY t.id, i.itemPackingTypeFk
)sub
GROUP BY sub.ticketFk
)sub2 ON sub2.ticketFk = t.id
WHERE t.routeFk = vRouteFk
ORDER BY d.id DESC
LIMIT 10000000000000000000
)sub3
GROUP BY sub3.id
ORDER BY sub3.priority;
END$$
DELIMITER ;
ALTER TABLE `vn`.`delivery` DROP FOREIGN KEY delivery_ticketFk_FK;
ALTER TABLE `vn`.`delivery` DROP COLUMN ticketFk;
ALTER TABLE `vn`.`delivery` ADD ticketFk INT DEFAULT NULL;
ALTER TABLE `vn`.`delivery` ADD CONSTRAINT delivery_ticketFk_FK FOREIGN KEY (`ticketFk`) REFERENCES `vn`.`ticket`(`id`);

View File

@ -0,0 +1,3 @@
UPDATE `salix`.`ACL`
SET model = 'InvoiceOut'
WHERE property IN ('negativeBases', 'negativeBasesCsv');

View File

@ -1,21 +0,0 @@
create or replace definer = root@localhost view User as
select `account`.`user`.`id` AS `id`,
`account`.`user`.`realm` AS `realm`,
`account`.`user`.`name` AS `name`,
`account`.`user`.`nickname` AS `nickname`,
`account`.`user`.`bcryptPassword` AS `password`,
`account`.`user`.`role` AS `role`,
`account`.`user`.`active` AS `active`,
`account`.`user`.`email` AS `email`,
`account`.`user`.`emailVerified` AS `emailVerified`,
`account`.`user`.`verificationToken` AS `verificationToken`,
`account`.`user`.`lang` AS `lang`,
`account`.`user`.`lastPassChange` AS `lastPassChange`,
`account`.`user`.`created` AS `created`,
`account`.`user`.`updated` AS `updated`,
`account`.`user`.`image` AS `image`,
`account`.`user`.`recoverPass` AS `recoverPass`,
`account`.`user`.`sync` AS `sync`,
`account`.`user`.`hasGrant` AS `hasGrant`
from `account`.`user`;

View File

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

View File

@ -0,0 +1,16 @@
ALTER TABLE `vn`.`client` ADD rating INT UNSIGNED DEFAULT NULL NULL COMMENT 'información proporcionada por Informa';
ALTER TABLE `vn`.`client` ADD recommendedCredit INT UNSIGNED DEFAULT NULL NULL COMMENT 'información proporcionada por Informa';
CREATE TABLE `vn`.`clientInforma` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`clientFk` int(11) NOT NULL,
`rating` int(10) unsigned DEFAULT NULL,
`recommendedCredit` int(10) unsigned DEFAULT NULL,
`workerFk` int(10) unsigned NOT NULL,
`created` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `informaWorkers_fk_idx` (`workerFk`),
KEY `informaClientFk` (`clientFk`),
CONSTRAINT `informa_ClienteFk` FOREIGN KEY (`clientFk`) REFERENCES `client` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `informa_workers_fk` FOREIGN KEY (`workerFk`) REFERENCES `worker` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci COMMENT='información proporcionada por Informa, se actualiza desde el hook de client (salix)';

View File

@ -0,0 +1,64 @@
DELETE FROM `salix`.`ACL` WHERE id=7;
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('Client', 'setRating', 'WRITE', 'ALLOW', 'ROLE', 'financial');
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('Client', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Client', 'addressesPropagateRe', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'canBeInvoiced', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'canCreateTicket', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'consumption', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'createAddress', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'createWithUser', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'extendedListFilter', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'getAverageInvoiced', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'getCard', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'getDebt', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'getMana', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'transactions', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'hasCustomerRole', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'isValidClient', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'lastActiveTickets', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'sendSms', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'setPassword', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'summary', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'updateAddress', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'updateFiscalData', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'updateUser', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'uploadFile', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'campaignMetricsPdf', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'campaignMetricsEmail', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'clientWelcomeHtml', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'clientWelcomeEmail', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'printerSetupHtml', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'printerSetupEmail', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'sepaCoreEmail', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'letterDebtorPdf', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'letterDebtorStHtml', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'letterDebtorStEmail', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'letterDebtorNdHtml', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'letterDebtorNdEmail', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'clientDebtStatementPdf', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'clientDebtStatementHtml', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'clientDebtStatementEmail', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'creditRequestPdf', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'creditRequestHtml', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'creditRequestEmail', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'incotermsAuthorizationPdf', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'incotermsAuthorizationHtml', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'incotermsAuthorizationEmail', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'consumptionSendQueued', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'filter', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'getClientOrSupplierReference', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'upsert', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'create', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'replaceById', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'updateAttributes', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'updateAttributes', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'deleteById', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'replaceOrCreate', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'updateAll', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'upsertWithWhere', '*', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,2 @@
DROP PROCEDURE `vn`.`refund`;
DROP PROCEDURE `vn`.`ticket_doRefund`;

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`entry` DROP COLUMN `notes`;

View File

@ -0,0 +1,9 @@
-- vn.companyI18n definition
CREATE TABLE `vn`.`companyI18n` (
`companyFk` smallint(5) unsigned NOT NULL,
`lang` char(2) CHARACTER SET utf8mb3 NOT NULL,
`footnotes` longtext COLLATE utf8mb3_unicode_ci DEFAULT NULL,
PRIMARY KEY (`companyFk`,`lang`),
CONSTRAINT `companyI18n_FK` FOREIGN KEY (`companyFk`) REFERENCES `company` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;

View File

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

View File

@ -0,0 +1,5 @@
DROP PROCEDURE IF EXISTS `vn`.`sale_setQuantity`;
DROP PROCEDURE IF EXISTS `vn`.`collection_updateSale`;
DROP PROCEDURE IF EXISTS `vn`.`replaceMovimientosMark`;
DROP PROCEDURE IF EXISTS `vn`.`saleTracking_Replace`;
DROP PROCEDURE IF EXISTS `vn`.`sale_updateOriginalQuantity`;

View File

@ -0,0 +1,5 @@
UPDATE vn.supplier s
JOIN vn.country c ON c.id = s.countryFk
SET s.nif = MID(REPLACE(s.nif, ' ', ''), 3, LENGTH(REPLACE(s.nif, ' ', '')) - 1)
WHERE s.isVies = TRUE
AND c.code = LEFT(REPLACE(s.nif, ' ', ''), 2);

View File

@ -0,0 +1,5 @@
UPDATE IGNORE vn.client c
JOIN vn.country co ON co.id = c.countryFk
SET c.fi = MID(REPLACE(c.fi, ' ', ''), 3, LENGTH(REPLACE(c.fi, ' ', '')) - 1)
WHERE c.isVies = TRUE
AND co.code = LEFT(REPLACE(c.fi, ' ', ''), 2);

View File

@ -173,10 +173,6 @@ INSERT INTO `vn`.`sector`(`id`, `description`, `warehouseFk`, `isPreviousPrepare
(1, 'First sector', 1, 1, 'FIRST'),
(2, 'Second sector', 2, 0, 'SECOND');
INSERT INTO `vn`.`operator` (`workerFk`, `numberOfWagons`, `trainFk`, `itemPackingTypeFk`, `warehouseFk`, `sectorFk`, `labelerFk`)
VALUES ('1106', '1', '1', 'H', '1', '1', '1'),
('1107', '1', '1', 'V', '1', '2', '1');
INSERT INTO `vn`.`printer` (`id`, `name`, `path`, `isLabeler`, `sectorFk`, `ipAddress`)
VALUES
(1, 'printer1', 'path1', 0, 1 , NULL),
@ -550,7 +546,8 @@ INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`
VALUES
(1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, util.VN_CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants', 1, '400664487V'),
(2, 'Farmer King', 'The farmer', 4000020002, 1, '87945234L', 0, util.VN_CURDATE(), 1, 'supplier address 2', 'GOTHAM', 2, 43022, 1, 2, 10, 93, 2, 8, 18, 'animals', 1, '400664487V'),
(442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, '06815934E', 0, util.VN_CURDATE(), 1, 'supplier address 3', 'GOTHAM', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V');
(442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, '06815934E', 0, util.VN_CURDATE(), 1, 'supplier address 3', 'GOTHAM', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V'),
(1381, 'Ornamentales', 'Ornamentales', 7185000440, 1, '03815934E', 0, util.VN_CURDATE(), 1, 'supplier address 4', 'GOTHAM', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V');
INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`)
VALUES
@ -1200,6 +1197,11 @@ INSERT INTO `vn`.`train`(`id`, `name`)
(1, 'Train1'),
(2, 'Train2');
INSERT INTO `vn`.`operator` (`workerFk`, `numberOfWagons`, `trainFk`, `itemPackingTypeFk`, `warehouseFk`, `sectorFk`, `labelerFk`)
VALUES
('1106', '1', '1', 'H', '1', '1', '1'),
('1107', '1', '1', 'V', '1', '1', '1');
INSERT INTO `vn`.`collection`(`id`, `workerFk`, `stateFk`, `created`, `trainFk`)
VALUES
(1, 1106, 5, DATE_ADD(util.VN_CURDATE(),INTERVAL +1 DAY), 1),
@ -1407,16 +1409,16 @@ INSERT INTO `vn`.`travel`(`id`,`shipped`, `landed`, `warehouseInFk`, `warehouseO
(7, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 5, 4, 1, 50.00, 500, 'seventh travel', 2, 1),
(8, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 5, 1, 1, 50.00, 500, 'eight travel', 1, 2);
INSERT INTO `vn`.`entry`(`id`, `supplierFk`, `created`, `travelFk`, `isConfirmed`, `companyFk`, `invoiceNumber`, `reference`, `isExcludedFromAvailable`, `isRaid`, `notes`, `evaNotes`)
INSERT INTO `vn`.`entry`(`id`, `supplierFk`, `created`, `travelFk`, `isConfirmed`, `companyFk`, `invoiceNumber`, `reference`, `isExcludedFromAvailable`, `isRaid`, `evaNotes`)
VALUES
(1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1, 1, 442, 'IN2001', 'Movement 1', 0, 0, '', ''),
(2, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 2, 0, 442, 'IN2002', 'Movement 2', 0, 0, 'this is the note two', 'observation two'),
(3, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 3, 0, 442, 'IN2003', 'Movement 3', 0, 0, 'this is the note three', 'observation three'),
(4, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 2, 0, 69, 'IN2004', 'Movement 4', 0, 0, 'this is the note four', 'observation four'),
(5, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 5, 0, 442, 'IN2005', 'Movement 5', 0, 0, 'this is the note five', 'observation five'),
(6, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 6, 0, 442, 'IN2006', 'Movement 6', 0, 0, 'this is the note six', 'observation six'),
(7, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 7, 0, 442, 'IN2007', 'Movement 7', 0, 0, 'this is the note seven', 'observation seven'),
(8, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 7, 0, 442, 'IN2008', 'Movement 8', 1, 1, '', '');
(1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1, 1, 442, 'IN2001', 'Movement 1', 0, 0, ''),
(2, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 2, 0, 442, 'IN2002', 'Movement 2', 0, 0, 'observation two'),
(3, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 3, 0, 442, 'IN2003', 'Movement 3', 0, 0, 'observation three'),
(4, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 2, 0, 69, 'IN2004', 'Movement 4', 0, 0, 'observation four'),
(5, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 5, 0, 442, 'IN2005', 'Movement 5', 0, 0, 'observation five'),
(6, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 6, 0, 442, 'IN2006', 'Movement 6', 0, 0, 'observation six'),
(7, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 7, 0, 442, 'IN2007', 'Movement 7', 0, 0, 'observation seven'),
(8, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 7, 0, 442, 'IN2008', 'Movement 8', 1, 1, '');
INSERT INTO `bs`.`waste`(`buyer`, `year`, `week`, `family`, `itemFk`, `itemTypeFk`, `saleTotal`, `saleWaste`, `rate`)
VALUES
@ -2788,7 +2790,7 @@ INSERT INTO `vn`.`profileType` (`id`, `name`)
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES
('lilium', 'dev', 'http://localhost:8080/#/'),
('lilium', 'dev', 'http://localhost:9000/#/'),
('salix', 'dev', 'http://localhost:5000/#!/');
INSERT INTO `vn`.`report` (`id`, `name`, `paperSizeFk`, `method`)

View File

@ -42776,7 +42776,7 @@ CREATE DEFINER=`root`@`localhost` FUNCTION `hasAnyNegativeBase`() RETURNS tinyin
BEGIN
/* Calcula si existe alguna base imponible negativa
* Requiere la tabla temporal vn.ticketToInvoice(id)
* Requiere la tabla temporal tmp.ticketToInvoice(id)
*
* returns BOOLEAN
*/
@ -42787,7 +42787,7 @@ BEGIN
(KEY (ticketFk))
ENGINE = MEMORY
SELECT id ticketFk
FROM ticketToInvoice;
FROM tmp.ticketToInvoice;
CALL ticket_getTax(NULL);
@ -55223,7 +55223,7 @@ DELIMITER ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `invoiceExpenceMake`(IN vInvoice INT)
BEGIN
/* Inserta las partidas de gasto correspondientes a la factura
* REQUIERE tabla ticketToInvoice
* REQUIERE tabla tmp.ticketToInvoice
* @param vInvoice Numero de factura
*/
DELETE FROM invoiceOutExpence
@ -55233,7 +55233,7 @@ BEGIN
SELECT vInvoice,
expenceFk,
SUM(ROUND(quantity * price * (100 - discount)/100,2)) amount
FROM ticketToInvoice t
FROM tmp.ticketToInvoice t
JOIN sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
GROUP BY i.expenceFk
@ -55243,7 +55243,7 @@ BEGIN
SELECT vInvoice,
tst.expenceFk,
SUM(ROUND(ts.quantity * ts.price ,2)) amount
FROM ticketToInvoice t
FROM tmp.ticketToInvoice t
JOIN ticketService ts ON ts.ticketFk = t.id
JOIN ticketServiceType tst ON tst.id = ts.ticketServiceTypeFk
HAVING amount != 0;
@ -55270,9 +55270,9 @@ BEGIN
SET vMaxTicketDate = vn2008.DAYEND(vMaxTicketDate);
DROP TEMPORARY TABLE IF EXISTS `ticketToInvoice`;
DROP TEMPORARY TABLE IF EXISTS `tmp`.`ticketToInvoice`;
CREATE TEMPORARY TABLE `ticketToInvoice`
CREATE TEMPORARY TABLE `tmp`.`ticketToInvoice`
(PRIMARY KEY (`id`))
ENGINE = MEMORY
SELECT Id_Ticket id FROM vn2008.Tickets WHERE (Fecha BETWEEN vMinDateTicket
@ -55305,8 +55305,8 @@ BEGIN
SET vMinTicketDate = util.firstDayOfYear(vMaxTicketDate - INTERVAL 1 YEAR);
SET vMaxTicketDate = util.dayend(vMaxTicketDate);
DROP TEMPORARY TABLE IF EXISTS `ticketToInvoice`;
CREATE TEMPORARY TABLE `ticketToInvoice`
DROP TEMPORARY TABLE IF EXISTS `tmp`.`ticketToInvoice`;
CREATE TEMPORARY TABLE `tmp`.`ticketToInvoice`
(PRIMARY KEY (`id`))
ENGINE = MEMORY
SELECT id FROM ticket t
@ -55333,9 +55333,9 @@ DELIMITER ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `invoiceFromTicket`(IN vTicket INT)
BEGIN
DROP TEMPORARY TABLE IF EXISTS `ticketToInvoice`;
DROP TEMPORARY TABLE IF EXISTS `tmp`.`ticketToInvoice`;
CREATE TEMPORARY TABLE `ticketToInvoice`
CREATE TEMPORARY TABLE `tmp`.`ticketToInvoice`
(PRIMARY KEY (`id`))
ENGINE = MEMORY
SELECT id FROM vn.ticket
@ -55931,9 +55931,9 @@ BEGIN
JOIN invoiceOut io ON io.companyFk = s.id
WHERE io.id = vInvoiceFk;
DROP TEMPORARY TABLE IF EXISTS ticketToInvoice;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice;
CREATE TEMPORARY TABLE ticketToInvoice
CREATE TEMPORARY TABLE tmp.ticketToInvoice
SELECT id
FROM ticket
WHERE refFk = vInvoiceRef;
@ -56408,9 +56408,9 @@ BEGIN
JOIN client c ON c.id = io.clientFk
WHERE io.id = vInvoice;
DROP TEMPORARY TABLE IF EXISTS ticketToInvoice;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice;
CREATE TEMPORARY TABLE ticketToInvoice
CREATE TEMPORARY TABLE tmp.ticketToInvoice
SELECT id
FROM ticket
WHERE refFk = vInvoiceRef;
@ -56456,7 +56456,7 @@ CREATE DEFINER=`root`@`localhost` PROCEDURE `invoiceOut_exportationFromClient`(
vCompanyFk INT)
BEGIN
/**
* Genera tabla temporal ticketToInvoice necesaría para el proceso de facturación
* Genera tabla temporal tmp.ticketToInvoice necesaría para el proceso de facturación
* Los abonos quedan excluidos en las exportaciones
*
* @param vMaxTicketDate Fecha hasta la cual cogerá tickets para facturar
@ -56467,8 +56467,8 @@ BEGIN
SET vMinTicketDate = util.firstDayOfYear(vMaxTicketDate - INTERVAL 1 YEAR);
SET vMaxTicketDate = util.dayend(vMaxTicketDate);
DROP TEMPORARY TABLE IF EXISTS `ticketToInvoice`;
CREATE TEMPORARY TABLE `ticketToInvoice`
DROP TEMPORARY TABLE IF EXISTS `tmp`.`ticketToInvoice`;
CREATE TEMPORARY TABLE `tmp`.`ticketToInvoice`
(PRIMARY KEY (`id`))
ENGINE = MEMORY
SELECT t.id
@ -56503,7 +56503,7 @@ CREATE DEFINER=`root`@`localhost` PROCEDURE `invoiceOut_new`(
BEGIN
/**
* Creación de facturas emitidas.
* requiere previamente tabla ticketToInvoice(id).
* requiere previamente tabla tmp.ticketToInvoice(id).
*
* @param vSerial serie a la cual se hace la factura
* @param vInvoiceDate fecha de la factura
@ -56531,13 +56531,13 @@ BEGIN
SELECT t.clientFk, t.companyFk
INTO vClientFk, vCompanyFk
FROM ticketToInvoice tt
FROM tmp.ticketToInvoice tt
JOIN ticket t ON t.id = tt.id
LIMIT 1;
-- Eliminem de ticketToInvoice els tickets que no han de ser facturats
-- Eliminem de tmp.ticketToInvoice els tickets que no han de ser facturats
DELETE ti.*
FROM ticketToInvoice ti
FROM tmp.ticketToInvoice ti
JOIN ticket t ON t.id = ti.id
JOIN sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
@ -56552,7 +56552,7 @@ BEGIN
SELECT SUM(s.quantity * s.price * (100 - s.discount)/100), ts.id
INTO vIsAnySaleToInvoice, vIsAnyServiceToInvoice
FROM ticketToInvoice t
FROM tmp.ticketToInvoice t
LEFT JOIN sale s ON s.ticketFk = t.id
LEFT JOIN ticketService ts ON ts.ticketFk = t.id;
@ -56593,13 +56593,13 @@ BEGIN
WHERE id = vNewInvoiceId;
UPDATE ticket t
JOIN ticketToInvoice ti ON ti.id = t.id
JOIN tmp.ticketToInvoice ti ON ti.id = t.id
SET t.refFk = vNewRef;
DROP TEMPORARY TABLE IF EXISTS tmp.updateInter;
CREATE TEMPORARY TABLE tmp.updateInter ENGINE = MEMORY
SELECT s.id,ti.id ticket_id,vWorker Id_Trabajador
FROM ticketToInvoice ti
FROM tmp.ticketToInvoice ti
LEFT JOIN ticketState ts ON ti.id = ts.ticket
JOIN state s
WHERE IFNULL(ts.alertLevel,0) < 3 and s.`code` = getAlert3State(ti.id);
@ -56609,7 +56609,7 @@ BEGIN
INSERT INTO ticketLog (action, userFk, originFk, description)
SELECT 'UPDATE', account.myUser_getId(), ti.id, CONCAT('Crea factura ', vNewRef)
FROM ticketToInvoice ti;
FROM tmp.ticketToInvoice ti;
CALL invoiceExpenceMake(vNewInvoiceId);
CALL invoiceTaxMake(vNewInvoiceId,vTaxArea);
@ -56647,7 +56647,7 @@ BEGIN
(KEY (ticketFk))
ENGINE = MEMORY
SELECT id ticketFk
FROM ticketToInvoice;
FROM tmp.ticketToInvoice;
CALL `ticket_getTax`('NATIONAL');
@ -56725,7 +56725,7 @@ BEGIN
DROP TEMPORARY TABLE tmp.ticketServiceTax;
END IF;
END IF;
DROP TEMPORARY TABLE `ticketToInvoice`;
DROP TEMPORARY TABLE `tmp`.`ticketToInvoice`;
END ;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
@ -56876,7 +56876,7 @@ BEGIN
(KEY (ticketFk))
ENGINE = MEMORY
SELECT id ticketFk
FROM ticketToInvoice;
FROM tmp.ticketToInvoice;
CALL ticket_getTax(vTaxArea);
@ -68689,7 +68689,7 @@ DELIMITER ;
/*!50003 SET character_set_client = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection = @saved_col_connection */ ;
/*!50003 DROP PROCEDURE IF EXISTS `ticketToInvoiceByAddress` */;
/*!50003 DROP PROCEDURE IF EXISTS `tmp`.`ticketToInvoiceByAddress` */;
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
@ -68709,9 +68709,9 @@ BEGIN
SET vEnded = util.dayEnd(vEnded);
DROP TEMPORARY TABLE IF EXISTS vn.ticketToInvoice;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice;
CREATE TEMPORARY TABLE vn.ticketToInvoice
CREATE TEMPORARY TABLE tmp.ticketToInvoice
SELECT id
FROM vn.ticket
WHERE addressFk = vAddress
@ -68745,9 +68745,9 @@ BEGIN
SET vEnded = util.dayEnd(vEnded);
DROP TEMPORARY TABLE IF EXISTS vn.ticketToInvoice;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice;
CREATE TEMPORARY TABLE vn.ticketToInvoice
CREATE TEMPORARY TABLE tmp.ticketToInvoice
SELECT id
FROM vn.ticket
WHERE clientFk = vClient
@ -68808,9 +68808,9 @@ BEGIN
JOIN vn.client c ON c.id = io.clientFk
WHERE io.id = vInvoice;
DROP TEMPORARY TABLE IF EXISTS vn.ticketToInvoice;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice;
CREATE TEMPORARY TABLE vn.ticketToInvoice
CREATE TEMPORARY TABLE tmp.ticketToInvoice
SELECT id
FROM vn.ticket
WHERE refFk = vInvoiceRef;

View File

@ -68,6 +68,7 @@ TABLES=(
time
volumeConfig
workCenter
companyI18n
)
dump_tables ${TABLES[@]}

View File

@ -156,14 +156,14 @@ let actions = {
await this.waitForSpinnerLoad();
},
accessToSection: async function(state) {
accessToSection: async function(state, name = 'Others') {
await this.waitForSelector('vn-left-menu');
let nested = await this.evaluate(state => {
return document.querySelector(`vn-left-menu li li > a[ui-sref="${state}"]`) != null;
}, state);
if (nested) {
let selector = 'vn-left-menu vn-item-section > vn-icon[icon=keyboard_arrow_down]';
let selector = `vn-left-menu li[name="${name}"]`;
await this.evaluate(selector => {
document.querySelector(selector).scrollIntoViewIfNeeded();
}, selector);
@ -218,6 +218,23 @@ let actions = {
return handle.jsonValue();
},
getValue: async function(selector) {
return await this.waitToGetProperty(selector, 'value');
},
getValues: async function(selectorMap) {
const values = {};
for (const key in selectorMap)
values[key] = await this.waitToGetProperty(selectorMap[key], 'value');
return values;
},
innerText: async function(selector) {
const element = await this.$(selector);
const handle = await element.getProperty('innerText');
return handle.jsonValue();
},
waitPropertyLength: async function(selector, property, minLength) {
await this.waitForFunction((selector, property, minLength) => {
const element = document.querySelector(selector);

View File

@ -283,12 +283,6 @@ export default {
cancelEditAddressButton: 'vn-client-address-edit > form > vn-button-bar > vn-button > button',
watcher: 'vn-client-address-edit vn-watcher'
},
clientWebAccess: {
enableWebAccessCheckbox: 'vn-check[label="Enable web access"]',
userName: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.name"]',
email: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.email"]',
saveButton: 'button[type=submit]'
},
clientNotes: {
addNoteFloatButton: 'vn-float-button',
note: 'vn-textarea[ng-model="$ctrl.note.text"]',
@ -312,15 +306,6 @@ export default {
clientMandate: {
firstMandateText: 'vn-client-mandate vn-card vn-table vn-tbody > vn-tr'
},
clientLog: {
lastModificationPreviousValue: 'vn-client-log vn-tr table tr td.before',
lastModificationCurrentValue: 'vn-client-log vn-tr table tr td.after',
namePreviousValue: 'vn-client-log vn-tr table tr:nth-child(1) td.before',
nameCurrentValue: 'vn-client-log vn-tr table tr:nth-child(1) td.after',
activePreviousValue: 'vn-client-log vn-tr:nth-child(2) table tr:nth-child(2) td.before',
activeCurrentValue: 'vn-client-log vn-tr:nth-child(2) table tr:nth-child(2) td.after'
},
clientBalance: {
company: 'vn-client-balance-index vn-autocomplete[ng-model="$ctrl.companyId"]',
newPaymentButton: `vn-float-button`,
@ -755,6 +740,7 @@ export default {
anyDocument: 'vn-ticket-dms-index > vn-data-viewer vn-tbody vn-tr'
},
ticketFuture: {
searchResult: 'vn-ticket-future tbody tr',
openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
originDated: 'vn-date-picker[label="Origin date"]',
futureDated: 'vn-date-picker[label="Destination date"]',
@ -770,7 +756,6 @@ export default {
problems: 'vn-check[label="With problems"]',
tableButtonSearch: 'vn-button[vn-tooltip="Search"]',
moveButton: 'vn-button[vn-tooltip="Future tickets"]',
acceptButton: '.vn-confirm.shown button[response="accept"]',
firstCheck: 'tbody > tr:nth-child(1) > td > vn-check',
multiCheck: 'vn-multi-check',
tableId: 'vn-textfield[name="id"]',
@ -1361,18 +1346,6 @@ export default {
notes: 'vn-supplier-basic-data vn-textarea[ng-model="$ctrl.supplier.note"]',
saveButton: 'vn-supplier-basic-data button[type="submit"]',
},
supplierFiscalData: {
socialName: 'vn-supplier-fiscal-data vn-textfield[ng-model="$ctrl.supplier.name"]',
taxNumber: 'vn-supplier-fiscal-data vn-textfield[ng-model="$ctrl.supplier.nif"]',
account: 'vn-supplier-fiscal-data vn-textfield[ng-model="$ctrl.supplier.account"]',
sageTaxType: 'vn-supplier-fiscal-data vn-autocomplete[ng-model="$ctrl.supplier.sageTaxTypeFk"]',
sageWihholding: 'vn-supplier-fiscal-data vn-autocomplete[ng-model="$ctrl.supplier.sageWithholdingFk"]',
postCode: 'vn-supplier-fiscal-data vn-datalist[ng-model="$ctrl.supplier.postCode"]',
city: 'vn-supplier-fiscal-data vn-datalist[ng-model="$ctrl.supplier.city"]',
province: 'vn-supplier-fiscal-data vn-autocomplete[ng-model="$ctrl.supplier.provinceFk"]',
country: 'vn-supplier-fiscal-data vn-autocomplete[ng-model="$ctrl.supplier.countryFk"]',
saveButton: 'vn-supplier-fiscal-data button[type="submit"]',
},
supplierBillingData: {
payMethod: 'vn-supplier-billing-data vn-autocomplete[ng-model="$ctrl.supplier.payMethodFk"]',
payDem: 'vn-supplier-billing-data vn-autocomplete[ng-model="$ctrl.supplier.payDemFk"]',

View File

@ -79,27 +79,17 @@ describe('SmartTable SearchBar integration', () => {
it('should order by first id', async() => {
await page.loginAndModule('developer', 'item');
await page.accessToSection('item.fixedPrice');
await page.doSearch();
await page.keyboard.press('Enter');
const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value');
expect(result).toEqual('1');
await page.waitForTextInField(selectors.itemFixedPrice.firstItemID, '1');
});
it('should order by last id', async() => {
it('should order by last id, reload page and have same order', async() => {
await page.waitToClick(selectors.itemFixedPrice.orderColumnId);
const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value');
expect(result).toEqual('3');
});
it('should reload page and have same order', async() => {
await page.reload({
waitUntil: 'networkidle2'
});
const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value');
expect(result).toEqual('3');
await page.waitForTextInField(selectors.itemFixedPrice.firstItemID, '13');
});
});
});

View File

@ -1,88 +1,56 @@
/* eslint max-len: ["error", { "code": 150 }]*/
import selectors from '../../helpers/selectors';
import getBrowser from '../../helpers/puppeteer';
describe('Client Edit web access path', () => {
const $ = {
enableWebAccess: 'vn-client-web-access vn-check[label="Enable web access"]',
userName: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.name"]',
email: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.email"]',
saveButton: 'vn-client-web-access button[type=submit]',
nameValue: 'vn-client-log .change:nth-child(1) .basic-json:nth-child(1) vn-json-value',
activeValue: 'vn-client-log .change:nth-child(2) .basic-json:nth-child(2) vn-json-value'
};
describe('Client web access path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('salesPerson', 'client');
await page.accessToSearchResult('max');
await page.accessToSection('client.card.webAccess');
});
afterAll(async() => {
await browser.close();
});
it('should uncheck the Enable web access checkbox', async() => {
await page.waitToClick(selectors.clientWebAccess.enableWebAccessCheckbox);
await page.waitToClick(selectors.clientWebAccess.saveButton);
const message = await page.waitForSnackbar();
it('should modify and save web access attributes', async() => {
await page.accessToSection('client.card.webAccess');
await page.click($.enableWebAccess);
await page.click($.saveButton);
const enableMessage = await page.waitForSnackbar();
await page.overwrite($.userName, 'Legion');
await page.overwrite($.email, 'legion@marvel.com');
await page.click($.saveButton);
const modifyMessage = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it(`should update the name`, async() => {
await page.clearInput(selectors.clientWebAccess.userName);
await page.write(selectors.clientWebAccess.userName, 'Legion');
await page.waitToClick(selectors.clientWebAccess.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it(`should update the email`, async() => {
await page.clearInput(selectors.clientWebAccess.email);
await page.write(selectors.clientWebAccess.email, 'legion@marvel.com');
await page.waitToClick(selectors.clientWebAccess.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should reload the section and confirm web access is now unchecked', async() => {
await page.reloadSection('client.card.webAccess');
const result = await page.checkboxState(selectors.clientWebAccess.enableWebAccessCheckbox);
const hasAccess = await page.checkboxState($.enableWebAccess);
const userName = await page.getValue($.userName);
const email = await page.getValue($.email);
expect(result).toBe('unchecked');
});
it('should confirm web access name have been updated', async() => {
const result = await page.waitToGetProperty(selectors.clientWebAccess.userName, 'value');
expect(result).toEqual('Legion');
});
it('should confirm web access email have been updated', async() => {
const result = await page.waitToGetProperty(selectors.clientWebAccess.email, 'value');
expect(result).toEqual('legion@marvel.com');
});
it(`should navigate to the log section`, async() => {
await page.accessToSection('client.card.log');
});
const logName = await page.innerText($.nameValue);
const logActive = await page.innerText($.activeValue);
it(`should confirm the last log shows the updated client name and no modifications on active checkbox`, async() => {
let namePreviousValue = await page
.waitToGetProperty(selectors.clientLog.namePreviousValue, 'innerText');
let nameCurrentValue = await page
.waitToGetProperty(selectors.clientLog.nameCurrentValue, 'innerText');
expect(enableMessage.type).toBe('success');
expect(modifyMessage.type).toBe('success');
expect(namePreviousValue).toEqual('MaxEisenhardt');
expect(nameCurrentValue).toEqual('Legion');
});
expect(hasAccess).toBe('unchecked');
expect(userName).toEqual('Legion');
expect(email).toEqual('legion@marvel.com');
it(`should confirm the penultimate log shows the updated active and no modifications on client name`, async() => {
let activePreviousValue = await page
.waitToGetProperty(selectors.clientLog.activePreviousValue, 'innerText');
let activeCurrentValue = await page
.waitToGetProperty(selectors.clientLog.activeCurrentValue, 'innerText');
expect(activePreviousValue).toEqual('✓');
expect(activeCurrentValue).toEqual('✗');
expect(logName).toEqual('Legion');
expect(logActive).toEqual('✗');
});
});

View File

@ -246,6 +246,7 @@ describe('Ticket Edit sale path', () => {
it('should select the third sale and create a claim of it', async() => {
await page.accessToSearchResult('16');
await page.accessToSection('ticket.card.sale');
await page.waitToClick(selectors.ticketSales.firstSaleCheckbox);
await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox);
await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuCreateClaim);

View File

@ -126,10 +126,11 @@ describe('Ticket Future path', () => {
});
it('should check the three last tickets and move to the future', async() => {
await page.waitForNumberOfElements(selectors.ticketFuture.searchResult, 4);
await page.waitToClick(selectors.ticketFuture.multiCheck);
await page.waitToClick(selectors.ticketFuture.firstCheck);
await page.waitToClick(selectors.ticketFuture.moveButton);
await page.waitToClick(selectors.ticketFuture.acceptButton);
await page.waitToClick(selectors.globalItems.acceptButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Tickets moved successfully!');

View File

@ -35,7 +35,7 @@ describe('InvoiceIn serial path', () => {
});
it('should go to index and check if the search-panel has the correct params', async() => {
await page.click(selectors.invoiceInSerial.goToIndex);
await page.waitToClick(selectors.invoiceInSerial.goToIndex);
const params = await page.$$(selectors.invoiceInIndex.topbarSearchParams);
const serial = await params[0].getProperty('title');
const isBooked = await params[1].getProperty('title');

View File

@ -1,6 +1,6 @@
import getBrowser from '../../helpers/puppeteer';
describe('InvoiceIn negative bases path', () => {
describe('InvoiceOut negative bases path', () => {
let browser;
let page;
const httpRequests = [];
@ -9,11 +9,11 @@ describe('InvoiceIn negative bases path', () => {
browser = await getBrowser();
page = browser.page;
page.on('request', req => {
if (req.url().includes(`InvoiceIns/negativeBases`))
if (req.url().includes(`InvoiceOuts/negativeBases`))
httpRequests.push(req.url());
});
await page.loginAndModule('administrative', 'invoiceIn');
await page.accessToSection('invoiceIn.negative-bases');
await page.loginAndModule('administrative', 'invoiceOut');
await page.accessToSection('invoiceOut.negative-bases');
});
afterAll(async() => {

View File

@ -20,7 +20,6 @@ describe('Entry basic data path', () => {
it('should edit the basic data', async() => {
await page.write(selectors.entryBasicData.reference, 'new movement 8');
await page.write(selectors.entryBasicData.invoiceNumber, 'new movement 8');
await page.write(selectors.entryBasicData.notes, 'new notes');
await page.write(selectors.entryBasicData.observations, ' edited');
await page.autocompleteSearch(selectors.entryBasicData.supplier, 'Plants nick');
await page.autocompleteSearch(selectors.entryBasicData.currency, 'eur');
@ -53,12 +52,6 @@ describe('Entry basic data path', () => {
expect(result).toEqual('new movement 8');
});
it('should confirm the note was edited', async() => {
const result = await page.waitToGetProperty(selectors.entryBasicData.notes, 'value');
expect(result).toEqual('new notes');
});
it('should confirm the observation was edited', async() => {
const result = await page.waitToGetProperty(selectors.entryBasicData.observations, 'value');

View File

@ -1,6 +1,20 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
const $ = {
saveButton: 'vn-supplier-fiscal-data button[type="submit"]',
};
const $inputs = {
province: 'vn-supplier-fiscal-data [name="province"]',
country: 'vn-supplier-fiscal-data [name="country"]',
postcode: 'vn-supplier-fiscal-data [name="postcode"]',
city: 'vn-supplier-fiscal-data [name="city"]',
socialName: 'vn-supplier-fiscal-data [name="socialName"]',
taxNumber: 'vn-supplier-fiscal-data [name="taxNumber"]',
account: 'vn-supplier-fiscal-data [name="account"]',
sageWithholding: 'vn-supplier-fiscal-data [ng-model="$ctrl.supplier.sageWithholdingFk"]',
sageTaxType: 'vn-supplier-fiscal-data [ng-model="$ctrl.supplier.sageTaxTypeFk"]'
};
describe('Supplier fiscal data path', () => {
let browser;
let page;
@ -10,102 +24,44 @@ describe('Supplier fiscal data path', () => {
page = browser.page;
await page.loginAndModule('administrative', 'supplier');
await page.accessToSearchResult('2');
await page.accessToSection('supplier.card.fiscalData');
});
afterAll(async() => {
await browser.close();
});
it('should attempt to edit the fiscal data but fail as the tax number is invalid', async() => {
await page.clearInput(selectors.supplierFiscalData.city);
await page.clearInput(selectors.supplierFiscalData.province);
await page.clearInput(selectors.supplierFiscalData.country);
await page.clearInput(selectors.supplierFiscalData.postCode);
await page.write(selectors.supplierFiscalData.city, 'Valencia');
await page.waitForTimeout(1000); // must repeat this action twice or fails. also #2699 may be a cool solution to this.
await page.clearInput(selectors.supplierFiscalData.city);
await page.write(selectors.supplierFiscalData.city, 'Valencia');
await page.clearInput(selectors.supplierFiscalData.socialName);
await page.write(selectors.supplierFiscalData.socialName, 'Farmer King SL');
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, '0123456789');
await page.autocompleteSearch(selectors.supplierFiscalData.sageWihholding, 'retencion estimacion objetiva');
await page.autocompleteSearch(selectors.supplierFiscalData.sageTaxType, 'operaciones no sujetas');
it('should attempt to edit the fiscal data and check data is saved', async() => {
await page.accessToSection('supplier.card.fiscalData');
await page.clearInput($inputs.province);
await page.clearInput($inputs.country);
await page.clearInput($inputs.postcode);
await page.overwrite($inputs.city, 'Valencia');
await page.overwrite($inputs.socialName, 'Farmer King SL');
await page.overwrite($inputs.taxNumber, 'Wrong tax number');
await page.overwrite($inputs.account, '0123456789');
await page.autocompleteSearch($inputs.sageWithholding, 'retencion estimacion objetiva');
await page.autocompleteSearch($inputs.sageTaxType, 'operaciones no sujetas');
await page.click($.saveButton);
const errorMessage = await page.waitForSnackbar();
await page.overwrite($inputs.taxNumber, '12345678Z');
await page.click($.saveButton);
const successMessage = await page.waitForSnackbar();
await page.waitToClick(selectors.supplierFiscalData.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Invalid Tax number');
});
it('should save the changes as the tax number is valid this time', async() => {
await page.clearInput(selectors.supplierFiscalData.taxNumber);
await page.write(selectors.supplierFiscalData.taxNumber, '12345678Z');
await page.waitToClick(selectors.supplierFiscalData.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should reload the section', async() => {
await page.reloadSection('supplier.card.fiscalData');
});
const values = await page.getValues($inputs);
it('should check the socialName was edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierFiscalData.socialName, 'value');
expect(result).toEqual('Farmer King SL');
});
it('should check the taxNumber was edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierFiscalData.taxNumber, 'value');
expect(result).toEqual('12345678Z');
});
it('should check the account was edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierFiscalData.account, 'value');
expect(result).toEqual('0123456789');
});
it('should check the sageWihholding was edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierFiscalData.sageWihholding, 'value');
expect(result).toEqual('RETENCION ESTIMACION OBJETIVA');
});
it('should check the sageTaxType was edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierFiscalData.sageTaxType, 'value');
expect(result).toEqual('Operaciones no sujetas');
});
it('should check the postCode was edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierFiscalData.postCode, 'value');
expect(result).toEqual('46000');
});
it('should check the city was edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierFiscalData.city, 'value');
expect(result).toEqual('Valencia');
});
it('should check the province was edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierFiscalData.province, 'value');
expect(result).toEqual('Province one (España)');
});
it('should check the country was edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierFiscalData.country, 'value');
expect(result).toEqual('España');
expect(errorMessage.text).toContain('Invalid Tax number');
expect(successMessage.type).toBe('success');
expect(values).toEqual({
province: 'Province one (España)',
country: 'España',
postcode: '46000',
city: 'Valencia',
socialName: 'Farmer King SL',
taxNumber: '12345678Z',
account: '0123456789',
sageWithholding: 'RETENCION ESTIMACION OBJETIVA',
sageTaxType: 'Operaciones no sujetas'
});
});
});

View File

@ -174,7 +174,6 @@ export default class Autocomplete extends Field {
refreshDisplayed() {
let display = '';
let hasTemplate = this.$transclude && this.$transclude.isSlotFilled('tplItem');
if (this._selection && this.showField) {
if (this.multiple && Array.isArray(this._selection)) {
@ -182,19 +181,8 @@ export default class Autocomplete extends Field {
if (display.length > 0) display += ', ';
display += item[this.showField];
}
} else {
} else
display = this._selection[this.showField];
if (hasTemplate) {
let template = this.$transclude(() => {}, null, 'tplItem');
const element = template[0];
const description = element.querySelector('.text-secondary');
if (description) description.remove();
const displayElement = angular.element(element);
const displayText = displayElement.text();
display = this.$interpolate(displayText)(this._selection);
}
}
}
this.input.value = display;

View File

@ -0,0 +1,5 @@
<div class="letter">
{{::$ctrl.val && $ctrl.val.charAt(0).toUpperCase()}}
</div>
<div class="image" ng-transclude>
</div>

View File

@ -0,0 +1,63 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
/**
* Displays colored avatar based on value.
*
* @property {*} val The value
*/
export default class Avatar extends Component {
get val() {
return this._val;
}
set val(value) {
this._val = value;
const val = value || '';
let hash = 0;
for (let i = 0; i < val.length; i++)
hash += val.charCodeAt(i);
const color = '#' + colors[hash % colors.length];
const el = this.element;
el.style.backgroundColor = color;
el.title = val;
}
}
ngModule.vnComponent('vnAvatar', {
template: require('./index.html'),
controller: Avatar,
bindings: {
val: '@?'
},
transclude: true
});
const colors = [
'e2553d', // Coral
'FFA07A', // Salmon
'FFDAB9', // Peach
'a17077', // Pink
'bf0e99', // Pink light
'52a500', // Green chartreuse
'00aeae', // Cian
'b754cf', // Purple middle
'8a69cd', // Blue lavender
'1fa8a1', // Green ocean
'DC143C', // Red crimson
'5681cf', // Blue steel
'FF1493', // Ping intense
'02ba02', // Green lime
'1E90FF', // Blue sky
'8B008B', // Purple dark
'cc7000', // Orange bright
'00b5b8', // Turquoise
'8B0000', // Red dark
'008080', // Green bluish
'2F4F4F', // Gray board
'7e7e7e', // Gray
'5d5d5d', // Gray dark
];

View File

@ -0,0 +1,32 @@
@import "variables";
vn-avatar {
display: block;
border-radius: 50%;
overflow: hidden;
height: 36px;
width: 36px;
font-size: 22px;
background-color: $color-main;
position: relative;
& > * {
width: 100%;
height: 100%;
}
& > .letter {
display: flex;
align-items: center;
justify-content: center;
}
& > .image {
position: absolute;
top: 0;
left: 0;
& > img {
width: 100%;
height: 100%;
}
}
}

View File

@ -166,7 +166,7 @@ export default class Field extends FormInput {
if (event.defaultPrevented) return;
event.preventDefault();
this.field = null;
this.input.dispatchEvent(new Event('change'));
this.element.dispatchEvent(new Event('change'));
}
buildInput(type) {

View File

@ -17,6 +17,7 @@ import './pagination/pagination';
import './searchbar/searchbar';
import './scroll-up/scroll-up';
import './autocomplete';
import './avatar';
import './button';
import './button-menu';
import './calendar';
@ -32,6 +33,7 @@ import './float-button';
import './icon-menu';
import './icon-button';
import './input-number';
import './json-value';
import './label-value';
import './range';
import './input-time';

View File

@ -0,0 +1,73 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
const maxStrLen = 50;
/**
* Displays pretty JSON value.
*
* @property {*} value The value
*/
export default class Controller extends Component {
get value() {
return this._value;
}
set value(value) {
const wasEmpty = this._value === undefined;
this._value = value;
let text;
let cssClass;
const type = typeof value;
if (value == null) {
text = '∅';
cssClass = 'null';
} else {
cssClass = type;
switch (type) {
case 'boolean':
text = value ? '✓' : '✗';
cssClass = value ? 'true' : 'false';
break;
case 'string':
text = value.length <= maxStrLen
? value
: value.substring(0, maxStrLen) + '...';
break;
case 'object':
if (value instanceof Date) {
const hasZeroTime =
value.getHours() === 0 &&
value.getMinutes() === 0 &&
value.getSeconds() === 0;
const format = hasZeroTime ? 'dd/MM/yyyy' : 'dd/MM/yyyy HH:mm:ss';
text = this.$filter('date')(value, format);
} else
text = value;
break;
default:
text = value;
}
}
const el = this.element;
el.textContent = text;
el.title = type == 'string' && value.length > maxStrLen ? value : '';
cssClass = `json-${cssClass}`;
if (wasEmpty)
el.classList.add(cssClass);
else
el.classList.replace(this.className, cssClass);
}
}
ngModule.vnComponent('vnJsonValue', {
controller: Controller,
bindings: {
value: '<?'
}
});

View File

@ -0,0 +1,79 @@
import './index';
describe('Component vnJsonValue', () => {
let controller;
let $scope;
let $element;
let el;
beforeEach(ngModule('vnCore'));
beforeEach(inject(($componentController, $rootScope) => {
$scope = $rootScope.$new();
$element = angular.element('<vn-json-value></vn-json-value>');
controller = $componentController('vnJsonValue', {$element, $scope});
el = controller.element;
}));
describe('set value()', () => {
it('should display null symbol when value is null equivalent', () => {
controller.value = null;
expect(el.textContent).toEqual('∅');
expect(el.className).toContain('json-null');
});
it('should display ballot when value is false', () => {
controller.value = false;
expect(el.textContent).toEqual('✗');
expect(el.className).toContain('json-false');
});
it('should display check when value is true', () => {
controller.value = true;
expect(el.textContent).toEqual('✓');
expect(el.className).toContain('json-true');
});
it('should display string when value is an string', () => {
controller.value = 'Foo';
expect(el.textContent).toEqual('Foo');
expect(el.className).toContain('json-string');
});
it('should display only date when value is date with time set to zero', () => {
const date = Date.vnNew();
date.setHours(0, 0, 0, 0);
controller.value = date;
expect(el.textContent).toEqual('01/01/2001');
expect(el.className).toContain('json-object');
});
it('should display full date without time when value is date with time', () => {
const date = Date.vnNew();
date.setHours(15, 45);
controller.value = date;
expect(el.textContent).toEqual('01/01/2001 15:45:00');
expect(el.className).toContain('json-object');
});
it('should display object when value is an object', () => {
controller.value = {foo: 'bar'};
expect(el.textContent).toEqual('[object Object]');
expect(el.className).toContain('json-object');
});
it('should display number when value is a number', () => {
controller.value = 2050;
expect(el.textContent).toEqual('2050');
expect(el.className).toContain('json-number');
});
});
});

View File

@ -0,0 +1,23 @@
vn-json-value {
display: inline;
&.json-string {
color: #d172cc;
}
&.json-object {
color: #d1a572;
}
&.json-number {
color: #85d0ff;
}
&.json-true {
color: #7dc489;
}
&.json-false {
color: #c74949;
}
&.json-null {
color: #cd7c7c;
font-style: italic;
}
}

View File

@ -41,10 +41,15 @@ vn-table {
display: table-row;
height: 48px;
}
vn-thead, .vn-thead,
vn-tbody, .vn-tbody,
vn-tfoot, .vn-tfoot,
thead, tbody, tfoot {
& > thead,
& > tbody,
& > tfoot,
& > vn-thead,
& > vn-tbody,
& > vn-tfoot,
& > .vn-thead,
& > .vn-tbody,
& > .vn-tfoot {
& > * {
display: table-row;
@ -111,14 +116,14 @@ vn-table {
color: inherit;
}
}
a.vn-tbody {
& > a.vn-tbody {
&.clickable {
@extend %clickable;
}
}
vn-tbody > *,
.vn-tbody > *,
tbody > * {
& > vn-tbody > *,
& > .vn-tbody > *,
& > tbody > * {
border-bottom: $border-thin;
&:last-child {

View File

@ -2,7 +2,6 @@
$font-size: 11pt;
$menu-width: 256px;
$right-menu-width: 318px;
$topbar-height: 56px;
$mobile-width: 800px;
$float-spacing: 20px;

View File

@ -88,13 +88,13 @@ vn-layout {
}
&.right-menu {
& > vn-topbar > .end {
width: 80px + $right-menu-width;
width: 80px + $menu-width;
}
& > .main-view {
padding-right: $right-menu-width;
padding-right: $menu-width;
}
[fixed-bottom-right] {
right: $right-menu-width;
right: $menu-width;
}
}
& > .main-view {

View File

@ -3,71 +3,212 @@
url="{{$ctrl.url}}"
filter="$ctrl.filter"
link="{originFk: $ctrl.originId}"
where="{changedModel: $ctrl.changedModel,
changedModelId: $ctrl.changedModelId}"
where="{changedModel: $ctrl.changedModel, changedModelId: $ctrl.changedModelId}"
data="$ctrl.logs"
limit="20"
order="creationDate DESC, id DESC"
limit="20">
</vn-crud-model>
<vn-crud-model
url="{{$ctrl.url}}/{{$ctrl.originId}}/models"
data="models"
order="changedModel"
auto-load="true">
</vn-crud-model>
<vn-data-viewer model="model" class="vn-w-xl">
<vn-card>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="creationDate">Date</vn-th>
<vn-th field="userFk" shrink>User</vn-th>
<vn-th field="changedModel" ng-if="$ctrl.showModelName" shrink>Model</vn-th>
<vn-th field="action" shrink>Action</vn-th>
<vn-th field="changedModelValue" ng-if="$ctrl.showModelName">Name</vn-th>
<vn-th expand>Changes</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="log in $ctrl.logs">
<vn-td shrink-datetime>
{{::log.creationDate | date:'dd/MM/yyyy HH:mm'}}
</vn-td>
<vn-td>
<span ng-class="{'link': log.user.worker.id, 'value': !log.user.worker.id}"
ng-click="$ctrl.showWorkerDescriptor($event, log.user.worker.id)"
translate>{{::log.user.name || 'System' | translate}}
<vn-data-viewer model="model" class="vn-w-md vn-px-sm">
<div class="change vn-mb-sm" ng-repeat="log in $ctrl.logs">
<div class="user-wrapper">
<vn-avatar class="vn-mt-xs"
ng-class="::{system: !log.user}"
val="{{::log.user ? log.user.nickname : 'System'}}"
ng-click="$ctrl.showWorkerDescriptor($event, log)">
<img
ng-if="::log.user.image"
ng-src="/api/Images/user/160x160/{{::log.userFk}}/download?access_token={{::$ctrl.vnToken.token}}">
</img>
</vn-avatar>
<div class="arrow bg-panel"></div>
<div class="line"></div>
</div>
<vn-card class="detail vn-pa-sm">
<div class="header vn-mb-sm">
<div
class="date text-secondary text-caption"
title="{{::log.creationDate | date:'dd/MM/yyyy HH:mm'}}">
{{::$ctrl.relativeDate(log.creationDate)}}
</div>
<span class="chip" ng-class="::$ctrl.actionsClass[log.action]" translate>
{{::$ctrl.actionsText[log.action]}}
</span>
</div>
<div
class="model vn-mb-sm"
title="{{::log.changedModelValue}}"
ng-if="::log.changedModel || log.changedModelValue">
<span class="model-name"
ng-if="::$ctrl.showModelName"
title="{{::log.changedModel}}">
{{::log.changedModelI18n}}
</span>
<span class="model-id"
ng-if="::log.changedModelId">
#{{::log.changedModelId}}
</span>
<span class="model-value">
{{::log.changedModelValue}}
</span>
</div>
<div class="changes"
ng-class="::log.props.length ? 'props' : 'no-props'"
vn-id="changes">
<vn-icon icon="visibility"
class="expand-button"
ng-click="$ctrl.toggleAttributes(log, changes, true)">
</vn-icon>
<vn-icon icon="visibility_off"
class="shrink-button"
ng-click="$ctrl.toggleAttributes(log, changes, false)">
</vn-icon>
<div class="changes-wrapper">
<span ng-if="::log.props.length"
class="attributes">
<span ng-if="!log.expand" ng-repeat="prop in ::log.props"
class="basic-json">
<span class="json-field"
title="{{::prop.name}}">
{{::prop.nameI18n}}:
</span>
<vn-json-value value="::$ctrl.mainVal(prop, log.action)"></vn-json-value><span ng-if="::!$last">,</span>
</span>
</vn-td>
<vn-td ng-if="$ctrl.showModelName">
{{::log.changedModel}}
</vn-td>
<vn-td shrink translate>
{{::$ctrl.actionsText[log.action]}}
</vn-td>
<vn-td ng-if="$ctrl.showModelName">
{{::log.changedModelValue}}
</vn-td>
<vn-td expand>
<table class="attributes">
<thead>
<tr>
<th translate class="field">Field</th>
<th translate>Before</th>
<th translate>After</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="prop in ::log.props">
<td class="field">{{prop.name}}</td>
<td class="before">{{prop.old}}</td>
<td class="after">{{prop.new}}</td>
</tr>
</tbody>
</table>
<div ng-if="log.description != null">
{{::log.description}}
<div ng-if="log.expand"
class="expanded-json">
<div ng-repeat="prop in ::log.props">
<span class="json-field"
title="{{::prop.name}}">
{{::prop.nameI18n}}:
</span>
<vn-json-value value="::$ctrl.mainVal(prop, log.action)"></vn-json-value>
<span ng-if="::log.action == 'update'">
<vn-json-value value="::prop.old"></vn-json-value>
</span>
</div>
</div>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
<vn-pagination model="model"></vn-pagination>
</vn-card>
</span>
<span ng-if="::!log.props.length"
class="description">
{{::log.description}}
</span>
<span ng-if="::!log.description && !log.props.length"
class="no-changes"
translate>
No changes
</span>
</div>
</vn-card>
</div>
</div>
</vn-data-viewer>
<vn-side-menu side="right">
<form vn-vertical
ng-model-options="{updateOn: 'change blur'}"
class="vn-pa-md filter">
<vn-textfield
label="Name"
ng-model="filter.changedModelValue">
</vn-textfield>
<vn-vertical>
<vn-radio
label="All"
val="all"
ng-model="filter.who">
</vn-radio>
<vn-radio
label="User"
val="user"
ng-model="filter.who">
</vn-radio>
<vn-radio
label="System"
val="system"
ng-model="filter.who">
</vn-radio>
</div>
<vn-autocomplete
ng-show="filter.who != 'system'"
label="User"
ng-model="filter.userFk"
value-field="id"
show-field="nickname"
fields="['id', 'name', 'nickname', 'image']"
search-function="$ctrl.searchUser($search)"
url="{{$ctrl.url}}/{{$ctrl.originId}}/editors"
order="nickname">
<tpl-item>
<div style="display: flex;">
<vn-avatar
class="vn-mr-sm"
val="{{::nickname}}">
<img
ng-if="::image"
ng-src="/api/Images/user/160x160/{{::id}}/download?access_token={{::$ctrl.vnToken.token}}">
</img>
</vn-avatar>
<div>
<div>{{::nickname}}</div>
<div class="text-secondary text-caption">{{::name}}</div>
</div>
</div>
</tpl-item>
</vn-autocomplete>
<vn-autocomplete
label="Model"
ng-model="filter.changedModel"
value-field="changedModel"
show-field="changedModel"
data="models">
</vn-autocomplete>
<vn-textfield
label="Id"
ng-model="filter.changedModelId">
</vn-textfield>
<vn-vertical>
<vn-check
label="Creates"
ng-model="filter.actions.insert">
</vn-check>
<vn-check
label="Updates"
ng-model="filter.actions.update">
</vn-check>
<vn-check
label="Deletes"
ng-model="filter.actions.delete">
</vn-check>
<vn-check
label="Views"
ng-model="filter.actions.select">
</vn-check>
</div>
<vn-date-picker
label="Date"
ng-model="filter.from">
</vn-date-picker>
<vn-date-picker
label="To"
ng-model="filter.to">
</vn-date-picker>
<vn-button-bar vn-vertical class="vn-mt-sm">
<vn-button
label="Filter"
ng-click="$ctrl.applyFilter(filter)">
</vn-button>
<vn-button
label="Reset"
class="flat"
ng-click="$ctrl.resetFilter()"
ng-if="model.userFilter">
</vn-button>
</vn-button-bar>
</form>
</vn-side-menu>
<vn-worker-descriptor-popover vn-id="workerDescriptor">
</vn-worker-descriptor-popover>

View File

@ -13,11 +13,17 @@ export default class Controller extends Section {
delete: 'Deletes',
select: 'Views'
};
this.actionsClass = {
insert: 'success',
update: 'warning',
delete: 'alert',
select: 'notice'
};
this.filter = {
include: [{
relation: 'user',
scope: {
fields: ['name'],
fields: ['nickname', 'name', 'image'],
include: {
relation: 'worker',
scope: {
@ -27,6 +33,20 @@ export default class Controller extends Section {
},
}],
};
this.dateFilter = this.$filter('date');
this.lang = this.$translate.use();
this.today = Date.vnNew();
this.today.setHours(0, 0, 0, 0);
}
$postLink() {
this.resetFilter();
this.$.$watch(
() => this.$.filter,
() => this.applyFilter(),
true
);
}
get logs() {
@ -42,6 +62,7 @@ export default class Controller extends Section {
const oldValues = log.oldInstance || empty;
const newValues = log.newInstance || empty;
const locale = validations[log.changedModel]?.locale || empty;
log.changedModelI18n = locale.name || log.changedModel;
let props = Object.keys(oldValues).concat(Object.keys(newValues));
props = [...new Set(props)];
@ -49,9 +70,10 @@ export default class Controller extends Section {
log.props = [];
for (const prop of props) {
log.props.push({
name: locale[prop] || prop,
old: this.formatValue(oldValues[prop]),
new: this.formatValue(newValues[prop])
name: prop,
nameI18n: locale.columns?.[prop] || prop,
old: this.castJsonValue(oldValues[prop]),
new: this.castJsonValue(newValues[prop])
});
}
}
@ -61,36 +83,112 @@ export default class Controller extends Section {
return !(this.changedModel && this.changedModelId);
}
formatValue(value) {
let type = typeof value;
castJsonValue(value) {
return typeof value === 'string' && validDate.test(value)
? new Date(value)
: value;
}
if (type === 'string' && validDate.test(value)) {
value = new Date(value);
type = typeof value;
mainVal(prop, action) {
return action == 'delete' ? prop.old : prop.new;
}
toggleAttributes(log, changesEl, force) {
log.expand = force;
changesEl.classList.toggle('expanded', force);
}
relativeDate(dateVal) {
if (dateVal == null) return '';
const date = new Date(dateVal);
const dateZeroTime = new Date(dateVal);
dateZeroTime.setHours(0, 0, 0, 0);
const diff = Math.trunc((this.today.getTime() - dateZeroTime.getTime()) / (1000 * 3600 * 24));
let format;
if (diff == 0)
format = `'${this.$t('today')}'`;
else if (diff == 1)
format = `'${this.$t('yesterday')}'`;
else if (diff > 1 && diff < 7)
format = `'${date.toLocaleDateString(this.lang, {weekday: 'short'})}'`;
else if (this.today.getFullYear() == date.getFullYear())
format = `d '${date.toLocaleDateString(this.lang, {month: 'short'})}'`;
else
format = `dd/MM/yyyy`;
return this.dateFilter(date, `${format} HH:mm`);
}
resetFilter() {
this.$.filter = {who: 'all'};
}
applyFilter() {
const filter = this.$.filter;
function getParam(prop, value) {
if (value == null || value == '') return null;
switch (prop) {
case 'changedModelValue':
return {[prop]: {like: `%${value}%`}};
case 'who':
switch (value) {
case 'all':
return null;
case 'user':
return {userFk: {neq: null}};
case 'system':
return {userFk: null};
}
case 'actions':
const inq = [];
for (const action in value) {
if (value[action])
inq.push(action);
}
return inq.length ? {action: {inq}} : null;
case 'from':
if (filter.to) {
return {creationDate: {gte: value}};
} else {
const to = new Date(value);
to.setHours(23, 59, 59, 999);
return {creationDate: {between: [value, to]}};
}
case 'to':
const to = new Date(value);
to.setHours(23, 59, 59, 999);
return {creationDate: {lte: to}};
default:
return {[prop]: value};
}
}
switch (type) {
case 'boolean':
return value ? '✓' : '✗';
case 'object':
if (value instanceof Date) {
const hasZeroTime =
value.getHours() === 0 &&
value.getMinutes() === 0 &&
value.getSeconds() === 0;
const format = hasZeroTime ? 'dd/MM/yyyy' : 'dd/MM/yyyy HH:mm:ss';
return this.$filter('date')(value, format);
}
else
return value;
default:
return value;
const and = [];
for (const prop in filter) {
const param = getParam(prop, filter[prop]);
if (param) and.push(param);
}
const lbFilter = and.length ? {where: {and}} : null;
return this.$.model.applyFilter(lbFilter);
}
searchUser(search) {
if (/^[0-9]+$/.test(search)) {
return {id: search};
} else {
return {or: [
{name: search},
{nickname: {like: `%${search}%`}}
]}
}
}
showWorkerDescriptor(event, workerId) {
if (!workerId) return;
this.$.workerDescriptor.show(event.target, workerId);
showWorkerDescriptor(event, log) {
if (log.user?.worker)
this.$.workerDescriptor.show(event.target, log.userFk);
}
}

View File

@ -0,0 +1,97 @@
import './index';
describe('Salix Component vnLog', () => {
let controller;
let $scope;
let $element;
beforeEach(ngModule('salix'));
beforeEach(inject(($componentController, $rootScope) => {
$scope = $rootScope.$new();
$element = angular.element('<vn-log></vn-log>');
controller = $componentController('vnLog', {$element, $scope});
}));
describe('relativeDate()', () => {
let date;
beforeEach(() => {
date = Date.vnNew();
});
it('should return empty string when date is null', () => {
const ret = controller.relativeDate(null);
expect(ret).toEqual('');
});
it('should return empty string when date is undefined', () => {
const ret = controller.relativeDate(undefined);
expect(ret).toEqual('');
});
it('should return today and time when date is today', () => {
const ret = controller.relativeDate(date);
expect(ret).toEqual('today 12:00');
});
it('should return yesterday and time when date is yesterday', () => {
date.setDate(date.getDate() - 1);
const ret = controller.relativeDate(date);
expect(ret).toEqual('yesterday 12:00');
});
it('should return abreviated weekday name and time when date is on past week', () => {
date.setDate(date.getDate() - 3);
const ret = controller.relativeDate(date);
expect(ret).toEqual('Fri 12:00');
});
it('should return abreviated month name, day number and time when date is on this year', () => {
date.setDate(date.getDate() + 20);
const ret = controller.relativeDate(date);
expect(ret).toEqual('21 Jan 12:00');
});
it('should return abreviated month name, day number, year and time when date is on different year', () => {
date.setDate(date.getDate() - 20);
const ret = controller.relativeDate(date);
expect(ret).toEqual('12/12/2000 12:00');
});
it('should convert to date and return string when date is not a Date class instance', () => {
const ret = controller.relativeDate(date.toJSON());
expect(ret).toEqual('today 12:00');
});
});
describe('castJsonValue()', () => {
it('should return date when string has valid JSON date format', () => {
const now = Date.vnNew();
const ret = controller.castJsonValue(now.toJSON());
expect(ret).toBeInstanceOf(Date);
});
it('should return same value when is string with invalid JSON date format', () => {
const ret = controller.castJsonValue('Foo');
expect(ret).toEqual('Foo');
});
it('should return same value when is not an string', () => {
const ret = controller.castJsonValue(1001);
expect(ret).toEqual(1001);
});
});
});

View File

@ -13,3 +13,6 @@ Views: Visualiza
System: Sistema
note: nota
Changes: Cambios
No changes: No hay cambios
today: hoy
yesterday: ayer

View File

@ -1,66 +1,152 @@
@import "variables";
vn-log {
vn-td {
vertical-align: initial !important;
}
.changes {
display: none;
}
.label {
color: $color-font-secondary;
}
.value {
color: $color-font;
}
.change {
display: flex;
@media screen and (max-width: 1570px) {
vn-table .expendable {
& > .user-wrapper {
position: relative;
padding-right: 10px;
& > vn-avatar {
cursor: pointer;
&.system {
background-color: $color-main !important;
}
}
& > .arrow {
height: 8px;
width: 8px;
position: absolute;
transform: rotateY(0deg) rotate(45deg);
top: 18px;
right: -4px;
z-index: 1;
}
& > .line {
position: absolute;
background-color: $color-main;
width: 2px;
left: 17px;
z-index: -1;
top: 44px;
bottom: -8px;
}
}
&:last-child > .user-wrapper > .line {
display: none;
}
.changes {
padding-top: 10px;
display: block;
.detail {
position: relative;
flex-grow: 1;
width: 100%;
border-radius: 2px;
overflow: hidden;
& > .header {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
& > .chip {
padding: 2px 4px;
border-radius: 4px;
display: inline-block;
color: $color-font-bg;
&.notice {
background-color: $color-notice-medium;
}
&.success {
background-color: $color-success-medium;
}
&.warning {
background-color: $color-main-medium;
}
&.alert {
background-color: lighten($color-alert, 5%);
}
}
.date {
float: right;
}
}
& > .model {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
& > .model-name {
text-transform: capitalize;
}
& > .model-value {
font-style: italic;
color: #c7bd2b;
}
& > .model-id {
color: $color-font-secondary;
font-size: .9em;
}
}
}
}
.attributes {
width: 100%;
.changes {
overflow: hidden;
background-color: rgba(255, 255, 255, .05);
border-radius: 4px;
color: $color-font-secondary;
transition: max-height 150ms ease-in-out;
max-height: 28px;
position: relative;
tr {
height: 10px;
& > .expand-button,
& > .shrink-button {
display: none;
}
&.props {
padding-right: 24px;
& > td {
padding: 2px;
& > .expand-button,
& > .shrink-button {
position: absolute;
top: 6px;
right: 8px;
font-size: inherit;
float: right;
cursor: pointer;
}
& > td.field,
& > th.field {
width: 20%;
color: gray;
& > .expand-button {
display: block;
}
& > td.before,
& > th.before,
& > td.after,
& > th.after {
width: 40%;
white-space: pre-line;
&.expanded {
max-height: 500px;
padding-right: 0;
& > .changes-wrapper {
text-overflow: initial;
white-space: initial;
}
& > .shrink-button {
display: block;
}
& > .expand-button {
display: none;
}
}
}
& > .changes-wrapper {
padding: 4px 6px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
& > .no-changes {
font-style: italic;
}
.json-field {
text-transform: capitalize;
}
}
}
}
.ellipsis {
white-space: nowrap;
overflow: hidden;
max-width: 400px;
text-overflow: ellipsis;
display: inline-block;
}
.no-ellipsize,
[no-ellipsize] {
text-overflow: '';
white-space: normal;
overflow: auto;
}
.alignSpan {
overflow: hidden;
display: inline-block;
}

View File

@ -0,0 +1,40 @@
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethod('editors', {
description: 'Get the list of entity editors',
accepts: [
{
arg: 'id',
type: 'integer',
description: 'The model id',
required: true
}, {
arg: 'filter',
type: 'Object',
description: 'The user filter object'
}
],
returns: {
type: [Self],
root: true
},
http: {
path: `/:id/editors`,
verb: 'GET'
}
});
Self.editors = async(id, filter) => {
const res = await Self.find({
fields: ['userFk'],
where: {originFk: id}
});
const userIds = new Set(res.map(x => x.userFk));
filter = mergeFilters(filter, {
where: {id: {inq: [...userIds]}}
});
return await Self.app.models.VnUser.find(filter);
};
};

View File

@ -0,0 +1,44 @@
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethod('models', {
description: 'Get the list of entity models',
accepts: [
{
arg: 'id',
type: 'integer',
description: 'The model id',
required: true
}, {
arg: 'filter',
type: 'Object',
description: 'The filter object'
}
],
returns: {
type: [Self],
root: true
},
http: {
path: `/:id/models`,
verb: 'GET'
}
});
Self.models = async(id, filter) => {
filter = mergeFilters(filter, {
fields: ['changedModel'],
where: {
originFk: id,
changedModel: {neq: null}
}
});
const res = await Self.find(filter);
const set = new Set();
return res.filter(x => set.has(x.changedModel)
? false
: set.add(x.changedModel)
);
};
};

View File

@ -0,0 +1,10 @@
module.exports = function(Self) {
Object.assign(Self, {
setup() {
Self.super_.setup.call(this);
require('../methods/log/editors')(this);
require('../methods/log/models')(this);
}
});
};

View File

@ -0,0 +1,4 @@
{
"name": "Log",
"base": "VnModel"
}

View File

@ -21,19 +21,21 @@ module.exports = function(Self) {
let orgBeginTransaction = this.beginTransaction;
this.beginTransaction = function(options, cb) {
options = options || {};
if (!options.timeout)
options.timeout = 120000;
if (options.timeout === undefined)
options.timeout = 120 * 1000;
return orgBeginTransaction.call(this, options, cb);
};
});
// Register field ACL validation
/* this.beforeRemote('prototype.patchAttributes', ctx => this.checkUpdateAcls(ctx));
/*
this.beforeRemote('prototype.patchAttributes', ctx => this.checkUpdateAcls(ctx));
this.beforeRemote('updateAll', ctx => this.checkUpdateAcls(ctx));
this.beforeRemote('patchOrCreate', ctx => this.checkInsertAcls(ctx));
this.beforeRemote('create', ctx => this.checkInsertAcls(ctx));
this.beforeRemote('replaceById', ctx => this.checkInsertAcls(ctx));
this.beforeRemote('replaceOrCreate', ctx => this.checkInsertAcls(ctx)); */
this.beforeRemote('replaceOrCreate', ctx => this.checkInsertAcls(ctx));
*/
this.remoteMethod('crud', {
description: `Create, update or/and delete instances from model with a single request`,

View File

@ -156,6 +156,19 @@
"Component cost not set": "Componente coste no está estabecido",
"Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº 2": "Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº 2",
"Description cannot be blank": "Description cannot be blank",
"company": "Company",
"country": "Country",
"clientId": "Id client",
"clientSocialName": "Client",
"amount": "Amount",
"taxableBase": "Taxable base",
"ticketFk": "Id ticket",
"isActive": "Active",
"hasToInvoice": "Invoice",
"isTaxDataChecked": "Data checked",
"comercialId": "Id Comercial",
"comercialName": "Comercial",
"Added observation": "Added observation",
"Comment added to client": "Comment added to client"
"Comment added to client": "Comment added to client",
"This ticket is already a refund": "This ticket is already a refund"
}

View File

@ -272,8 +272,23 @@
"Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº {{id}}",
"Not exist this branch": "La rama no existe",
"This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado",
"Collection does not exist": "La colección no existe",
"Cannot obtain exclusive lock": "No se puede obtener un bloqueo exclusivo",
"Insert a date range": "Inserte un rango de fechas",
"Added observation": "{{user}} añadió esta observacion: {{text}}",
"Comment added to client": "Observación añadida al cliente {{clientFk}}",
"Cannot create a new claimBeginning from a different ticket": "No se puede crear una línea de reclamación de un ticket diferente al origen"
"Added observation": "{{user}} añadió esta observacion: {{text}}",
"Comment added to client": "Observación añadida al cliente {{clientFk}}",
"Cannot create a new claimBeginning from a different ticket": "No se puede crear una línea de reclamación de un ticket diferente al origen",
"company": "Compañía",
"country": "País",
"clientId": "Id cliente",
"clientSocialName": "Cliente",
"amount": "Importe",
"taxableBase": "Base",
"ticketFk": "Id ticket",
"isActive": "Activo",
"hasToInvoice": "Facturar",
"isTaxDataChecked": "Datos comprobados",
"comercialId": "Id comercial",
"comercialName": "Comercial",
"Invalid NIF for VIES": "Invalid NIF for VIES"
}

View File

@ -15,7 +15,7 @@
"legacyUtcDateProcessing": false,
"timezone": "local",
"connectTimeout": 40000,
"acquireTimeout": 60000,
"acquireTimeout": 90000,
"waitForConnections": true
},
"osticket": {

View File

@ -0,0 +1,7 @@
name: mail
columns:
id: id
receiver: receiver
replyTo: reply to
subject: subject
body: body

View File

@ -0,0 +1,7 @@
name: mail
columns:
id: id
receiver: receptor
replyTo: responder a
subject: asunto
body: cuerpo

View File

@ -1,6 +1,6 @@
{
"name": "RoleLog",
"base": "VnModel",
"base": "Log",
"options": {
"mysql": {
"table": "account.roleLog"

View File

@ -1,6 +1,6 @@
{
"name": "UserLog",
"base": "VnModel",
"base": "Log",
"options": {
"mysql": {
"table": "account.userLog"

View File

@ -6,6 +6,7 @@
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
vn-focus
panel="vn-user-search-panel"
info="Search user by id, name or nickname"
model="model"

View File

@ -0,0 +1,6 @@
name: claim beginning
columns:
id: id
quantity: quantity
claimFk: claim
saleFk: sale

View File

@ -0,0 +1,6 @@
name: comienzo reclamación
columns:
id: id
quantity: cantidad
claimFk: reclamación
saleFk: línea

View File

@ -0,0 +1,9 @@
name: claim development
columns:
id: id
claimFk: claim
claimResponsibleFk: responsible
claimReasonFk: reason
claimResultFk: result
claimRedeliveryFk: redelivery
workerFk: worker

View File

@ -0,0 +1,9 @@
name: desarrollo reclamación
columns:
id: id
claimFk: reclamación
claimResponsibleFk: responsable
claimReasonFk: motivo
claimResultFk: resultado
claimRedeliveryFk: reenvío
workerFk: trabajador

View File

@ -0,0 +1,4 @@
name: claim dms
columns:
dmsFk: dms
claimFk: claim

View File

@ -0,0 +1,4 @@
name: documento reclamación
columns:
dmsFk: dms
claimFk: reclamación

View File

@ -0,0 +1,7 @@
name: claim end
columns:
id: id
claimFk: claim
saleFk: sale
workerFk: worker
claimDestinationFk: destination

View File

@ -0,0 +1,7 @@
name: final reclamación
columns:
id: id
claimFk: reclamación
saleFk: línea
workerFk: trabajador
claimDestinationFk: destino

View File

@ -0,0 +1,7 @@
name: claim observation
columns:
id: id
claimFk: claim
text: text
created: created
workerFk: worker

View File

@ -0,0 +1,7 @@
name: observación reclamación
columns:
id: id
claimFk: reclamación
text: texto
created: creado
workerFk: tabajador

View File

@ -0,0 +1,16 @@
name: claim
columns:
id: id
observation: observation
ticketCreated: ticket created
isChargedToMana: charged to mana
created: created
responsibility: responsibility
hasToPickUp: has to pickUp
ticketFk: ticket
claimStateFk: claim state
workerFk: worker
packages: packages
rma: rma
clientFk: client
claimFk: claim

View File

@ -0,0 +1,16 @@
name: reclamación
columns:
id: id
observation: observación
ticketCreated: ticket creado
isChargedToMana: cargado al maná
created: creado
responsibility: responsabilidad
hasToPickUp: es recogida
ticketFk: ticket
claimStateFk: estado reclamación
workerFk: trabajador
packages: paquetes
rma: rma
clientFk: cliente
claimFk: reclamación

View File

@ -109,6 +109,11 @@ module.exports = Self => {
zoneFk: zone.id
}, myOptions);
await models.TicketRefund.create({
refundTicketFk: newRefundTicket.id,
originalTicketFk: claim.ticket().id
}, myOptions);
await saveObservation({
description: `Reclama ticket: ${claim.ticketFk}`,
ticketFk: newRefundTicket.id,

View File

@ -1,6 +1,6 @@
{
"name": "ClaimLog",
"base": "VnModel",
"base": "Log",
"options": {
"mysql": {
"table": "claimLog"

View File

@ -16,7 +16,7 @@
value="{{$ctrl.claimedTotal | currency: 'EUR':2}}">
</vn-label-value>
</vn-card>
<vn-card class="vn-pa-lg vn-w-lg">
<vn-card class="vn-pa-md vn-w-lg">
<smart-table
model="model"
options="$ctrl.smartTableOptions"
@ -31,10 +31,10 @@
translate-attr="{title: 'Imports claim details'}">
</vn-button>
<vn-button
label="Change destination"
disabled="$ctrl.checked.length == 0"
ng-click="changeDestination.show()">
</vn-button>
label="Change destination"
disabled="$ctrl.checked.length == 0"
ng-click="changeDestination.show()">
</vn-button>
<vn-range
label="Responsability"
min-label="Company"
@ -45,15 +45,15 @@
step="1"
on-change="$ctrl.save({responsibility: value})">
</vn-range>
<vn-check class="right"
vn-one
label="Is paid with mana"
ng-model="$ctrl.claim.isChargedToMana"
on-change="$ctrl.save({isChargedToMana: value})">
</vn-check>
</vn-tool-bar>
<vn-check class="right"
vn-one
label="Is paid with mana"
ng-model="$ctrl.claim.isChargedToMana"
on-change="$ctrl.save({isChargedToMana: value})">
</vn-check>
</section>
</slot-actions>
</slot-actions>
<slot-table>
<table model="model">
<thead>

View File

@ -17,6 +17,10 @@ class Controller extends Descriptor {
}
sendPickupOrder() {
if (!this.claim.client.email) {
this.vnApp.showError(this.$t('The client does not have an email'));
return;
}
return this.vnEmail.send(`Claims/${this.claim.id}/claim-pickup-email`, {
recipient: this.claim.client.email,
recipientId: this.claim.clientFk

View File

@ -20,3 +20,4 @@ Photos: Fotos
Go to the claim: Ir a la reclamación
Sale tracking: Líneas preparadas
Ticket tracking: Estados del ticket
The client does not have an email: El cliente no tiene email

View File

@ -1,48 +1 @@
<vn-crud-model
vn-id="model"
auto-load="true"
filter="::$ctrl.filter"
url="ClaimDms"
link="{claimFk: $ctrl.$params.id}"
limit="20"
data="$ctrl.photos">
</vn-crud-model>
<vn-horizontal class="photo-list drop-zone" vn-droppable="$ctrl.onDrop($event)">
<section class="empty-rows" ng-if="!$ctrl.photos.length">
<section><vn-icon icon="image"></vn-icon></section>
<section translate>Drag & Drop photos here...</section>
</section>
<section class="photo" ng-repeat="photo in $ctrl.photos">
<section class="image vn-shadow" on-error-src
ng-style="{'background': 'url(' + $ctrl.getImagePath(photo.dmsFk) + ')'}"
zoom-image="{{$ctrl.getImagePath(photo.dmsFk)}}"
ng-if="photo.dms.contentType != 'video/mp4'">
</section>
<video id="videobcg" muted="muted" controls ng-if="photo.dms.contentType == 'video/mp4'"
class="video">
<source src="{{$ctrl.getImagePath(photo.dmsFk)}}" type="video/mp4">
</video>
<section class="actions">
<vn-button
class="round"
ng-click="confirm.show($index)"
title="{{'Remove file' | translate}}"
tabindex="-1"
icon="delete">
</vn-button>
</section>
</section>
</vn-horizontal>
<vn-confirm
vn-id="confirm"
message="This file will be deleted"
question="Are you sure you want to continue?"
on-accept="$ctrl.deleteDms($data)">
</vn-confirm>
<vn-float-button
icon="add"
vn-tooltip="Select file"
vn-bind="+"
ng-click="$ctrl.openUploadDialog()"
fixed-bottom-right>
</vn-float-button>

View File

@ -1,105 +1,17 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
constructor($element, $, vnFile) {
constructor($element, $) {
super($element, $);
this.vnFile = vnFile;
this.filter = {
include: [
{
relation: 'dms'
}
]
};
}
deleteDms(index) {
const dmsFk = this.photos[index].dmsFk;
return this.$http.post(`ClaimDms/${dmsFk}/removeFile`)
.then(() => {
this.$.model.remove(index);
this.vnApp.showSuccess(this.$t('File deleted'));
});
}
onDrop($event) {
const files = $event.dataTransfer.files;
this.setDefaultParams().then(() => {
this.dms.files = files;
this.create();
});
}
setDefaultParams() {
const filter = {
where: {code: 'claim'}
};
return this.$http.get('DmsTypes/findOne', {filter}).then(res => {
const dmsTypeId = res.data && res.data.id;
const companyId = this.vnConfig.companyFk;
const warehouseId = this.vnConfig.warehouseFk;
this.dms = {
hasFile: false,
hasFileAttached: false,
reference: this.claim.id,
warehouseId: warehouseId,
companyId: companyId,
dmsTypeId: dmsTypeId,
description: this.$t('FileDescription', {
claimId: this.claim.id,
clientId: this.claim.client.id,
clientName: this.claim.client.name
}).toUpperCase()
};
});
}
openUploadDialog() {
const element = document.createElement('input');
element.setAttribute('type', 'file');
element.setAttribute('multiple', true);
element.click();
element.addEventListener('change', () =>
this.setDefaultParams().then(() => {
this.dms.files = element.files;
this.create();
})
);
}
create() {
const query = `claims/${this.claim.id}/uploadFile`;
const options = {
method: 'POST',
url: query,
params: this.dms,
headers: {'Content-Type': undefined},
transformRequest: files => {
const formData = new FormData();
for (let i = 0; i < files.length; i++)
formData.append(files[i].name, files[i]);
return formData;
},
data: this.dms.files
};
this.$http(options).then(() => {
this.vnApp.showSuccess(this.$t('File uploaded!'));
this.$.model.refresh();
});
}
getImagePath(dmsId) {
return this.vnFile.getPath(`/api/Claims/${dmsId}/downloadFile`);
async $onInit() {
const url = await this.vnApp.getUrl(`claim/${this.$params.id}/photos`);
window.location.href = url;
}
}
Controller.$inject = ['$element', '$scope', 'vnFile'];
ngModule.vnComponent('vnClaimPhotos', {
template: require('./index.html'),
controller: Controller,

View File

@ -1,70 +0,0 @@
import './index';
import crudModel from 'core/mocks/crud-model';
describe('Claim', () => {
describe('Component vnClaimPhotos', () => {
let $scope;
let $httpBackend;
let controller;
beforeEach(ngModule('claim'));
beforeEach(inject(($componentController, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
controller = $componentController('vnClaimPhotos', {$element: null, $scope});
controller.$.model = crudModel;
controller.claim = {
id: 1,
client: {id: 1101, name: 'Bruce Wayne'}
};
}));
describe('deleteDms()', () => {
it('should make an HTTP Post query', () => {
jest.spyOn(controller.vnApp, 'showSuccess');
jest.spyOn(controller.$.model, 'remove');
const dmsId = 1;
const dmsIndex = 0;
controller.photos = [{dmsFk: 1}];
$httpBackend.expectPOST(`ClaimDms/${dmsId}/removeFile`).respond();
controller.deleteDms(dmsIndex);
$httpBackend.flush();
expect(controller.$.model.remove).toHaveBeenCalledWith(dmsIndex);
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('setDefaultParams()', () => {
it('should make an HTTP GET query, then set all dms properties', () => {
$httpBackend.expectRoute('GET', `DmsTypes/findOne`).respond({});
controller.setDefaultParams();
$httpBackend.flush();
expect(controller.dms).toBeDefined();
});
});
describe('create()', () => {
it('should make an HTTP Post query, then refresh the model data', () => {
const claimId = 1;
const dmsIndex = 0;
jest.spyOn(controller.vnApp, 'showSuccess');
jest.spyOn(controller.$.model, 'refresh');
controller.photos = [{dmsFk: 1}];
controller.dmsIndex = dmsIndex;
controller.dms = {files: []};
$httpBackend.expectPOST(`claims/${claimId}/uploadFile`).respond({});
controller.create();
$httpBackend.flush();
expect(controller.$.model.refresh).toHaveBeenCalled();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
});
});

View File

@ -1,5 +0,0 @@
Are you sure you want to continue?: ¿Seguro que quieres continuar?
Drag & Drop photos here...: Arrastra y suelta fotos aquí...
File deleted: Archivo eliminado
File uploaded!: Archivo subido!
Select file: Seleccionar fichero

View File

@ -1,47 +0,0 @@
@import "./variables";
vn-claim-photos {
height: 100%;
.drop-zone {
color: $color-font-secondary;
box-sizing: border-box;
border-radius: 8px;
text-align: center;
min-height: 100%;
.empty-rows {
padding: 80px $spacing-md;
font-size: 1.375rem
}
vn-icon {
font-size: 3rem
}
}
.photo-list {
padding: $spacing-md;
min-height: 100%;
.photo {
width: 512px;
height: 288px;
}
}
.video {
width: 100%;
height: 100%;
object-fit: cover;
cursor: pointer;
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),
0 3px 1px -2px rgba(0,0,0,.2),
0 1px 5px 0 rgba(0,0,0,.12);
border: 2px solid transparent;
}
.video:hover {
border: 2px solid $color-primary
}
}

View File

@ -0,0 +1,20 @@
name: address
columns:
id: id
nickname: nickname
street: street
city: city
postalCode: postal code
phone: phone
mobile: mobile
isActive: active
longitude: longitude
latitude: latitude
isEqualizated: equalizated
isLogifloraAllowed: logiflora allowed
provinceFk: province
clientFk: client
agencyModeFk: agency
addressFk: address
incotermsFk: incoterms
customsAgentFk: customs agent

View File

@ -0,0 +1,20 @@
name: dirección
columns:
id: id
nickname: apodo
street: calle
city: ciudad
postalCode: código postal
phone: teléfono
mobile: móvil
isActive: activo
longitude: longitud
latitude: latitud
isEqualizated: igualado
isLogifloraAllowed: logiflora permitido
provinceFk: provincia
clientFk: cliente
agencyModeFk: agencia
addressFk: dirección
incotermsFk: incoterms
customsAgentFk: agente adunanas

View File

@ -0,0 +1,6 @@
name: client contact
columns:
id: id
name: name
phone: phone
clientFk: client

View File

@ -0,0 +1,6 @@
name: contacto cliente
columns:
id: id
name: nombre
phone: teléfono
clientFk: cliente

View File

@ -0,0 +1,4 @@
name: client dms
columns:
dmsFk: dms
clientFk: client

View File

@ -0,0 +1,4 @@
name: documento cliente
columns:
dmsFk: dms
clientFk: client

View File

@ -0,0 +1,7 @@
name: client observation
columns:
id: id
clientFk: client
text: text
created: created
workerFk: worker

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