Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5525-ibanSEPA-CORE
This commit is contained in:
commit
4b40c8b067
|
@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
## [2338.01] - 2023-09-21
|
||||
|
||||
### Added
|
||||
|
||||
- (Ticket -> Servicios) Se pueden abonar servicios
|
||||
### Changed
|
||||
- (Trabajadores -> Calendario) Icono de check arreglado cuando pulsas un tipo de dia
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE `vn`.`province` ADD CONSTRAINT `countryName_UN` UNIQUE KEY (`countryFk`,`name`);
|
|
@ -671,8 +671,8 @@ export default {
|
|||
firstAddServiceTypeButton: 'vn-ticket-service vn-icon-button[vn-tooltip="New service type"]',
|
||||
firstServiceType: 'vn-ticket-service vn-autocomplete[ng-model="service.ticketServiceTypeFk"]',
|
||||
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"]',
|
||||
fistDeleteServiceButton: 'vn-ticket-service form vn-horizontal:nth-child(1) vn-icon-button[icon="delete"]',
|
||||
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(2) vn-icon-button[icon="delete"]',
|
||||
newServiceTypeName: '.vn-dialog.shown vn-textfield[ng-model="newServiceType.name"]',
|
||||
serviceLine: 'vn-ticket-service > form > vn-card > vn-one:nth-child(2) > vn-horizontal',
|
||||
saveServiceButton: 'button[type=submit]',
|
||||
|
|
|
@ -82,32 +82,24 @@ describe('InvoiceIn basic data path', () => {
|
|||
await page.waitToClick(selectors.invoiceInBasicData.confirm);
|
||||
let message = await page.waitForSnackbar();
|
||||
|
||||
expect(message.text).toContain('The company can\'t be empty');
|
||||
|
||||
await page.clearInput(selectors.invoiceInBasicData.companyId);
|
||||
await page.autocompleteSearch(selectors.invoiceInBasicData.companyId, 'VNL');
|
||||
|
||||
await page.waitToClick(selectors.invoiceInBasicData.confirm);
|
||||
message = await page.waitForSnackbar();
|
||||
|
||||
expect(message.text).toContain('The warehouse can\'t be empty');
|
||||
|
||||
await page.clearInput(selectors.invoiceInBasicData.warehouseId);
|
||||
await page.autocompleteSearch(selectors.invoiceInBasicData.warehouseId, 'Warehouse One');
|
||||
|
||||
await page.waitToClick(selectors.invoiceInBasicData.confirm);
|
||||
message = await page.waitForSnackbar();
|
||||
|
||||
expect(message.text).toContain('The DMS Type can\'t be empty');
|
||||
|
||||
await page.clearInput(selectors.invoiceInBasicData.dmsTypeId);
|
||||
await page.autocompleteSearch(selectors.invoiceInBasicData.dmsTypeId, 'Ticket');
|
||||
|
||||
await page.waitToClick(selectors.invoiceInBasicData.confirm);
|
||||
message = await page.waitForSnackbar();
|
||||
|
||||
expect(message.text).toContain('The description can\'t be empty');
|
||||
|
||||
await page.waitToClick(selectors.invoiceInBasicData.description);
|
||||
await page.write(selectors.invoiceInBasicData.description, 'Dms without edition.');
|
||||
|
||||
|
|
|
@ -5,6 +5,10 @@ export const validators = {
|
|||
if (validator.isEmpty(value ? String(value) : ''))
|
||||
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) => {
|
||||
if (!validator.isEmpty(value))
|
||||
throw new Error(_($translate, `Value should be empty`));
|
||||
|
@ -104,9 +108,8 @@ export function checkNull($translate, value, conf) {
|
|||
export function _($translate, text, params = []) {
|
||||
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]);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
|
|
@ -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 integer: El valor debe ser entero
|
||||
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
|
||||
Value can't be blank: El valor no puede estar en blanco
|
||||
Value can't be null: El valor no puede ser nulo
|
||||
|
|
|
@ -307,7 +307,7 @@
|
|||
"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",
|
||||
"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",
|
||||
"This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado",
|
||||
"You don't have enough privileges.": "No tienes suficientes permisos.",
|
||||
|
|
|
@ -13,7 +13,7 @@ module.exports = Self => {
|
|||
const bankEntity = await Self.app.models.BankEntity.findById(this.bankEntityFk);
|
||||
const filter = {
|
||||
fields: ['code'],
|
||||
where: {id: bankEntity.countryFk}
|
||||
where: {id: bankEntity?.countryFk}
|
||||
};
|
||||
|
||||
const country = await Self.app.models.Country.findOne(filter);
|
||||
|
|
|
@ -5,8 +5,7 @@ module.exports = Self => {
|
|||
accepts: [
|
||||
{
|
||||
arg: 'salesIds',
|
||||
type: ['number'],
|
||||
required: true
|
||||
type: ['number']
|
||||
},
|
||||
{
|
||||
arg: 'servicesIds',
|
||||
|
@ -41,6 +40,7 @@ module.exports = Self => {
|
|||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
let refundTicket = null;
|
||||
try {
|
||||
const refundAgencyMode = await models.AgencyMode.findOne({
|
||||
include: {
|
||||
|
@ -55,42 +55,55 @@ module.exports = Self => {
|
|||
|
||||
const refoundZoneId = refundAgencyMode.zones()[0].id;
|
||||
|
||||
const salesFilter = {
|
||||
where: {id: {inq: salesIds}},
|
||||
include: {
|
||||
relation: 'components',
|
||||
scope: {
|
||||
fields: ['saleFk', 'componentFk', 'value']
|
||||
if (salesIds) {
|
||||
const salesFilter = {
|
||||
where: {id: {inq: salesIds}},
|
||||
include: {
|
||||
relation: 'components',
|
||||
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 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 now = Date.vnNew();
|
||||
const [firstTicketId] = ticketsIds;
|
||||
|
||||
const components = sale.components();
|
||||
for (const component of components)
|
||||
component.saleFk = createdSale.id;
|
||||
|
||||
await models.SaleComponent.create(components, myOptions);
|
||||
// eslint-disable-next-line max-len
|
||||
refundTicket = await createTicketRefund(firstTicketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions);
|
||||
}
|
||||
|
||||
if (servicesIds && servicesIds.length > 0) {
|
||||
|
@ -101,8 +114,8 @@ module.exports = Self => {
|
|||
for (const service of services) {
|
||||
await models.TicketService.create({
|
||||
description: service.description,
|
||||
quantity: - service.quantity,
|
||||
price: service.price,
|
||||
quantity: service.quantity,
|
||||
price: - service.price,
|
||||
taxClassFk: service.taxClassFk,
|
||||
ticketFk: refundTicket.id,
|
||||
ticketServiceTypeFk: service.ticketServiceTypeFk,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<vn-crud-model
|
||||
<vn-crud-model
|
||||
vn-id="model"
|
||||
url="TicketServices"
|
||||
link="{ticketFk: $ctrl.$params.id}"
|
||||
|
@ -24,7 +24,21 @@
|
|||
</vn-watcher>
|
||||
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
|
||||
<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
|
||||
data="ticketServiceTypes"
|
||||
label="Description"
|
||||
|
@ -43,10 +57,12 @@
|
|||
</vn-autocomplete>
|
||||
<vn-input-number
|
||||
vn-one
|
||||
step="1"
|
||||
step="1"
|
||||
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"
|
||||
rule="TicketService">
|
||||
rule="TicketService"
|
||||
min="0">
|
||||
</vn-input-number>
|
||||
<vn-input-number
|
||||
vn-one
|
||||
|
@ -60,7 +76,7 @@
|
|||
vn-tooltip="Remove service"
|
||||
icon="delete"
|
||||
ng-click="model.remove($index)">
|
||||
</vn-icon-button>
|
||||
</vn-icon-button>
|
||||
</vn-auto>
|
||||
</vn-horizontal>
|
||||
<vn-icon-button
|
||||
|
@ -76,18 +92,11 @@
|
|||
disabled="!watcher.dataChanged()"
|
||||
label="Save">
|
||||
</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>
|
||||
</form>
|
||||
|
||||
<!-- Create service type dialog -->
|
||||
<vn-dialog class="edit"
|
||||
<vn-dialog class="edit"
|
||||
vn-id="newServiceTypeDialog"
|
||||
on-accept="$ctrl.onNewServiceTypeAccept($data)"
|
||||
on-close="newServiceType = null"
|
||||
|
@ -106,4 +115,4 @@
|
|||
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
||||
<button response="accept" translate>Create</button>
|
||||
</tpl-buttons>
|
||||
</vn-dialog>
|
||||
</vn-dialog>
|
||||
|
|
|
@ -6,6 +6,8 @@ class Controller extends Section {
|
|||
$onInit() {
|
||||
this.services = [];
|
||||
this.getDefaultTaxClass();
|
||||
this.isDataSaved = false;
|
||||
this.checkeds = [];
|
||||
}
|
||||
|
||||
getDefaultTaxClass() {
|
||||
|
@ -49,6 +51,26 @@ class Controller extends Section {
|
|||
.then(() => this.$.model.refresh())
|
||||
.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', {
|
||||
|
|
|
@ -19,12 +19,11 @@ describe('Ticket component vnTicketService', () => {
|
|||
|
||||
describe('getDefaultTaxClass', () => {
|
||||
it('should set the default tax class in the controller', () => {
|
||||
$httpBackend.whenRoute('GET', `TaxClasses/findOne`)
|
||||
.respond({
|
||||
id: 4000,
|
||||
name: 'Whatever',
|
||||
code: 'GG'
|
||||
});
|
||||
$httpBackend.whenRoute('GET', `TaxClasses/findOne`).respond({
|
||||
id: 4000,
|
||||
name: 'Whatever',
|
||||
code: 'GG',
|
||||
});
|
||||
controller.getDefaultTaxClass();
|
||||
$httpBackend.flush();
|
||||
|
||||
|
@ -49,15 +48,15 @@ describe('Ticket component vnTicketService', () => {
|
|||
it('should set the description of the selected service upon service type creation', () => {
|
||||
const service = {
|
||||
id: 1,
|
||||
quantity: 10
|
||||
quantity: 10,
|
||||
};
|
||||
$scope.newServiceType = {
|
||||
name: 'Totally new stuff'
|
||||
name: 'Totally new stuff',
|
||||
};
|
||||
|
||||
$httpBackend.when('POST', 'TicketServiceTypes').respond({
|
||||
id: 4001,
|
||||
name: 'Totally new stuff'
|
||||
name: 'Totally new stuff',
|
||||
});
|
||||
controller.onNewServiceTypeAccept(service);
|
||||
$httpBackend.flush();
|
||||
|
@ -65,4 +64,20 @@ describe('Ticket component vnTicketService', () => {
|
|||
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]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,4 +2,6 @@ Service: Servicios
|
|||
Tax class: Tipo IVA
|
||||
Add service: Añadir 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.
|
Loading…
Reference in New Issue