Merge branch 'test' into dev
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Guillermo Bonet 2023-10-19 13:22:33 +02:00
commit 5c51d7fb04
34 changed files with 324 additions and 218 deletions

View File

@ -18,6 +18,7 @@ describe('setSaleQuantity()', () => {
it('should change quantity sale', async() => { it('should change quantity sale', async() => {
const tx = await models.Ticket.beginTransaction({}); const tx = await models.Ticket.beginTransaction({});
spyOn(models.Item, 'getVisibleAvailable').and.returnValue((new Promise(resolve => resolve({available: 100}))));
try { try {
const options = {transaction: tx}; const options = {transaction: tx};

View File

@ -30434,6 +30434,7 @@ CREATE TABLE `item` (
`editorFk` int(10) unsigned DEFAULT NULL, `editorFk` int(10) unsigned DEFAULT NULL,
`recycledPlastic` int(11) DEFAULT NULL, `recycledPlastic` int(11) DEFAULT NULL,
`nonRecycledPlastic` int(11) DEFAULT NULL, `nonRecycledPlastic` int(11) DEFAULT NULL,
`minQuantity` int(10) unsigned DEFAULT NULL COMMENT 'Cantidad mínima para una línea de venta',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `item_supplyResponseFk_idx` (`supplyResponseFk`), UNIQUE KEY `item_supplyResponseFk_idx` (`supplyResponseFk`),
KEY `Color` (`inkFk`), KEY `Color` (`inkFk`),

View File

@ -189,5 +189,6 @@
"The sales do not exists": "The sales do not exists", "The sales do not exists": "The sales do not exists",
"Ticket without Route": "Ticket without route", "Ticket without Route": "Ticket without route",
"Booking completed": "Booking complete", "Booking completed": "Booking complete",
"The ticket is in preparation": "The ticket [{{ticketId}}]({{{ticketUrl}}}) of the sales person {{salesPersonId}} is in preparation" "The ticket is in preparation": "The ticket [{{ticketId}}]({{{ticketUrl}}}) of the sales person {{salesPersonId}} is in preparation",
"You can only add negative amounts in refund tickets": "You can only add negative amounts in refund tickets"
} }

View File

@ -320,5 +320,7 @@
"The response is not a PDF": "La respuesta no es un PDF", "The response is not a PDF": "La respuesta no es un PDF",
"Ticket without Route": "Ticket sin ruta", "Ticket without Route": "Ticket sin ruta",
"Booking completed": "Reserva completada", "Booking completed": "Reserva completada",
"The ticket is in preparation": "El ticket [{{ticketId}}]({{{ticketUrl}}}) del comercial {{salesPersonId}} está en preparación" "The ticket is in preparation": "El ticket [{{ticketId}}]({{{ticketUrl}}}) del comercial {{salesPersonId}} está en preparación",
"The amount cannot be less than the minimum": "La cantidad no puede ser menor que la cantidad mímina",
"quantityLessThanMin": "La cantidad no puede ser menor que la cantidad mímina"
} }

View File

@ -64,7 +64,7 @@ describe('claim regularizeClaim()', () => {
claimEnds = await importTicket(ticketId, claimId, userId, options); claimEnds = await importTicket(ticketId, claimId, userId, options);
for (claimEnd of claimEnds) for (const claimEnd of claimEnds)
await claimEnd.updateAttributes({claimDestinationFk: trashDestination}, options); await claimEnd.updateAttributes({claimDestinationFk: trashDestination}, options);
let claimBefore = await models.Claim.findById(claimId, null, options); let claimBefore = await models.Claim.findById(claimId, null, options);

View File

@ -17,6 +17,7 @@ Search claim by id or client name: Buscar reclamaciones por identificador o nomb
Claim deleted!: Reclamación eliminada! Claim deleted!: Reclamación eliminada!
claim: reclamación claim: reclamación
Photos: Fotos Photos: Fotos
Development: Trazabilidad
Go to the claim: Ir a la reclamación Go to the claim: Ir a la reclamación
Sale tracking: Líneas preparadas Sale tracking: Líneas preparadas
Ticket tracking: Estados del ticket Ticket tracking: Estados del ticket

View File

@ -131,6 +131,9 @@
"nonRecycledPlastic": { "nonRecycledPlastic": {
"type": "number" "type": "number"
}, },
"minQuantity": {
"type": "number"
},
"packingOut": { "packingOut": {
"type": "number" "type": "number"
}, },
@ -154,6 +157,10 @@
"mysql":{ "mysql":{
"columnName": "doPhoto" "columnName": "doPhoto"
} }
},
"minQuantity": {
"type": "number",
"description": "Min quantity"
} }
}, },
"relations": { "relations": {

View File

@ -33,6 +33,8 @@
rule rule
info="Full name calculates based on tags 1-3. Is not recommended to change it manually"> info="Full name calculates based on tags 1-3. Is not recommended to change it manually">
</vn-textfield> </vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete <vn-autocomplete
url="ItemTypes" url="ItemTypes"
label="Type" label="Type"
@ -50,6 +52,30 @@
</div> </div>
</tpl-item> </tpl-item>
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete
label="Generic"
url="Items/withName"
ng-model="$ctrl.item.genericFk"
vn-name="generic"
show-field="name"
value-field="id"
search-function="$ctrl.itemSearchFunc($search)"
order="id DESC"
tabindex="1">
<tpl-item>
<div>{{::name}}</div>
<div class="text-caption text-secondary">
#{{::id}}
</div>
</tpl-item>
<append>
<vn-icon-button
icon="filter_alt"
vn-click-stop="$ctrl.showFilterDialog($ctrl.item)"
vn-tooltip="Filter...">
</vn-icon-button>
</append>
</vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete <vn-autocomplete
@ -128,30 +154,13 @@
ng-model="$ctrl.item.stemMultiplier" ng-model="$ctrl.item.stemMultiplier"
vn-name="stemMultiplier"> vn-name="stemMultiplier">
</vn-input-number> </vn-input-number>
<vn-autocomplete <vn-input-number
label="Generic" min="1"
url="Items/withName" label="Minimum sales quantity"
ng-model="$ctrl.item.genericFk" ng-model="$ctrl.item.minQuantity"
vn-name="generic" vn-name="minQuantity"
show-field="name" rule>
value-field="id" </vn-input-number>
search-function="$ctrl.itemSearchFunc($search)"
order="id DESC"
tabindex="1">
<tpl-item>
<div>{{::name}}</div>
<div class="text-caption text-secondary">
#{{::id}}
</div>
</tpl-item>
<append>
<vn-icon-button
icon="filter_alt"
vn-click-stop="$ctrl.showFilterDialog($ctrl.item)"
vn-tooltip="Filter...">
</vn-icon-button>
</append>
</vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-input-number <vn-input-number

View File

@ -16,3 +16,4 @@ This item does need a photo: Este artículo necesita una foto
Do photo: Hacer foto Do photo: Hacer foto
Recycled Plastic: Plástico reciclado Recycled Plastic: Plástico reciclado
Non recycled plastic: Plástico no reciclado Non recycled plastic: Plástico no reciclado
Minimum sales quantity: Cantidad mínima de venta

View File

@ -128,6 +128,9 @@
<vn-label-value label="Non recycled plastic" <vn-label-value label="Non recycled plastic"
value="{{$ctrl.summary.item.nonRecycledPlastic}}"> value="{{$ctrl.summary.item.nonRecycledPlastic}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Minimum sales quantity"
value="{{$ctrl.summary.item.minQuantity}}">
</vn-label-value>
</vn-one> </vn-one>
<vn-one name="tags"> <vn-one name="tags">
<h4 ng-show="$ctrl.isBuyer || $ctrl.isReplenisher"> <h4 ng-show="$ctrl.isBuyer || $ctrl.isReplenisher">

View File

@ -2,3 +2,4 @@ Barcode: Códigos de barras
Other data: Otros datos Other data: Otros datos
Go to the item: Ir al artículo Go to the item: Ir al artículo
WarehouseFk: Calculado sobre el almacén de {{ warehouseName }} WarehouseFk: Calculado sobre el almacén de {{ warehouseName }}
Minimum sales quantity: Cantidad mínima de venta

View File

@ -100,8 +100,7 @@ module.exports = Self => {
)); ));
stmt = new ParameterizedSQL(` stmt = new ParameterizedSQL(`
SELECT SELECT i.id,
i.id,
i.name, i.name,
i.subName, i.subName,
i.image, i.image,
@ -116,15 +115,17 @@ module.exports = Self => {
i.stars, i.stars,
tci.price, tci.price,
tci.available, tci.available,
w.lastName AS lastName, w.lastName,
w.firstName, w.firstName,
tci.priceKg, tci.priceKg,
ink.hex ink.hex,
i.minQuantity
FROM tmp.ticketCalculateItem tci FROM tmp.ticketCalculateItem tci
JOIN vn.item i ON i.id = tci.itemFk JOIN vn.item i ON i.id = tci.itemFk
JOIN vn.itemType it ON it.id = i.typeFk JOIN vn.itemType it ON it.id = i.typeFk
JOIN vn.worker w on w.id = it.workerFk JOIN vn.worker w on w.id = it.workerFk
LEFT JOIN vn.ink ON ink.id = i.inkFk`); LEFT JOIN vn.ink ON ink.id = i.inkFk
`);
// Apply order by tag // Apply order by tag
if (orderBy.isTag) { if (orderBy.isTag) {

View File

@ -37,9 +37,24 @@
value="{{::item.value7}}"> value="{{::item.value7}}">
</vn-label-value> </vn-label-value>
</div> </div>
<vn-horizontal>
<vn-one>
<vn-rating ng-if="::item.stars" <vn-rating ng-if="::item.stars"
ng-model="::item.stars"> ng-model="::item.stars"/>
</vn-rating> </vn-one>
<vn-horizontal
class="text-right text-caption alert vn-mr-xs"
ng-if="::item.minQuantity">
<vn-one>
<vn-icon
icon="production_quantity_limits"
translate-attr="{title: 'Minimal quantity'}"
class="text-subtitle1">
</vn-icon>
</vn-one>
{{::item.minQuantity}}
</vn-horizontal>
</vn-horizontal>
<div class="footer"> <div class="footer">
<div class="price"> <div class="price">
<vn-one> <vn-one>

View File

@ -1 +1,2 @@
Order created: Orden creada Order created: Orden creada
Minimal quantity: Cantidad mínima

View File

@ -44,4 +44,7 @@ vn-order-catalog {
height: 30px; height: 30px;
position: relative; position: relative;
} }
.alert {
color: $color-alert;
}
} }

View File

@ -55,7 +55,7 @@ module.exports = Self => {
const refoundZoneId = refundAgencyMode.zones()[0].id; const refoundZoneId = refundAgencyMode.zones()[0].id;
if (salesIds) { if (salesIds.length) {
const salesFilter = { const salesFilter = {
where: {id: {inq: salesIds}}, where: {id: {inq: salesIds}},
include: { include: {
@ -91,16 +91,14 @@ module.exports = Self => {
await models.SaleComponent.create(components, myOptions); await models.SaleComponent.create(components, myOptions);
} }
} }
if (!refundTicket) { if (!refundTicket) {
const servicesFilter = { const servicesFilter = {
where: {id: {inq: servicesIds}} where: {id: {inq: servicesIds}}
}; };
const services = await models.TicketService.find(servicesFilter, myOptions); const services = await models.TicketService.find(servicesFilter, myOptions);
const ticketsIds = [...new Set(services.map(service => service.ticketFk))]; const firstTicketId = services[0].ticketFk;
const now = Date.vnNew(); const now = Date.vnNew();
const [firstTicketId] = ticketsIds;
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
refundTicket = await createTicketRefund(firstTicketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions); refundTicket = await createTicketRefund(firstTicketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions);
@ -114,8 +112,8 @@ module.exports = Self => {
for (const service of services) { for (const service of services) {
await models.TicketService.create({ await models.TicketService.create({
description: service.description, description: service.description,
quantity: service.quantity, quantity: - service.quantity,
price: - service.price, price: service.price,
taxClassFk: service.taxClassFk, taxClassFk: service.taxClassFk,
ticketFk: refundTicket.id, ticketFk: refundTicket.id,
ticketServiceTypeFk: service.ticketServiceTypeFk, ticketServiceTypeFk: service.ticketServiceTypeFk,

View File

@ -1,21 +1,9 @@
/* eslint max-len: ["error", { "code": 150 }]*/
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
describe('sale updateQuantity()', () => { describe('sale updateQuantity()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
const ctx = { const ctx = {
req: { req: {
accessToken: {userId: 9}, accessToken: {userId: 9},
@ -23,6 +11,18 @@ describe('sale updateQuantity()', () => {
__: () => {} __: () => {}
} }
}; };
function getActiveCtx(userId) {
return {
active: {
accessToken: {userId},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
}
};
}
it('should throw an error if the quantity is greater than it should be', async() => { it('should throw an error if the quantity is greater than it should be', async() => {
const ctx = { const ctx = {
@ -32,13 +32,16 @@ describe('sale updateQuantity()', () => {
__: () => {} __: () => {}
} }
}; };
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
spyOn(models.Item, 'getVisibleAvailable').and.returnValue((new Promise(resolve => resolve({available: 100}))));
const tx = await models.Sale.beginTransaction({}); const tx = await models.Sale.beginTransaction({});
let error; let error;
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await models.Sale.updateQuantity(ctx, 17, 99, options); await models.Sale.updateQuantity(ctx, 17, 31, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -50,7 +53,6 @@ describe('sale updateQuantity()', () => {
}); });
it('should add quantity if the quantity is greater than it should be and is role advanced', async() => { it('should add quantity if the quantity is greater than it should be and is role advanced', async() => {
const tx = await models.Sale.beginTransaction({});
const saleId = 17; const saleId = 17;
const buyerId = 35; const buyerId = 35;
const ctx = { const ctx = {
@ -60,6 +62,9 @@ describe('sale updateQuantity()', () => {
__: () => {} __: () => {}
} }
}; };
const tx = await models.Sale.beginTransaction({});
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(buyerId));
spyOn(models.Item, 'getVisibleAvailable').and.returnValue((new Promise(resolve => resolve({available: 100}))));
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
@ -87,6 +92,8 @@ describe('sale updateQuantity()', () => {
}); });
it('should update the quantity of a given sale current line', async() => { it('should update the quantity of a given sale current line', async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(9));
const tx = await models.Sale.beginTransaction({}); const tx = await models.Sale.beginTransaction({});
const saleId = 25; const saleId = 25;
const newQuantity = 4; const newQuantity = 4;
@ -119,6 +126,8 @@ describe('sale updateQuantity()', () => {
__: () => {} __: () => {}
} }
}; };
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const saleId = 17; const saleId = 17;
const newQuantity = -10; const newQuantity = -10;
@ -140,6 +149,8 @@ describe('sale updateQuantity()', () => {
}); });
it('should update a negative quantity when is a ticket refund', async() => { it('should update a negative quantity when is a ticket refund', async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(9));
const tx = await models.Sale.beginTransaction({}); const tx = await models.Sale.beginTransaction({});
const saleId = 13; const saleId = 13;
const newQuantity = -10; const newQuantity = -10;
@ -159,4 +170,70 @@ describe('sale updateQuantity()', () => {
throw e; throw e;
} }
}); });
it('should throw an error if the quantity is less than the minimum quantity of the item', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = minQuantity - 1;
let error;
try {
const options = {transaction: tx};
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toEqual(new Error('The amount cannot be less than the minimum'));
});
it('should change quantity if has minimum quantity and new quantity is equal than item available', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = minQuantity - 1;
try {
const options = {transaction: tx};
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Item, 'getVisibleAvailable').and.returnValue((new Promise(resolve => resolve({available: newQuantity}))));
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
}); });

View File

@ -1,4 +1,3 @@
let UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('updateQuantity', { Self.remoteMethodCtx('updateQuantity', {
@ -64,17 +63,6 @@ module.exports = Self => {
const sale = await models.Sale.findById(id, filter, myOptions); const sale = await models.Sale.findById(id, filter, myOptions);
const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*');
if (newQuantity > sale.quantity && !isRoleAdvanced)
throw new UserError('The new quantity should be smaller than the old one');
const ticketRefund = await models.TicketRefund.findOne({
where: {refundTicketFk: sale.ticketFk},
fields: ['id']}
, myOptions);
if (newQuantity < 0 && !ticketRefund)
throw new UserError('You can only add negative amounts in refund tickets');
const oldQuantity = sale.quantity; const oldQuantity = sale.quantity;
const result = await sale.updateAttributes({quantity: newQuantity}, myOptions); const result = await sale.updateAttributes({quantity: newQuantity}, myOptions);

View File

@ -63,17 +63,6 @@ module.exports = Self => {
} }
}, myOptions); }, myOptions);
const itemInfo = await models.Item.getVisibleAvailable(
itemId,
ticket.warehouseFk,
ticket.shipped,
myOptions
);
const isPackaging = item.family == 'EMB';
if (!isPackaging && itemInfo.available < quantity)
throw new UserError(`This item is not available`);
const newSale = await models.Sale.create({ const newSale = await models.Sale.create({
ticketFk: id, ticketFk: id,
itemFk: item.id, itemFk: item.id,

View File

@ -1,3 +1,6 @@
const UserError = require('vn-loopback/util/user-error');
const LoopBackContext = require('loopback-context');
module.exports = Self => { module.exports = Self => {
require('../methods/sale/getClaimableFromTicket')(Self); require('../methods/sale/getClaimableFromTicket')(Self);
require('../methods/sale/reserve')(Self); require('../methods/sale/reserve')(Self);
@ -13,4 +16,77 @@ module.exports = Self => {
Self.validatesPresenceOf('concept', { Self.validatesPresenceOf('concept', {
message: `Concept cannot be blank` message: `Concept cannot be blank`
}); });
Self.observe('before save', async ctx => {
const models = Self.app.models;
const changes = ctx.data || ctx.instance;
const instance = ctx.currentInstance;
const newQuantity = changes?.quantity;
if (newQuantity == null) return;
const loopBackContext = LoopBackContext.getCurrentContext();
ctx.req = loopBackContext.active;
if (await models.ACL.checkAccessAcl(ctx, 'Sale', 'canForceQuantity', 'WRITE')) return;
const ticketId = changes?.ticketFk || instance?.ticketFk;
const itemId = changes?.itemFk || instance?.itemFk;
const ticket = await models.Ticket.findById(
ticketId,
{
fields: ['id', 'clientFk', 'warehouseFk', 'shipped'],
include: {
relation: 'client',
scope: {
fields: ['id', 'clientTypeFk'],
include: {
relation: 'type',
scope: {
fields: ['code', 'description']
}
}
}
}
},
ctx.options);
if (ticket?.client()?.type()?.code === 'loses') return;
const isRefund = await models.TicketRefund.findOne({
fields: ['id'],
where: {refundTicketFk: ticketId}
}, ctx.options);
if (isRefund) return;
if (newQuantity < 0)
throw new UserError('You can only add negative amounts in refund tickets');
const item = await models.Item.findOne({
fields: ['family', 'minQuantity'],
where: {id: itemId},
}, ctx.options);
if (item.family == 'EMB') return;
const itemInfo = await models.Item.getVisibleAvailable(
itemId,
ticket.warehouseFk,
ticket.shipped,
ctx.options
);
const oldQuantity = instance?.quantity ?? null;
const quantityAdded = newQuantity - oldQuantity;
if (itemInfo.available < quantityAdded)
throw new UserError(`This item is not available`);
if (await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*')) return;
if (newQuantity < item.minQuantity && itemInfo.available != newQuantity)
throw new UserError('The amount cannot be less than the minimum');
if (!ctx.isNewInstance && newQuantity > oldQuantity)
throw new UserError('The new quantity should be smaller than the old one');
});
}; };

View File

@ -16,8 +16,8 @@ class Controller extends SearchPanel {
this.$http.get('ItemPackingTypes', {filter}).then(res => { this.$http.get('ItemPackingTypes', {filter}).then(res => {
for (let ipt of res.data) { for (let ipt of res.data) {
itemPackingTypes.push({ itemPackingTypes.push({
code: ipt.code, description: this.$t(ipt.description),
description: this.$t(ipt.description) code: ipt.code
}); });
} }
this.itemPackingTypes = itemPackingTypes; this.itemPackingTypes = itemPackingTypes;

View File

@ -163,26 +163,16 @@ export default class Controller extends Section {
return {'futureId': value}; return {'futureId': value};
case 'liters': case 'liters':
return {'liters': value}; return {'liters': value};
case 'lines':
return {'lines': value};
case 'futureLiters': case 'futureLiters':
return {'futureLiters': value}; return {'futureLiters': value};
case 'lines':
return {'lines': value};
case 'futureLines': case 'futureLines':
return {'futureLines': value}; return {'futureLines': value};
case 'ipt': case 'ipt':
return {or: return {'ipt': {like: `%${value}%`}};
[
{'ipt': {like: `%${value}%`}},
{'ipt': null}
]
};
case 'futureIpt': case 'futureIpt':
return {or: return {'futureIpt': {like: `%${value}%`}};
[
{'futureIpt': {like: `%${value}%`}},
{'futureIpt': null}
]
};
case 'totalWithVat': case 'totalWithVat':
return {'totalWithVat': value}; return {'totalWithVat': value};
case 'futureTotalWithVat': case 'futureTotalWithVat':

View File

@ -18,3 +18,4 @@ Multiple invoice: Factura múltiple
Make invoice...: Crear factura... Make invoice...: Crear factura...
Invoice selected tickets: Facturar tickets seleccionados Invoice selected tickets: Facturar tickets seleccionados
Are you sure to invoice tickets: ¿Seguro que quieres facturar {{ticketsAmount}} tickets? Are you sure to invoice tickets: ¿Seguro que quieres facturar {{ticketsAmount}} tickets?
Rounding: Redondeo

View File

@ -311,7 +311,7 @@
clear-disabled="true" clear-disabled="true"
suffix="%"> suffix="%">
</vn-input-number> </vn-input-number>
<vn-vertical ng-if="$ctrl.usesMana && $ctrl.currentWorkerMana != 0"> <vn-vertical ng-if="$ctrl.usesMana">
<vn-radio <vn-radio
label="Promotion mana" label="Promotion mana"
val="mana" val="mana"

View File

@ -97,14 +97,6 @@ class Controller extends Section {
}); });
}); });
this.getUsesMana(); this.getUsesMana();
this.getCurrentWorkerMana();
}
getCurrentWorkerMana() {
this.$http.get(`WorkerManas/getCurrentWorkerMana`)
.then(res => {
this.currentWorkerMana = res.data;
});
} }
getUsesMana() { getUsesMana() {

View File

@ -120,12 +120,10 @@ describe('Ticket', () => {
const expectedAmount = 250; const expectedAmount = 250;
$httpBackend.expect('GET', 'Tickets/1/getSalesPersonMana').respond(200, expectedAmount); $httpBackend.expect('GET', 'Tickets/1/getSalesPersonMana').respond(200, expectedAmount);
$httpBackend.expect('GET', 'Sales/usesMana').respond(200); $httpBackend.expect('GET', 'Sales/usesMana').respond(200);
$httpBackend.expect('GET', 'WorkerManas/getCurrentWorkerMana').respond(200, expectedAmount);
controller.getMana(); controller.getMana();
$httpBackend.flush(); $httpBackend.flush();
expect(controller.edit.mana).toEqual(expectedAmount); expect(controller.edit.mana).toEqual(expectedAmount);
expect(controller.currentWorkerMana).toEqual(expectedAmount);
}); });
}); });

View File

@ -29,7 +29,7 @@
disabled="watcher.dataChanged() || !$ctrl.checkeds.length" disabled="watcher.dataChanged() || !$ctrl.checkeds.length"
label="Pay" label="Pay"
ng-click="$ctrl.createRefund()" ng-click="$ctrl.createRefund()"
vn-acl="invoicing, claimManager, salesAssistant" vn-acl="invoicing, claimManager, salesAssistant, buyer"
vn-acl-action="remove"> vn-acl-action="remove">
</vn-button> </vn-button>
</vn-button-bar> </vn-button-bar>
@ -37,7 +37,9 @@
<vn-check <vn-check
tabindex="1" tabindex="1"
on-change="$ctrl.addChecked(service.id)" on-change="$ctrl.addChecked(service.id)"
disabled="!service.id"> disabled="!service.id"
vn-acl="invoicing, claimManager, salesAssistant, buyer"
vn-acl-action="remove">
</vn-check> </vn-check>
<vn-autocomplete vn-two vn-focus <vn-autocomplete vn-two vn-focus
data="ticketServiceTypes" data="ticketServiceTypes"
@ -68,7 +70,8 @@
vn-one vn-one
label="Price" label="Price"
ng-model="service.price" ng-model="service.price"
step="0.01"> step="0.01"
min="0">
</vn-input-number> </vn-input-number>
<vn-auto> <vn-auto>
<vn-icon-button <vn-icon-button

View File

@ -1,26 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('getCurrentWorkerMana', {
description: 'Returns the mana of the logged worker',
accessType: 'READ',
accepts: [],
returns: {
type: 'number',
root: true
},
http: {
path: `/getCurrentWorkerMana`,
verb: 'GET'
}
});
Self.getCurrentWorkerMana = async ctx => {
let userId = ctx.req.accessToken.userId;
let workerMana = await Self.app.models.WorkerMana.findOne({
where: {workerFk: userId},
fields: 'amount'
});
return workerMana ? workerMana.amount : 0;
};
};

View File

@ -1,15 +0,0 @@
const app = require('vn-loopback/server/server');
describe('workerMana getCurrentWorkerMana()', () => {
it('should get the mana of the logged worker', async() => {
let mana = await app.models.WorkerMana.getCurrentWorkerMana({req: {accessToken: {userId: 18}}});
expect(mana).toEqual(124);
});
it('should return 0 if the user doesnt uses mana', async() => {
let mana = await app.models.WorkerMana.getCurrentWorkerMana({req: {accessToken: {userId: 9}}});
expect(mana).toEqual(0);
});
});

View File

@ -66,46 +66,36 @@ module.exports = Self => {
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.timeControlCalculate'); stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.timeControlCalculate');
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.timeBusinessCalculate'); stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.timeBusinessCalculate');
const destroyAllWhere = {
timed: {between: [started, ended]},
isSendMail: true
};
const updateAllWhere = {
year: args.year,
week: args.week
};
const tmpUserSQL = `
CREATE OR REPLACE TEMPORARY TABLE tmp.user
SELECT id as userFk
FROM vn.worker`;
let tmpUser = new ParameterizedSQL(tmpUserSQL);
if (args.workerId) { if (args.workerId) {
await models.WorkerTimeControl.destroyAll({ destroyAllWhere.userFk = args.workerId;
userFk: args.workerId, updateAllWhere.workerFk = args.workerId;
timed: {between: [started, ended]}, tmpUser = new ParameterizedSQL(tmpUserSQL + ' WHERE id = ?', [args.workerId]);
isSendMail: true
}, myOptions);
const where = {
workerFk: args.workerId,
year: args.year,
week: args.week
};
await models.WorkerTimeControlMail.updateAll(where, {
updated: Date.vnNew(), state: 'SENDED'
}, myOptions);
stmt = new ParameterizedSQL('DROP TEMPORARY TABLE IF EXISTS tmp.`user`');
stmts.push(stmt);
stmt = new ParameterizedSQL('CREATE TEMPORARY TABLE tmp.`user` SELECT id userFk FROM account.user WHERE id = ?', [args.workerId]);
stmts.push(stmt);
} else {
await models.WorkerTimeControl.destroyAll({
timed: {between: [started, ended]},
isSendMail: true
}, myOptions);
const where = {
year: args.year,
week: args.week
};
await models.WorkerTimeControlMail.updateAll(where, {
updated: Date.vnNew(), state: 'SENDED'
}, myOptions);
stmt = new ParameterizedSQL('DROP TEMPORARY TABLE IF EXISTS tmp.`user`');
stmts.push(stmt);
stmt = new ParameterizedSQL('CREATE TEMPORARY TABLE IF NOT EXISTS tmp.`user` SELECT id as userFk FROM vn.worker w JOIN account.`user` u ON u.id = w.id WHERE id IS NOT NULL');
stmts.push(stmt);
} }
await models.WorkerTimeControl.destroyAll(destroyAllWhere, myOptions);
await models.WorkerTimeControlMail.updateAll(updateAllWhere, {
updated: Date.vnNew(),
state: 'SENDED'
}, myOptions);
stmts.push(tmpUser);
stmt = new ParameterizedSQL( stmt = new ParameterizedSQL(
`CALL vn.timeControl_calculate(?, ?) `CALL vn.timeControl_calculate(?, ?)
`, [started, ended]); `, [started, ended]);

View File

@ -46,7 +46,7 @@ module.exports = Self => {
SELECT DISTINCT w.id, w.code, u.name, u.nickname, u.active, b.departmentFk SELECT DISTINCT w.id, w.code, u.name, u.nickname, u.active, b.departmentFk
FROM worker w FROM worker w
JOIN account.user u ON u.id = w.id JOIN account.user u ON u.id = w.id
JOIN business b ON b.workerFk = w.id LEFT JOIN business b ON b.workerFk = w.id
) w`); ) w`);
stmt.merge(conn.makeSuffix(filter)); stmt.merge(conn.makeSuffix(filter));

View File

@ -1,3 +0,0 @@
module.exports = Self => {
require('../methods/worker-mana/getCurrentWorkerMana')(Self);
};

View File

@ -117,7 +117,7 @@
<td class="number">{{service.price | currency('EUR', $i18n.locale)}}</td> <td class="number">{{service.price | currency('EUR', $i18n.locale)}}</td>
<td class="centered" width="5%"></td> <td class="centered" width="5%"></td>
<td class="centered">{{service.taxDescription}}</td> <td class="centered">{{service.taxDescription}}</td>
<td class="number">{{service.price | currency('EUR', $i18n.locale)}}</td> <td class="number">{{service.total | currency('EUR', $i18n.locale)}}</td>
</tr> </tr>
</tbody> </tbody>
<tfoot> <tfoot>

View File

@ -2,7 +2,8 @@ SELECT
tc.code taxDescription, tc.code taxDescription,
ts.description, ts.description,
ts.quantity, ts.quantity,
ts.price ts.price,
ts.quantity * ts.price total
FROM ticketService ts FROM ticketService ts
JOIN taxClass tc ON tc.id = ts.taxClassFk JOIN taxClass tc ON tc.id = ts.taxClassFk
WHERE ts.ticketFk = ? WHERE ts.ticketFk = ?