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

This commit is contained in:
Javi Gallego 2023-09-14 10:27:03 +02:00
commit eff0975eb2
12 changed files with 135 additions and 71 deletions

View File

@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [2338.01] - 2023-09-21 ## [2338.01] - 2023-09-21
### Added ### Added
- (Ticket -> Servicios) Se pueden abonar servicios
### Changed ### Changed
- (Trabajadores -> Calendario) Icono de check arreglado cuando pulsas un tipo de dia - (Trabajadores -> Calendario) Icono de check arreglado cuando pulsas un tipo de dia

View File

@ -1,4 +1,4 @@
INSERT INTO `account`.`role` (`id`, `name`, `description`, `hasLogin`) INSERT INTO `account`.`role` (`name`, `description`, `hasLogin`)
VALUES ('claimViewer','Trabajadores que consulta las reclamaciones ',1); VALUES ('claimViewer','Trabajadores que consulta las reclamaciones ',1);
INSERT INTO `account`.`roleInherit` (`role`,`inheritsFrom`) INSERT INTO `account`.`roleInherit` (`role`,`inheritsFrom`)
@ -10,7 +10,7 @@ INSERT INTO `account`.`roleInherit` (`role`,`inheritsFrom`)
'buyer', 'buyer',
'deliveryBoss', 'deliveryBoss',
'handmadeBoss' 'handmadeBoss'
) );
DELETE FROM `salix`.`ACL` DELETE FROM `salix`.`ACL`
WHERE `model`= 'claim' WHERE `model`= 'claim'
@ -28,5 +28,4 @@ INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`princip
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`) INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`)
VALUES ('Claim','findById','READ','ALLOW','ROLE','claimViewer'); VALUES ('Claim','findById','READ','ALLOW','ROLE','claimViewer');
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`) INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`)
VALUES ('Claim','getSummary','READ','ALLOW','ROLE','claimViewer'); VALUES ('Claim','getSummary','READ','ALLOW','ROLE','claimViewer');

View File

@ -671,8 +671,8 @@ export default {
firstAddServiceTypeButton: 'vn-ticket-service vn-icon-button[vn-tooltip="New service type"]', firstAddServiceTypeButton: 'vn-ticket-service vn-icon-button[vn-tooltip="New service type"]',
firstServiceType: 'vn-ticket-service vn-autocomplete[ng-model="service.ticketServiceTypeFk"]', firstServiceType: 'vn-ticket-service vn-autocomplete[ng-model="service.ticketServiceTypeFk"]',
firstQuantity: 'vn-ticket-service vn-input-number[ng-model="service.quantity"]', firstQuantity: 'vn-ticket-service vn-input-number[ng-model="service.quantity"]',
firstPrice: 'vn-ticket-service vn-horizontal:nth-child(1) vn-input-number[ng-model="service.price"]', firstPrice: 'vn-ticket-service vn-horizontal:nth-child(2) vn-input-number[ng-model="service.price"]',
fistDeleteServiceButton: 'vn-ticket-service form vn-horizontal:nth-child(1) vn-icon-button[icon="delete"]', fistDeleteServiceButton: 'vn-ticket-service form vn-horizontal:nth-child(2) vn-icon-button[icon="delete"]',
newServiceTypeName: '.vn-dialog.shown vn-textfield[ng-model="newServiceType.name"]', newServiceTypeName: '.vn-dialog.shown vn-textfield[ng-model="newServiceType.name"]',
serviceLine: 'vn-ticket-service > form > vn-card > vn-one:nth-child(2) > vn-horizontal', serviceLine: 'vn-ticket-service > form > vn-card > vn-one:nth-child(2) > vn-horizontal',
saveServiceButton: 'button[type=submit]', saveServiceButton: 'button[type=submit]',

View File

@ -5,6 +5,10 @@ export const validators = {
if (validator.isEmpty(value ? String(value) : '')) if (validator.isEmpty(value ? String(value) : ''))
throw new Error(_($translate, `Value can't be empty`)); throw new Error(_($translate, `Value can't be empty`));
}, },
negative: $translate => {
if (validator < 0)
throw new Error(_($translate, `Negative numbers are not allowed. Please enter a valid number.`));
},
absence: ($translate, value) => { absence: ($translate, value) => {
if (!validator.isEmpty(value)) if (!validator.isEmpty(value))
throw new Error(_($translate, `Value should be empty`)); throw new Error(_($translate, `Value should be empty`));
@ -104,9 +108,8 @@ export function checkNull($translate, value, conf) {
export function _($translate, text, params = []) { export function _($translate, text, params = []) {
text = $translate.instant(text); text = $translate.instant(text);
for (let i = 0; i < params.length; i++) { for (let i = 0; i < params.length; i++)
text = text.replace('%s', params[i]); text = text.replace('%s', params[i]);
}
return text; return text;
} }

View File

@ -16,6 +16,7 @@ Value can't be empty: El valor no puede estar vacío
Value should be empty: El valor debe estar vacío Value should be empty: El valor debe estar vacío
Value should be integer: El valor debe ser entero Value should be integer: El valor debe ser entero
Value should be a number: El valor debe ser numérico Value should be a number: El valor debe ser numérico
Negative numbers are not allowed. Please enter a valid number: No se permiten números negativos. Por favor, ingrese un número válido
Invalid value: Valor incorrecto Invalid value: Valor incorrecto
Value can't be blank: El valor no puede estar en blanco Value can't be blank: El valor no puede estar en blanco
Value can't be null: El valor no puede ser nulo Value can't be null: El valor no puede ser nulo

View File

@ -307,7 +307,7 @@
"Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}", "Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}",
"You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado", "You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado",
"This ticket cannot be left empty.": "Este ticket no se puede dejar vacío. %s", "This ticket cannot be left empty.": "Este ticket no se puede dejar vacío. %s",
"The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias", "The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias",
"You cannot assign/remove an alias that you are not assigned to": "No puede asignar/eliminar un alias que no tenga asignado", "You cannot assign/remove an alias that you are not assigned to": "No puede asignar/eliminar un alias que no tenga asignado",
"This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado", "This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado",
"You don't have enough privileges.": "No tienes suficientes permisos.", "You don't have enough privileges.": "No tienes suficientes permisos.",

View File

@ -5,8 +5,7 @@ module.exports = Self => {
accepts: [ accepts: [
{ {
arg: 'salesIds', arg: 'salesIds',
type: ['number'], type: ['number']
required: true
}, },
{ {
arg: 'servicesIds', arg: 'servicesIds',
@ -41,6 +40,7 @@ module.exports = Self => {
myOptions.transaction = tx; myOptions.transaction = tx;
} }
let refundTicket = null;
try { try {
const refundAgencyMode = await models.AgencyMode.findOne({ const refundAgencyMode = await models.AgencyMode.findOne({
include: { include: {
@ -55,42 +55,55 @@ module.exports = Self => {
const refoundZoneId = refundAgencyMode.zones()[0].id; const refoundZoneId = refundAgencyMode.zones()[0].id;
const salesFilter = { if (salesIds) {
where: {id: {inq: salesIds}}, const salesFilter = {
include: { where: {id: {inq: salesIds}},
relation: 'components', include: {
scope: { relation: 'components',
fields: ['saleFk', 'componentFk', 'value'] scope: {
fields: ['saleFk', 'componentFk', 'value']
}
} }
};
const sales = await models.Sale.find(salesFilter, myOptions);
const ticketsIds = [...new Set(sales.map(sale => sale.ticketFk))];
const now = Date.vnNew();
const [firstTicketId] = ticketsIds;
// eslint-disable-next-line max-len
refundTicket = await createTicketRefund(firstTicketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions);
for (const sale of sales) {
const createdSale = await models.Sale.create({
ticketFk: refundTicket.id,
itemFk: sale.itemFk,
quantity: - sale.quantity,
concept: sale.concept,
price: sale.price,
discount: sale.discount,
}, myOptions);
const components = sale.components();
for (const component of components)
component.saleFk = createdSale.id;
await models.SaleComponent.create(components, myOptions);
} }
};
const sales = await models.Sale.find(salesFilter, myOptions);
const ticketsIds = [...new Set(sales.map(sale => sale.ticketFk))];
let [firstTicketId] = ticketsIds;
if (!firstTicketId) {
[ticketServices] = await models.TicketService.find({where: {id: {inq: servicesIds}}}, myOptions);
firstTicketId = ticketServices.ticketFk;
} }
const now = Date.vnNew();
const refundTicket = await createTicketRefund(firstTicketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions); if (!refundTicket) {
const servicesFilter = {
where: {id: {inq: servicesIds}}
};
const services = await models.TicketService.find(servicesFilter, myOptions);
const ticketsIds = [...new Set(services.map(service => service.ticketFk))];
for (const sale of sales) { const now = Date.vnNew();
const createdSale = await models.Sale.create({ const [firstTicketId] = ticketsIds;
ticketFk: refundTicket.id,
itemFk: sale.itemFk,
quantity: - sale.quantity,
concept: sale.concept,
price: sale.price,
discount: sale.discount,
}, myOptions);
const components = sale.components(); // eslint-disable-next-line max-len
for (const component of components) refundTicket = await createTicketRefund(firstTicketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions);
component.saleFk = createdSale.id;
await models.SaleComponent.create(components, myOptions);
} }
if (servicesIds && servicesIds.length > 0) { if (servicesIds && servicesIds.length > 0) {
@ -101,8 +114,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,4 +1,4 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="TicketServices" url="TicketServices"
link="{ticketFk: $ctrl.$params.id}" link="{ticketFk: $ctrl.$params.id}"
@ -24,7 +24,21 @@
</vn-watcher> </vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md"> <form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg"> <vn-card class="vn-pa-lg">
<vn-horizontal ng-repeat="service in $ctrl.services track by $index"> <vn-button-bar>
<vn-button
disabled="watcher.dataChanged() || !$ctrl.checkeds.length"
label="Pay"
ng-click="$ctrl.createRefund()"
vn-acl="invoicing, claimManager, salesAssistant"
vn-acl-action="remove">
</vn-button>
</vn-button-bar>
<vn-horizontal ng-repeat="service in $ctrl.services">
<vn-check
tabindex="1"
on-change="$ctrl.addChecked(service.id)"
disabled="!service.id">
</vn-check>
<vn-autocomplete vn-two vn-focus <vn-autocomplete vn-two vn-focus
data="ticketServiceTypes" data="ticketServiceTypes"
label="Description" label="Description"
@ -43,10 +57,12 @@
</vn-autocomplete> </vn-autocomplete>
<vn-input-number <vn-input-number
vn-one vn-one
step="1" step="1"
label="Quantity" label="Quantity"
info="To create services with negative amounts mark the service on the source ticket and press the pay button."
ng-model="service.quantity" ng-model="service.quantity"
rule="TicketService"> rule="TicketService"
min="0">
</vn-input-number> </vn-input-number>
<vn-input-number <vn-input-number
vn-one vn-one
@ -60,7 +76,7 @@
vn-tooltip="Remove service" vn-tooltip="Remove service"
icon="delete" icon="delete"
ng-click="model.remove($index)"> ng-click="model.remove($index)">
</vn-icon-button> </vn-icon-button>
</vn-auto> </vn-auto>
</vn-horizontal> </vn-horizontal>
<vn-icon-button <vn-icon-button
@ -76,18 +92,11 @@
disabled="!watcher.dataChanged()" disabled="!watcher.dataChanged()"
label="Save"> label="Save">
</vn-submit> </vn-submit>
<!-- # #2680 Undo changes button bugs -->
<!-- <vn-button
class="cancel"
label="Undo changes"
disabled="!watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button> -->
</vn-button-bar> </vn-button-bar>
</form> </form>
<!-- Create service type dialog --> <!-- Create service type dialog -->
<vn-dialog class="edit" <vn-dialog class="edit"
vn-id="newServiceTypeDialog" vn-id="newServiceTypeDialog"
on-accept="$ctrl.onNewServiceTypeAccept($data)" on-accept="$ctrl.onNewServiceTypeAccept($data)"
on-close="newServiceType = null" on-close="newServiceType = null"
@ -106,4 +115,4 @@
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/> <input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Create</button> <button response="accept" translate>Create</button>
</tpl-buttons> </tpl-buttons>
</vn-dialog> </vn-dialog>

View File

@ -6,6 +6,8 @@ class Controller extends Section {
$onInit() { $onInit() {
this.services = []; this.services = [];
this.getDefaultTaxClass(); this.getDefaultTaxClass();
this.isDataSaved = false;
this.checkeds = [];
} }
getDefaultTaxClass() { getDefaultTaxClass() {
@ -49,6 +51,26 @@ class Controller extends Section {
.then(() => this.$.model.refresh()) .then(() => this.$.model.refresh())
.then(() => this.$.watcher.notifySaved()); .then(() => this.$.watcher.notifySaved());
} }
createRefund() {
if (!this.checkeds.length) return;
const params = {servicesIds: this.checkeds, withWarehouse: false};
const query = 'Sales/refund';
this.$http.post(query, params).then(res => {
const refundTicket = res.data;
this.vnApp.showSuccess(this.$t('The following refund ticket have been created', {
ticketId: refundTicket.id
}));
this.$state.go('ticket.card.sale', {id: refundTicket.id});
});
}
addChecked(id) {
if (this.checkeds.includes(id))
return this.checkeds = this.checkeds.filter(check => check != id);
this.checkeds.push(id);
}
} }
ngModule.vnComponent('vnTicketService', { ngModule.vnComponent('vnTicketService', {

View File

@ -19,12 +19,11 @@ describe('Ticket component vnTicketService', () => {
describe('getDefaultTaxClass', () => { describe('getDefaultTaxClass', () => {
it('should set the default tax class in the controller', () => { it('should set the default tax class in the controller', () => {
$httpBackend.whenRoute('GET', `TaxClasses/findOne`) $httpBackend.whenRoute('GET', `TaxClasses/findOne`).respond({
.respond({ id: 4000,
id: 4000, name: 'Whatever',
name: 'Whatever', code: 'GG',
code: 'GG' });
});
controller.getDefaultTaxClass(); controller.getDefaultTaxClass();
$httpBackend.flush(); $httpBackend.flush();
@ -49,15 +48,15 @@ describe('Ticket component vnTicketService', () => {
it('should set the description of the selected service upon service type creation', () => { it('should set the description of the selected service upon service type creation', () => {
const service = { const service = {
id: 1, id: 1,
quantity: 10 quantity: 10,
}; };
$scope.newServiceType = { $scope.newServiceType = {
name: 'Totally new stuff' name: 'Totally new stuff',
}; };
$httpBackend.when('POST', 'TicketServiceTypes').respond({ $httpBackend.when('POST', 'TicketServiceTypes').respond({
id: 4001, id: 4001,
name: 'Totally new stuff' name: 'Totally new stuff',
}); });
controller.onNewServiceTypeAccept(service); controller.onNewServiceTypeAccept(service);
$httpBackend.flush(); $httpBackend.flush();
@ -65,4 +64,20 @@ describe('Ticket component vnTicketService', () => {
expect(service.ticketServiceTypeFk).toEqual(4001); expect(service.ticketServiceTypeFk).toEqual(4001);
}); });
}); });
describe('addChecked', () => {
it('should add an item to the checkeds array', () => {
controller.checkeds = [];
controller.addChecked(1);
expect(controller.checkeds).toEqual([1]);
});
it('should remove an item if it is already in the checkeds array', () => {
controller.checkeds = [1, 2, 3];
controller.addChecked(2);
expect(controller.checkeds).toEqual([1, 3]);
});
});
}); });

View File

@ -2,4 +2,6 @@ Service: Servicios
Tax class: Tipo IVA Tax class: Tipo IVA
Add service: Añadir servicio Add service: Añadir servicio
Remove service: Quitar servicio Remove service: Quitar servicio
New service type: Nuevo tipo de servicio New service type: Nuevo tipo de servicio
Pay: Abonar
To create services with negative amounts mark the service on the source ticket and press the pay button.: Para crear sevicios con cantidades negativas marcar servicio en el ticket origen y apretar el boton abonar.

View File

@ -8,8 +8,8 @@ description:
<a href='https://www.youtube.com/watch?v=qhb0kgQF3o8' title='Youtube' target='_blank' <a href='https://www.youtube.com/watch?v=qhb0kgQF3o8' title='Youtube' target='_blank'
style='color:#8dba25'>https://www.youtube.com/watch?v=qhb0kgQF3o8</a>. También style='color:#8dba25'>https://www.youtube.com/watch?v=qhb0kgQF3o8</a>. También
necesitarás el GoLabel, el programa para imprimir las cintas. necesitarás el GoLabel, el programa para imprimir las cintas.
downloadFrom: Puedes descargarlo desde este enlace <a href='https://godex.s3-accelerate.amazonaws.com/_6f5glRrVhQAEBGhdUsqJA.file?v01' downloadFrom: Puedes descargarlo desde este enlace <a href='https://cdn.verdnatura.es/public/GoLabel.zip'
title='Descargar GoLabel' target='_blank' style='color:#8dba25'>https://godex.s3-accelerate.amazonaws.com/_6f5glRrVhQAEBGhdUsqJA.file?v01</a> title='Descargar GoLabel' target='_blank' style='color:#8dba25'>https://cdn.verdnatura.es/public/GoLabel.zip</a>
downloadDriver: En este enlace puedes descargar el driver de la impresora <a href='https://es.seagullscientific.com/support/downloads/drivers/godex/download/' downloadDriver: En este enlace puedes descargar el driver de la impresora <a href='https://es.seagullscientific.com/support/downloads/drivers/godex/download/'
title='Descargar driver' target='_blank' style='color:#8dba25'>https://es.seagullscientific.com/support/downloads/drivers/godex/download/</a> title='Descargar driver' target='_blank' style='color:#8dba25'>https://es.seagullscientific.com/support/downloads/drivers/godex/download/</a>
sections: sections:
@ -40,4 +40,4 @@ help: Cualquier duda que te surja, no dudes en consultarla, <strong>¡estamos pa
atenderte!</strong> atenderte!</strong>
salesPersonName: Soy tu comercial y mi nombre es salesPersonName: Soy tu comercial y mi nombre es
salesPersonPhone: Teléfono y whatsapp salesPersonPhone: Teléfono y whatsapp
salesPersonEmail: Dirección de e-mail salesPersonEmail: Dirección de e-mail