Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4927-permitir-razonSocial-duplicada

This commit is contained in:
Alexandre Riera 2023-01-20 07:15:03 +01:00
commit 292372395e
55 changed files with 833 additions and 3741 deletions

View File

@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [Artículo](Datos Básicos) Añadido campo Unidades/Caja
### Changed
- [Reclamaciones](Descriptor) Cambiado el campo Agencia por Zona
- [Tickets](Líneas preparadas) Actualizada sección para que sea más visual
- [Supplier](Crear/Editar) Permite añadir Proveedores con la misma razón social pero con países distintos

View File

@ -0,0 +1,49 @@
const {Report} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('previousLabel', {
description: 'Returns the previa label pdf',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The item id',
http: {source: 'path'}
}],
returns: [
{
arg: 'body',
type: 'file',
root: true
}, {
arg: 'Content-Type',
type: 'String',
http: {target: 'header'}
}, {
arg: 'Content-Disposition',
type: 'String',
http: {target: 'header'}
}
],
http: {
path: '/:id/previousLabel',
verb: 'GET'
}
});
Self.previousLabel = async(ctx, id) => {
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('previa-label', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="previa-${id}.pdf"`];
};
};

View File

@ -3,4 +3,5 @@ module.exports = Self => {
require('../methods/collection/newCollection')(Self);
require('../methods/collection/getSectors')(Self);
require('../methods/collection/setSaleQuantity')(Self);
require('../methods/collection/previousLabel')(Self);
};

View File

@ -0,0 +1,3 @@
UPDATE `vn`.`collection`
SET sectorFk=1
WHERE id=1;

View File

@ -0,0 +1,3 @@
ALTER TABLE `vn`.`itemPackingType` ADD isActive BOOLEAN NOT NULL;
UPDATE `vn`.`itemPackingType` SET isActive = 0 WHERE code IN ('P', 'F');
UPDATE `vn`.`itemPackingType` SET isActive = 1 WHERE code IN ('V', 'H');

View File

@ -0,0 +1,23 @@
DROP FUNCTION IF EXISTS `vn`.`priceFixed_getRate2`;
DELIMITER $$
$$
CREATE FUNCTION `vn`.`priceFixed_getRate2`(vFixedPriceFk INT, vRate3 DOUBLE)
RETURNS DOUBLE
BEGIN
DECLARE vWarehouse INT;
DECLARE vRate2 DOUBLE;
SELECT round(vRate3 * (1 + ((r.rate2 - r.rate3)/100)), 2) INTO vRate2
FROM vn.rate r
JOIN vn.priceFixed p ON p.id = vFixedPriceFk
WHERE r.dated <= p.started
AND r.warehouseFk = p.warehouseFk
ORDER BY r.dated DESC
LIMIT 1;
RETURN vRate2;
END$$
DELIMITER ;

View File

@ -0,0 +1,73 @@
DROP TRIGGER IF EXISTS vn.XDiario_beforeUpdate;
USE vn;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` TRIGGER `vn`.`XDiario_beforeUpdate`
BEFORE UPDATE ON `XDiario`
FOR EACH ROW
BEGIN
IF NOT NEW.SUBCTA <=> OLD.SUBCTA THEN
IF NEW.SUBCTA <=> '' THEN
SET NEW.SUBCTA = NULL;
END IF;
IF NEW.SUBCTA IS NOT NULL AND NOT LENGTH(NEW.SUBCTA) <=> 10 THEN
CALL util.throw('INVALID_STRING_LENGTH');
END IF;
END IF;
IF NOT NEW.CONTRA <=> OLD.CONTRA THEN
IF NEW.CONTRA <=> '' THEN
SET NEW.CONTRA = NULL;
END IF;
IF NEW.CONTRA IS NOT NULL AND NOT LENGTH(NEW.CONTRA) <=> 10 THEN
CALL util.throw('INVALID_STRING_LENGTH');
END IF;
END IF;
IF NOT NEW.FECHA <=> OLD.FECHA THEN
CALL XDiario_checkDate(NEW.FECHA);
END IF;
IF NOT NEW.FECHA_EX <=> OLD.FECHA_EX THEN
CALL XDiario_checkDate(NEW.FECHA_EX);
END IF;
IF NOT NEW.FECHA_OP <=> OLD.FECHA_OP THEN
CALL XDiario_checkDate(NEW.FECHA_OP);
END IF;
IF NOT NEW.FECHA_RT <=> OLD.FECHA_RT THEN
CALL XDiario_checkDate(NEW.FECHA_RT);
END IF;
IF NOT NEW.FECREGCON <=> OLD.FECREGCON THEN
CALL XDiario_checkDate(NEW.FECREGCON);
END IF;
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS vn.XDiario_beforeInsert;
USE vn;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` TRIGGER `vn`.`XDiario_beforeInsert`
BEFORE INSERT ON `XDiario`
FOR EACH ROW
BEGIN
IF NEW.SUBCTA <=> '' THEN
SET NEW.SUBCTA = NULL;
END IF;
IF NEW.SUBCTA IS NOT NULL AND NOT LENGTH(NEW.SUBCTA) <=> 10 THEN
CALL util.throw('INVALID_STRING_LENGTH');
END IF;
IF NEW.CONTRA <=> '' THEN
SET NEW.CONTRA = NULL;
END IF;
IF NEW.CONTRA IS NOT NULL AND NOT LENGTH(NEW.CONTRA) <=> 10 THEN
CALL util.throw('INVALID_STRING_LENGTH');
END IF;
CALL XDiario_checkDate(NEW.FECHA);
CALL XDiario_checkDate(NEW.FECHA_EX);
CALL XDiario_checkDate(NEW.FECHA_OP);
CALL XDiario_checkDate(NEW.FECHA_RT);
CALL XDiario_checkDate(NEW.FECREGCON);
END$$
DELIMITER ;

View File

@ -1215,7 +1215,7 @@ INSERT INTO `vn`.`tag`(`id`, `code`, `name`, `isFree`, `isQuantitatif`, `sourceT
(7, NULL, 'Ancho de la base', 1, 1, NULL, 'mm',NULL, NULL),
(23, 'stems', 'Tallos', 1, 1, NULL, NULL, NULL, 'stems'),
(27, NULL, 'Longitud(cm)', 1, 1, NULL, 'cm', NULL, NULL),
(36, NULL, 'Proveedor', 1, 0, NULL, NULL, NULL, NULL),
(36, 'producer', 'Proveedor', 1, 0, NULL, NULL, NULL, 'producer'),
(56, NULL, 'Genero', 1, 0, NULL, NULL, NULL, NULL),
(58, NULL, 'Variedad', 1, 0, NULL, NULL, NULL, NULL),
(67, 'category', 'Categoria', 1, 0, NULL, NULL, NULL, NULL),

View File

@ -80202,3 +80202,5 @@ USE `vncontrol`;
-- Dump completed on 2022-11-21 7:57:28

View File

@ -417,8 +417,8 @@ export default {
fourthFixedPrice: 'vn-fixed-price tr:nth-child(5)',
fourthItemID: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.itemFk"]',
fourthWarehouse: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.warehouseFk"]',
fourthPPU: 'vn-fixed-price tr:nth-child(5) > td:nth-child(4)',
fourthPPP: 'vn-fixed-price tr:nth-child(5) > td:nth-child(5)',
fourthGroupingPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(4)',
fourthPackingPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(5)',
fourthHasMinPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(6) > vn-check[ng-model="price.hasMinPrice"]',
fourthMinPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(6) > vn-input-number[ng-model="price.minPrice"]',
fourthStarted: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.started"]',
@ -679,7 +679,10 @@ export default {
moveToTicketButton: '.vn-popover.shown vn-icon[icon="arrow_forward_ios"]',
moveToNewTicketButton: '.vn-popover.shown vn-button[label="New ticket"]',
stateMenuButton: 'vn-ticket-sale vn-tool-bar > vn-button-menu[label="State"]',
moreMenuState: 'body > div > div > div.content > div.filter.ng-scope > vn-textfield'
moreMenuState: 'body > div > div > div.content > div.filter.ng-scope > vn-textfield',
firstSaleHistoryButton: 'vn-ticket-sale vn-tr:nth-child(1) vn-icon-button[icon="history"]',
firstSaleHistory: 'form vn-table div > vn-tbody > vn-tr',
closeHistory: 'div.window vn-button[icon="clear"]'
},
ticketTracking: {
createStateButton: 'vn-float-button'

View File

@ -24,8 +24,8 @@ describe('Item fixed prices path', () => {
it('should fill the fixed price data', async() => {
const now = new Date();
await page.autocompleteSearch(selectors.itemFixedPrice.fourthWarehouse, 'Warehouse one');
await page.write(selectors.itemFixedPrice.fourthPPU, '1');
await page.write(selectors.itemFixedPrice.fourthPPP, '1');
await page.writeOnEditableTD(selectors.itemFixedPrice.fourthGroupingPrice, '1');
await page.writeOnEditableTD(selectors.itemFixedPrice.fourthPackingPrice, '1');
await page.write(selectors.itemFixedPrice.fourthMinPrice, '1');
await page.pickDate(selectors.itemFixedPrice.fourthStarted, now);
await page.pickDate(selectors.itemFixedPrice.fourthEnded, now);

View File

@ -196,6 +196,15 @@ describe('Ticket Edit sale path', () => {
expect(result).toContain('22.50');
});
it('should check in the history that logs has been added', async() => {
await page.waitToClick(selectors.ticketSales.firstSaleHistoryButton);
await page.waitForSelector(selectors.ticketSales.firstSaleHistory);
const result = await page.countElement(selectors.ticketSales.firstSaleHistory);
expect(result).toBeGreaterThan(0);
await page.waitToClick(selectors.ticketSales.closeHistory);
});
it('should recalculate price of sales', async() => {
await page.waitToClick(selectors.ticketSales.firstSaleCheckbox);
await page.waitToClick(selectors.ticketSales.secondSaleCheckbox);

View File

@ -55,7 +55,7 @@ describe('Ticket Future path', () => {
await page.autocompleteSearch(selectors.ticketFuture.ipt, 'Horizontal');
await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
});
it('should search with the destination IPT', async() => {
@ -68,7 +68,7 @@ describe('Ticket Future path', () => {
await page.autocompleteSearch(selectors.ticketFuture.futureIpt, 'Horizontal');
await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
});
it('should search with the origin grouped state', async() => {
@ -152,50 +152,6 @@ describe('Ticket Future path', () => {
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
});
it('should search in smart-table with especified Lines', async() => {
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.write(selectors.ticketFuture.tableLines, '0');
await page.keyboard.press('Enter');
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.write(selectors.ticketFuture.tableLines, '1');
await page.keyboard.press('Enter');
await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
});
it('should search in smart-table with especified Liters', async() => {
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.write(selectors.ticketFuture.tableLiters, '0');
await page.keyboard.press('Enter');
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.write(selectors.ticketFuture.tableLiters, '28');
await page.keyboard.press('Enter');
await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
});
it('should check the three last tickets and move to the future', async() => {
await page.waitToClick(selectors.ticketFuture.multiCheck);
await page.waitToClick(selectors.ticketFuture.firstCheck);

View File

@ -19,4 +19,5 @@ import './user-popover';
import './upload-photo';
import './bank-entity';
import './log';
import './instance-log';
import './sendSms';

View File

@ -0,0 +1,12 @@
<vn-dialog
vn-id="instanceLog">
<tpl-body>
<vn-log
url="{{$ctrl.url}}"
origin-id="$ctrl.originId"
changed-model="$ctrl.changedModel"
changed-model-id="$ctrl.changedModelId">
</vn-log>
</tpl-body>
</vn-dialog>

View File

@ -0,0 +1,21 @@
import ngModule from '../../module';
import Section from '../section';
import './style.scss';
export default class Controller extends Section {
open() {
this.$.instanceLog.show();
}
}
ngModule.vnComponent('vnInstanceLog', {
controller: Controller,
template: require('./index.html'),
bindings: {
model: '<',
originId: '<',
changedModelId: '<',
changedModel: '@',
url: '@'
}
});

View File

@ -0,0 +1,13 @@
.vn-dialog {
& > .window:not(:has(.empty-rows)) {
width:60%;
vn-log {
vn-card {
visibility: hidden;
& > * {
visibility: visible;
}
}
}
}
}

View File

@ -252,6 +252,9 @@
"Receipt's bank was not found": "No se encontró el banco del recibo",
"This receipt was not compensated": "Este recibo no ha sido compensado",
"Client's email was not found": "No se encontró el email del cliente",
"App name does not exist": "El nombre de aplicación no es válido",
"Try again": "Vuelve a intentarlo",
"Aplicación bloqueada por el usuario 9": "Aplicación bloqueada por el usuario 9",
"Failed to upload file": "Error al subir archivo",
"The DOCUWARE PDF document does not exists": "The DOCUWARE PDF document does not exists",
"A supplier with the same name already exists. Change the country.": "Un proveedor con el mismo nombre ya existe. Cambie el país."

View File

@ -19,9 +19,9 @@ class Controller extends ModuleCard {
}, {
relation: 'ticket',
scope: {
fields: ['agencyModeFk'],
fields: ['zoneFk'],
include: {
relation: 'agencyMode'
relation: 'zone'
}
}
}, {

View File

@ -27,16 +27,16 @@
<slot-body>
<div class="attributes">
<vn-label-value
label="State"
label="State"
value="{{$ctrl.claim.claimState.description}}">
</vn-label-value>
<vn-label-value
label="Created"
label="Created"
value="{{$ctrl.claim.created | date: 'dd/MM/yyyy HH:mm'}}">
</vn-label-value>
<vn-label-value
label="Salesperson">
<span
<span
ng-click="workerDescriptor.show($event, $ctrl.claim.client.salesPersonFk)"
class="link">
{{$ctrl.claim.client.salesPersonUser.name}}
@ -44,19 +44,23 @@
</vn-label-value>
<vn-label-value
label="Attended by">
<span
<span
ng-click="workerDescriptor.show($event, $ctrl.claim.worker.userFk)"
class="link">
{{$ctrl.claim.worker.user.name}}
</span>
</vn-label-value>
<vn-label-value
label="Agency"
value="{{$ctrl.claim.ticket.agencyMode.name}}">
label="Zone">
<span
ng-click="zoneDescriptor.show($event, $ctrl.claim.ticket.zoneFk)"
class="link">
{{$ctrl.claim.ticket.zoneFk}}
</span>
</vn-label-value>
<vn-label-value
label="Ticket">
<span
<span
ng-click="ticketDescriptor.show($event, $ctrl.claim.ticketFk)"
class="link">
{{$ctrl.claim.ticketFk}}
@ -94,12 +98,15 @@
question="Delete claim"
message="Are you sure you want to delete this claim?">
</vn-confirm>
<vn-worker-descriptor-popover
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<vn-ticket-descriptor-popover
<vn-ticket-descriptor-popover
vn-id="ticketDescriptor">
</vn-ticket-descriptor-popover>
<vn-popup vn-id="summary">
<vn-claim-summary claim="$ctrl.claim"></vn-claim-summary>
</vn-popup>
</vn-popup>
<vn-zone-descriptor-popover
vn-id="zoneDescriptor">
</vn-zone-descriptor-popover>

View File

@ -0,0 +1,39 @@
module.exports = Self => {
Self.remoteMethod('getRate2', {
description: 'Return the rate2',
accessType: 'READ',
accepts: [
{
arg: 'fixedPriceId',
type: 'integer',
description: 'The fixedPrice Id',
required: true
},
{
arg: 'rate3',
type: 'number',
description: `The price rate 3`,
required: true
}
],
returns: {
type: 'object',
root: true
},
http: {
path: `/getRate2`,
verb: 'GET'
}
});
Self.getRate2 = async(fixedPriceId, rate3, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const [result] = await Self.rawSql(`SELECT vn.priceFixed_getRate2(?, ?) as rate2`,
[fixedPriceId, rate3], myOptions);
return result;
};
};

View File

@ -0,0 +1,39 @@
const models = require('vn-loopback/server/server').models;
describe('getRate2()', () => {
it(`should return new rate2 if exists rate`, async() => {
const tx = await models.FixedPrice.beginTransaction({});
try {
const options = {transaction: tx};
const fixedPriceId = 1;
const rate3 = 2;
const result = await models.FixedPrice.getRate2(fixedPriceId, rate3, options);
expect(result.rate2).toEqual(1.9);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it(`should return null if not exists rate`, async() => {
const tx = await models.FixedPrice.beginTransaction({});
try {
const options = {transaction: tx};
const fixedPriceId = 13;
const rate3 = 2;
const result = await models.FixedPrice.getRate2(fixedPriceId, rate3, options);
expect(result.rate2).toEqual(null);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,4 +1,5 @@
module.exports = Self => {
require('../methods/fixed-price/filter')(Self);
require('../methods/fixed-price/upsertFixedPrice')(Self);
require('../methods/fixed-price/getRate2')(Self);
};

View File

@ -13,6 +13,9 @@
},
"description": {
"type": "string"
},
"isActive":{
"type": "boolean"
}
},
"acls": [
@ -23,4 +26,4 @@
"permission": "ALLOW"
}
]
}
}

View File

@ -41,14 +41,12 @@
<span translate>Warehouse</span>
</th>
<th
field="rate2"
vn-tooltip="Price By Unit">
<span translate>P.P.U.</span>
field="rate2">
<span translate>Grouping price</span>
</th>
<th
field="rate3"
vn-tooltip="Price By Package">
<span translate>P.P.P.</span>
field="rate3">
<span translate>Packing price</span>
</th>
<th field="minPrice">
<span translate>Min price</span>
@ -72,7 +70,7 @@
show-field="name"
value-field="id"
search-function="$ctrl.itemSearchFunc($search)"
on-change="$ctrl.upsertPrice(price)"
on-change="$ctrl.upsertPrice(price, true)"
order="id DESC"
tabindex="1">
<tpl-item>
@ -112,18 +110,32 @@
</vn-autocomplete>
</td>
<td shrink-field>
<vn-input-number
ng-model="price.rate2"
on-change="$ctrl.upsertPrice(price)"
step="0.01">
</vn-input-number>
<vn-td-editable number>
<text>{{price.rate2 | currency: 'EUR':2}}</text>
<field>
<vn-input-number
class="dense"
vn-focus
ng-model="price.rate2"
on-change="$ctrl.upsertPrice(price)"
step="0.01">
</vn-input-number>
</field>
</vn-td-editable>
</td>
<td shrink-field>
<vn-input-number
ng-model="price.rate3"
on-change="$ctrl.upsertPrice(price)"
step="0.01">
</vn-input-number>
<vn-td-editable number>
<text>{{price.rate3 | currency: 'EUR':2}}</text>
<field>
<vn-input-number
class="dense"
vn-focus
ng-model="price.rate3"
on-change="$ctrl.upsertPrice(price); $ctrl.recalculateRate2(price)"
step="0.01"s>
</vn-input-number>
</field>
</vn-td-editable>
</td>
<td shrink-field-expand class="minPrice">
<vn-check

View File

@ -62,7 +62,10 @@ export default class Controller extends Section {
});
}
upsertPrice(price) {
upsertPrice(price, resetMinPrice) {
if (resetMinPrice)
delete price['minPrice'];
price.hasMinPrice = price.minPrice ? true : false;
let requiredFields = ['itemFk', 'started', 'ended', 'rate2', 'rate3'];
@ -110,6 +113,24 @@ export default class Controller extends Section {
return {[param]: value};
}
}
recalculateRate2(price) {
if (!price.id || !price.rate3) return;
const query = 'FixedPrices/getRate2';
const params = {
fixedPriceId: price.id,
rate3: price.rate3
};
this.$http.get(query, {params})
.then(res => {
const rate2 = res.data.rate2;
if (rate2) {
price.rate2 = rate2;
this.upsertPrice(price);
}
});
}
}
ngModule.vnComponent('vnFixedPrice', {

View File

@ -85,5 +85,25 @@ describe('fixed price', () => {
expect(controller.$.model.remove).toHaveBeenCalled();
});
});
describe('recalculateRate2()', () => {
it(`should rate2 recalculate`, () => {
jest.spyOn(controller.vnApp, 'showSuccess');
const price = {
id: 1,
itemFk: 1,
rate2: 2,
rate3: 2
};
const response = {rate2: 1};
controller.recalculateRate2(price);
const query = `FixedPrices/getRate2?fixedPriceId=${price.id}&rate3=${price.rate3}`;
$httpBackend.expectGET(query).respond(response);
$httpBackend.flush();
expect(price.rate2).toEqual(response.rate2);
});
});
});
});

View File

@ -3,5 +3,3 @@ Search prices by item ID or code: Buscar por ID de artículo o código
Search fixed prices: Buscar precios fijados
Add fixed price: Añadir precio fijado
This row will be removed: Esta linea se eliminará
Price By Unit: Precio Por Unidad
Price By Package: Precio Por Paquete

View File

@ -0,0 +1,46 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('last', {
description: 'Gets the latest version of a access file',
accepts: [
{
arg: 'appName',
type: 'string',
required: true,
description: 'The app name'
}
],
returns: {
type: 'number',
root: true
},
http: {
path: `/:appName/last`,
verb: 'GET'
}
});
Self.last = async(ctx, appName) => {
const models = Self.app.models;
const versions = await models.MdbVersion.find({
where: {app: appName},
fields: ['version']
});
if (!versions.length)
throw new UserError('App name does not exist');
let maxNumber = 0;
for (let mdb of versions) {
if (mdb.version > maxNumber)
maxNumber = mdb.version;
}
let response = {
version: maxNumber
};
return response;
};
};

View File

@ -11,20 +11,22 @@ module.exports = Self => {
type: 'string',
required: true,
description: 'The app name'
},
{
arg: 'newVersion',
}, {
arg: 'toVersion',
type: 'number',
required: true,
description: `The new version number`
},
{
}, {
arg: 'branch',
type: 'string',
required: true,
description: `The branch name`
},
{
}, {
arg: 'fromVersion',
type: 'string',
required: true,
description: `The old version number`
}, {
arg: 'unlock',
type: 'boolean',
required: false,
@ -41,16 +43,13 @@ module.exports = Self => {
}
});
Self.upload = async(ctx, appName, newVersion, branch, unlock, options) => {
Self.upload = async(ctx, appName, toVersion, branch, fromVersion, unlock, options) => {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const myOptions = {};
const $t = ctx.req.__; // $translate
const TempContainer = models.TempContainer;
const AccessContainer = models.AccessContainer;
const fileOptions = {};
let tx;
if (typeof options == 'object')
@ -63,6 +62,7 @@ module.exports = Self => {
let srcFile;
try {
const userId = ctx.req.accessToken.userId;
const mdbApp = await models.MdbApp.findById(appName, null, myOptions);
if (mdbApp && mdbApp.locked && mdbApp.userFk != userId) {
@ -71,6 +71,19 @@ module.exports = Self => {
}));
}
const existBranch = await models.MdbBranch.findOne({
where: {name: branch}
}, myOptions);
if (!existBranch)
throw new UserError('Not exist this branch');
let lastMethod = await Self.last(ctx, appName, myOptions);
lastMethod.version++;
if (lastMethod.version != toVersion)
throw new UserError('Try again');
const tempContainer = await TempContainer.container('access');
const uploaded = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions);
const files = Object.values(uploaded.files).map(file => {
@ -83,7 +96,7 @@ module.exports = Self => {
const accessContainer = await AccessContainer.container('.archive');
const destinationFile = path.join(
accessContainer.client.root, accessContainer.name, appName, `${newVersion}.7z`);
accessContainer.client.root, accessContainer.name, appName, `${toVersion}.7z`);
if (process.env.NODE_ENV == 'test')
await fs.unlink(srcFile);
@ -104,7 +117,7 @@ module.exports = Self => {
await fs.mkdir(branchPath, {recursive: true});
const destinationBranch = path.join(branchPath, `${appName}.7z`);
const destinationRelative = `../../.archive/${appName}/${newVersion}.7z`;
const destinationRelative = `../../.archive/${appName}/${toVersion}.7z`;
try {
await fs.unlink(destinationBranch);
} catch (e) {}
@ -112,7 +125,7 @@ module.exports = Self => {
if (branch == 'master') {
const destinationRoot = path.join(accessContainer.client.root, `${appName}.7z`);
const rootRelative = `./.archive/${appName}/${newVersion}.7z`;
const rootRelative = `./.archive/${appName}/${toVersion}.7z`;
try {
await fs.unlink(destinationRoot);
} catch (e) {}
@ -120,10 +133,18 @@ module.exports = Self => {
}
}
await models.MdbVersionTree.create({
app: appName,
version: toVersion,
branchFk: branch,
fromVersion,
userFk: userId
}, myOptions);
await models.MdbVersion.upsert({
app: appName,
branchFk: branch,
version: newVersion
version: toVersion
}, myOptions);
if (unlock) await models.MdbApp.unlock(ctx, appName, myOptions);
@ -133,7 +154,7 @@ module.exports = Self => {
if (tx) await tx.rollback();
if (fs.existsSync(srcFile))
await fs.unlink(srcFile);
fs.unlink(srcFile);
throw e;
}

View File

@ -8,6 +8,9 @@
"MdbVersion": {
"dataSource": "vn"
},
"MdbVersionTree": {
"dataSource": "vn"
},
"AccessContainer": {
"dataSource": "accessStorage"
}

View File

@ -1,3 +1,4 @@
module.exports = Self => {
require('../methods/mdbVersion/upload')(Self);
require('../methods/mdbVersion/last')(Self);
};

View File

@ -0,0 +1,35 @@
{
"name": "MdbVersionTree",
"base": "VnModel",
"options": {
"mysql": {
"table": "mdbVersionTree"
}
},
"properties": {
"app": {
"type": "string",
"description": "The app name",
"id": true
},
"version": {
"type": "number"
},
"branchFk": {
"type": "string"
},
"fromVersion": {
"type": "number"
},
"userFk": {
"type": "number"
}
},
"relations": {
"branch": {
"type": "belongsTo",
"model": "MdbBranch",
"foreignKey": "branchFk"
}
}
}

View File

@ -108,16 +108,26 @@ module.exports = Self => {
switch (param) {
case 'id':
return {'f.id': value};
case 'lines':
case 'linesMax':
return {'f.lines': {lte: value}};
case 'liters':
case 'litersMax':
return {'f.liters': {lte: value}};
case 'futureId':
return {'f.futureId': value};
case 'ipt':
return {'f.ipt': value};
return {or:
[
{'f.ipt': {like: `%${value}%`}},
{'f.ipt': null}
]
};
case 'futureIpt':
return {'f.futureIpt': value};
return {or:
[
{'f.futureIpt': {like: `%${value}%`}},
{'f.futureIpt': null}
]
};
case 'state':
return {'f.stateCode': {like: `%${value}%`}};
case 'futureState':
@ -203,7 +213,6 @@ module.exports = Self => {
tmp.ticket_problems`);
const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql, myOptions);
return result[ticketsIndex];

View File

@ -19,7 +19,7 @@ describe('ticket getTicketsFuture()', () => {
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4);
expect(result.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {
await tx.rollback();
@ -43,7 +43,7 @@ describe('ticket getTicketsFuture()', () => {
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4);
expect(result.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {
@ -93,7 +93,7 @@ describe('ticket getTicketsFuture()', () => {
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4);
expect(result.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {
@ -118,7 +118,7 @@ describe('ticket getTicketsFuture()', () => {
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(1);
expect(result.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {
@ -143,7 +143,7 @@ describe('ticket getTicketsFuture()', () => {
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4);
expect(result.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {
@ -168,7 +168,7 @@ describe('ticket getTicketsFuture()', () => {
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4);
expect(result.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {
@ -187,13 +187,13 @@ describe('ticket getTicketsFuture()', () => {
originDated: today,
futureDated: today,
warehouseFk: 1,
ipt: 0
ipt: 'H'
};
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(0);
expect(result.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {
@ -218,7 +218,7 @@ describe('ticket getTicketsFuture()', () => {
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4);
expect(result.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {
@ -237,13 +237,13 @@ describe('ticket getTicketsFuture()', () => {
originDated: today,
futureDated: today,
warehouseFk: 1,
futureIpt: 0
futureIpt: 'H'
};
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(0);
expect(result.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {
@ -268,7 +268,7 @@ describe('ticket getTicketsFuture()', () => {
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(1);
expect(result.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {
@ -293,7 +293,7 @@ describe('ticket getTicketsFuture()', () => {
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4);
expect(result.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {

View File

@ -26,6 +26,9 @@
"PackingSiteConfig": {
"dataSource": "vn"
},
"ExpeditionMistake": {
"dataSource": "vn"
},
"PrintServerQueue": {
"dataSource": "vn"
},

View File

@ -0,0 +1,33 @@
{
"name": "ExpeditionMistake",
"base": "VnModel",
"options": {
"mysql": {
"table": "expeditionMistake"
}
},
"properties": {
"created": {
"type": "date"
}
},
"relations": {
"expedition": {
"type": "belongsTo",
"model": "Expedition",
"foreignKey": "expeditionFk"
},
"worker": {
"type": "belongsTo",
"model": "Worker",
"foreignKey": "workerFk"
},
"type": {
"type": "belongsTo",
"model": "MistakeType",
"foreignKey": "typeFk"
}
}
}

View File

@ -25,7 +25,10 @@ class Controller extends SearchPanel {
getItemPackingTypes() {
let itemPackingTypes = [];
this.$http.get('ItemPackingTypes').then(res => {
const filter = {
where: {isActive: true}
};
this.$http.get('ItemPackingTypes', {filter}).then(res => {
for (let ipt of res.data) {
itemPackingTypes.push({
code: ipt.code,

View File

@ -39,6 +39,7 @@ export default class Controller extends Section {
field: 'ipt',
autocomplete: {
url: 'ItemPackingTypes',
where: `{isActive: true}`,
showField: 'description',
valueField: 'code'
}
@ -47,6 +48,7 @@ export default class Controller extends Section {
field: 'futureIpt',
autocomplete: {
url: 'ItemPackingTypes',
where: `{isActive: true}`,
showField: 'description',
valueField: 'code'
}

View File

@ -25,7 +25,10 @@ class Controller extends SearchPanel {
getItemPackingTypes() {
let itemPackingTypes = [];
this.$http.get('ItemPackingTypes').then(res => {
const filter = {
where: {isActive: true}
};
this.$http.get('ItemPackingTypes', {filter}).then(res => {
for (let ipt of res.data) {
itemPackingTypes.push({
description: this.$t(ipt.description),

View File

@ -129,12 +129,12 @@
class="link">
{{::ticket.id}}
</span></td>
<td shrink-date>
<td>
<span class="chip {{$ctrl.compareDate(ticket.shipped)}}">
{{::ticket.shipped | date: 'dd/MM/yyyy'}}
{{::ticket.shipped | date: 'dd/MM/yyyy HH:mm'}}
</span>
</td>
<td>{{::ticket.ipt}}</td>
<td>{{::ticket.ipt | dashIfEmpty}}</td>
<td>
<span
class="chip {{$ctrl.stateColor(ticket.state)}}">
@ -150,12 +150,12 @@
{{::ticket.futureId}}
</span>
</td>
<td shrink-date>
<td>
<span class="chip {{$ctrl.compareDate(ticket.futureShipped)}}">
{{::ticket.futureShipped | date: 'dd/MM/yyyy'}}
{{::ticket.futureShipped | date: 'dd/MM/yyyy HH:mm'}}
</span>
</td>
<td>{{::ticket.futureIpt}}</td>
<td>{{::ticket.futureIpt | dashIfEmpty}}</td>
<td>
<span
class="chip {{$ctrl.stateColor(ticket.futureState)}}">

View File

@ -34,6 +34,7 @@ export default class Controller extends Section {
field: 'ipt',
autocomplete: {
url: 'ItemPackingTypes',
where: `{isActive: true}`,
showField: 'description',
valueField: 'code'
}
@ -42,6 +43,7 @@ export default class Controller extends Section {
field: 'futureIpt',
autocomplete: {
url: 'ItemPackingTypes',
where: `{isActive: true}`,
showField: 'description',
valueField: 'code'
}

View File

@ -30,7 +30,7 @@
ng-click="moreOptions.show($event)"
ng-show="$ctrl.hasSelectedSales()">
</vn-button>
<vn-button
<vn-button
disabled="!$ctrl.hasSelectedSales() || !$ctrl.isEditable"
ng-click="deleteLines.show()"
vn-tooltip="Remove lines"
@ -53,7 +53,7 @@
<vn-thead>
<vn-tr>
<vn-th shrink>
<vn-multi-check model="model"
<vn-multi-check model="model"
on-change="$ctrl.resetChanges()">
</vn-multi-check>
</vn-th>
@ -68,6 +68,7 @@
<vn-th number>Disc</vn-th>
<vn-th number>Amount</vn-th>
<vn-th shrink>Packaging</vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
@ -84,13 +85,13 @@
vn-tooltip="{{::$ctrl.$t('Claim')}}: {{::sale.claim.claimFk}}">
</vn-icon>
</a>
<vn-icon
ng-show="::(sale.visible < 0)"
<vn-icon
ng-show="::(sale.visible < 0)"
color-main
icon="warning"
vn-tooltip="Visible: {{::sale.visible || 0}}">
</vn-icon>
<vn-icon ng-show="sale.reserved"
<vn-icon ng-show="sale.reserved"
icon="icon-reserve"
translate-attr="{title: 'Reserved'}">
</vn-icon>
@ -108,21 +109,21 @@
</vn-icon>
</vn-td>
<vn-td shrink>
<img
<img
ng-src="{{$root.imagePath('catalog', '50x50', sale.itemFk)}}"
zoom-image="{{$root.imagePath('catalog', '1600x900', sale.itemFk)}}"
on-error-src/>
</vn-td>
<vn-td shrink>
<vn-chip
class="transparent"
<vn-chip
class="transparent"
ng-class="{'alert': sale.visible < 0}">
{{::sale.visible}}
</vn-chip>
</vn-td>
<vn-td shrink>
<vn-chip
class="transparent"
<vn-chip
class="transparent"
ng-class="{'alert': sale.available < 0}">
{{::sale.available}}
</vn-chip>
@ -195,7 +196,7 @@
translate-attr="{title: !$ctrl.isLocked ? 'Edit discount' : ''}"
ng-click="$ctrl.showEditDiscountPopover($event, sale)"
ng-if="sale.id">
{{(sale.discount / 100) | percentage}}
{{(sale.discount / 100) | percentage}}
</span>
</vn-td>
<vn-td number>
@ -204,6 +205,22 @@
<vn-td shrink>
{{::sale.item.itemPackingTypeFk | dashIfEmpty}}
</vn-td>
<vn-td shrink>
<vn-icon-button
vn-none
vn-tooltip="History"
icon="history"
ng-click="log.open()">
</vn-icon-button>
<vn-instance-log
vn-id="log"
url="TicketLogs"
origin-id="$ctrl.$params.id"
changed-model="Sale"
changed-model-id="sale.id">
</vn-instance-log>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
@ -383,8 +400,8 @@
</tr>
</thead>
<tbody>
<tr
class="clickable"
<tr
class="clickable"
ng-repeat="ticket in $ctrl.transfer.lastActiveTickets track by ticket.id"
ng-click="$ctrl.transferSales(ticket.id)">
<td shrink>{{::ticket.id}}</td>
@ -392,22 +409,22 @@
<td shrink>{{::ticket.agencyName}}</td>
<td expand>{{::ticket.address}}
<span vn-tooltip="
{{::ticket.nickname}}
{{::ticket.name}}
{{::ticket.street}}
{{::ticket.postalCode}}
{{::ticket.nickname}}
{{::ticket.name}}
{{::ticket.street}}
{{::ticket.postalCode}}
{{::ticket.city}}">
{{::ticket.nickname}}
{{::ticket.name}}
{{::ticket.street}}
{{::ticket.postalCode}}
{{::ticket.nickname}}
{{::ticket.name}}
{{::ticket.street}}
{{::ticket.postalCode}}
{{::ticket.city}}
</span>
</td>
</tr>
<tr>
<td
ng-if="!$ctrl.transfer.lastActiveTickets.length"
ng-if="!$ctrl.transfer.lastActiveTickets.length"
class="empty-rows"
colspan="4"
translate>
@ -503,4 +520,4 @@
vn-acl-action="remove">
Refund
</vn-item>
</vn-menu>
</vn-menu>

View File

@ -13,9 +13,9 @@ New ticket: Nuevo ticket
Edit price: Editar precio
You are going to delete lines of the ticket: Vas a eliminar lineas del ticket
This ticket will be removed from current route! Continue anyway?: ¡Se eliminará el ticket de la ruta actual! ¿Continuar de todas formas?
You have to allow pop-ups in your web browser to use this functionality:
You have to allow pop-ups in your web browser to use this functionality:
Debes permitir los pop-pups en tu navegador para que esta herramienta funcione correctamente
Disc: Dto
Disc: Dto
Available: Disponible
What is the day of receipt of the ticket?: ¿Cual es el día de preparación del pedido?
Add claim: Crear reclamación
@ -39,3 +39,4 @@ Packaging: Encajado
Refund: Abono
Promotion mana: Maná promoción
Claim mana: Maná reclamación
History: Historial

3588
print/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,9 +3,12 @@ SELECT
i.name,
i.stems,
i.size,
b.packing
b.packing,
p.name as 'producer'
FROM vn.item i
JOIN cache.last_buy clb ON clb.item_id = i.id
JOIN vn.buy b ON b.id = clb.buy_id
JOIN vn.entry e ON e.id = b.entryFk
JOIN vn.producer p ON p.id = i.producerFk
WHERE i.id = ? AND clb.warehouse_id = ?

View File

@ -0,0 +1,12 @@
const Stylesheet = require(`vn-print/core/stylesheet`);
const path = require('path');
const vnPrintPath = path.resolve('print');
module.exports = new Stylesheet([
`${vnPrintPath}/common/css/spacing.css`,
`${vnPrintPath}/common/css/misc.css`,
`${vnPrintPath}/common/css/layout.css`,
`${vnPrintPath}/common/css/report.css`,
`${__dirname}/style.css`])
.mergeStyles();

View File

@ -0,0 +1,85 @@
* {
box-sizing: border-box;
padding-right: 1%;
}
.label {
font-size: 1.2em;
font-family: Arial, Helvetica, sans-serif;
}
.barcode {
float: left;
width: 40%;
}
.barcode h1 {
text-align: center;
font-size: 1.8em;
margin: 0 0 10px 0
}
.barcode .image {
text-align: center
}
.barcode .image img {
width: 170px
}
.data {
float: left;
width: 60%;
}
.data .header {
background-color: #000;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
margin-bottom: 25px;
text-align: right;
font-size: 1.2em;
padding: 0.2em;
color: #FFF
}
.data .sector,
.data .producer {
text-transform: uppercase;
text-align: right;
font-size: 1.5em;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.data .sector-sm {
text-transform: uppercase;
text-align: right;
font-size: 1.2em;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.data .producer {
text-justify: inter-character;
}
.data .details {
border-top: 4px solid #000;
padding-top: 2px;
}
.data .details .package {
padding-right: 5px;
float: left;
width: 50%;
}
.package .packing,
.package .dated,
.package .labelNumber {
text-align: right
}
.package .packing {
font-size: 1.8em;
font-weight: 400
}
.data .details .size {
background-color: #000;
text-align: center;
font-size: 3em;
padding: 0.2em 0;
float: left;
width: 50%;
color: #FFF
}

View File

@ -0,0 +1,2 @@
previous: PREVIOUS
report: Report

View File

@ -0,0 +1,2 @@
previous: PREVIA
report: Ticket

View File

@ -0,0 +1,11 @@
{
"width": "10.4cm",
"height": "4.8cm",
"margin": {
"top": "0cm",
"right": "0cm",
"bottom": "0cm",
"left": "0cm"
},
"printBackground": true
}

View File

@ -0,0 +1,26 @@
<DOCTYPE html>
<body>
<div class="label">
<div class="barcode">
<h1>{{previa.saleGroupFk}}</h1>
<div class="image">
<img v-bind:src="barcode" />
</div>
</div>
<div class="data">
<div class="header">{{ $t('previous') }}</div>
<div v-if="sector.description.length > 16" class="sector-sm">
{{sector.description}}
</div>
<div v-else class="sector">{{sector.description}}</div>
<div class="producer">{{ $t('report') }}#{{previa.ticketFk}}</div>
<div class="details">
<div class="package">
<div class="packing">{{previa.itemPackingTypeFk}}</div>
<div class="dated">{{previa.shippingHour}}:{{previa.shippingMinute}}</div>
</div>
<div class="size">{{previa.items}}</div>
</div>
</div>
</div>
</body>

View File

@ -0,0 +1,42 @@
const Component = require(`vn-print/core/component`);
const reportBody = new Component('report-body');
const qrcode = require('qrcode');
const UserError = require('vn-loopback/util/user-error');
module.exports = {
name: 'previa-label',
async serverPrefetch() {
this.previa = await this.fetchPrevia(this.id);
this.sector = await this.fetchSector(this.id);
this.barcode = await this.getBarcodeBase64(this.id);
if (this.previa)
this.previa = this.previa[0];
if (!this.sector)
throw new UserError('Something went wrong - no sector found');
},
methods: {
fetchPrevia(id) {
return this.findOneFromDef('previa', [id]);
},
getBarcodeBase64(id) {
const data = String(id);
return qrcode.toDataURL(data, {margin: 0});
},
fetchSector(id) {
return this.findOneFromDef('sector', [id]);
}
},
components: {
'report-body': reportBody.build()
},
props: {
id: {
type: Number,
required: true,
description: 'The saleGroupFk id'
},
}
};

View File

@ -0,0 +1 @@
CALL vn.previousSticker_get(?)

View File

@ -0,0 +1,4 @@
SELECT s.description
FROM vn.saleGroup sg
JOIN vn.sector s ON sg.sectorFk = s.id
WHERE sg.id = ?