Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2723-Format_emails_for_summaries
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Jorge Padawan 2021-01-25 11:04:45 +01:00
commit 2b4f4cf8f4
27 changed files with 250 additions and 98 deletions

View File

@ -1 +0,0 @@
12271-wisemen

View File

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

View File

@ -770,25 +770,25 @@ INSERT INTO `vn`.`intrastat`(`id`, `description`, `taxClassFk`, `taxCodeFk`)
(05080000, 'Coral y materiales similares', 2, 2),
(06021010, 'Plantas vivas: Esqueje/injerto, Vid', 1, 1);
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `isOnOffer`, `expenceFk`, `isBargain`, `comment`, `relevancy`, `image`, `taxClassFk`, `subName`, `minPrice`)
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `isOnOffer`, `expenceFk`, `isBargain`, `comment`, `relevancy`, `image`, `taxClassFk`, `subName`, `minPrice`, `stars`)
VALUES
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '1', 1, NULL, 0),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '2', 1, NULL, 0),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, '3', 1, NULL, 0),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 1, 4751000000, 0, NULL, 0, '4', 2, NULL, 0),
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, '5', 2, NULL, 0),
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '6', 2, NULL, 0),
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '7', 2, NULL, 0),
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '8', 1, NULL, 0),
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '9', 1, NULL, 0),
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, '10', 1, NULL, 0),
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 1, 4751000000, 0, NULL, 0, '11', 2, NULL, 0),
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, '12', 2, NULL, 0),
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '13', 2, NULL, 0),
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '', 2, NULL, 0),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '', 2, NULL, 0),
(16, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '', 2, NULL, 0),
(71, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 1, 4751000000, 0, NULL, 0, '', 2, NULL, 0);
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '1', 1, NULL, 0, 1),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '2', 1, NULL, 0, 2),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, '3', 1, NULL, 0, 5),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 1, 4751000000, 0, NULL, 0, '4', 2, NULL, 0, 3),
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, '5', 2, NULL, 0, 3),
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '6', 2, NULL, 0, 4),
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '7', 2, NULL, 0, 4),
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '8', 1, NULL, 0, 5),
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '9', 1, NULL, 0, 4),
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, '10', 1, NULL, 0, 4),
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 1, 4751000000, 0, NULL, 0, '11', 2, NULL, 0, 4),
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, '12', 2, NULL, 0, 3),
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '13', 2, NULL, 0, 2),
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '', 2, NULL, 0, 4),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '', 2, NULL, 0, 0),
(16, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '', 2, NULL, 0, 0),
(71, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 1, 4751000000, 0, NULL, 0, '', 2, NULL, 0, 0);
INSERT INTO `vn`.`priceFixed`(`id`, `itemFk`, `rate0`, `rate1`, `rate2`, `rate3`, `started`, `ended`, `bonus`, `warehouseFk`, `created`)
VALUES

View File

