diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1dbc270b9..0573a6790 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,10 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [2340.01] - 2023-10-05
+
+### Added
+### Changed
+
+### Fixed
+
## [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
diff --git a/back/model-config.json b/back/model-config.json
index b88956dee..ebc0e321b 100644
--- a/back/model-config.json
+++ b/back/model-config.json
@@ -15,6 +15,9 @@
},
"Bank": {
"dataSource": "vn"
+ },
+ "Buyer": {
+ "dataSource": "vn"
},
"Campaign": {
"dataSource": "vn"
diff --git a/back/models/buyer.json b/back/models/buyer.json
new file mode 100644
index 000000000..a17d3b538
--- /dev/null
+++ b/back/models/buyer.json
@@ -0,0 +1,28 @@
+{
+ "name": "Buyer",
+ "base": "VnModel",
+ "options": {
+ "mysql": {
+ "table": "buyer"
+ }
+ },
+ "properties": {
+ "userFk": {
+ "type": "number",
+ "required": true,
+ "id": true
+ },
+ "nickname": {
+ "type": "string",
+ "required": true
+ }
+ },
+ "acls": [
+ {
+ "accessType": "READ",
+ "principalType": "ROLE",
+ "principalId": "employee",
+ "permission": "ALLOW"
+ }
+ ]
+}
diff --git a/db/changes/233601/00-createClaimReader.sql b/db/changes/233601/00-createClaimReader.sql
index 666bf232e..e913c0ed9 100644
--- a/db/changes/233601/00-createClaimReader.sql
+++ b/db/changes/233601/00-createClaimReader.sql
@@ -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);
INSERT INTO `account`.`roleInherit` (`role`,`inheritsFrom`)
@@ -10,7 +10,7 @@ INSERT INTO `account`.`roleInherit` (`role`,`inheritsFrom`)
'buyer',
'deliveryBoss',
'handmadeBoss'
- )
+ );
DELETE FROM `salix`.`ACL`
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`)
VALUES ('Claim','findById','READ','ALLOW','ROLE','claimViewer');
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`)
- VALUES ('Claim','getSummary','READ','ALLOW','ROLE','claimViewer');
-
+ VALUES ('Claim','getSummary','READ','ALLOW','ROLE','claimViewer');
\ No newline at end of file
diff --git a/db/changes/233801/00-firstScript.sql b/db/changes/233801/00-firstScript.sql
new file mode 100644
index 000000000..47b96b3bc
--- /dev/null
+++ b/db/changes/233801/00-firstScript.sql
@@ -0,0 +1 @@
+ALTER TABLE `vn`.`province` ADD CONSTRAINT `countryName_UN` UNIQUE KEY (`countryFk`,`name`);
diff --git a/db/changes/233401/01-deviceLog_acl.sql b/db/changes/233801/01-deviceLog_acl.sql
similarity index 100%
rename from db/changes/233401/01-deviceLog_acl.sql
rename to db/changes/233801/01-deviceLog_acl.sql
diff --git a/db/changes/234001/.gitkeep b/db/changes/234001/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js
index fe3633723..1b18eb866 100644
--- a/e2e/helpers/selectors.js
+++ b/e2e/helpers/selectors.js
@@ -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]',
diff --git a/front/core/lib/validator.js b/front/core/lib/validator.js
index b0b8f7389..51134f30a 100644
--- a/front/core/lib/validator.js
+++ b/front/core/lib/validator.js
@@ -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;
}
diff --git a/front/core/locale/es.yml b/front/core/locale/es.yml
index e9c2311b4..96c34d98c 100644
--- a/front/core/locale/es.yml
+++ b/front/core/locale/es.yml
@@ -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
diff --git a/loopback/locale/es.json b/loopback/locale/es.json
index ccd63e0ff..73a3920d2 100644
--- a/loopback/locale/es.json
+++ b/loopback/locale/es.json
@@ -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.",
diff --git a/modules/client/front/summary/index.html b/modules/client/front/summary/index.html
index 15a55ec8c..c622913bb 100644
--- a/modules/client/front/summary/index.html
+++ b/modules/client/front/summary/index.html
@@ -255,7 +255,7 @@
value="{{$ctrl.summary.averageInvoiced.invoiced | currency: 'EUR':2}}">
+ value="{{$ctrl.claimingRate($ctrl.summary.claimsRatio.claimingRate / 100) | percentage}}">
diff --git a/modules/supplier/back/models/supplier-account.js b/modules/supplier/back/models/supplier-account.js
index 691e72580..7c68e2c98 100644
--- a/modules/supplier/back/models/supplier-account.js
+++ b/modules/supplier/back/models/supplier-account.js
@@ -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);
diff --git a/modules/ticket/back/methods/sale/refund.js b/modules/ticket/back/methods/sale/refund.js
index 12f240ae2..3c41aab1e 100644
--- a/modules/ticket/back/methods/sale/refund.js
+++ b/modules/ticket/back/methods/sale/refund.js
@@ -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,
diff --git a/modules/ticket/front/services/index.html b/modules/ticket/front/services/index.html
index bb5505ce6..bc288a8a2 100644
--- a/modules/ticket/front/services/index.html
+++ b/modules/ticket/front/services/index.html
@@ -1,4 +1,4 @@
-
-
-
\ No newline at end of file
+
diff --git a/modules/ticket/front/services/index.js b/modules/ticket/front/services/index.js
index 4866fd8bb..d8c209ea4 100644
--- a/modules/ticket/front/services/index.js
+++ b/modules/ticket/front/services/index.js
@@ -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', {
diff --git a/modules/ticket/front/services/index.spec.js b/modules/ticket/front/services/index.spec.js
index affe8a6e7..9293d4c65 100644
--- a/modules/ticket/front/services/index.spec.js
+++ b/modules/ticket/front/services/index.spec.js
@@ -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]);
+ });
+ });
});
diff --git a/modules/ticket/front/services/locale/es.yml b/modules/ticket/front/services/locale/es.yml
index 08788fc09..08e6c968c 100644
--- a/modules/ticket/front/services/locale/es.yml
+++ b/modules/ticket/front/services/locale/es.yml
@@ -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
\ No newline at end of file
+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.
\ No newline at end of file
diff --git a/package.json b/package.json
index 44a651af4..d250071a6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "salix-back",
- "version": "23.38.01",
+ "version": "23.40.01",
"author": "Verdnatura Levante SL",
"description": "Salix backend",
"license": "GPL-3.0",
diff --git a/print/templates/email/printer-setup/locale/es.yml b/print/templates/email/printer-setup/locale/es.yml
index b96d6d5f4..39e83b1a8 100644
--- a/print/templates/email/printer-setup/locale/es.yml
+++ b/print/templates/email/printer-setup/locale/es.yml
@@ -8,8 +8,8 @@ description:
https://www.youtube.com/watch?v=qhb0kgQF3o8. También
necesitarás el GoLabel, el programa para imprimir las cintas.
- downloadFrom: Puedes descargarlo desde este enlace https://godex.s3-accelerate.amazonaws.com/_6f5glRrVhQAEBGhdUsqJA.file?v01
+ downloadFrom: Puedes descargarlo desde este enlace https://cdn.verdnatura.es/public/GoLabel.zip
downloadDriver: En este enlace puedes descargar el driver de la impresora https://es.seagullscientific.com/support/downloads/drivers/godex/download/
sections:
@@ -40,4 +40,4 @@ help: Cualquier duda que te surja, no dudes en consultarla, ¡estamos pa
atenderte!
salesPersonName: Soy tu comercial y mi nombre es
salesPersonPhone: Teléfono y whatsapp
-salesPersonEmail: Dirección de e-mail
\ No newline at end of file
+salesPersonEmail: Dirección de e-mail