Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into test
gitea/salix/test This commit looks good Details

This commit is contained in:
Joan Sanchez 2019-11-14 08:17:58 +01:00
commit f02a68cbd8
293 changed files with 5385 additions and 4029 deletions

View File

@ -28,8 +28,10 @@ module.exports = Self => {
const models = Self.app.models;
const accessToken = ctx.req.accessToken;
const sender = await models.Account.findById(accessToken.userId);
const recipient = to.replace('@', '');
return sendMessage(to, `@${sender.name}: ${message}`);
if (sender.name != recipient)
return sendMessage(to, `@${sender.name}: ${message}`);
};
async function sendMessage(name, message) {
@ -70,6 +72,12 @@ module.exports = Self => {
}
function send(uri, body) {
if (process.env.NODE_ENV !== 'production') {
return new Promise(resolve => {
return resolve({statusCode: 200, message: 'Fake notification sent'});
});
}
const options = {
method: 'POST',
uri: uri,

View File

@ -0,0 +1,18 @@
const app = require('vn-loopback/server/server');
describe('chat sendMessage()', () => {
it('should return a "Fake notification sent" as response', async() => {
let ctx = {req: {accessToken: {userId: 1}}};
let response = await app.models.Chat.sendMessage(ctx, '@salesPerson', 'I changed something');
expect(response.statusCode).toEqual(200);
expect(response.message).toEqual('Fake notification sent');
});
it('should not return a response', async() => {
let ctx = {req: {accessToken: {userId: 18}}};
let response = await app.models.Chat.sendMessage(ctx, '@salesPerson', 'I changed something');
expect(response).toBeUndefined();
});
});

View File

@ -0,0 +1,10 @@
USE `vn`;
UPDATE `vn`.`country` SET `ibanLength` = '24' WHERE (`id` = 1);
UPDATE `vn`.`country` SET `ibanLength` = '27' WHERE (`id` = 2);
UPDATE `vn`.`country` SET `ibanLength` = '22' WHERE (`id` = 3);
UPDATE `vn`.`country` SET `ibanLength` = '24' WHERE (`id` = 4);
UPDATE `vn`.`country` SET `ibanLength` = '18' WHERE (`id` = 5);
UPDATE `vn`.`country` SET `ibanLength` = '25' WHERE (`id` = 8);
UPDATE `vn`.`country` SET `ibanLength` = '27' WHERE (`id` = 19);
UPDATE `vn`.`country` SET `ibanLength` = '24' WHERE (`id` = 30);

View File

@ -0,0 +1,8 @@
USE `vn`;
UPDATE `vn`.`sample` SET `description` = 'Bienvenida como nuevo cliente' WHERE (`id` = '12');
UPDATE `vn`.`sample` SET `description` = 'Instalación y configuración de impresora de coronas' WHERE (`id` = '13');
UPDATE `vn`.`sample` SET `description` = 'Solicitud de domiciliación bancaria' WHERE (`id` = '14');
UPDATE `vn`.`sample` SET `description` = 'Aviso inicial por saldo deudor' WHERE (`id` = '15');
UPDATE `vn`.`sample` SET `description` = 'Aviso reiterado por saldo deudor' WHERE (`id` = '16');
UPDATE `vn`.`sample` SET `isVisible` = '0' WHERE (`id` = '17');

View File

@ -0,0 +1,86 @@
USE `vn`;
ALTER TABLE `vn`.`ticketRequest`
DROP FOREIGN KEY `fgnAtender`;
ALTER TABLE `vn`.`ticketRequest`
CHANGE COLUMN `atenderFk` `attenderFk` INT(11) NULL DEFAULT NULL ;
ALTER TABLE `vn`.`ticketRequest`
ADD CONSTRAINT `fgnAtender`
FOREIGN KEY (`attenderFk`)
REFERENCES `vn`.`worker` (`id`)
ON UPDATE CASCADE;
USE `vn2008`;
CREATE
OR REPLACE ALGORITHM = UNDEFINED
DEFINER = `root`@`%`
SQL SECURITY DEFINER
VIEW `vn2008`.`Ordenes` AS
SELECT
`tr`.`id` AS `Id_ORDEN`,
`tr`.`description` AS `ORDEN`,
`tr`.`requesterFk` AS `requesterFk`,
`tr`.`attenderFk` AS `attenderFk`,
`tr`.`quantity` AS `CANTIDAD`,
`tr`.`itemFk` AS `Id_ARTICLE`,
`tr`.`price` AS `PRECIOMAX`,
`tr`.`isOk` AS `isOk`,
`tr`.`saleFk` AS `Id_Movimiento`,
`tr`.`ticketFk` AS `ticketFk`,
`tr`.`response` AS `COMENTARIO`,
`tr`.`created` AS `odbc_date`,
`tr`.`ordered` AS `datORDEN`,
`tr`.`shipped` AS `datTICKET`,
`tr`.`salesPersonCode` AS `CodVENDEDOR`,
`tr`.`buyerCode` AS `CodCOMPRADOR`,
`tr`.`price__` AS `PREU`,
`tr`.`clientFk` AS `Id_CLIENTE`,
`tr`.`ok__` AS `OK`,
`tr`.`total` AS `TOTAL`,
`tr`.`buyed` AS `datCOMPRA`,
`tr`.`ko__` AS `KO`
FROM
`vn`.`ticketRequest` `tr`;
USE `vn`;
DROP TRIGGER IF EXISTS `vn`.`ticketRequest_beforeInsert`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` TRIGGER `vn`.`ticketRequest_beforeInsert` BEFORE INSERT ON `ticketRequest` FOR EACH ROW
BEGIN
IF NEW.ticketFk IS NULL THEN
SET NEW.ticketFk = (SELECT s.ticketFk FROM sale s WHERE s.id = NEW.saleFk);
END IF;
IF NEW.requesterFk IS NULL THEN
SET NEW.requesterFk = (SELECT w.id FROM worker w WHERE w.code = NEW.salesPersonCode);
END IF;
IF NEW.attenderFk IS NULL THEN
SET NEW.attenderFk = (SELECT w.id FROM worker w WHERE w.code = NEW.buyerCode);
END IF;
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS `vn`.`ticketRequest_beforeUpdate`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` TRIGGER `vn`.`ticketRequest_beforeUpdate` BEFORE UPDATE ON `ticketRequest` FOR EACH ROW
BEGIN
IF NEW.saleFk <> OLD.saleFk THEN
SET NEW.ticketFk = (SELECT s.ticketFk FROM sale s WHERE s.id = NEW.saleFk);
END IF;
IF NEW.salesPersonCode <> OLD.salesPersonCode THEN
SET NEW.requesterFk = (SELECT w.id FROM worker w WHERE w.code = NEW.salesPersonCode);
END IF;
IF NEW.buyerCode <> OLD.buyerCode THEN
SET NEW.attenderFk = (SELECT w.id FROM worker w WHERE w.code = NEW.buyerCode);
END IF;
END$$
DELIMITER ;

File diff suppressed because one or more lines are too long

View File

@ -53,13 +53,14 @@ INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossF
INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`, `ibanLength`)
VALUES
(1, 'España', 0, 'ES', 1, 22),
(2, 'Italia', 1, 'IT', 1, 25),
(3, 'Alemania', 1, 'DE', 1, 20),
(4, 'Rumania', 1, 'RO', 1, 22),
(5, 'Holanda', 1, 'NL', 1, 16),
(19,'Francia', 1, 'FR', 1, 25),
(30,'Canarias', 1, 'IC', 1, 22);
(1, 'España', 0, 'ES', 1, 24),
(2, 'Italia', 1, 'IT', 1, 27),
(3, 'Alemania', 1, 'DE', 1, 22),
(4, 'Rumania', 1, 'RO', 1, 24),
(5, 'Holanda', 1, 'NL', 1, 18),
(8, 'Portugal', 1, 'PT', 1, 27),
(19,'Francia', 1, 'FR', 1, 27),
(30,'Canarias', 1, 'IC', 1, 24);
INSERT INTO `vn`.`warehouse`(`id`, `name`, `isComparative`, `isInventory`, `hasAvailable`, `isManaged`, `hasStowaway`, `hasDms`)
VALUES
@ -195,9 +196,9 @@ INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city
VALUES
(101, 'Bruce Wayne', '84612325V', 'Batman', 'Alfred', '1007 Mountain Drive, Gotham', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'BruceWayne@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1),
(102, 'Petter Parker', '87945234L', 'Spider man', 'Aunt May', '20 Ingram Street', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'PetterParker@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1),
(103, 'Clark Kent', '06815934E', 'Super man', 'lois lane', '344 Clinton Street', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'ClarkKent@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 0, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1),
(103, 'Clark Kent', '06815934E', 'Super man', 'lois lane', '344 Clinton Street', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'ClarkKent@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 0, 19, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1),
(104, 'Tony Stark', '06089160W', 'Iron man', 'Pepper Potts', '10880 Malibu Point', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'TonyStark@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1),
(105, 'Max Eisenhardt', '251628698', 'Magneto', 'Rogue', 'Unknown Whereabouts', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'MaxEisenhardt@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 1, NULL, 0, 0, 18, 0, 1),
(105, 'Max Eisenhardt', '251628698', 'Magneto', 'Rogue', 'Unknown Whereabouts', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'MaxEisenhardt@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 8, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 1, NULL, 0, 0, 18, 0, 1),
(106, 'DavidCharlesHaller', '53136686Q', 'Legion', 'Charles Xavier', 'Evil hideout', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'DavidCharlesHaller@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 0, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 0, NULL, 0, 0, 19, 0, 1),
(107, 'Hank Pym', '09854837G', 'Ant man', 'Hawk', 'Anthill', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'HankPym@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1),
(108, 'Charles Xavier', '22641921P', 'Professor X', 'Beast', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1),
@ -1460,7 +1461,7 @@ INSERT INTO `vn`.`receipt`(`id`, `invoiceFk`, `amountPaid`, `amountUnpaid`, `pay
(1, 'Cobro web', 100.50, 0.00, CURDATE(), 9, 1, 101, CURDATE(), 442, 1),
(2, 'Cobro web', 200.50, 0.00, DATE_ADD(CURDATE(), INTERVAL -5 DAY), 9, 1, 101, DATE_ADD(CURDATE(), INTERVAL -5 DAY), 442, 1),
(3, 'Cobro en efectivo', 300.00, 100.00, DATE_ADD(CURDATE(), INTERVAL -10 DAY), 9, 1, 102, DATE_ADD(CURDATE(), INTERVAL -10 DAY), 442, 0),
(4, 'Cobro en efectivo', -400.00, -50.00, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 9, 1, 103, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 442, 0);
(4, 'Cobro en efectivo', 400.00, -50.00, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 9, 1, 103, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 442, 0);
INSERT INTO `vn2008`.`workerTeam`(`id`, `team`, `user`)
VALUES
@ -1471,7 +1472,7 @@ INSERT INTO `vn2008`.`workerTeam`(`id`, `team`, `user`)
(5, 3, 103),
(6, 3, 104);
INSERT INTO `vn`.`ticketRequest`(`id`, `description`, `requesterFk`, `atenderFk`, `quantity`, `itemFk`, `price`, `isOk`, `saleFk`, `ticketFk`, `created`)
INSERT INTO `vn`.`ticketRequest`(`id`, `description`, `requesterFk`, `attenderFk`, `quantity`, `itemFk`, `price`, `isOk`, `saleFk`, `ticketFk`, `created`)
VALUES
(1, 'Ranged weapon longbow 2m', 18, 35, 5, 1, 9.10, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -15 DAY)),
(2, 'Melee weapon combat first 15cm', 18, 35, 10, 2, 1.07, 0, NULL, 1, DATE_ADD(CURDATE(), INTERVAL -15 DAY)),

View File

@ -47,16 +47,17 @@ TABLES=(
claimResult
ticketUpdateAction
state
sample
)
dump_tables ${TABLES[@]}
TABLES=(
vn2008
time
accion_dits
businessReasonEnd
container
department
escritos
Grupos
iva_group_codigo
tarifa_componentes

View File

@ -112,7 +112,7 @@
"You cannot move a parent to its own sons": "No puedes mover un elemento padre a uno de sus hijos",
"You can't create a claim for a removed ticket": "No puedes crear una reclamación para un ticket eliminado",
"You cannot delete this ticket because is already invoiced, deleted or prepared": "No puedes eliminar este tiquet porque ya está facturado, eliminado o preparado",
"You cannot delete a ticket that it's being prepared": "No puedes eliminar un ticket que está siendo preparado",
"You cannot delete a ticket that part of it is being prepared": "No puedes eliminar un ticket en el que una parte que está siendo preparada",
"You must delete all the buy requests first": "Debes eliminar todas las peticiones de compra primero",
"Has deleted the ticket id": "Ha eliminado el ticket id [#{{id}}]({{{url}}})",
"You cannot remove this ticket because is already invoiced, deleted or prepared": "You cannot remove this ticket because is already invoiced, deleted or prepared"

View File

@ -1,3 +1,3 @@
module.exports = function(app) {
require('../../../print/server.js')(app);
require('../../../print/boot.js')(app);
};

16
loopback/util/http.js Normal file
View File

@ -0,0 +1,16 @@
/**
* Serializes an object to a query params
*
* @param {Object} obj The params object
* @return {String} Serialized params
*/
exports.httpParamSerializer = function(obj) {
let query = '';
for (let param in obj) {
if (query != '')
query += '&';
query += `${param}=${obj[param]}`;
}
return query;
};

View File

@ -15,7 +15,7 @@
<vn-treeview vn-id="treeview" root-label="Locations"
fetch-func="$ctrl.onFetch($item)"
sort-func="$ctrl.onSort($a, $b)">
<vn-check
<vn-check acl-role="deliveryBoss"
ng-model="item.selected"
on-change="$ctrl.onSelection(value, item)"
triple-state="true"

View File

@ -35,7 +35,7 @@ class Controller {
{
relation: 'client',
scope: {
fields: ['salesPersonFk', 'name'],
fields: ['salesPersonFk', 'name', 'email'],
include: {
relation: 'salesPerson',
scope: {

View File

@ -1,13 +1,14 @@
import ngModule from '../module';
class Controller {
constructor($scope, $state, $http, $translate, vnApp, aclService) {
constructor($scope, $state, $http, $translate, vnApp, aclService, $httpParamSerializer) {
this.$scope = $scope;
this.$state = $state;
this.$http = $http;
this.$translate = $translate;
this.vnApp = vnApp;
this.aclService = aclService;
this.$httpParamSerializer = $httpParamSerializer;
this.moreOptions = [
{callback: this.showPickupOrder, name: 'Show Pickup order'},
{callback: this.confirmPickupOrder, name: 'Send Pickup order'},
@ -60,7 +61,12 @@ class Controller {
}
showPickupOrder() {
let url = `report/rpt-claim-pickup-order?claimFk=${this.claim.id}`;
const params = {
clientId: this.claim.clientFk,
claimId: this.claim.id
};
const serializedParams = this.$httpParamSerializer(params);
let url = `api/report/claim-pickup-order?${serializedParams}`;
window.open(url);
}
@ -70,7 +76,14 @@ class Controller {
sendPickupOrder(response) {
if (response === 'accept') {
this.$http.post(`email/claim-pickup-order`, {claimFk: this.claim.id}).then(
const params = {
recipient: this.claim.client.email,
clientId: this.claim.clientFk,
claimId: this.claim.id
};
const serializedParams = this.$httpParamSerializer(params);
const url = `email/claim-pickup-order?${serializedParams}`;
this.$http.get(url).then(
() => this.vnApp.showMessage(this.$translate.instant('Notification sent!'))
);
}
@ -90,7 +103,7 @@ class Controller {
}
}
Controller.$inject = ['$scope', '$state', '$http', '$translate', 'vnApp', 'aclService'];
Controller.$inject = ['$scope', '$state', '$http', '$translate', 'vnApp', 'aclService', '$httpParamSerializer'];
ngModule.component('vnClaimDescriptor', {
template: require('./index.html'),

View File

@ -1,20 +1,27 @@
import './index.js';
describe('Item Component vnClaimDescriptor', () => {
let $httpParamSerializer;
let $httpBackend;
let controller;
beforeEach(ngModule('claim'));
beforeEach(angular.mock.inject(($componentController, _$httpBackend_) => {
beforeEach(angular.mock.inject(($componentController, _$httpBackend_, _$httpParamSerializer_) => {
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
controller = $componentController('vnClaimDescriptor');
controller.claim = {id: 2};
controller.claim = {id: 2, clientFk: 101, client: {email: 'client@email'}};
}));
describe('showPickupOrder()', () => {
it('should open a new window showing a pickup order PDF document', () => {
let expectedPath = 'report/rpt-claim-pickup-order?claimFk=2';
const params = {
clientId: controller.claim.clientFk,
claimId: controller.claim.id
};
const serializedParams = $httpParamSerializer(params);
let expectedPath = `api/report/claim-pickup-order?${serializedParams}`;
spyOn(window, 'open');
controller.showPickupOrder();
@ -38,8 +45,15 @@ describe('Item Component vnClaimDescriptor', () => {
it('should make a query and call vnApp.showMessage() if the response is accept', () => {
spyOn(controller.vnApp, 'showMessage');
$httpBackend.when('POST', `email/claim-pickup-order`, {claimFk: 2}).respond();
$httpBackend.expect('POST', `email/claim-pickup-order`, {claimFk: 2}).respond();
const params = {
recipient: 'client@email',
clientId: controller.claim.clientFk,
claimId: controller.claim.id
};
const serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `email/claim-pickup-order?${serializedParams}`).respond();
$httpBackend.expect('GET', `email/claim-pickup-order?${serializedParams}`).respond();
controller.sendPickupOrder('accept');
$httpBackend.flush();

View File

@ -2,6 +2,8 @@ let request = require('request-promise-native');
let UserError = require('vn-loopback/util/user-error');
let getFinalState = require('vn-loopback/util/hook').getFinalState;
let isMultiple = require('vn-loopback/util/hook').isMultiple;
const httpParamSerializer = require('vn-loopback/util/http').httpParamSerializer;
const LoopBackContext = require('loopback-context');
module.exports = Self => {
// Methods
@ -239,15 +241,18 @@ module.exports = Self => {
});
}
const options = {
method: 'POST',
uri: 'http://127.0.0.1:3000/api/email/payment-update',
body: {
clientFk: instance.id
},
json: true
// Send email to client
if (!instance.email) return;
const loopBackContext = LoopBackContext.getCurrentContext();
const headers = loopBackContext.active.http.req.headers;
const params = {
clientId: instance.id,
recipient: instance.email
};
await request(options);
const serializedParams = httpParamSerializer(params);
const query = `${headers.origin}/api/email/payment-update?${serializedParams}`;
await request.get(query);
}
});

View File

@ -1,4 +1,9 @@
<mg-ajax path="ClientSamples" options="vnPost"></mg-ajax>
<vn-crud-model auto-load="true"
url="Companies"
data="companiesData"
order="code">
</vn-crud-model>
<vn-watcher
vn-id="watcher"
data="$ctrl.clientSample"
@ -8,9 +13,13 @@
<form name="form" ng-submit="$ctrl.onSubmit()" compact>
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-autocomplete
vn-one
vn-id="sampleType"
<vn-textfield vn-one
label="Recipient"
ng-model="$ctrl.clientSample.recipient">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one vn-id="sampleType"
ng-model="$ctrl.clientSample.typeFk"
model="ClientSample.typeFk"
fields="['code','hasCompany']"
@ -19,11 +28,10 @@
value-field="id"
label="Sample">
</vn-autocomplete>
<vn-autocomplete
vn-one
ng-model="$ctrl.clientSample.companyFk"
<vn-autocomplete vn-one
ng-model="$ctrl.companyId"
model="ClientSample.companyFk"
url="Companies"
data="companiesData"
show-field="code"
value-field="id"
label="Company"
@ -41,5 +49,9 @@
<vn-dialog
vn-id="show-preview"
on-open="$ctrl.onPreviewOpen()">
<tpl-body></tpl-body>
<tpl-body class="client-sample-dialog">
<div class="loading">
<vn-spinner enable="true"></vn-spinner>
</div>
</tpl-body>
</vn-dialog>

View File

@ -1,33 +1,84 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
class Controller {
constructor($scope, $state, $http, vnApp, $translate) {
this.$scope = $scope;
this.$state = $state;
this.$stateParams = $state.params;
this.$http = $http;
class Controller extends Component {
constructor($element, $, vnApp, $httpParamSerializer, vnConfig) {
super($element, $);
this.vnApp = vnApp;
this.$translate = $translate;
this.$httpParamSerializer = $httpParamSerializer;
this.vnConfig = vnConfig;
this.clientSample = {
clientFk: this.$stateParams.id
clientFk: this.$params.id,
companyFk: vnConfig.companyFk
};
}
jsonToQuery(json) {
let query = '';
for (let param in json) {
if (query != '')
query += '&';
query += `${param}=${json[param]}`;
}
get client() {
return this._client;
}
return query;
set client(value) {
this._client = value;
if (value)
this.clientSample.recipient = value.email;
}
get companyId() {
if (!this.clientSample.companyFk)
this.clientSample.companyFk = this.vnConfig.companyFk;
return this.clientSample.companyFk;
}
set companyId(value) {
this.clientSample.companyFk = value;
}
showPreview() {
let sampleType = this.$scope.sampleType.selection;
let params = {clientFk: this.$stateParams.id};
let sampleType = this.$.sampleType.selection;
if (!sampleType)
return this.vnApp.showError(this.$translate.instant('Choose a sample'));
if (sampleType.hasCompany && !this.clientSample.companyFk)
return this.vnApp.showError(this.$translate.instant('Choose a company'));
const params = {
clientId: this.$params.id,
recipient: this.clientSample.recipient,
isPreview: true
};
if (sampleType.hasCompany)
params.companyId = this.clientSample.companyFk;
const serializedParams = this.$httpParamSerializer(params);
const query = `email/${sampleType.code}?${serializedParams}`;
this.$http.get(query).then(res => {
this.$.showPreview.show();
let dialog = document.body.querySelector('div.vn-dialog');
let body = dialog.querySelector('tpl-body');
let scroll = dialog.querySelector('div:first-child');
body.innerHTML = res.data;
scroll.scrollTop = 0;
});
}
onSubmit() {
this.$.watcher.check();
this.$.watcher.realSubmit().then(() =>
this.sendSample()
);
}
sendSample() {
let sampleType = this.$.sampleType.selection;
let params = {
clientId: this.$params.id,
recipient: this.clientSample.recipient
};
if (!sampleType)
return this.vnApp.showError(this.$translate.instant('Choose a sample'));
@ -36,50 +87,22 @@ class Controller {
return this.vnApp.showError(this.$translate.instant('Choose a company'));
if (sampleType.hasCompany)
params.companyFk = this.clientSample.companyFk;
params.companyId = this.clientSample.companyFk;
let query = `email/${sampleType.code}?${this.jsonToQuery(params)}`;
const serializedParams = this.$httpParamSerializer(params);
const query = `email/${sampleType.code}?${serializedParams}`;
this.$http.get(query).then(res => {
if (res.data) {
let dialog = this.$scope.showPreview.element;
let body = dialog.querySelector('tpl-body');
let scroll = dialog.querySelector('div:first-child');
body.innerHTML = res.data;
this.$scope.showPreview.show();
scroll.scrollTop = 0;
}
});
}
onSubmit() {
this.$scope.watcher.check();
this.$scope.watcher.realSubmit().then(() =>
this.sendSample()
);
}
sendSample() {
let sampleType = this.$scope.sampleType.selection;
let params = {clientFk: this.$stateParams.id};
if (sampleType.hasCompany)
params.companyFk = this.clientSample.companyFk;
let query = `email/${sampleType.code}?${this.jsonToQuery(params)}`;
this.$http.post(query).then(res => {
if (res) {
this.vnApp.showSuccess(this.$translate.instant('Notification sent!'));
this.$state.go('client.card.sample.index');
}
this.vnApp.showSuccess(this.$translate.instant('Notification sent!'));
this.$state.go('client.card.sample.index');
});
}
}
Controller.$inject = ['$scope', '$state', '$http', 'vnApp', '$translate'];
Controller.$inject = ['$element', '$scope', 'vnApp', '$httpParamSerializer', 'vnConfig'];
ngModule.component('vnClientSampleCreate', {
template: require('./index.html'),
controller: Controller
controller: Controller,
bindings: {
client: '<'
}
});

View File

@ -2,14 +2,16 @@ import './index';
describe('Client', () => {
describe('Component vnClientSampleCreate', () => {
let $httpParamSerializer;
let $scope;
let $element;
let $httpBackend;
let $state;
let controller;
beforeEach(ngModule('client'));
beforeEach(angular.mock.inject(($componentController, _$httpBackend_, $rootScope, _$state_) => {
beforeEach(angular.mock.inject(($componentController, _$httpBackend_, $rootScope, _$state_, _$httpParamSerializer_) => {
$scope = $rootScope.$new();
$scope.sampleType = {};
$scope.watcher = {
@ -35,30 +37,24 @@ describe('Client', () => {
$state = _$state_;
$state.params.id = 101;
$httpBackend = _$httpBackend_;
controller = $componentController('vnClientSampleCreate', {$scope, $state});
$httpParamSerializer = _$httpParamSerializer_;
$element = angular.element('<vn-client-sample-create></vn-client-sample-create>');
controller = $componentController('vnClientSampleCreate', {$element, $scope});
}));
describe('jsonToQuery()', () => {
it(`should convert a JSON object with clientFk property to query params`, () => {
let myObject = {clientFk: 101};
let result = controller.jsonToQuery(myObject);
expect(result).toEqual('clientFk=101');
});
it(`should convert a JSON object with clientFk and companyFk properties to query params`, () => {
let myObject = {clientFk: 101, companyFk: 442};
let result = controller.jsonToQuery(myObject);
expect(result).toEqual('clientFk=101&companyFk=442');
});
});
describe('showPreview()', () => {
it(`should perform a query (GET) and open a sample preview`, () => {
spyOn(controller.$scope.showPreview, 'show');
spyOn(controller.$.showPreview, 'show');
const element = document.createElement('div');
document.body.querySelector = () => {
return {
querySelector: () => {
return element;
}
};
};
controller.$scope.sampleType.selection = {
controller.$.sampleType.selection = {
hasCompany: false,
code: 'MyReport'
};
@ -69,18 +65,32 @@ describe('Client', () => {
let event = {preventDefault: () => {}};
$httpBackend.when('GET', `email/MyReport?clientFk=101`).respond(true);
$httpBackend.expect('GET', `email/MyReport?clientFk=101`);
const params = {
clientId: 101,
isPreview: true
};
const serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `email/MyReport?${serializedParams}`).respond(true);
$httpBackend.expect('GET', `email/MyReport?${serializedParams}`);
controller.showPreview(event);
$httpBackend.flush();
expect(controller.$scope.showPreview.show).toHaveBeenCalledWith();
expect(controller.$.showPreview.show).toHaveBeenCalledWith();
});
it(`should perform a query (GET) with companyFk param and open a sample preview`, () => {
spyOn(controller.$scope.showPreview, 'show');
spyOn(controller.$.showPreview, 'show');
const element = document.createElement('div');
document.body.querySelector = () => {
return {
querySelector: () => {
return element;
}
};
};
controller.$scope.sampleType.selection = {
controller.$.sampleType.selection = {
hasCompany: true,
code: 'MyReport'
};
@ -92,12 +102,19 @@ describe('Client', () => {
let event = {preventDefault: () => {}};
$httpBackend.when('GET', `email/MyReport?clientFk=101&companyFk=442`).respond(true);
$httpBackend.expect('GET', `email/MyReport?clientFk=101&companyFk=442`);
const params = {
clientId: 101,
companyId: 442,
isPreview: true
};
const serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `email/MyReport?${serializedParams}`).respond(true);
$httpBackend.expect('GET', `email/MyReport?${serializedParams}`);
controller.showPreview(event);
$httpBackend.flush();
expect(controller.$scope.showPreview.show).toHaveBeenCalledWith();
expect(controller.$.showPreview.show).toHaveBeenCalledWith();
});
});
@ -114,7 +131,7 @@ describe('Client', () => {
it(`should perform a query (GET) and call go() method`, () => {
spyOn(controller.$state, 'go');
controller.$scope.sampleType.selection = {
controller.$.sampleType.selection = {
hasCompany: false,
code: 'MyReport'
};
@ -123,8 +140,13 @@ describe('Client', () => {
clientFk: 101
};
$httpBackend.when('POST', `email/MyReport?clientFk=101`).respond(true);
$httpBackend.expect('POST', `email/MyReport?clientFk=101`);
const params = {
clientId: 101
};
const serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `email/MyReport?${serializedParams}`).respond(true);
$httpBackend.expect('GET', `email/MyReport?${serializedParams}`);
controller.sendSample();
$httpBackend.flush();
@ -134,7 +156,7 @@ describe('Client', () => {
it(`should perform a query (GET) with companyFk param and call go() method`, () => {
spyOn(controller.$state, 'go');
controller.$scope.sampleType.selection = {
controller.$.sampleType.selection = {
hasCompany: true,
code: 'MyReport'
};
@ -144,8 +166,14 @@ describe('Client', () => {
companyFk: 442
};
$httpBackend.when('POST', `email/MyReport?clientFk=101&companyFk=442`).respond(true);
$httpBackend.expect('POST', `email/MyReport?clientFk=101&companyFk=442`);
const params = {
clientId: 101,
companyId: 442
};
const serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `email/MyReport?${serializedParams}`).respond(true);
$httpBackend.expect('GET', `email/MyReport?${serializedParams}`);
controller.sendSample();
$httpBackend.flush();

View File

@ -1,36 +1,34 @@
vn-client-sample-create {
vn-dialog {
& > div {
padding: 0 !important
div.vn-dialog {
tpl-body.client-sample-dialog {
width: 800px;
.container, .container h1 {
font-family: "Roboto","Helvetica","Arial",sans-serif;
font-size: 1em !important;
h1 {
font-weight: bold;
margin: auto
}
p {
margin: 1em 0
}
footer p {
font-size: 10px !important;
line-height: 10px
}
}
tpl-body {
min-width: 800px;
.title h1 {
font-size: 2em !important;
margin: 0
}
.container, .container h1 {
font-family: "Roboto","Helvetica","Arial",sans-serif;
font-size: 1em !important;
h1 {
font-weight: bold;
margin: auto
}
p {
margin: 1em 0
}
footer p {
font-size: 10px !important;
line-height: 10px
}
}
.title h1 {
font-size: 2em !important;
margin: 0
}
.loading {
text-align: center
}
}
}
}

View File

@ -42,6 +42,24 @@ export default class Controller {
scope: {
fields: ['id', 'name']
}
},
{
relation: 'worker',
scope: {
fields: ['userFk'],
include: {
relation: 'user',
scope: {
fields: ['id'],
include: {
relation: 'emailUser',
scope: {
fields: ['email']
}
}
}
}
}
}
]
};

View File

@ -1,12 +1,13 @@
import ngModule from '../module';
class Controller {
constructor($, $http, vnApp, $translate, aclService) {
constructor($, $http, vnApp, $translate, aclService, $httpParamSerializer) {
this.$http = $http;
this.vnApp = vnApp;
this.$translate = $translate;
this.$ = $;
this.aclService = aclService;
this.$httpParamSerializer = $httpParamSerializer;
this.moreOptions = [
{callback: this.showRouteReport, name: 'Show route report'},
{callback: this.sendRouteReport, name: 'Send route report'},
@ -36,13 +37,26 @@ class Controller {
}
showRouteReport() {
let url = `report/rpt-route?routeFk=${this.route.id}`;
const user = this.route.worker.user;
const params = {
clientId: user.id,
routeId: this.route.id
};
const serializedParams = this.$httpParamSerializer(params);
let url = `api/report/driver-route?${serializedParams}`;
window.open(url);
}
sendRouteReport() {
let url = `email/driver-route?routeFk=${this.route.id}`;
this.$http.post(url).then(() => {
const user = this.route.worker.user;
const params = {
recipient: user.emailUser.email,
clientId: user.id,
routeId: this.route.id
};
const serializedParams = this.$httpParamSerializer(params);
const url = `email/driver-route?${serializedParams}`;
this.$http.get(url).then(() => {
this.vnApp.showSuccess(this.$translate.instant('Report sent'));
});
}
@ -62,7 +76,7 @@ class Controller {
}
}
Controller.$inject = ['$scope', '$http', 'vnApp', '$translate', 'aclService'];
Controller.$inject = ['$scope', '$http', 'vnApp', '$translate', 'aclService', '$httpParamSerializer'];
ngModule.component('vnRouteDescriptor', {
template: require('./index.html'),

View File

@ -76,7 +76,7 @@ module.exports = Self => {
case 'ticketFk':
return {'t.id': value};
case 'attenderFk':
return {'tr.atenderFk': value};
return {'tr.attenderFk': value};
case 'isOk':
return {'tr.isOk': value};
case 'clientFk':
@ -106,7 +106,7 @@ module.exports = Self => {
tr.ticketFk,
tr.quantity,
tr.price,
tr.atenderFk attenderFk,
tr.attenderFk,
tr.description,
tr.response,
tr.saleFk,
@ -131,7 +131,7 @@ module.exports = Self => {
LEFT JOIN sale s ON s.id = tr.saleFk
LEFT JOIN worker wk ON wk.id = c.salesPersonFk
LEFT JOIN account.user u ON u.id = wk.userFk
LEFT JOIN worker wka ON wka.id = tr.atenderFk
LEFT JOIN worker wka ON wka.id = tr.attenderFk
LEFT JOIN account.user ua ON ua.id = wka.userFk`);
stmt.merge(conn.makeSuffix(filter));

View File

@ -38,7 +38,7 @@ module.exports = Self => {
return sale.itemShelving();
});
if (hasItemShelvingSales)
throw new UserError(`You cannot delete a ticket that it's being prepared`);
throw new UserError(`You cannot delete a ticket that part of it is being prepared`);
// Check for existing claim
const claimOfATicket = await models.Claim.findOne({where: {ticketFk: id}});

View File

@ -1,6 +1,6 @@
const app = require('vn-loopback/server/server');
xdescribe('ticket deleted()', () => {
describe('ticket deleted()', () => {
let ticket;
let ctx;

View File

@ -33,13 +33,6 @@
"isOk": {
"type": "Boolean"
},
"attenderFk": {
"type": "Number",
"required": true,
"mysql": {
"columnName": "atenderFk"
}
},
"response": {
"type": "String"
}

View File

@ -15,7 +15,15 @@ class Controller {
{
relation: 'client',
scope: {
fields: ['salesPersonFk', 'name', 'isActive', 'isFreezed', 'isTaxDataChecked', 'credit'],
fields: [
'salesPersonFk',
'name',
'isActive',
'isFreezed',
'isTaxDataChecked',
'credit',
'email'
],
include: {
relation: 'salesPerson',
scope: {

View File

@ -197,7 +197,7 @@
<vn-confirm
vn-id="confirm-delivery-note"
on-response="$ctrl.sendDeliveryNote($response)"
on-accept="$ctrl.sendDeliveryNote()"
question="Send Delivery Note"
message="Are you sure you want to send it?">
</vn-confirm>

View File

@ -2,9 +2,10 @@ import ngModule from '../module';
import Component from 'core/lib/component';
class Controller extends Component {
constructor($element, $, aclService) {
constructor($element, $, aclService, $httpParamSerializer) {
super($element, $);
this.aclService = aclService;
this.$httpParamSerializer = $httpParamSerializer;
this.moreOptions = [
{name: 'Add turn', callback: this.showAddTurnDialog},
{name: 'Show Delivery Note', callback: this.showDeliveryNote},
@ -198,10 +199,27 @@ class Controller extends Component {
}
showDeliveryNote() {
let url = `report/rpt-delivery-note?ticketFk=${this.ticket.id}`;
const params = {
clientId: this.ticket.client.id,
ticketId: this.ticket.id
};
const serializedParams = this.$httpParamSerializer(params);
let url = `api/report/delivery-note?${serializedParams}`;
window.open(url);
}
sendDeliveryNote() {
const params = {
recipient: this.ticket.client.email,
clientId: this.ticket.client.id,
ticketId: this.ticket.id
};
const serializedParams = this.$httpParamSerializer(params);
this.$http.get(`email/delivery-note?${serializedParams}`).then(
() => this.vnApp.showMessage(this.$translate.instant('Notification sent!'))
);
}
showSMSDialog() {
const address = this.ticket.address;
this.newSMS = {
@ -272,17 +290,9 @@ class Controller extends Component {
confirmDeliveryNote() {
this.$.confirmDeliveryNote.show();
}
sendDeliveryNote(response) {
if (response === 'accept') {
this.$http.post(`email/delivery-note`, {ticketFk: this.ticket.id}).then(
() => this.vnApp.showMessage(this.$translate.instant('Notification sent!'))
);
}
}
}
Controller.$inject = ['$element', '$scope', 'aclService'];
Controller.$inject = ['$element', '$scope', 'aclService', '$httpParamSerializer'];
ngModule.component('vnTicketDescriptor', {
template: require('./index.html'),

View File

@ -1,13 +1,14 @@
import './index.js';
describe('Ticket Component vnTicketDescriptor', () => {
let $httpParamSerializer;
let $httpBackend;
let controller;
let $state;
beforeEach(ngModule('ticket'));
beforeEach(angular.mock.inject(($componentController, _$httpBackend_, $rootScope, $compile, _$state_) => {
beforeEach(angular.mock.inject(($componentController, _$httpBackend_, $rootScope, $compile, _$state_, _$httpParamSerializer_) => {
let $element = $compile(`<vn-autocomplete></vn-autocomplete>`)($rootScope);
$state = _$state_;
$state.getCurrentPath = () => {
@ -17,8 +18,9 @@ describe('Ticket Component vnTicketDescriptor', () => {
];
};
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
controller = $componentController('vnTicketDescriptor', {$element});
controller._ticket = {id: 2, invoiceOut: {id: 1}};
controller._ticket = {id: 2, invoiceOut: {id: 1}, client: {id: 101, email: 'client@email'}};
controller.cardReload = ()=> {
return true;
};
@ -82,7 +84,12 @@ describe('Ticket Component vnTicketDescriptor', () => {
describe('showDeliveryNote()', () => {
it('should open a new window showing a delivery note PDF document', () => {
let expectedPath = 'report/rpt-delivery-note?ticketFk=2';
const params = {
clientId: controller.ticket.client.id,
ticketId: controller.ticket.id
};
const serializedParams = $httpParamSerializer(params);
let expectedPath = `api/report/delivery-note?${serializedParams}`;
spyOn(window, 'open');
controller.showDeliveryNote();
@ -90,6 +97,26 @@ describe('Ticket Component vnTicketDescriptor', () => {
});
});
describe('sendDeliveryNote()', () => {
it('should make a query and call vnApp.showMessage()', () => {
spyOn(controller.vnApp, 'showMessage');
const params = {
recipient: 'client@email',
clientId: controller.ticket.client.id,
ticketId: controller.ticket.id
};
const serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `email/delivery-note?${serializedParams}`).respond();
$httpBackend.expect('GET', `email/delivery-note?${serializedParams}`).respond();
controller.sendDeliveryNote();
$httpBackend.flush();
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Notification sent!');
});
});
describe('makeInvoice()', () => {
it('should make a query and call $state.reload() method if the response is accept', () => {
spyOn(controller.$state, 'reload');

View File

@ -30,8 +30,11 @@
"isConfirmed": {
"type": "Boolean"
},
"isRaid": {
"type": "Boolean"
"isVirtual": {
"type": "Boolean",
"mysql": {
"columnName": "isRaid"
}
},
"commission": {
"type": "Number"

2
package-lock.json generated
View File

@ -13500,7 +13500,7 @@
"dependencies": {
"jsesc": {
"version": "0.5.0",
"resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
"integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
"dev": true
}

57
print/boot.js Normal file
View File

@ -0,0 +1,57 @@
const express = require('express');
const path = require('path');
const fs = require('fs');
const templatesPath = path.resolve(__dirname, './templates');
const componentsPath = path.resolve(__dirname, './core/components');
module.exports = app => {
global.appPath = __dirname;
process.env.OPENSSL_CONF = '/etc/ssl/';
// Extended locale intl polyfill
const IntlPolyfill = require('intl');
Intl.NumberFormat = IntlPolyfill.NumberFormat;
Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
// Init database instance
require('./core/database').init();
// Init SMTP Instance
require('./core/smtp').init();
//
require('./core/mixins');
require('./core/filters');
require('./core/directives');
// Init router
require('./core/router')(app);
/**
* Serve component static files
*/
const componentsDir = fs.readdirSync(componentsPath);
componentsDir.forEach(componentName => {
const componentDir = path.join(componentsPath, '/', componentName);
const assetsDir = `${componentDir}/assets`;
app.use(`/api/${componentName}/assets`, express.static(assetsDir));
});
/**
* Serve static files
*/
const templatesDir = fs.readdirSync(templatesPath);
templatesDir.forEach(directory => {
const templateTypeDir = path.join(templatesPath, '/', directory);
const templates = fs.readdirSync(templateTypeDir);
templates.forEach(templateName => {
const templateDir = path.join(templatesPath, '/', directory, '/', templateName);
const assetsDir = `${templateDir}/assets`;
app.use(`/api/${templateName}/assets`, express.static(assetsDir));
});
});
};

View File

@ -1,40 +1,33 @@
/**
* Email only stylesheet
*
*/
body {
background-color: #EEE
}
.container {
max-width: 600px;
min-width: 320px;
margin: 0 auto;
color: #555
}
.main {
-webkit-text-size-adjust: none;
-ms-text-size-adjust: none;
background-color: #FFF;
padding: 20px
font-weight: 400;
color: #555;
margin: 0
}
.main a {
.grid {
background-color: #FFF
}
.grid a {
color: #8dba25
}
.main h1 {
color: #999
.grid-block {
min-width: 300px;
max-width: 600px;
margin: 0 auto;
color: #333
}
.main h3 {
font-size: 16px
}
.title {
background-color: #95d831;
text-transform: uppercase;
text-align: center;
padding: 35px 0
}
.title h1 {
font-size: 32px;
color: #333;
margin: 0
h1 {
font-weight: 100;
font-size: 1.5em
}

View File

@ -1,10 +1,34 @@
.container {
font-family: "Roboto", "Helvetica", "Arial", sans-serif;
font-size: 16px
/**
* CSS layout elements
*
*/
.grid {
font-family: Helvetica, Arial, sans-serif;
font-size: 16px !important;
width: 100%
}
.grid-row {
background-color: transparent
}
.grid-block {
box-sizing: border-box;
min-height: 40px
}
.grid-block.empty {
height: 40px
}
.grid-block.white {
background-color: #FFF
}
.columns {
overflow: hidden
overflow: hidden;
box-sizing: border-box;
}
.columns .size100 {
@ -18,6 +42,7 @@
}
.columns .size50 {
box-sizing: border-box;
width: 50%;
float: left
}
@ -173,7 +198,7 @@ table {
}
.panel .row-oriented td, .panel .row-oriented th {
padding: 10px 0
padding: 8px 10px
}
.row-oriented > tbody > tr > td {
@ -199,8 +224,8 @@ table {
margin-left: -1px;
margin-right: 1px;
margin-top: 10px;
padding: 5px 0;
color: #999;
padding: 5px 0
}
.line .vertical-aligned {

View File

@ -1,3 +1,7 @@
/**
* CSS misc classes
*
*/
.uppercase {
text-transform: uppercase
}

View File

@ -1,5 +1,9 @@
/**
* Report only stylesheet
*
*/
body {
zoom: 0.55
zoom: 0.53
}
.title {

View File

@ -0,0 +1,349 @@
/**
* CSS spacing classes
*
* vn-[p|m][t|r|b|l|a|x|y]-[none|auto|xs|sm|md|lg|xl]
* T D S
*
* T - type
* - values: p (padding), m (margin)
*
* D - direction
* - values:
* t (top), r (right), b (bottom), l (left),
* a (all), x (both left & right), y (both top & bottom)
*
* S - size
* - values:
* none,
* auto (ONLY for specific margins: vn-ml-*, vn-mr-*, vn-mx-*),
* xs (extra small),
* sm (small),
* md (medium),
* lg (large),
* xl (extra large)
*/
/* ++++++++++++++++++++++++++++++++++++++++++++++++ Padding */
.vn-pa-none {
padding: 0;
}
.vn-pl-none {
padding-left: 0;
}
.vn-pr-none {
padding-right: 0;
}
.vn-pt-none {
padding-top: 0;
}
.vn-pb-none {
padding-bottom: 0;
}
.vn-py-none {
padding-top: 0;
padding-bottom: 0;
}
.vn-px-none {
padding-left: 0;
padding-right: 0;
}
.vn-pa-xs {
padding: 4px;
}
.vn-pl-xs {
padding-left: 4px;
}
.vn-pr-xs {
padding-right: 4px;
}
.vn-pt-xs {
padding-top: 4px;
}
.vn-pb-xs {
padding-bottom: 4px;
}
.vn-py-xs {
padding-top: 4px;
padding-bottom: 4px;
}
.vn-px-xs {
padding-left: 4px;
padding-right: 4px;
}
/* Small */
.vn-pa-sm {
padding: 8px;
}
.vn-pl-sm {
padding-left: 8px;
}
.vn-pr-sm {
padding-right: 8px;
}
.vn-pt-sm {
padding-top: 8px;
}
.vn-pb-sm {
padding-bottom: 8px;
}
.vn-py-sm {
padding-top: 8px;
padding-bottom: 8px;
}
.vn-px-sm {
padding-left: 8px;
padding-right: 8px;
}
/* Medium */
.vn-pa-md {
padding: 16px;
}
.vn-pl-md {
padding-left: 16px;
}
.vn-pr-md {
padding-right: 16px;
}
.vn-pt-md {
padding-top: 16px;
}
.vn-pb-md {
padding-bottom: 16px;
}
.vn-py-md {
padding-top: 16px;
padding-bottom: 16px;
}
.vn-px-md {
padding-left: 16px;
padding-right: 16px;
}
/* Large */
.vn-pa-lg {
padding: 32px;
}
.vn-pl-lg {
padding-left: 32px;
}
.vn-pr-lg {
padding-right: 32px;
}
.vn-pt-lg {
padding-top: 32px;
}
.vn-pb-lg {
padding-bottom: 32px;
}
.vn-py-lg {
padding-top: 32px;
padding-bottom: 32px;
}
.vn-px-lg {
padding-left: 32px;
padding-right: 32px;
}
/* Extra large */
.vn-pa-xl {
padding: 100px;
}
.vn-pl-xl {
padding-left: 100px;
}
.vn-pr-xl {
padding-right: 100px;
}
.vn-pt-xl {
padding-top: 100px;
}
.vn-pb-xl {
padding-bottom: 100px;
}
.vn-py-xl {
padding-top: 100px;
padding-bottom: 100px;
}
.vn-px-xl {
padding-left: 100px;
padding-right: 100px;
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++ Margin */
/* None */
.vn-ma-none {
padding: 0;
}
.vn-ml-none {
padding-left: 0;
}
.vn-mr-none {
padding-right: 0;
}
.vn-mt-none {
padding-top: 0;
}
.vn-mb-none {
padding-bottom: 0;
}
.vn-my-none {
padding-top: 0;
padding-bottom: 0;
}
.vn-mx-none {
padding-left: 0;
padding-right: 0;
}
/* Auto */
.vn-ml-none {
padding-left: auto;
}
.vn-mr-none {
padding-right: auto;
}
.vn-mx-none {
padding-left: auto;
padding-right: auto;
}
/* Extra small */
.vn-ma-xs {
margin: 4px;
}
.vn-mt-xs {
margin-top: 4px;
}
.vn-ml-xs {
margin-left: 4px;
}
.vn-mr-xs {
margin-right: 4px;
}
.vn-mb-xs {
margin-bottom: 4px;
}
.vn-my-xs {
margin-top: 4px;
margin-bottom: 4px;
}
.vn-mx-xs {
margin-left: 4px;
margin-right: 4px;
}
/* Small */
.vn-ma-sm {
margin: 8px;
}
.vn-mt-sm {
margin-top: 8px;
}
.vn-ml-sm {
margin-left: 8px;
}
.vn-mr-sm {
margin-right: 8px;
}
.vn-mb-sm {
margin-bottom: 8px;
}
.vn-my-sm {
margin-top: 8px;
margin-bottom: 8px;
}
.vn-mx-sm {
margin-left: 8px;
margin-right: 8px;
}
/* Medium */
.vn-ma-md {
margin: 16px;
}
.vn-mt-md {
margin-top: 16px;
}
.vn-ml-md {
margin-left: 16px;
}
.vn-mr-md {
margin-right: 16px;
}
.vn-mb-md {
margin-bottom: 16px;
}
.vn-my-md {
margin-top: 16px;
margin-bottom: 16px;
}
.vn-mx-md {
margin-left: 16px;
margin-right: 16px;
}
/* Large */
.vn-ma-lg {
margin: 32px;
}
.vn-mt-lg {
margin-top: 32px;
}
.vn-ml-lg {
margin-left: 32px;
}
.vn-mr-lg {
margin-right: 32px;
}
.vn-mb-lg {
margin-bottom: 32px;
}
.vn-my-lg {
margin-top: 32px;
margin-bottom: 32px;
}
.vn-mx-lg {
margin-left: 32px;
margin-right: 32px;
}
/* Extra large */
.vn-ma-xl {
margin: 100px;
}
.vn-mt-xl {
margin-top: 100px;
}
.vn-ml-xl {
margin-left: 100px;
}
.vn-mr-xl {
margin-right: 100px;
}
.vn-mb-xl {
margin-bottom: 100px;
}
.vn-my-xl {
margin-top: 100px;
margin-bottom: 100px;
}
.vn-mx-xl {
margin-left: 100px;
margin-right: 100px;
}

View File

@ -1,10 +1,15 @@
{
"app": {
"host": "http://localhost:5000",
"port": 3000,
"defaultLanguage": "es",
"senderMail": "nocontestar@verdnatura.es",
"senderName": "Verdnatura"
},
"i18n": {
"locale": "es",
"fallbackLocale": "es",
"silentTranslationWarn": true
},
"pdf": {
"format": "A4",
"border": "1.5cm",

View File

@ -1,25 +0,0 @@
[
{"type": "email", "name": "client-welcome"},
{"type": "email", "name": "printer-setup"},
{"type": "email", "name": "payment-update"},
{"type": "email", "name": "letter-debtor-st"},
{"type": "email", "name": "letter-debtor-nd"},
{"type": "email", "name": "claim-pickup-order"},
{"type": "email", "name": "sepa-core"},
{"type": "email", "name": "client-lcr"},
{"type": "email", "name": "driver-route"},
{"type": "email", "name": "delivery-note"},
{"type": "report", "name": "rpt-delivery-note"},
{"type": "report", "name": "rpt-claim-pickup-order"},
{"type": "report", "name": "rpt-letter-debtor"},
{"type": "report", "name": "rpt-sepa-core"},
{"type": "report", "name": "rpt-receipt"},
{"type": "report", "name": "rpt-zone"},
{"type": "report", "name": "rpt-route"},
{"type": "report", "name": "rpt-lcr"},
{"type": "report", "name": "rpt-item-label"},
{"type": "static", "name": "email-header"},
{"type": "static", "name": "email-footer"},
{"type": "static", "name": "report-header"},
{"type": "static", "name": "report-footer"}
]

104
print/core/component.js Normal file
View File

@ -0,0 +1,104 @@
const Vue = require('vue');
const VueI18n = require('vue-i18n');
const renderer = require('vue-server-renderer').createRenderer();
Vue.use(VueI18n);
const fs = require('fs');
const yaml = require('js-yaml');
const juice = require('juice');
const path = require('path');
const config = require('./config');
class Component {
constructor(name) {
this.name = name;
}
get path() {
return `./components/${this.name}`;
}
get template() {
const templatePath = `${this.path}/${this.name}.html`;
const fullPath = path.resolve(__dirname, templatePath);
return fs.readFileSync(fullPath, 'utf8');
}
get locale() {
if (!this._locale)
this.getLocale();
return this._locale;
}
getLocale() {
const mergedLocale = {messages: {}};
const localePath = path.resolve(__dirname, `${this.path}/locale`);
if (!fs.existsSync(localePath))
return mergedLocale;
const localeDir = fs.readdirSync(localePath);
localeDir.forEach(locale => {
const fullPath = path.join(localePath, '/', locale);
const yamlLocale = fs.readFileSync(fullPath, 'utf8');
const jsonLocale = yaml.safeLoad(yamlLocale);
const localeName = locale.replace('.yml', '');
mergedLocale.messages[localeName] = jsonLocale;
});
this._locale = mergedLocale;
}
get stylesheet() {
let mergedStyles = '';
const stylePath = path.resolve(__dirname, `${this.path}/assets/css`);
if (!fs.existsSync(stylePath))
return mergedStyles;
return require(`${stylePath}/import`);
}
get attachments() {
const attachmentsPath = `${this.path}/attachments.json`;
const fullPath = path.resolve(__dirname, attachmentsPath);
if (!fs.existsSync(fullPath))
return [];
return require(fullPath);
}
build() {
const fullPath = path.resolve(__dirname, this.path);
if (!fs.existsSync(fullPath))
throw new Error(`Sample "${this.name}" not found`);
const component = require(`${this.path}/${this.name}`);
component.i18n = this.locale;
component.attachments = this.attachments;
component.template = juice.inlineContent(this.template, this.stylesheet, {
inlinePseudoElements: true
});
return component;
}
async render() {
const component = this.build();
const i18n = new VueI18n(config.i18n);
const app = new Vue({
i18n: i18n,
render: h => h(component, {
props: this.args
})
});
return renderer.renderToString(app);
}
}
module.exports = Component;

View File

@ -0,0 +1,9 @@
const Stylesheet = require(`${appPath}/core/stylesheet`);
module.exports = new Stylesheet([
`${appPath}/common/css/spacing.css`,
`${appPath}/common/css/misc.css`,
`${appPath}/common/css/layout.css`,
`${appPath}/common/css/email.css`,
`${__dirname}/style.css`])
.mergeStyles();

View File

@ -0,0 +1,22 @@
div {
display: inline-block;
box-sizing: border-box
}
a {
background-color: #F5F5F5;
border: 1px solid #CCC;
display: flex;
vertical-align: middle;
box-sizing: border-box;
min-width: 150px;
text-decoration: none;
border-radius: 3px;
color: #8dba25
}
a > div.icon {
font-weight: bold;
font-size: 18px;
color: #555
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M2 12.5C2 9.46 4.46 7 7.5 7H18c2.21 0 4 1.79 4 4s-1.79 4-4 4H9.5C8.12 15 7 13.88 7 12.5S8.12 10 9.5 10H17v2H9.41c-.55 0-.55 1 0 1H18c1.1 0 2-.9 2-2s-.9-2-2-2H7.5C5.57 9 4 10.57 4 12.5S5.57 16 7.5 16H17v2H7.5C4.46 18 2 15.54 2 12.5z"/><path fill="none" d="M0 0h24v24H0V0z"/></svg>

After

Width:  |  Height:  |  Size: 371 B

View File

@ -0,0 +1,6 @@
<div class="vn-mx-xs" v-if="attachment.component">
<a target="_blank" class="vn-py-sm vn-px-md" v-bind:href="path">
<div class="text">{{attachment.filename}}</div>
<div class="icon vn-pl-md">&#x25BC;</div>
</a>
</div>

View File

@ -0,0 +1,37 @@
module.exports = {
name: 'attachment',
computed: {
path() {
const filename = this.attachment.filename;
const component = this.attachment.component;
if (this.attachment.cid)
return `/api/${component}/assets/files/${filename}`;
else
return `/api/report/${component}?${this.getHttpParams()}`;
}
},
methods: {
getHttpParams() {
const props = this.args;
let query = '';
for (let param in props) {
if (query != '')
query += '&';
query += `${param}=${props[param]}`;
}
return query;
}
},
props: {
attachment: {
type: Object,
required: true
},
args: {
type: Object,
required: false
}
}
};

View File

@ -0,0 +1,9 @@
const Stylesheet = require(`${appPath}/core/stylesheet`);
module.exports = new Stylesheet([
`${appPath}/common/css/spacing.css`,
`${appPath}/common/css/misc.css`,
`${appPath}/common/css/layout.css`,
`${appPath}/common/css/email.css`,
`${__dirname}/style.css`])
.mergeStyles();

View File

@ -1,21 +1,12 @@
@media (max-width: 400px) {
.buttons a {
display: block;
width: 100%
}
}
.buttons {
font-size: 14px !important;
width: 100%
}
.buttons a {
display: inline-block;
box-sizing: border-box;
text-decoration: none;
font-size: 16px;
width: 100%;
color: #fff;
width: 50%
}
.buttons .btn {
@ -23,18 +14,20 @@
text-align: center
}
.buttons .btn .text {
display: inline-block;
padding: 22px 0
}
.buttons .btn .icon {
background-color: #95d831;
box-sizing: border-box;
font-weight: bold;
text-align: center;
padding: 16.5px 0;
float: right;
width: 70px
color: #333;
float: left
}
.buttons .btn .text {
display: inline-block;
}
.networks {

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1,22 @@
[
{
"filename": "facebook.png",
"path": "/assets/images/facebook.png",
"cid": "facebook.png"
},
{
"filename": "twitter.png",
"path": "/assets/images/twitter.png",
"cid": "twitter.png"
},
{
"filename": "instagram.png",
"path": "/assets/images/instagram.png",
"cid": "instagram.png"
},
{
"filename": "linkedin.png",
"path": "/assets/images/linkedin.png",
"cid": "linkedin.png"
}
]

View File

@ -0,0 +1,47 @@
<footer>
<!-- Action button block -->
<div class="buttons">
<div class="columns">
<div class="size50">
<a href="https://www.verdnatura.es" target="_blank">
<div class="btn">
<!-- <span class="icon vn-pa-sm"><img v-bind:src="getEmailSrc('action.png')"/></span> -->
<span class="text vn-pa-sm">{{ $t('buttons.webAcccess')}}</span>
</div>
</a>
</div>
<div class="size50">
<a href="https://goo.gl/forms/j8WSL151ZW6QtlT72" target="_blank">
<div class="btn">
<!-- <span class="icon vn-pa-sm"><img v-bind:src="getEmailSrc('info.png')"/></span> -->
<span class="text vn-pa-sm">{{ $t('buttons.info')}}</span>
</div>
</a>
</div>
</div>
</div>
<!-- Networks block -->
<div class="networks">
<a href="https://www.facebook.com/Verdnatura" target="_blank">
<img v-bind:src="getEmailSrc('facebook.png')" alt="Facebook"/>
</a>
<a href="https://www.twitter.com/Verdnatura" target="_blank">
<img v-bind:src="getEmailSrc('twitter.png')" alt="Twitter"/>
</a>
<a href="https://www.instagram.com/Verdnatura" target="_blank">
<img v-bind:src="getEmailSrc('instagram.png')" alt="Instagram"/>
</a>
<a href="https://www.linkedin.com/company/verdnatura" target="_blank">
<img v-bind:src="getEmailSrc('linkedin.png')" alt="Linkedin"/>
</a>
</div>
<!-- Privacy block -->
<div class="privacy">
<p>{{$t('privacy.fiscalAddress')}}</p>
<p>{{$t('privacy.disclaimer')}}</p>
<p>{{$t('privacy.law')}}</p>
</div>
<!-- Privacy block end -->
</footer>

View File

@ -0,0 +1,4 @@
module.exports = {
name: 'email-footer',
props: ['isPreview', 'locale']
};

View File

@ -0,0 +1,19 @@
buttons:
webAcccess: Visita nuestra Web
info: Ayúdanos a mejorar
privacy:
fiscalAddress: VERDNATURA LEVANTE SL, B97367486 Avda. Espioca, 100, 46460 Silla
· www.verdnatura.es · clientes@verdnatura.es
disclaimer: '- AVISO - Este mensaje es privado y confidencial, y debe ser utilizado
exclusivamente por la persona destinataria del mismo. Si has recibido este mensaje
por error, te rogamos lo comuniques al remitente y borres dicho mensaje y cualquier
documento adjunto que pudiera contener. Verdnatura Levante SL no renuncia a la
confidencialidad ni a ningún privilegio por causa de transmisión errónea o mal
funcionamiento. Igualmente no se hace responsable de los cambios, alteraciones,
errores u omisiones que pudieran hacerse al mensaje una vez enviado.'
law: En cumplimiento de lo dispuesto en la Ley Orgánica 15/1999, de Protección de
Datos de Carácter Personal, te comunicamos que los datos personales que facilites
se incluirán en ficheros automatizados de VERDNATURA LEVANTE S.L., pudiendo en
todo momento ejercitar los derechos de acceso, rectificación, cancelación y oposición,
comunicándolo por escrito al domicilio social de la entidad. La finalidad del
fichero es la gestión administrativa, contabilidad, y facturación.

View File

@ -0,0 +1,19 @@
buttons:
webAcccess: Visitez notre site web
info: Aidez-nous à améliorer
privacy:
fiscalAddress: VERDNATURA LEVANTE SL, B97367486 Avda. Espioca, 100, 46460 Silla
· www.verdnatura.es · clientes@verdnatura.es
disclaimer: '- AVIS - Ce message est privé et confidentiel et doit être utilisé.exclusivamente
por la persona destinataria del mismo. Si has recibido este mensajepor error,
te rogamos lo comuniques al remitente y borres dicho mensaje y cualquier documentoadjunto
que pudiera contener. Verdnatura Levante SL no renuncia a la confidencialidad
ni aningún privilegio por causa de transmisión errónea o mal funcionamiento. Igualmente
no se haceresponsable de los cambios, alteraciones, errores u omisiones que pudieran
hacerse al mensaje una vez enviado.'
law: En cumplimiento de lo dispuesto en la Ley Orgánica 15/1999, de Protección de
Datos de Carácter Personal, te comunicamos que los datos personales que facilites
se incluirán en ficheros automatizados de VERDNATURA LEVANTE S.L.,pudiendo en
todo momento ejercitar los derechos de acceso, rectificación, cancelación y oposición,
comunicándolo porescrito al domicilio social de la entidad. La finalidad del fichero
es la gestión administrativa, contabilidad, y facturación.

View File

@ -0,0 +1,9 @@
const Stylesheet = require(`${appPath}/core/stylesheet`);
module.exports = new Stylesheet([
`${appPath}/common/css/spacing.css`,
`${appPath}/common/css/misc.css`,
`${appPath}/common/css/layout.css`,
`${appPath}/common/css/email.css`,
`${__dirname}/style.css`])
.mergeStyles();

View File

@ -0,0 +1,19 @@
header .logo {
margin-bottom: 15px;
}
header .logo img {
width: 50%
}
header .topbar {
background-color: #95d831;
height: 10px
}
.topbar:after {
overflow: hidden;
display: block;
content: ' ';
clear: both;
}

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
x="0px"
y="0px"
width="500"
height="68.596313"
viewBox="0 0 499.99999 68.596313"
enable-background="new 0 0 226.229 31.038"
xml:space="preserve"
id="svg2"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="verdnatura-white.svg"><metadata
id="metadata61"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs59" /><sodipodi:namedview
pagecolor="#333333"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1013"
id="namedview57"
showgrid="false"
inkscape:zoom="1.5909426"
inkscape:cx="268.25598"
inkscape:cy="112.75218"
inkscape:window-x="0"
inkscape:window-y="30"
inkscape:window-maximized="1"
inkscape:current-layer="svg2"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" /><g
id="Background"
transform="translate(2.2478643e-6,43.261169)" /><g
id="Guides"
transform="translate(2.2478643e-6,43.261169)" /><g
id="g883"
transform="matrix(2.2101465,0,0,2.2101465,0,-594.44542)"><g
transform="translate(0,268.962)"
style="fill:#ffffff"
id="g9"><path
style="clip-rule:evenodd;fill:#ffffff;fill-rule:evenodd"
inkscape:connector-curvature="0"
id="path11"
d="M 10.417,30.321 0,0 h 8.233 l 4.26,15.582 0.349,1.276 c 0.521,1.866 0.918,3.431 1.191,4.693 0.15,-0.618 0.335,-1.345 0.555,-2.182 0.219,-0.837 0.528,-1.935 0.925,-3.293 L 19.981,0 h 8.19 l -10.5,30.321 z" /></g><g
transform="translate(0,268.962)"
id="g13"><path
inkscape:connector-curvature="0"
style="clip-rule:evenodd;fill:#8ed300;fill-opacity:1;fill-rule:evenodd"
id="path15"
d="m 139.809,19.787 c -0.665,0.357 -1.748,0.686 -3.25,0.988 -0.727,0.137 -1.283,0.254 -1.667,0.35 -0.95,0.247 -1.661,0.563 -2.134,0.947 -0.472,0.384 -0.799,0.899 -0.979,1.544 -0.223,0.796 -0.155,1.438 0.204,1.925 0.359,0.488 0.945,0.731 1.757,0.731 1.252,0 2.375,-0.36 3.369,-1.081 0.994,-0.721 1.653,-1.665 1.98,-2.831 z m 5.106,10.534 h -7.458 c 0.017,-0.356 0.048,-0.726 0.094,-1.11 l 0.159,-1.192 c -1.318,1.026 -2.627,1.786 -3.927,2.279 -1.299,0.493 -2.643,0.739 -4.031,0.739 -2.158,0 -3.7,-0.593 -4.625,-1.779 -0.925,-1.187 -1.106,-2.788 -0.542,-4.804 0.519,-1.851 1.431,-3.356 2.737,-4.515 1.307,-1.159 3.021,-1.972 5.142,-2.438 1.169,-0.247 2.641,-0.515 4.413,-0.803 2.646,-0.412 4.082,-1.016 4.304,-1.812 l 0.151,-0.539 c 0.182,-0.65 0.076,-1.145 -0.317,-1.483 -0.393,-0.339 -1.071,-0.508 -2.033,-0.508 -1.045,0 -1.934,0.214 -2.666,0.643 -0.731,0.428 -1.289,1.058 -1.673,1.887 h -6.748 c 1.065,-2.53 2.64,-4.413 4.723,-5.65 2.083,-1.237 4.724,-1.856 7.923,-1.856 1.991,0 3.602,0.241 4.833,0.722 1.231,0.481 2.095,1.209 2.59,2.185 0.339,0.701 0.483,1.536 0.432,2.504 -0.052,0.969 -0.377,2.525 -0.978,4.669 l -2.375,8.483 c -0.284,1.014 -0.416,1.812 -0.396,2.395 0.02,0.583 0.188,0.962 0.503,1.141 z" /></g><g
transform="translate(0,268.962)"
id="g17"><path
inkscape:connector-curvature="0"
style="clip-rule:evenodd;fill:#8ed300;fill-opacity:1;fill-rule:evenodd"
id="path19"
d="m 185.7,30.321 6.27,-22.393 h 7.049 l -1.097,3.918 c 1.213,-1.537 2.502,-2.659 3.867,-3.366 1.365,-0.707 2.951,-1.074 4.758,-1.101 l -2.03,7.25 c -0.304,-0.042 -0.608,-0.072 -0.912,-0.093 -0.303,-0.02 -0.592,-0.03 -0.867,-0.03 -1.126,0 -2.104,0.168 -2.932,0.504 -0.829,0.336 -1.561,0.854 -2.197,1.555 -0.406,0.467 -0.789,1.136 -1.149,2.007 -0.361,0.872 -0.814,2.282 -1.359,4.232 l -2.104,7.516 H 185.7 Z" /></g><g
transform="translate(0,268.962)"
id="g21"><path
inkscape:connector-curvature="0"
style="clip-rule:evenodd;fill:#8ed300;fill-opacity:1;fill-rule:evenodd"
id="path23"
d="m 217.631,19.787 c -0.664,0.357 -1.748,0.686 -3.25,0.988 -0.727,0.137 -1.282,0.254 -1.667,0.35 -0.95,0.247 -1.661,0.563 -2.134,0.947 -0.472,0.384 -0.799,0.899 -0.979,1.544 -0.223,0.796 -0.155,1.438 0.205,1.925 0.359,0.488 0.945,0.731 1.757,0.731 1.252,0 2.375,-0.36 3.369,-1.081 0.994,-0.721 1.654,-1.665 1.98,-2.831 z m 5.106,10.534 h -7.458 c 0.017,-0.356 0.048,-0.726 0.094,-1.11 l 0.159,-1.192 c -1.318,1.026 -2.627,1.786 -3.927,2.279 -1.299,0.493 -2.643,0.739 -4.031,0.739 -2.158,0 -3.7,-0.593 -4.625,-1.779 -0.926,-1.187 -1.106,-2.788 -0.542,-4.804 0.519,-1.851 1.431,-3.356 2.737,-4.515 1.306,-1.159 3.02,-1.972 5.142,-2.438 1.169,-0.247 2.641,-0.515 4.413,-0.803 2.647,-0.412 4.082,-1.016 4.304,-1.812 l 0.151,-0.539 c 0.182,-0.65 0.077,-1.145 -0.317,-1.483 -0.393,-0.339 -1.071,-0.508 -2.033,-0.508 -1.045,0 -1.934,0.214 -2.666,0.643 -0.731,0.428 -1.289,1.058 -1.672,1.887 h -6.748 c 1.065,-2.53 2.64,-4.413 4.723,-5.65 2.083,-1.237 4.724,-1.856 7.923,-1.856 1.99,0 3.601,0.241 4.833,0.722 1.232,0.481 2.095,1.209 2.591,2.185 0.339,0.701 0.483,1.536 0.431,2.504 -0.051,0.969 -0.377,2.525 -0.978,4.669 l -2.375,8.483 c -0.284,1.014 -0.416,1.812 -0.396,2.395 0.02,0.583 0.188,0.962 0.503,1.141 z" /></g><g
transform="translate(0,268.962)"
id="g25"><path
inkscape:connector-curvature="0"
style="clip-rule:evenodd;fill:#8ed300;fill-opacity:1;fill-rule:evenodd"
id="path27"
d="m 188.386,7.928 -6.269,22.393 h -7.174 l 0.864,-3.085 c -1.227,1.246 -2.476,2.163 -3.746,2.751 -1.27,0.588 -2.625,0.882 -4.067,0.882 -2.471,0 -4.154,-0.634 -5.048,-1.901 -0.895,-1.268 -0.993,-3.149 -0.294,-5.644 l 4.31,-15.396 h 7.338 l -3.508,12.53 c -0.516,1.842 -0.641,3.109 -0.375,3.803 0.266,0.694 0.967,1.041 2.105,1.041 1.275,0 2.323,-0.422 3.142,-1.267 0.819,-0.845 1.497,-2.223 2.031,-4.133 l 3.353,-11.974 z" /></g><g
transform="translate(0,268.962)"
id="g29"><path
inkscape:connector-curvature="0"
style="clip-rule:evenodd;fill:#8ed300;fill-opacity:1;fill-rule:evenodd"
id="path31"
d="m 149.937,12.356 1.239,-4.428 h 2.995 l 1.771,-6.326 h 7.338 l -1.771,6.326 h 3.753 l -1.24,4.428 h -3.753 l -2.716,9.702 c -0.416,1.483 -0.498,2.465 -0.247,2.946 0.25,0.48 0.905,0.721 1.964,0.721 l 0.549,-0.011 0.39,-0.031 -1.31,4.678 c -0.811,0.148 -1.596,0.263 -2.354,0.344 -0.758,0.081 -1.48,0.122 -2.167,0.122 -2.543,0 -4.108,-0.621 -4.695,-1.863 -0.587,-1.242 -0.313,-3.887 0.82,-7.936 l 2.428,-8.672 z" /></g><g
transform="translate(0,268.962)"
style="fill:#ffffff"
id="g33"><path
style="clip-rule:evenodd;fill:#ffffff;fill-rule:evenodd"
inkscape:connector-curvature="0"
id="path35"
d="m 73.875,18.896 c -0.561,2.004 -0.616,3.537 -0.167,4.601 0.449,1.064 1.375,1.595 2.774,1.595 1.399,0 2.605,-0.524 3.62,-1.574 1.015,-1.05 1.806,-2.59 2.375,-4.622 0.526,-1.879 0.556,-3.334 0.09,-4.363 -0.466,-1.029 -1.393,-1.543 -2.778,-1.543 -1.304,0 -2.487,0.528 -3.551,1.585 -1.064,1.057 -1.852,2.496 -2.363,4.321 z M 96.513,0 88.024,30.321 h -7.337 l 0.824,-2.944 c -1.166,1.22 -2.369,2.121 -3.61,2.703 -1.241,0.582 -2.583,0.874 -4.025,0.874 -2.802,0 -4.772,-1.081 -5.912,-3.243 -1.139,-2.162 -1.218,-4.993 -0.238,-8.493 0.988,-3.528 2.668,-6.404 5.042,-8.627 2.374,-2.224 4.927,-3.336 7.661,-3.336 1.47,0 2.695,0.296 3.676,0.887 0.981,0.591 1.681,1.465 2.099,2.62 L 89.217,0 Z" /><g
style="fill:#ffffff"
id="g37"><path
style="clip-rule:evenodd;fill:#ffffff;fill-rule:evenodd"
inkscape:connector-curvature="0"
id="path39"
d="m 73.875,18.896 c -0.561,2.004 -0.616,3.537 -0.167,4.601 0.449,1.064 1.375,1.595 2.774,1.595 1.399,0 2.605,-0.524 3.62,-1.574 1.015,-1.05 1.806,-2.59 2.375,-4.622 0.526,-1.879 0.556,-3.334 0.09,-4.363 -0.466,-1.029 -1.393,-1.543 -2.778,-1.543 -1.304,0 -2.487,0.528 -3.551,1.585 -1.064,1.057 -1.852,2.496 -2.363,4.321 z M 96.513,0 88.024,30.321 h -7.337 l 0.824,-2.944 c -1.166,1.22 -2.369,2.121 -3.61,2.703 -1.241,0.582 -2.583,0.874 -4.025,0.874 -2.802,0 -4.772,-1.081 -5.912,-3.243 -1.139,-2.162 -1.218,-4.993 -0.238,-8.493 0.988,-3.528 2.668,-6.404 5.042,-8.627 2.374,-2.224 4.927,-3.336 7.661,-3.336 1.47,0 2.695,0.296 3.676,0.887 0.981,0.591 1.681,1.465 2.099,2.62 L 89.217,0 Z" /></g></g><g
transform="translate(0,268.962)"
style="fill:#ffffff"
id="g41"><path
style="clip-rule:evenodd;fill:#ffffff;fill-rule:evenodd"
inkscape:connector-curvature="0"
id="path43"
d="M 46.488,30.321 52.757,7.928 h 7.049 l -1.098,3.918 C 59.921,10.309 61.21,9.187 62.576,8.48 63.942,7.773 68.591,7.406 70.398,7.379 l -2.03,7.25 c -0.304,-0.042 -0.608,-0.072 -0.911,-0.093 -0.304,-0.02 -0.592,-0.03 -0.867,-0.03 -1.126,0 -5.167,0.168 -5.997,0.504 -0.829,0.336 -1.561,0.854 -2.196,1.555 -0.406,0.467 -0.789,1.136 -1.149,2.007 -0.361,0.872 -0.814,2.282 -1.36,4.232 l -2.104,7.516 h -7.296 z" /></g><g
transform="translate(0,268.962)"
style="fill:#ffffff"
id="g45"><path
style="clip-rule:evenodd;fill:#ffffff;fill-rule:evenodd"
inkscape:connector-curvature="0"
id="path47"
d="m 32.673,16.742 8.351,-0.021 c 0.375,-1.436 0.308,-2.558 -0.201,-3.365 -0.509,-0.807 -1.402,-1.211 -2.68,-1.211 -1.209,0 -2.285,0.397 -3.229,1.19 -0.944,0.793 -1.69,1.93 -2.241,3.407 z m 6.144,6.536 h 7.043 c -1.347,2.456 -3.172,4.356 -5.477,5.7 -2.305,1.345 -4.885,2.017 -7.74,2.017 -3.473,0 -5.923,-1.054 -7.351,-3.161 -1.427,-2.107 -1.632,-4.98 -0.613,-8.618 1.038,-3.707 2.875,-6.641 5.512,-8.803 2.637,-2.163 5.678,-3.244 9.123,-3.244 3.555,0 6.04,1.099 7.456,3.298 1.417,2.198 1.582,5.234 0.498,9.109 l -0.239,0.814 -0.167,0.484 H 31.721 c -0.441,1.575 -0.438,2.777 0.01,3.606 0.448,0.829 1.332,1.244 2.65,1.244 0.975,0 1.836,-0.206 2.583,-0.617 0.747,-0.411 1.366,-1.021 1.853,-1.829 z" /><g
style="fill:#ffffff"
id="g49"><path
style="clip-rule:evenodd;fill:#ffffff;fill-rule:evenodd"
inkscape:connector-curvature="0"
id="path51"
d="m 32.673,16.742 8.351,-0.021 c 0.375,-1.436 0.308,-2.558 -0.201,-3.365 -0.509,-0.807 -1.402,-1.211 -2.68,-1.211 -1.209,0 -2.285,0.397 -3.229,1.19 -0.944,0.793 -1.69,1.93 -2.241,3.407 z m 6.144,6.536 h 7.043 c -1.347,2.456 -3.172,4.356 -5.477,5.7 -2.305,1.345 -4.885,2.017 -7.74,2.017 -3.473,0 -5.923,-1.054 -7.351,-3.161 -1.427,-2.107 -1.632,-4.98 -0.613,-8.618 1.038,-3.707 2.875,-6.641 5.512,-8.803 2.637,-2.163 5.678,-3.244 9.123,-3.244 3.555,0 6.04,1.099 7.456,3.298 1.417,2.198 1.582,5.234 0.498,9.109 l -0.239,0.814 -0.167,0.484 H 31.721 c -0.441,1.575 -0.438,2.777 0.01,3.606 0.448,0.829 1.332,1.244 2.65,1.244 0.975,0 1.836,-0.206 2.583,-0.617 0.747,-0.411 1.366,-1.021 1.853,-1.829 z" /></g></g><g
transform="translate(0,268.962)"
id="g53"><path
inkscape:connector-curvature="0"
style="fill:#8ed300;fill-opacity:1"
id="path55"
d="m 112.881,30.643 -6.404,-18.639 -6.455,18.639 h -7.254 l 9.565,-30.321 h 8.19 l 4.434,15.582 0.35,1.276 c 0.521,1.866 0.917,3.431 1.191,4.693 l 0.555,-2.182 c 0.219,-0.837 0.528,-1.935 0.925,-3.293 l 4.468,-16.076 h 8.19 l -10.501,30.321 z" /></g></g></svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,7 @@
[
{
"filename": "logo-black.png",
"path": "/assets/images/logo-black.png",
"cid": "logo-black.png"
}
]

View File

@ -0,0 +1,8 @@
<header>
<div class="logo">
<a href="https://www.verdnatura.es" target="_blank">
<img v-bind:src="getEmailSrc('logo-black.png')" alt="VerdNatura"/>
</a>
</div>
<div class="topbar"></div>
</header>

View File

@ -0,0 +1,4 @@
module.exports = {
name: 'email-header',
props: ['locale']
};

View File

@ -1,6 +1,6 @@
const CssReader = require(`${appPath}/lib/cssReader`);
const Stylesheet = require(`${appPath}/core/stylesheet`);
module.exports = new CssReader([
module.exports = new Stylesheet([
`${appPath}/common/css/layout.css`,
`${appPath}/common/css/report.css`,
`${appPath}/common/css/misc.css`,

View File

@ -0,0 +1,10 @@
numPages: Página {{page}} de {{pages}}
law:
phytosanitary: 'VERDNATURA LEVANTE SL - Pasaporte Fitosanitario R.P. Generalitat
Valenciana - Nº Comerciante: ES17462130'
privacy: En cumplimiento de lo dispuesto en la Ley Orgánica 15/1999, de Protección
de Datos de Carácter Personal, le comunicamos que los datos personales que facilite
se incluirán en ficheros automatizados de VERDNATURA LEVANTE S.L., pudiendo en
todo momento ejercitar los derechos de acceso, rectificación, cancelación y oposición,
comunicándolo por escrito al domicilio social de la entidad. La finalidad del
fichero es la gestión administrativa, contabilidad, y facturación.

View File

@ -0,0 +1,10 @@
numPages: Page {{page}} de {{pages}}
law:
phytosanitary: 'VERDNATURA LEVANTE SL - Passeport Phytosanitaire R.P. Generalitat
Valenciana - Numéro d''opérateur: ES17462130'
privacy: Conformément aux dispositions de la loi organique 15/1999 sur la protection
des données personnelles, nous vous informons que les données personnelles que
vous fournissez seront incluses dans des dossiers. VERDNATURA LEVANTE S.L., vous
pouvez à tout moment, exercer les droits d'accès, de rectification, d'annulation
et d'opposition, en communiquant par écrit au siège social de la société. Le dossier
a pour objet la gestion administrative, la comptabilité et la facturation.

View File

@ -0,0 +1,10 @@
numPages: Página {{page}} de {{pages}}
law:
phytosanitary: 'VERDNATURA LEVANTE S.L - Passaporte Fitossanitário R.P. Generalitat
Valenciana - Nº Comerciante: ES17462130'
privacy: Em cumprimento do disposto na lei Orgânica 15/1999, de Protecção de Dados
de Carácter Pessoal, comunicamos que os dados pessoais que facilite se incluirão
nos ficheiros automatizados de VERDNATURA LEVANTE S.L., podendo em todo momento
exercer os direitos de acesso, rectificação, cancelação e oposição, comunicando
por escrito ao domicílio social da entidade. A finalidade do ficheiro é a gestão
administrativa, contabilidade e facturação.

View File

@ -1,7 +1,7 @@
<footer>
<section class="page">
<section :if="leftText">{{leftText}}</section>
<section :if="centerText" class="uppercase">{{centerText}}</section>
<section v-if="leftText">{{leftText}}</section>
<section v-if="centerText" class="uppercase">{{centerText}}</section>
<section class="number">{{$t('numPages')}}</section>
</section>
<p class="phytosanitary">{{$t('law.phytosanitary')}}</p>

View File

@ -1,8 +1,4 @@
module.exports = {
name: 'report-footer',
created() {
if (this.locale)
this.$i18n.locale = this.locale;
},
props: ['leftText', 'centerText', 'locale']
};

View File

@ -1,6 +1,6 @@
const CssReader = require(`${appPath}/lib/cssReader`);
const Stylesheet = require(`${appPath}/core/stylesheet`);
module.exports = new CssReader([
module.exports = new Stylesheet([
`${appPath}/common/css/layout.css`,
`${appPath}/common/css/report.css`,
`${appPath}/common/css/misc.css`,

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" x="0px" y="0px" width="226.229px" height="31.038px" viewBox="0 0 226.229 31.038" enable-background="new 0 0 226.229 31.038" xml:space="preserve" id="svg2" inkscape:version="0.48.1 r9760" sodipodi:docname="logo.svg"><metadata id="metadata61"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/></cc:Work></rdf:RDF></metadata><defs id="defs59"/><sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1366" inkscape:window-height="710" id="namedview57" showgrid="false" inkscape:zoom="4.0755163" inkscape:cx="138.56745" inkscape:cy="16.509992" inkscape:window-x="0" inkscape:window-y="26" inkscape:window-maximized="1" inkscape:current-layer="svg2"/>
<g id="Background">
</g>
<g id="Guides">
</g>
<g id="Foreground">
<g id="g7">
<g id="g9">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.417,30.321L0,0h8.233l4.26,15.582l0.349,1.276 c0.521,1.866,0.918,3.431,1.191,4.693c0.15-0.618,0.335-1.345,0.555-2.182c0.219-0.837,0.528-1.935,0.925-3.293L19.981,0h8.19 L17.671,30.321H10.417z" id="path11"/>
</g>
<g id="g13">
<path fill-rule="evenodd" clip-rule="evenodd" fill="#A0CE67" d="M139.809,19.787c-0.665,0.357-1.748,0.686-3.25,0.988 c-0.727,0.137-1.283,0.254-1.667,0.35c-0.95,0.247-1.661,0.563-2.134,0.947c-0.472,0.384-0.799,0.899-0.979,1.544 c-0.223,0.796-0.155,1.438,0.204,1.925c0.359,0.488,0.945,0.731,1.757,0.731c1.252,0,2.375-0.36,3.369-1.081 c0.994-0.721,1.653-1.665,1.98-2.831L139.809,19.787z M144.915,30.321h-7.458c0.017-0.356,0.048-0.726,0.094-1.11l0.159-1.192 c-1.318,1.026-2.627,1.786-3.927,2.279c-1.299,0.493-2.643,0.739-4.031,0.739c-2.158,0-3.7-0.593-4.625-1.779 c-0.925-1.187-1.106-2.788-0.542-4.804c0.519-1.851,1.431-3.356,2.737-4.515c1.307-1.159,3.021-1.972,5.142-2.438 c1.169-0.247,2.641-0.515,4.413-0.803c2.646-0.412,4.082-1.016,4.304-1.812l0.151-0.539c0.182-0.65,0.076-1.145-0.317-1.483 c-0.393-0.339-1.071-0.508-2.033-0.508c-1.045,0-1.934,0.214-2.666,0.643c-0.731,0.428-1.289,1.058-1.673,1.887h-6.748 c1.065-2.53,2.64-4.413,4.723-5.65s4.724-1.856,7.923-1.856c1.991,0,3.602,0.241,4.833,0.722s2.095,1.209,2.59,2.185 c0.339,0.701,0.483,1.536,0.432,2.504c-0.052,0.969-0.377,2.525-0.978,4.669l-2.375,8.483c-0.284,1.014-0.416,1.812-0.396,2.395 s0.188,0.962,0.503,1.141L144.915,30.321z" id="path15" style="fill:#8ed300;fill-opacity:1"/>
</g>
<g id="g17">
<path fill-rule="evenodd" clip-rule="evenodd" fill="#A0CE67" d="M185.7,30.321l6.27-22.393h7.049l-1.097,3.918 c1.213-1.537,2.502-2.659,3.867-3.366c1.365-0.707,2.951-1.074,4.758-1.101l-2.03,7.25c-0.304-0.042-0.608-0.072-0.912-0.093 c-0.303-0.02-0.592-0.03-0.867-0.03c-1.126,0-2.104,0.168-2.932,0.504c-0.829,0.336-1.561,0.854-2.197,1.555 c-0.406,0.467-0.789,1.136-1.149,2.007c-0.361,0.872-0.814,2.282-1.359,4.232l-2.104,7.516H185.7z" id="path19" style="fill:#8ed300;fill-opacity:1"/>
</g>
<g id="g21">
<path fill-rule="evenodd" clip-rule="evenodd" fill="#A0CE67" d="M217.631,19.787c-0.664,0.357-1.748,0.686-3.25,0.988 c-0.727,0.137-1.282,0.254-1.667,0.35c-0.95,0.247-1.661,0.563-2.134,0.947c-0.472,0.384-0.799,0.899-0.979,1.544 c-0.223,0.796-0.155,1.438,0.205,1.925c0.359,0.488,0.945,0.731,1.757,0.731c1.252,0,2.375-0.36,3.369-1.081 c0.994-0.721,1.654-1.665,1.98-2.831L217.631,19.787z M222.737,30.321h-7.458c0.017-0.356,0.048-0.726,0.094-1.11l0.159-1.192 c-1.318,1.026-2.627,1.786-3.927,2.279c-1.299,0.493-2.643,0.739-4.031,0.739c-2.158,0-3.7-0.593-4.625-1.779 c-0.926-1.187-1.106-2.788-0.542-4.804c0.519-1.851,1.431-3.356,2.737-4.515c1.306-1.159,3.02-1.972,5.142-2.438 c1.169-0.247,2.641-0.515,4.413-0.803c2.647-0.412,4.082-1.016,4.304-1.812l0.151-0.539c0.182-0.65,0.077-1.145-0.317-1.483 c-0.393-0.339-1.071-0.508-2.033-0.508c-1.045,0-1.934,0.214-2.666,0.643c-0.731,0.428-1.289,1.058-1.672,1.887h-6.748 c1.065-2.53,2.64-4.413,4.723-5.65s4.724-1.856,7.923-1.856c1.99,0,3.601,0.241,4.833,0.722s2.095,1.209,2.591,2.185 c0.339,0.701,0.483,1.536,0.431,2.504c-0.051,0.969-0.377,2.525-0.978,4.669l-2.375,8.483c-0.284,1.014-0.416,1.812-0.396,2.395 c0.02,0.583,0.188,0.962,0.503,1.141L222.737,30.321z" id="path23" style="fill:#8ed300;fill-opacity:1"/>
</g>
<g id="g25">
<path fill-rule="evenodd" clip-rule="evenodd" fill="#A0CE67" d="M188.386,7.928l-6.269,22.393h-7.174l0.864-3.085 c-1.227,1.246-2.476,2.163-3.746,2.751s-2.625,0.882-4.067,0.882c-2.471,0-4.154-0.634-5.048-1.901 c-0.895-1.268-0.993-3.149-0.294-5.644l4.31-15.396h7.338l-3.508,12.53c-0.516,1.842-0.641,3.109-0.375,3.803 s0.967,1.041,2.105,1.041c1.275,0,2.323-0.422,3.142-1.267c0.819-0.845,1.497-2.223,2.031-4.133l3.353-11.974H188.386z" id="path27" style="fill:#8ed300;fill-opacity:1"/>
</g>
<g id="g29">
<path fill-rule="evenodd" clip-rule="evenodd" fill="#A0CE67" d="M149.937,12.356l1.239-4.428h2.995l1.771-6.326h7.338 l-1.771,6.326h3.753l-1.24,4.428h-3.753l-2.716,9.702c-0.416,1.483-0.498,2.465-0.247,2.946c0.25,0.48,0.905,0.721,1.964,0.721 l0.549-0.011l0.39-0.031l-1.31,4.678c-0.811,0.148-1.596,0.263-2.354,0.344c-0.758,0.081-1.48,0.122-2.167,0.122 c-2.543,0-4.108-0.621-4.695-1.863c-0.587-1.242-0.313-3.887,0.82-7.936l2.428-8.672H149.937z" id="path31" style="fill:#8ed300;fill-opacity:1"/>
</g>
<g id="g33">
<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M73.875,18.896c-0.561,2.004-0.616,3.537-0.167,4.601 s1.375,1.595,2.774,1.595c1.399,0,2.605-0.524,3.62-1.574s1.806-2.59,2.375-4.622c0.526-1.879,0.556-3.334,0.09-4.363 c-0.466-1.029-1.393-1.543-2.778-1.543c-1.304,0-2.487,0.528-3.551,1.585S74.386,17.071,73.875,18.896z M96.513,0l-8.489,30.321 h-7.337l0.824-2.944c-1.166,1.22-2.369,2.121-3.61,2.703s-2.583,0.874-4.025,0.874c-2.802,0-4.772-1.081-5.912-3.243 c-1.139-2.162-1.218-4.993-0.238-8.493c0.988-3.528,2.668-6.404,5.042-8.627c2.374-2.224,4.927-3.336,7.661-3.336 c1.47,0,2.695,0.296,3.676,0.887c0.981,0.591,1.681,1.465,2.099,2.62L89.217,0H96.513z" id="path35"/>
<g id="g37">
<path fill-rule="evenodd" clip-rule="evenodd" d="M73.875,18.896c-0.561,2.004-0.616,3.537-0.167,4.601s1.375,1.595,2.774,1.595 c1.399,0,2.605-0.524,3.62-1.574s1.806-2.59,2.375-4.622c0.526-1.879,0.556-3.334,0.09-4.363 c-0.466-1.029-1.393-1.543-2.778-1.543c-1.304,0-2.487,0.528-3.551,1.585S74.386,17.071,73.875,18.896z M96.513,0l-8.489,30.321 h-7.337l0.824-2.944c-1.166,1.22-2.369,2.121-3.61,2.703s-2.583,0.874-4.025,0.874c-2.802,0-4.772-1.081-5.912-3.243 c-1.139-2.162-1.218-4.993-0.238-8.493c0.988-3.528,2.668-6.404,5.042-8.627c2.374-2.224,4.927-3.336,7.661-3.336 c1.47,0,2.695,0.296,3.676,0.887c0.981,0.591,1.681,1.465,2.099,2.62L89.217,0H96.513z" id="path39"/>
</g>
</g>
<g id="g41">
<path fill-rule="evenodd" clip-rule="evenodd" d="M46.488,30.321l6.269-22.393h7.049l-1.098,3.918 c1.213-1.537,2.502-2.659,3.868-3.366s6.015-1.074,7.822-1.101l-2.03,7.25c-0.304-0.042-0.608-0.072-0.911-0.093 c-0.304-0.02-0.592-0.03-0.867-0.03c-1.126,0-5.167,0.168-5.997,0.504c-0.829,0.336-1.561,0.854-2.196,1.555 c-0.406,0.467-0.789,1.136-1.149,2.007c-0.361,0.872-0.814,2.282-1.36,4.232l-2.104,7.516H46.488z" id="path43"/>
</g>
<g id="g45">
<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M32.673,16.742l8.351-0.021 c0.375-1.436,0.308-2.558-0.201-3.365s-1.402-1.211-2.68-1.211c-1.209,0-2.285,0.397-3.229,1.19S33.224,15.265,32.673,16.742z M38.817,23.278h7.043c-1.347,2.456-3.172,4.356-5.477,5.7c-2.305,1.345-4.885,2.017-7.74,2.017 c-3.473,0-5.923-1.054-7.351-3.161c-1.427-2.107-1.632-4.98-0.613-8.618c1.038-3.707,2.875-6.641,5.512-8.803 c2.637-2.163,5.678-3.244,9.123-3.244c3.555,0,6.04,1.099,7.456,3.298c1.417,2.198,1.582,5.234,0.498,9.109l-0.239,0.814 l-0.167,0.484H31.721c-0.441,1.575-0.438,2.777,0.01,3.606c0.448,0.829,1.332,1.244,2.65,1.244c0.975,0,1.836-0.206,2.583-0.617 S38.33,24.086,38.817,23.278z" id="path47"/>
<g id="g49">
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.673,16.742l8.351-0.021c0.375-1.436,0.308-2.558-0.201-3.365 s-1.402-1.211-2.68-1.211c-1.209,0-2.285,0.397-3.229,1.19S33.224,15.265,32.673,16.742z M38.817,23.278h7.043 c-1.347,2.456-3.172,4.356-5.477,5.7c-2.305,1.345-4.885,2.017-7.74,2.017c-3.473,0-5.923-1.054-7.351-3.161 c-1.427-2.107-1.632-4.98-0.613-8.618c1.038-3.707,2.875-6.641,5.512-8.803c2.637-2.163,5.678-3.244,9.123-3.244 c3.555,0,6.04,1.099,7.456,3.298c1.417,2.198,1.582,5.234,0.498,9.109l-0.239,0.814l-0.167,0.484H31.721 c-0.441,1.575-0.438,2.777,0.01,3.606c0.448,0.829,1.332,1.244,2.65,1.244c0.975,0,1.836-0.206,2.583-0.617 S38.33,24.086,38.817,23.278z" id="path51"/>
</g>
</g>
<g id="g53">
<path fill="#A0CE67" d="M112.881,30.643l-6.404-18.639l-6.455,18.639h-7.254l9.565-30.321h8.19l4.434,15.582l0.35,1.276 c0.521,1.866,0.917,3.431,1.191,4.693l0.555-2.182c0.219-0.837,0.528-1.935,0.925-3.293l4.468-16.076h8.19l-10.501,30.321 H112.881z" id="path55" style="fill:#8ed300;fill-opacity:1"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -0,0 +1,5 @@
company:
fiscalAddress: VERDNATURA LEVANTE S.L., B97367486 Avda. Espioca, 100, 46460 Silla
- www.verdnatura.es - clientes@verdnatura.es
registry: 'CIF: B97367486 Registro Mercantil de Valencia, Tomo 8041, Libro 5334,
Folio 160, Sección 8, Hoja V 102076'

View File

@ -0,0 +1,5 @@
company:
fiscalAddress: VERDNATURA LEVANTE S.L., B97367486 Avda. Espioca, 100, 46460 Silla
- www.verdnatura.es - clientes@verdnatura.es
registry: 'CIF: B97367486 Registro Mercantil de Valencia, Tomo 8041, Libro 5334,
Folio 160, Sección 8, Hoja V 102076'

View File

@ -1,5 +1,5 @@
<header>
<img :src="embeded['/assets/images/report-logo.svg']" alt="Verdnatura"/>
<img v-bind:src="getReportSrc('report-logo.svg')" alt="Verdnatura"/>
<section>{{$t('company.fiscalAddress')}}</section>
<section>{{$t('company.registry')}}</section>
</header>

View File

@ -0,0 +1,4 @@
module.exports = {
name: 'report-header',
props: ['isPreview', 'locale']
};

View File

@ -14,19 +14,6 @@ for (let configFile of configFiles) {
Object.assign(config, require(configFile));
}
/* let proxyConf = {};
let proxyFiles = [
'../../nginx/config.yml',
`${configPath}/config.yml`,
`${configPath}/config.${env}.yml`
];
for (let proxyFile of proxyFiles) {
if (fs.existsSync(proxyFile))
Object.assign(proxyConf, require(proxyFile));
} */
// config.proxy = proxyConf;
config.env = env;
module.exports = config;

24
print/core/database.js Normal file
View File

@ -0,0 +1,24 @@
const mysql = require('mysql2/promise');
const config = require('./config.js');
module.exports = {
init() {
if (!this.pool) {
this.pool = mysql.createPool(config.mysql);
this.pool.on('connection', connection => {
connection.config.namedPlaceholders = true;
});
}
},
find(query, params) {
return this.pool.query(query, params).then(([rows]) => {
return rows;
});
},
findOne(query, params) {
return this.find(query, params).then(([rows]) => rows);
},
findFromDef() {
}
};

View File

@ -0,0 +1,2 @@
// Import global directives
require('./pin');

View File

@ -0,0 +1,9 @@
// DIRECTIVES NOT WORKING
const Vue = require('vue');
Vue.directive('pin', {
bind: function(el, binding, vnode) {
el.style.position = 'fixed';
el.style.top = binding.value + 'px';
el.style.backgroundColor = 'red';
}
});

93
print/core/email.js Normal file
View File

@ -0,0 +1,93 @@
const path = require('path');
const smtp = require('./smtp');
const Component = require('./component');
const Report = require('./report');
const db = require('./database');
const config = require('./config');
if (!process.env.OPENSSL_CONF)
process.env.OPENSSL_CONF = '/etc/ssl/';
class Email extends Component {
constructor(name, args) {
super(name);
this.args = args;
}
get path() {
return `../templates/email/${this.name}`;
}
async getSubject() {
if (!this.lang) await this.getLang();
const locale = this.locale.messages;
const userLocale = locale[this.lang];
if (!userLocale) {
const fallbackLocale = config.i18n.fallbackLocale;
return locale[fallbackLocale].subject;
}
return userLocale.subject;
}
async getLang() {
const clientId = this.args.clientId;
const lang = await db.findOne(`
SELECT lang FROM account.user
WHERE id = ?`, [clientId]).then(rows => {
return rows.lang;
});
this.lang = lang;
}
async send() {
const instance = this.build();
const rendered = await this.render();
const attachments = [];
const getAttachments = async(componentPath, files) => {
for (file of files) {
const fileCopy = Object.assign({}, file);
if (fileCopy.cid) {
const templatePath = `${componentPath}/${file.path}`;
const fullFilePath = path.resolve(__dirname, templatePath);
fileCopy.path = path.resolve(__dirname, fullFilePath);
} else {
const reportName = fileCopy.filename.replace('.pdf', '');
const report = new Report(reportName, this.args);
fileCopy.content = await report.toPdfStream();
}
attachments.push(fileCopy);
}
};
if (instance.components) {
const components = instance.components;
for (let componentName in components) {
const component = components[componentName];
const componentPath = `./components/${componentName}`;
await getAttachments(componentPath, component.attachments);
}
}
if (this.attachments)
await getAttachments(this.path, this.attachments);
const localeSubject = await this.getSubject();
const options = {
to: this.args.recipient,
subject: localeSubject,
html: rendered,
attachments: attachments
};
return smtp.send(options);
}
}
module.exports = Email;

View File

@ -0,0 +1,9 @@
const Vue = require('vue');
const config = require('../config');
const defaultLocale = config.i18n.locale;
Vue.filter('currency', function(value, currency = 'EUR', locale = defaultLocale) {
return new Intl.NumberFormat(locale, {
style: 'currency', currency
}).format(parseFloat(value));
});

View File

@ -0,0 +1,6 @@
const Vue = require('vue');
const strftime = require('strftime');
Vue.filter('date', function(value, specifiers) {
return strftime(specifiers, value);
});

View File

@ -0,0 +1,4 @@
// Import global filters
require('./date');
require('./currency');
require('./percentage');

View File

@ -0,0 +1,11 @@
const Vue = require('vue');
const config = require('../config');
const defaultLocale = config.i18n.locale;
Vue.filter('percentage', function(value, minFraction = 2, maxFraction = 2, locale = defaultLocale) {
return new Intl.NumberFormat(locale, {
style: 'percent',
minimumFractionDigits: minFraction,
maximumFractionDigits: maxFraction
}).format(parseFloat(value));
});

View File

@ -0,0 +1,23 @@
const Vue = require('vue');
const config = require('../config');
const imageSrc = {
methods: {
getEmailSrc(image) {
let src = `cid:${image}`;
if (this.isPreview === 'true')
src = `/api/${this.$options.name}/assets/images/${image}`;
return src;
},
getReportSrc(image) {
const assetsPath = `${config.app.host}/api/${this.$options.name}`;
const imagePath = `${assetsPath}/assets/images/${image}`;
return imagePath;
}
}
};
Vue.mixin(imageSrc);

View File

@ -0,0 +1,4 @@
// Import global mixins
require('./image-src');
require('./user-locale');
require('./prop-validator');

View File

@ -0,0 +1,26 @@
const Vue = require('vue');
const validator = {
created() {
const props = this.$options.props;
const invalidProps = [];
for (prop in props) {
const isObject = typeof props[prop] === 'object';
const isRequired = props[prop].required;
const isNotDefined = this[prop] === undefined;
if (isObject && isRequired && isNotDefined)
invalidProps.push(prop);
}
if (invalidProps.length > 0) {
const required = invalidProps.join(', ');
throw new Error(`Required params not found [${required}]`);
}
},
props: ['isPreview']
};
Vue.mixin(validator);

View File

@ -0,0 +1,24 @@
const Vue = require('vue');
const db = require('../database');
const userLocale = {
async serverPrefetch() {
if (this.clientId)
this.locale = await this.getLocale(this.clientId);
if (this.locale)
this.$i18n.locale = this.locale;
},
methods: {
getLocale(clientId) {
return db.findOne(`
SELECT lang FROM account.user
WHERE id = ?`, [clientId]).then(rows => {
return rows.lang;
});
}
},
props: ['clientId']
};
Vue.mixin(userLocale);

38
print/core/report.js Normal file
View File

@ -0,0 +1,38 @@
const fs = require('fs');
const pdf = require('html-pdf');
const path = require('path');
const config = require('./config');
const Component = require('./component');
if (!process.env.OPENSSL_CONF)
process.env.OPENSSL_CONF = '/etc/ssl/';
class Report extends Component {
constructor(name, args) {
super(name);
this.args = args;
}
get path() {
return `../templates/reports/${this.name}`;
}
async toPdfStream() {
const template = await this.render();
let options = config.pdf;
const optionsPath = `${this.path}/options.json`;
const fullPath = path.resolve(__dirname, optionsPath);
if (fs.existsSync(fullPath))
options = Object.assign(options, require(optionsPath));
return new Promise(resolve => {
pdf.create(template, options).toStream((err, stream) => {
resolve(stream);
});
});
}
}
module.exports = Report;

51
print/core/router.js Normal file
View File

@ -0,0 +1,51 @@
const Report = require('./report');
const Email = require('./email');
module.exports = app => {
app.get(`/api/report/:name`, async(req, res, next) => {
const args = req.query;
const requiredArgs = ['clientId'];
const hasRequiredArgs = requiredArgs.every(arg => {
return args[arg];
});
if (!hasRequiredArgs)
res.json({message: 'Required params recipient, clientId'});
try {
const report = new Report(req.params.name, args);
const stream = await report.toPdfStream();
res.setHeader('Content-type', 'application/pdf');
stream.pipe(res);
} catch (e) {
next(e);
}
});
app.get(`/api/email/:name`, async(req, res, next) => {
const args = req.query;
const requiredArgs = ['recipient', 'clientId'];
const hasRequiredArgs = requiredArgs.every(arg => {
return args[arg];
});
if (!hasRequiredArgs)
res.json({message: 'Required params recipient, clientId'});
try {
const email = new Email(req.params.name, args);
if (args.isPreview === 'true') {
const rendered = await email.render();
res.send(rendered);
} else {
await email.send();
res.status(200).json({message: 'Sent'});
}
} catch (e) {
next(e);
}
});
};

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