@ -432,6 +432,7 @@ export default {
moreMenuDeleteTicket: '.vn-menu [name="deleteTicket"]',
moreMenuRestoreTicket: '.vn-menu [name="restoreTicket"]',
moreMenuMakeInvoice: '.vn-menu [name="makeInvoice"]',
moreMenuRegenerateInvoice: '.vn-menu [name="regenerateInvoice"]',
moreMenuChangeShippedHour: '.vn-menu [name="changeShipped"]',
moreMenuPaymentSMS: '.vn-menu [name="sendPaymentSms"]',
moreMenuSendImportSms: '.vn-menu [name="sendImportSms"]',

View File

@ -129,10 +129,10 @@ describe('Ticket descriptor path', () => {
});
describe('Make invoice', () => {
it('should login as adminBoss role then search for a ticket', async() => {
it('should login as administrative role then search for a ticket', async() => {
const invoiceableTicketId = '14';
await page.loginAndModule('adminBoss', 'ticket');
await page.loginAndModule('administrative', 'ticket');
await page.accessToSearchResult(invoiceableTicketId);
await page.waitForState('ticket.card.summary');
});
@ -160,6 +160,18 @@ describe('Ticket descriptor path', () => {
expect(result).toEqual('T4444445');
});
it(`should regenerate the invoice using the descriptor menu`, async() => {
const expectedMessage = 'Invoice sent for a regeneration, will be available in a few minutes';
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitForContentLoaded();
await page.waitToClick(selectors.ticketDescriptor.moreMenuRegenerateInvoice);
await page.respondToDialog('accept');
const message = await page.waitForSnackbar();
expect(message.text).toContain(expectedMessage);
});
});
describe('SMS', () => {

View File

@ -20,6 +20,7 @@ describe('Travel extra community path', () => {
await page.waitToClick(selectors.travelExtraCommunity.removeContinentFilter);
await page.waitForSpinnerLoad();
await page.writeOnEditableTD(selectors.travelExtraCommunity.firstTravelReference, 'edited reference');
await page.waitForSpinnerLoad();
await page.writeOnEditableTD(selectors.travelExtraCommunity.firstTravelLockedKg, '1500');
await page.waitForTimeout(1000);
});

View File

@ -51,3 +51,4 @@ import './treeview';
import './wday-picker';
import './datalist';
import './contextmenu';
import './rating';

View File

@ -0,0 +1,5 @@
<div>
<vn-icon ng-repeat="star in ::$ctrl.stars" ng-class="::{active: star.isActive}"
icon="star_rate">
</vn-icon>
</div>

View File

@ -0,0 +1,39 @@
import ngModule from '../../module';
import FormInput from '../form-input';
import './style.scss';
export default class Rating extends FormInput {
constructor($element, $scope) {
super($element, $scope);
this.maxStars = 5;
this.stars = [];
}
get field() {
return super.field;
}
set field(value) {
super.field = value;
this.populateStars();
}
populateStars() {
for (let i = 0; i < this.maxStars; i++) {
const star = {isActive: false};
if (i < this.field)
star.isActive = true;
this.stars.push(star);
}
}
}
ngModule.vnComponent('vnRating', {
template: require('./index.html'),
controller: Rating,
bindings: {
maxStars: '<?',
}
});

View File

@ -0,0 +1,38 @@
describe('Component vnRating', () => {
let $element;
let $ctrl;
beforeEach(ngModule('vnCore'));
beforeEach(inject(($compile, $rootScope) => {
$element = $compile(`<vn-rating ng-model="$ctrl.stars"></vn-rating>`)($rootScope);
$ctrl = $element.controller('vnRating');
}));
afterEach(() => {
$element.remove();
});
describe('field() setter', () => {
it(`should change field value and then call the populateStars() method`, () => {
jest.spyOn($ctrl, 'populateStars');
$ctrl.field = 5;
expect($ctrl.populateStars).toHaveBeenCalledWith();
expect($ctrl.stars.length).toEqual(5);
});
});
describe('populateStars()', () => {
it(`should populate the stars array and mark four of them as active`, () => {
jest.spyOn($ctrl, 'populateStars');
$ctrl.field = 4;
const activeStars = $ctrl.stars.filter(star => star.isActive);
expect(activeStars.length).toEqual(4);
});
});
});

View File

@ -0,0 +1,11 @@
@import "variables";
vn-rating {
vn-icon {
color: $color-primary-light
}
vn-icon.active {
color: $color-primary
}
}

View File

@ -61,6 +61,8 @@ $color-hover-cd: rgba(0, 0, 0, .1);
$color-hover-dc: .7;
$color-disabled: .6;
$color-primary-medium: lighten($color-primary, 20%);
$color-primary-light: lighten($color-primary, 35%);
$color-font-link-medium: lighten($color-font-link, 20%);
$color-font-link-light: lighten($color-font-link, 35%);
$color-main-medium: lighten($color-main, 20%);

View File

@ -11,8 +11,8 @@ vn-descriptor-content {
& > img[ng-src] {
min-height: 16em;
display: block;
height: 100%;
width: 100%;
width: 256px;
height: 256px;
}
vn-float-button {

View File

@ -54,13 +54,15 @@
flex: 1;
}
& > .tags {
padding-bottom: 3px;
height: 48px;
& > vn-label-value {
font-size: .75rem;
}
}
vn-rating vn-icon {
font-size: 1rem
}
}
.footer {
font-size: .8rem;

View File

@ -9,7 +9,7 @@
{"state": "item.index", "icon": "icon-item"},
{"state": "item.request", "icon": "pan_tool"},
{"state": "item.waste", "icon": "icon-claims"},
{"state": "item.fixedPrice", "icon": ""}
{"state": "item.fixedPrice", "icon": "contact_support"}
],
"card": [
{"state": "item.card.basicData", "icon": "settings"},

View File

@ -108,6 +108,7 @@ module.exports = Self => {
i.value7,
i.tag8,
i.value8,
i.stars,
tci.price,
tci.available,
w.lastName AS lastName,

View File

@ -155,7 +155,8 @@ module.exports = Self => {
co.code companyCode,
zed.zoneFk,
zed.hourTheoretical,
zed.hourEffective
zed.hourEffective,
am.name AS agencyName
FROM hedera.order o
LEFT JOIN address a ON a.id = o.address_id
LEFT JOIN agencyMode am ON am.id = o.agency_id

View File

@ -36,6 +36,9 @@
value="{{::item.value7}}">
</vn-label-value>
</div>
<vn-rating ng-if="::item.stars"
ng-model="::item.stars">
</vn-rating>
<div class="footer">
<div class="price">
<vn-one>

View File

@ -2,11 +2,9 @@ import ngModule from '../module';
import Component from 'core/lib/component';
import './style.scss';
class Controller extends Component {}
ngModule.vnComponent('vnOrderCatalogView', {
template: require('./index.html'),
controller: Controller,
controller: Component,
bindings: {
order: '<',
model: '<'

View File

@ -15,8 +15,8 @@
<vn-th field="isConfirmed" center>Confirmed</vn-th>
<vn-th field="created" center expand>Created</vn-th>
<vn-th field="landed" default-order="DESC" center expand>Landed</vn-th>
<vn-th field="created" center translate-attr="{title: 'Theoretical hour'}">T. Hour</vn-th>
<vn-th field="created" center>Real hour</vn-th>
<vn-th field="created" center>Hour</vn-th>
<vn-th field="agencyName" center>Agency</vn-th>
<vn-th center>Total</vn-th>
</vn-tr>
</vn-thead>
@ -52,8 +52,11 @@
{{::order.landed | date:'dd/MM/yyyy'}}
</span>
</vn-td>
<vn-td shrink>{{::order.hourTheoretical | date: 'HH:mm' | dashIfEmpty}}</vn-td>
<vn-td shrink>{{::ticket.hourEffective | date: 'HH:mm' | dashIfEmpty}}</vn-td>
<vn-td shrink>{{::(order.hourTheoretical
? order.hourTheoretical
: order.hourEffective) | dashIfEmpty
}}</vn-td>
<vn-td expand>{{::order.agencyName}}</vn-td>
<vn-td number>{{::order.total | currency: 'EUR': 2 | dashIfEmpty}}</vn-td>
<vn-td shrink>
<vn-icon-button

View File

@ -1,5 +1,4 @@
const UserError = require('vn-loopback/util/user-error');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = function(Self) {
Self.remoteMethodCtx('makeInvoice', {
@ -26,52 +25,54 @@ module.exports = function(Self) {
});
Self.makeInvoice = async(ctx, id) => {
const conn = Self.dataSource.connector;
let userId = ctx.req.accessToken.userId;
let models = Self.app.models;
let tx = await Self.beginTransaction({});
const userId = ctx.req.accessToken.userId;
const models = Self.app.models;
const tx = await Self.beginTransaction({});
try {
let options = {transaction: tx};
const options = {transaction: tx};
let filter = {fields: ['id', 'clientFk', 'companyFk']};
let ticket = await models.Ticket.findById(id, filter, options);
const filter = {fields: ['id', 'clientFk', 'companyFk']};
const ticket = await models.Ticket.findById(id, filter, options);
let clientCanBeInvoiced = await models.Client.canBeInvoiced(ticket.clientFk);
const clientCanBeInvoiced = await models.Client.canBeInvoiced(ticket.clientFk);
if (!clientCanBeInvoiced)
throw new UserError(`This client can't be invoiced`);
let ticketCanBeInvoiced = await models.Ticket.canBeInvoiced(ticket.id);
const ticketCanBeInvoiced = await models.Ticket.canBeInvoiced(ticket.id);
if (!ticketCanBeInvoiced)
throw new UserError(`This ticket can't be invoiced`);
const query = `SELECT vn.invoiceSerial(?, ?, ?) AS serial`;
const [result] = await Self.rawSql(query, [ticket.clientFk, ticket.companyFk, 'R'], options);
const serial = result.serial;
let query = `SELECT vn.invoiceSerial(?, ?, ?) AS serial`;
let [result] = await Self.rawSql(query, [ticket.clientFk, ticket.companyFk, 'R'], options);
let serial = result.serial;
let stmts = [];
await Self.rawSql('CALL invoiceFromTicket(?)', [id], options);
await Self.rawSql('CALL invoiceOut_new(?, CURDATE(), null, @invoiceId)', [serial], options);
stmt = new ParameterizedSQL('CALL vn.invoiceOut_newFromTicket(?, ?, ?, @invoiceId)', [
ticket.id,
serial,
null
]);
stmts.push(stmt);
const [resultInvoice] = await Self.rawSql('SELECT @invoiceId id', [], options);
let invoiceIndex = stmts.push(`SELECT @invoiceId AS invoiceId`) - 1;
const invoiceId = resultInvoice.id;
const ticketInvoice = await models.Ticket.findById(id, {fields: ['refFk']}, options);
await models.TicketLog.create({
originFk: ticket.id,
userFk: userId,
action: 'insert',
changedModel: 'Ticket',
changedModelId: ticket.id,
newInstance: ticketInvoice
}, options);
let sql = ParameterizedSQL.join(stmts, ';');
result = await conn.executeStmt(sql);
let invoiceId = result[invoiceIndex][0].invoiceId;
if (serial != 'R' && invoiceId) {
query = `CALL vn.invoiceOutBooking(?)`;
await Self.rawSql(query, [invoiceId], options);
await Self.rawSql('CALL invoiceOutBooking(?)', [invoiceId], options);
await models.PrintServerQueue.create({
reportFk: 3, // Tarea #2734 (Nueva): crear informe facturas
param1: invoiceId,
workerFk: userId
}, options);
}
let user = await models.Worker.findOne({where: {userFk: userId}}, options);
query = `INSERT INTO printServerQueue(reportFk, param1, workerFk) VALUES (?, ?, ?)`;
await Self.rawSql(query, [3, invoiceId, user.id], options);
await tx.commit();
return {invoiceFk: invoiceId, serial};

View File

@ -17,6 +17,9 @@
"Packaging": {
"dataSource": "vn"
},
"PrintServerQueue": {
"dataSource": "vn"
},
"Sale": {
"dataSource": "vn"
},

View File

@ -0,0 +1,31 @@
{
"name": "PrintServerQueue",
"description": "Print server queue",
"base": "VnModel",
"options": {
"mysql": {
"table": "printServerQueue"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"priorityFk": {
"type": "number"
},
"reportFk": {
"type": "number",
"required": true
},
"param1": {
"type": "number"
},
"workerFk": {
"type": "number",
"required": true
}
}
}

View File

@ -84,6 +84,7 @@
ng-show="$ctrl.isInvoiced"
vn-acl="invoicing"
vn-acl-action="remove"
name="regenerateInvoice"
translate>
Regenerate invoice
</vn-item>

View File

@ -60,7 +60,8 @@ class Controller extends Section {
}
},
{relation: 'ship'},
{relation: 'stowaway'}]
{relation: 'stowaway'},
{relation: 'invoiceOut'}]
};
return this.$http.get(`Tickets/${this.ticketId}`, {filter})

View File

@ -39,28 +39,24 @@
<vn-date-picker
vn-one
label="Shipped from"
ng-model="filter.shippedFrom"
on-change="$ctrl.shippedFrom = value">
ng-model="$ctrl.shippedFrom">
</vn-date-picker>
<vn-date-picker
vn-one
label="Shipped to"
ng-model="filter.shippedTo"
on-change="$ctrl.shippedTo = value">
label="Landed to"
ng-model="$ctrl.landedTo">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>
<vn-date-picker
vn-one
label="Landed from"
ng-model="filter.landedFrom"
on-change="$ctrl.landedFrom = value">
label="Shipped to"
ng-model="$ctrl.shippedTo">
</vn-date-picker>
<vn-date-picker
vn-one
label="Landed to"
ng-model="filter.landedTo"
on-change="$ctrl.landedTo = value">
label="Landed from"
ng-model="$ctrl.landedFrom">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>

View File

@ -9,47 +9,47 @@ class Controller extends SearchPanel {
}
get shippedFrom() {
return this._shippedFrom;
return this.filter.shippedFrom;
}
set shippedFrom(value) {
this._shippedFrom = value;
this.filter.shippedFrom = value;
if (!this.filter.shippedTo)
this.filter.shippedTo = value;
if (!this.filter.landedFrom)
this.filter.landedFrom = value;
}
get shippedTo() {
return this._shippedTo;
return this.filter.shippedTo;
}
set shippedTo(value) {
this._shippedTo = value;
if (!this.filter.shippedFrom)
this.filter.shippedFrom = value;
}
get landedFrom() {
return this._landedFrom;
}
set landedFrom(value) {
this._landedFrom = value;
this.filter.shippedTo = value;
if (!this.filter.landedTo)
this.filter.landedTo = value;
}
get landedFrom() {
return this.filter.landedFrom;
}
set landedFrom(value) {
this.filter.landedFrom = value;
if (!this.filter.shippedFrom)
this.filter.shippedFrom = value;
}
get landedTo() {
return this._landedTo;
return this.filter.landedTo;
}
set landedTo(value) {
this._landedTo = value;
this.filter.landedTo = value;
if (!this.filter.landedFrom)
this.filter.landedFrom = value;
if (!this.filter.shippedTo)
this.filter.shippedTo = value;
}
}