Merge branch 'dev' into 3623-reportInvoice_intrastat
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Carlos Jimenez Ruiz 2022-02-28 10:12:36 +00:00
commit d2871ff3a6
17 changed files with 195 additions and 70 deletions

View File

@ -6,19 +6,22 @@ CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`ticket_getMovable`(vTicketFk INT, vDat
BEGIN
/**
* Cálcula el stock movible para los artículos de un ticket
* vDatedNew debe ser menor que vDatedOld, en los otros casos se
* asume que siempre es posible
*
* @param vTicketFk -> Ticket
* @param vDatedNew -> Nueva fecha
* @return Sales con Movible
*/
DECLARE vDatedOld DATETIME;
SET vDatedNew = DATE_ADD(vDatedNew, INTERVAL 1 DAY);
SELECT t.shipped INTO vDatedOld
FROM ticket t
WHERE t.id = vTicketFk;
CALL itemStock(vWarehouseFk, DATE_SUB(vDatedNew, INTERVAL 1 DAY), NULL);
CALL item_getMinacum(vWarehouseFk, vDatedNew, DATEDIFF(vDatedOld, vDatedNew), NULL);
CALL itemStock(vWarehouseFk, vDatedNew, NULL);
CALL item_getMinacum(vWarehouseFk, vDatedNew, DATEDIFF(DATE_SUB(vDatedOld, INTERVAL 1 DAY), vDatedNew), NULL);
SELECT s.id,
s.itemFk,

View File

@ -1,7 +1,13 @@
module.exports = Self => {
Self.getSynchronizer = async function() {
return await Self.findOne({fields: ['id']});
let NODE_ENV = process.env.NODE_ENV;
if (!NODE_ENV || NODE_ENV == 'development')
return null;
return await Self.findOne({
fields: ['id', 'rolePrefix', 'userPrefix', 'userHost']
});
};
Object.assign(Self.prototype, {
@ -14,17 +20,16 @@ module.exports = Self => {
},
async syncUser(userName, info, password) {
const mysqlHost = '%';
let mysqlUser = userName;
if (this.dbType == 'MySQL') mysqlUser = `!${mysqlUser}`;
if (this.dbType == 'MySQL')
mysqlUser = this.userPrefix + mysqlUser;
const [row] = await Self.rawSql(
`SELECT COUNT(*) AS nRows
FROM mysql.user
WHERE User = ?
AND Host = ?`,
[mysqlUser, mysqlHost]
[mysqlUser, this.userHost]
);
let userExists = row.nRows > 0;
@ -35,11 +40,10 @@ module.exports = Self => {
FROM mysql.global_priv
WHERE User = ?
AND Host = ?`,
[mysqlUser, mysqlHost]
[mysqlUser, this.userHost]
);
const priv = row && JSON.parse(row.priv);
const role = priv && priv.default_role;
isUpdatable = !row || (role && role.startsWith('z-'));
isUpdatable = !row || (priv && priv.autogenerated);
}
if (!isUpdatable) {
@ -51,31 +55,27 @@ module.exports = Self => {
if (password) {
if (!userExists) {
await Self.rawSql('CREATE USER ?@? IDENTIFIED BY ?',
[mysqlUser, mysqlHost, password]
);
[mysqlUser, this.userHost, password]);
userExists = true;
} else {
switch (this.dbType) {
case 'MariaDB':
await Self.rawSql('ALTER USER ?@? IDENTIFIED BY ?',
[mysqlUser, mysqlHost, password]
);
[mysqlUser, this.userHost, password]);
break;
default:
await Self.rawSql('SET PASSWORD FOR ?@? = PASSWORD(?)',
[mysqlUser, mysqlHost, password]
);
[mysqlUser, this.userHost, password]);
}
}
}
if (userExists && this.dbType == 'MariaDB') {
let role = `z-${info.user.role().name}`;
let role = `${this.rolePrefix}${info.user.role().name}`;
try {
await Self.rawSql('REVOKE ALL, GRANT OPTION FROM ?@?',
[mysqlUser, mysqlHost]
);
[mysqlUser, this.userHost]);
} catch (err) {
if (err.code == 'ER_REVOKE_GRANTS')
console.warn(`${err.code}: ${err.sqlMessage}: ${err.sql}`);
@ -83,21 +83,18 @@ module.exports = Self => {
throw err;
}
await Self.rawSql('GRANT ? TO ?@?',
[role, mysqlUser, mysqlHost]
);
[role, mysqlUser, this.userHost]);
if (role) {
await Self.rawSql('SET DEFAULT ROLE ? FOR ?@?',
[role, mysqlUser, mysqlHost]
);
[role, mysqlUser, this.userHost]);
} else {
await Self.rawSql('SET DEFAULT ROLE NONE FOR ?@?',
[mysqlUser, mysqlHost]
);
[mysqlUser, this.userHost]);
}
}
} else if (userExists)
await Self.rawSql('DROP USER ?@?', [mysqlUser, mysqlHost]);
await Self.rawSql('DROP USER ?@?', [mysqlUser, this.userHost]);
}
});
};

View File

@ -16,6 +16,18 @@
},
"mysqlPassword": {
"type": "string"
},
"rolePrefix": {
"type": "string"
},
"userPrefix": {
"type": "string"
},
"userHost": {
"type": "string"
},
"tplUser": {
"type": "string"
}
}
}

View File

@ -28,7 +28,7 @@
url="Workers/activeWithRole"
search-function="{firstName: $search}"
value-field="id"
where="{role: {inq: ['salesPerson', 'officeBoss']}}"
where="{role: {inq: ['salesBoss', 'salesPerson', 'officeBoss']}}"
label="Salesperson">
<tpl-item>{{firstName}} {{name}}</tpl-item>
</vn-autocomplete>
@ -38,7 +38,7 @@
url="Workers/activeWithRole"
search-function="{firstName: $search}"
value-field="id"
where="{role: 'salesPerson'}"
where="{role: {inq: ['salesBoss', 'salesPerson']}}"
label="Attended by">
<tpl-item>{{firstName}} {{name}}</tpl-item>
</vn-autocomplete>

View File

@ -1,7 +1,8 @@
const models = require('vn-loopback/server/server').models;
const soap = require('soap');
describe('client sendSms()', () => {
// #3673 sendSms tests excluded
xdescribe('client sendSms()', () => {
it('should now send a message and log it', async() => {
spyOn(soap, 'createClientAsync').and.returnValue('a so fake client');
const tx = await models.Client.beginTransaction({});

View File

@ -1,6 +1,7 @@
const app = require('vn-loopback/server/server');
describe('sms send()', () => {
// #3673 sendSms tests excluded
xdescribe('sms send()', () => {
it('should not return status error', async() => {
const ctx = {req: {accessToken: {userId: 1}}};
const result = await app.models.Sms.send(ctx, 1105, '123456789', 'My SMS Body');

View File

@ -3,13 +3,21 @@
url="Items/getWasteByWorker"
data="details">
</vn-crud-model>
<vn-data-viewer model="model">
<vn-card>
<section ng-repeat="detail in details" class="vn-pa-md">
<vn-horizontal class="header">
<h5><span><span translate>{{detail.buyer}}</span></h5>
<h5><span translate>{{detail.buyer}}</span></h5>
<vn-none>
<vn-icon
ng-class="{'hidden': !$ctrl.wasteConfig[detail.buyer].hidden}"
class="arrow pointer"
icon="keyboard_arrow_up"
vn-tooltip="Minimize/Maximize"
ng-click="$ctrl.toggleHidePanel(detail)">
</vn-icon>
</vn-none>
</vn-horizontal>
<vn-card ng-class="{'hidden': !$ctrl.wasteConfig[detail.buyer].hidden}">
<vn-table>
<vn-thead>
<vn-tr>
@ -29,6 +37,6 @@
</vn-tr>
</vn-tbody>
</vn-table>
</section>
</vn-card>
</section>
</vn-data-viewer>

View File

@ -2,7 +2,34 @@ import ngModule from '../../module';
import Section from 'salix/components/section';
import './style.scss';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.getWasteConfig();
}
getWasteConfig() {
return this.wasteConfig = JSON.parse(localStorage.getItem('wasteConfig')) || {};
}
setWasteConfig() {
localStorage.setItem('wasteConfig', JSON.stringify(this.wasteConfig));
}
toggleHidePanel(detail) {
if (!this.wasteConfig[detail.buyer]) {
this.wasteConfig[detail.buyer] = {
hidden: true
};
} else
this.wasteConfig[detail.buyer].hidden = !this.wasteConfig[detail.buyer].hidden;
this.setWasteConfig();
}
}
ngModule.vnComponent('vnItemWasteIndex', {
template: require('./index.html'),
controller: Section
controller: Controller
});

View File

@ -0,0 +1,53 @@
import './index.js';
import crudModel from 'core/mocks/crud-model';
describe('Item', () => {
describe('Component vnItemWasteIndex', () => {
let $scope;
let controller;
beforeEach(ngModule('item'));
beforeEach(inject(($componentController, $rootScope) => {
$scope = $rootScope.$new();
$scope.model = crudModel;
const $element = angular.element('<vn-item-waste-index></vn-item-waste-index>');
controller = $componentController('vnItemWasteIndex', {$element, $scope});
}));
describe('getWasteConfig / setWasteConfig', () => {
it('should return the local storage wasteConfig', () => {
const result = controller.getWasteConfig();
expect(result).toEqual({});
});
it('should set and return the local storage wasteConfig', () => {
controller.wasteConfig = {salesPerson: {hidden: true}};
controller.setWasteConfig();
const result = controller.getWasteConfig();
expect(result).toEqual(controller.wasteConfig);
});
});
describe('toggleHidePanel()', () => {
it('should make details hidden by default', () => {
controller.wasteConfig = {};
controller.toggleHidePanel({buyer: 'salesPerson'});
expect(controller.wasteConfig.salesPerson.hidden).toEqual(true);
});
it('should toggle hidden false', () => {
controller.wasteConfig = {salesPerson: {hidden: true}};
controller.toggleHidePanel({buyer: 'salesPerson'});
expect(controller.wasteConfig.salesPerson.hidden).toEqual(false);
});
});
});
});

View File

@ -1,21 +1,24 @@
@import "variables";
@import "effects";
vn-item-waste-index,
vn-item-waste-detail {
.header {
margin-bottom: 16px;
text-transform: uppercase;
font-size: 1.25rem;
line-height: 1;
padding: 7px;
padding-bottom: 7px;
padding-bottom: 4px;
font-weight: lighter;
background-color: $color-bg;
border-bottom: 1px solid #f7931e;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 12px 0 5px 0;
color: gray;
font-size: 1.2rem;
border-bottom: $border;
margin-bottom: 10px;
& > vn-none > vn-icon {
@extend %clickable-light;
color: $color-button;
font-size: 1.4rem;
}
vn-none > .arrow {
transition: transform 200ms;
}
}
vn-table vn-th.waste-family,
@ -23,4 +26,12 @@ vn-item-waste-detail {
max-width: 64px;
width: 64px
}
.hidden {
display: none;
}
.header > vn-none > .arrow.hidden {
display: block;
transform: rotate(180deg);
}
}

View File

@ -1,3 +1,4 @@
Family: Familia
Percentage: Porcentaje
Dwindle: Mermas
Minimize/Maximize: Minimizar/Maximizar

View File

@ -86,8 +86,8 @@ describe('sale priceDifference()', () => {
const firstItem = result.items[0];
const secondtItem = result.items[1];
expect(firstItem.movable).toEqual(440);
expect(secondtItem.movable).toEqual(1980);
expect(firstItem.movable).toEqual(410);
expect(secondtItem.movable).toEqual(1870);
await tx.rollback();
} catch (e) {

View File

@ -1,7 +1,8 @@
const models = require('vn-loopback/server/server').models;
const soap = require('soap');
describe('ticket sendSms()', () => {
// #3673 sendSms tests excluded
xdescribe('ticket sendSms()', () => {
it('should send a message and log it', async() => {
const tx = await models.Ticket.beginTransaction({});

View File

@ -18,7 +18,14 @@
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="sale in $ctrl.ticket.sale.items track by sale.id">
<vn-td number>{{("000000"+sale.itemFk).slice(-6)}}</vn-td>
<vn-td number>
<span
title="{{::sale.item.name}}"
vn-click-stop="itemDescriptor.show($event, sale.itemFk, sale.id)"
class="link">
{{::sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td vn-fetched-tags>
<div>
<vn-one title="{{::sale.item.name}}">{{::sale.item.name}}</vn-one>
@ -83,5 +90,9 @@
</div>
</div>
</vn-side-menu>
<vn-item-descriptor-popover
vn-id="item-descriptor"
warehouse-fk="$ctrl.ticket.warehouseFk"
ticket-fk="$ctrl.ticket.id">
</vn-item-descriptor-popover>

View File

@ -12,8 +12,6 @@ module.exports = {
const failedtickets = [];
for (const ticket of tickets) {
try {
await db.rawSql('START TRANSACTION');
await db.rawSql(`CALL vn.ticket_closeByTicket(?)`, [ticket.id]);
const invoiceOut = await db.findOne(`
@ -91,9 +89,7 @@ module.exports = {
const email = new Email('delivery-note-link', args);
await email.send();
}
await db.rawSql('COMMIT');
} catch (error) {
await db.rawSql('ROLLBACK');
// Domain not found
if (error.responseCode == 450)
return invalidEmail(ticket);

View File

@ -5,7 +5,7 @@ h2 {
.table-title {
margin-bottom: 15px;
font-size: 0.8rem
font-size: .8rem
}
.table-title h2 {
@ -16,9 +16,12 @@ h2 {
font-size: 22px
}
#nickname h2 {
max-width: 400px;
word-wrap: break-word
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
#phytosanitary {

View File

@ -85,7 +85,7 @@
<!-- End of rectified invoices block -->
<!-- Sales block -->
<div class="vn-mt-lg no-page-break" v-for="ticket in tickets">
<div class="vn-mt-lg" v-for="ticket in tickets">
<div class="table-title clearfix">
<div class="pull-left">
<h2>{{$t('deliveryNote')}}</strong>