3830-ticket_sale #927

Merged
carlosjr merged 9 commits from 3830-ticket_sale into dev 2022-04-13 09:30:49 +00:00
25 changed files with 298 additions and 40 deletions

View File

@ -0,0 +1,6 @@
UPDATE `salix`.`ACL`
SET `property`='refund'
WHERE `model`='Sale' AND `property`='payBack';
INSERT INTO `salix`.`ACL`(`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES('Sale', 'refundAll', 'WRITE', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,113 @@
DROP PROCEDURE IF EXISTS vn.ticket_doRefund;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_doRefund`(IN vOriginTicket INT, OUT vNewTicket INT)
BEGIN
DECLARE vDone BIT DEFAULT 0;
DECLARE vCustomer MEDIUMINT;
DECLARE vWarehouse TINYINT;
DECLARE vCompany MEDIUMINT;
DECLARE vAddress MEDIUMINT;
DECLARE vRefundAgencyMode INT;
DECLARE vItemFk INT;
DECLARE vQuantity DECIMAL (10,2);
DECLARE vConcept VARCHAR(50);
DECLARE vPrice DECIMAL (10,2);
DECLARE vDiscount TINYINT;
DECLARE vSaleNew INT;
DECLARE vSaleMain INT;
DECLARE vZoneFk INT;
DECLARE vDescription VARCHAR(50);
DECLARE vTaxClassFk INT;
DECLARE vTicketServiceTypeFk INT;
DECLARE cSales CURSOR FOR
SELECT *
FROM tmp.sale;
DECLARE cTicketServices CURSOR FOR
SELECT *
FROM tmp.ticketService;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = 1;
SELECT id INTO vRefundAgencyMode
FROM agencyMode WHERE `name` = 'ABONO';
SELECT clientFk, warehouseFk, companyFk, addressFk
INTO vCustomer, vWarehouse, vCompany, vAddress
FROM ticket
WHERE id = vOriginTicket;
SELECT id INTO vZoneFk
FROM zone WHERE agencyModeFk = vRefundAgencyMode
LIMIT 1;
INSERT INTO vn.ticket (
clientFk,
shipped,
addressFk,
agencyModeFk,
nickname,
warehouseFk,
companyFk,
landed,
zoneFk
)
SELECT
vCustomer,
CURDATE(),
vAddress,
vRefundAgencyMode,
a.nickname,
vWarehouse,
vCompany,
CURDATE(),
vZoneFk
FROM address a
WHERE a.id = vAddress;
SET vNewTicket = LAST_INSERT_ID();
SET vDone := 0;
OPEN cSales;
FETCH cSales INTO vSaleMain, vItemFk, vQuantity, vConcept, vPrice, vDiscount;
WHILE NOT vDone DO
INSERT INTO vn.sale(ticketFk, itemFk, quantity, concept, price, discount)
VALUES( vNewTicket, vItemFk, vQuantity, vConcept, vPrice, vDiscount );
SET vSaleNew = LAST_INSERT_ID();
INSERT INTO vn.saleComponent(saleFk,componentFk,`value`)
SELECT vSaleNew,componentFk,`value`
FROM vn.saleComponent
WHERE saleFk = vSaleMain;
FETCH cSales INTO vSaleMain, vItemFk, vQuantity, vConcept, vPrice, vDiscount;
END WHILE;
CLOSE cSales;
SET vDone := 0;
OPEN cTicketServices;
FETCH cTicketServices INTO vDescription, vQuantity, vPrice, vTaxClassFk, vTicketServiceTypeFk;
WHILE NOT vDone DO
INSERT INTO vn.ticketService(description, quantity, price, taxClassFk, ticketFk, ticketServiceTypeFk)
VALUES(vDescription, vQuantity, vPrice, vTaxClassFk, vNewTicket, vTicketServiceTypeFk);
FETCH cTicketServices INTO vDescription, vQuantity, vPrice, vTaxClassFk, vTicketServiceTypeFk;
END WHILE;
CLOSE cTicketServices;
INSERT INTO vn.ticketRefund(refundTicketFk, originalTicketFk)
VALUES(vNewTicket, vOriginTicket);
END$$
DELIMITER ;

View File

@ -576,7 +576,7 @@ export default {
moreMenuUnmarkReseved: 'vn-item[name="unreserve"]',
moreMenuUpdateDiscount: 'vn-item[name="discount"]',
moreMenuRecalculatePrice: 'vn-item[name="calculatePrice"]',
moreMenuPayBack: 'vn-item[name="payBack"]',
moreMenuRefund: 'vn-item[name="refund"]',
moreMenuUpdateDiscountInput: 'vn-input-number[ng-model="$ctrl.edit.discount"] input',
transferQuantityInput: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable > span > text',
transferQuantityCell: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable',

View File

@ -213,10 +213,10 @@ describe('Ticket Edit sale path', () => {
await page.accessToSection('ticket.card.sale');
});
it('should select the third sale and create a pay back', async() => {
it('should select the third sale and create a refund', async() => {
await page.waitToClick(selectors.ticketSales.firstSaleCheckbox);
await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuPayBack);
await page.waitToClick(selectors.ticketSales.moreMenuRefund);
await page.waitForState('ticket.card.sale');
});

View File

@ -23,10 +23,21 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-agency-term:before {
content: "\e950";
}
.icon-deaulter:before {
content: "\e94b";
}
.icon-100:before {
content: "\e95a";
}
.icon-history:before {
content: "\e968";
}
.icon-Person:before {
content: "\e901";
}
.icon-accessory:before {
content: "\e90a";
}
@ -74,6 +85,7 @@
}
.icon-bucket:before {
content: "\e97a";
color: #000;
}
.icon-buscaman:before {
content: "\e93b";
@ -83,26 +95,32 @@
}
.icon-calc_volum .path1:before {
content: "\e915";
color: rgb(0, 0, 0);
}
.icon-calc_volum .path2:before {
content: "\e916";
margin-left: -1em;
color: rgb(0, 0, 0);
}
.icon-calc_volum .path3:before {
content: "\e917";
margin-left: -1em;
color: rgb(0, 0, 0);
}
.icon-calc_volum .path4:before {
content: "\e918";
margin-left: -1em;
color: rgb(0, 0, 0);
}
.icon-calc_volum .path5:before {
content: "\e919";
margin-left: -1em;
color: rgb(0, 0, 0);
}
.icon-calc_volum .path6:before {
content: "\e91a";
margin-left: -1em;
color: rgb(255, 255, 255);
}
.icon-calendar:before {
content: "\e93d";
@ -137,9 +155,6 @@
.icon-credit:before {
content: "\e927";
}
.icon-defaulter:before {
content: "\e94b";
}
.icon-deletedTicket:before {
content: "\e935";
}
@ -206,9 +221,6 @@
.icon-headercol:before {
content: "\e958";
}
.icon-history:before {
content: "\e968";
}
.icon-info:before {
content: "\e952";
}
@ -281,9 +293,6 @@
.icon-pbx:before {
content: "\e93c";
}
.icon-Person:before {
content: "\e901";
}
.icon-pets:before {
content: "\e947";
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 160 KiB

View File

@ -219,7 +219,7 @@
"The worker has a marked absence that day": "El trabajador tiene marcada una ausencia ese día",
"You can not modify is pay method checked": "No se puede modificar el campo método de pago validado",
"Can't transfer claimed sales": "No puedes transferir lineas reclamadas",
"You don't have privileges to create pay back": "No tienes permisos para crear un abono",
"You don't have privileges to create refund": "No tienes permisos para crear un abono",
"The item is required": "El artículo es requerido",
"The agency is already assigned to another autonomous": "La agencia ya está asignada a otro autónomo",
"date in the future": "Fecha en el futuro",

View File

@ -7,7 +7,7 @@
"menus": {
"main": [
{"state": "route.index", "icon": "icon-delivery"},
{"state": "route.agencyTerm.index", "icon": "contact_support"}
{"state": "route.agencyTerm.index", "icon": "icon-agency-term"}
],
"card": [
{"state": "route.card.basicData", "icon": "settings"},

View File

@ -15,7 +15,7 @@
{"state": "supplier.card.address.index", "icon": "icon-delivery"},
{"state": "supplier.card.account", "icon": "icon-account"},
{"state": "supplier.card.contact", "icon": "contact_phone"},
{"state": "supplier.card.agencyTerm.index", "icon": "contact_support"},
{"state": "supplier.card.agencyTerm.index", "icon": "icon-agency-term"},
{"state": "supplier.card.log", "icon": "history"},
{"state": "supplier.card.consumption", "icon": "show_chart"}
]

View File

@ -1,7 +1,7 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('payBack', {
Self.remoteMethodCtx('refund', {
description: 'Create ticket with the selected lines changing the sign to the quantites',
accessType: 'WRITE',
accepts: [{
@ -21,12 +21,12 @@ module.exports = Self => {
root: true
},
http: {
path: `/payBack`,
path: `/refund`,
verb: 'post'
}
});
Self.payBack = async(ctx, sales, ticketId, options) => {
Self.refund = async(ctx, sales, ticketId, options) => {
const myOptions = {};
let tx;
@ -47,21 +47,35 @@ module.exports = Self => {
const hasValidRole = isClaimManager || isSalesAssistant;
if (!hasValidRole)
throw new UserError(`You don't have privileges to create pay back`);
throw new UserError(`You don't have privileges to create refund`);
for (let sale of sales)
salesIds.push(sale.id);
const query = `
DROP TEMPORARY TABLE IF EXISTS tmp.sale;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketService;
CREATE TEMPORARY TABLE tmp.sale
SELECT s.id, s.itemFk, - s.quantity, s.concept, s.price, s.discount
FROM sale s
WHERE s.id IN (?);
CREATE TEMPORARY TABLE tmp.ticketService(
description VARCHAR(50),
quantity DECIMAL (10,2),
price DECIMAL (10,2),
taxClassFk INT,
ticketServiceTypeFk INT
);
CALL vn.ticket_doRefund(?, @newTicket);
DROP TEMPORARY TABLE tmp.sale;`;
DROP TEMPORARY TABLE tmp.sale;
DROP TEMPORARY TABLE tmp.ticketService;`;
await Self.rawSql(query, [salesIds, ticketId], myOptions);
const [newTicket] = await Self.rawSql('SELECT @newTicket id', null, myOptions);
ticketId = newTicket.id;

View File

@ -0,0 +1,78 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('refundAll', {
description: 'Create ticket with all lines and services changing the sign to the quantites',
Review

This endpoint should call the existing endpoint (modules/ticket/back/methods/sale/refund.js) which already has the functionality and validations.

Doing this we avoid repeating validation's code and so.

refoundAll should just get the ticket sales and then call refund with the sales and the ticket id.

This endpoint should call the existing endpoint (modules/ticket/back/methods/sale/refund.js) which already has the functionality and validations. Doing this we avoid repeating validation's code and so. refoundAll should just get the ticket sales and then call refund with the sales and the ticket id.
accessType: 'WRITE',
accepts: [{
arg: 'ticketId',
type: 'number',
required: true,
description: 'The ticket id'
}],
returns: {
type: 'number',
root: true
},
http: {
path: `/refundAll`,
verb: 'post'
}
});
Self.refundAll = async(ctx, ticketId, options) => {
const myOptions = {};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const userId = ctx.req.accessToken.userId;
const isClaimManager = await Self.app.models.Account.hasRole(userId, 'claimManager');
const isSalesAssistant = await Self.app.models.Account.hasRole(userId, 'salesAssistant');
const hasValidRole = isClaimManager || isSalesAssistant;
if (!hasValidRole)
throw new UserError(`You don't have privileges to create refund`);
const query = `
DROP TEMPORARY TABLE IF EXISTS tmp.sale;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketService;
CREATE TEMPORARY TABLE tmp.sale
SELECT s.id, s.itemFk, - s.quantity, s.concept, s.price, s.discount
FROM sale s
JOIN ticket t ON t.id = s.ticketFk
WHERE t.id IN (?);
CREATE TEMPORARY TABLE tmp.ticketService
SELECT ts.description, - ts.quantity, ts.price, ts.taxClassFk, ts.ticketServiceTypeFk
FROM ticketService ts
WHERE ts.ticketFk IN (?);
CALL vn.ticket_doRefund(?, @newTicket);
DROP TEMPORARY TABLE tmp.sale;
DROP TEMPORARY TABLE tmp.ticketService;`;
await Self.rawSql(query, [ticketId, ticketId, ticketId], myOptions);
const [newTicket] = await Self.rawSql('SELECT @newTicket id', null, myOptions);
ticketId = newTicket.id;
if (tx) await tx.commit();
return ticketId;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -1,6 +1,6 @@
const models = require('vn-loopback/server/server').models;
describe('sale payBack()', () => {
describe('sale refund()', () => {
it('should create ticket with the selected lines changing the sign to the quantites', async() => {
const tx = await models.Sale.beginTransaction({});
const ctx = {req: {accessToken: {userId: 9}}};
@ -14,7 +14,7 @@ describe('sale payBack()', () => {
try {
const options = {transaction: tx};
const response = await models.Sale.payBack(ctx, sales, ticketId, options);
const response = await models.Sale.refund(ctx, sales, ticketId, options);
const [newTicketId] = await models.Sale.rawSql('SELECT MAX(t.id) id FROM vn.ticket t;', null, options);
expect(response).toEqual(newTicketId.id);
@ -26,7 +26,7 @@ describe('sale payBack()', () => {
}
});
it(`should throw an error if the user doesn't have privileges to create a pay back`, async() => {
it(`should throw an error if the user doesn't have privileges to create a refund`, async() => {
const tx = await models.Sale.beginTransaction({});
const ctx = {req: {accessToken: {userId: 1}}};
@ -40,7 +40,7 @@ describe('sale payBack()', () => {
try {
const options = {transaction: tx};
await models.Sale.payBack(ctx, sales, ticketId, options);
await models.Sale.refund(ctx, sales, ticketId, options);
await tx.rollback();
} catch (e) {
@ -49,6 +49,6 @@ describe('sale payBack()', () => {
}
expect(error).toBeDefined();
expect(error.message).toEqual(`You don't have privileges to create pay back`);
expect(error.message).toEqual(`You don't have privileges to create refund`);
});
});

View File

@ -6,7 +6,8 @@ module.exports = Self => {
require('../methods/sale/updateQuantity')(Self);
require('../methods/sale/updateConcept')(Self);
require('../methods/sale/recalculatePrice')(Self);
require('../methods/sale/payBack')(Self);
require('../methods/sale/refund')(Self);
require('../methods/sale/refundAll')(Self);
require('../methods/sale/canEdit')(Self);
Self.validatesPresenceOf('concept', {

View File

@ -139,6 +139,11 @@
translate>
Recalculate components
</vn-item>
<vn-item
ng-click="refundAllConfirmation.show()"
translate>
Refund all
</vn-item>
</vn-list>
</vn-menu>
@ -292,4 +297,12 @@
on-accept="$ctrl.recalculateComponents()"
question="Are you sure you want to recalculate the components?"
message="Recalculate components">
</vn-confirm>
<!-- Refund all confirmation dialog -->
<vn-confirm
vn-id="refundAllConfirmation"
on-accept="$ctrl.refundAll()"
question="Are you sure you want to refund all?"
message="Refund all">
</vn-confirm>

View File

@ -272,6 +272,14 @@ class Controller extends Section {
.then(() => this.reload())
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')));
}
refundAll() {
const params = {ticketId: this.id};
const query = `Sales/refundAll`;
return this.$http.post(query, params).then(res => {
this.$state.go('ticket.card.sale', {id: res.data});
});
}
}
Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail'];

View File

@ -262,6 +262,19 @@ describe('Ticket Component vnTicketDescriptorMenu', () => {
});
});
describe('refundAll()', () => {
it('should make a query and go to ticket.card.sale', () => {
vicent marked this conversation as resolved Outdated

show a go?

show a go?
jest.spyOn(controller.$state, 'go').mockReturnValue();
vicent marked this conversation as resolved Outdated

do you need this spy to return a value?

do you need this spy to return a value?
const expectedParams = {ticketId: ticket.id};
$httpBackend.expect('POST', `Sales/refundAll`, expectedParams).respond({ticketId: 16});
controller.refundAll();
$httpBackend.flush();
expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: {ticketId: ticket.id}});
});
});
describe('showSMSDialog()', () => {
it('should set the destionationFk and destination properties and then call the sms open() method', () => {
controller.$.sms = {open: () => {}};

View File

@ -7,4 +7,5 @@ Send PDF: Enviar PDF
Send CSV: Enviar CSV
Send CSV Delivery Note: Enviar albarán en CSV
Send PDF Delivery Note: Enviar albarán en PDF
Show Proforma: Ver proforma
Show Proforma: Ver proforma
Refund all: Abonar todo

View File

@ -29,4 +29,5 @@ SMS Minimum import: 'SMS Importe minimo'
SMS Pending payment: 'SMS Pago pendiente'
Restore ticket: Restaurar ticket
You are going to restore this ticket: Vas a restaurar este ticket
Are you sure you want to restore this ticket?: ¿Seguro que quieres restaurar el ticket?
Are you sure you want to restore this ticket?: ¿Seguro que quieres restaurar el ticket?
Are you sure you want to refund all?: ¿Seguro que quieres abonar todo?

View File

@ -512,10 +512,10 @@
Unmark as reserved
</vn-item>
<vn-item translate
name="payBack"
ng-click="$ctrl.createPayBack()"
name="refund"
ng-click="$ctrl.createRefund()"
vn-acl="claimManager, salesAssistant"
vn-acl-action="remove">
Pay Back
Refund
</vn-item>
</vn-menu>

View File

@ -479,12 +479,12 @@ class Controller extends Section {
});
}
createPayBack() {
createRefund() {
const sales = this.selectedValidSales();
if (!sales) return;
const params = {sales: sales, ticketId: this.ticket.id};
const query = `Sales/payBack`;
const query = `Sales/refund`;
this.resetChanges();
this.$http.post(query, params).then(res => {
this.$state.go('ticket.card.sale', {id: res.data});

View File

@ -703,15 +703,15 @@ describe('Ticket', () => {
});
});
describe('createPayBack()', () => {
describe('createRefund()', () => {
it('should make an HTTP POST query and then call to the $state go() method', () => {
jest.spyOn(controller, 'selectedValidSales').mockReturnValue(controller.sales);
jest.spyOn(controller, 'resetChanges');
jest.spyOn(controller.$state, 'go');
const expectedId = 9999;
$httpBackend.expect('POST', `Sales/payBack`).respond(200, expectedId);
controller.createPayBack();
$httpBackend.expect('POST', `Sales/refund`).respond(200, expectedId);
controller.createRefund();
$httpBackend.flush();
expect(controller.resetChanges).toHaveBeenCalledWith();

View File

@ -36,6 +36,6 @@ Warehouse: Almacen
Agency: Agencia
Shipped: F. envio
Packaging: Encajado
Pay Back: Abono
Refund: Abono
Promotion mana: Maná promoción
Claim mana: Maná reclamación
Claim mana: Maná reclamación