Merge branch 'dev' into 3449-client_credit
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Carlos Jimenez Ruiz 2021-12-24 10:48:15 +00:00
commit a969f1f234
39 changed files with 569 additions and 57 deletions

View File

@ -0,0 +1,2 @@
INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId)
VALUES ('Sale','payBack','WRITE','ALLOW','ROLE','employee');

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`payMethod` ADD hasVerified TINYINT(1) DEFAULT 0 NULL;

View File

@ -0,0 +1,90 @@
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 vRsMainTicket CURSOR FOR
SELECT *
FROM tmp.sale;
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 vRsMainTicket ;
FETCH vRsMainTicket 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 vRsMainTicket INTO vSaleMain, vItemFk, vQuantity, vConcept, vPrice, vDiscount;
END WHILE;
CLOSE vRsMainTicket;
END;
$$
DELIMITER ;

View File

@ -0,0 +1,3 @@
UPDATE `vn`.`department`
SET `notificationEmail` = 'finanzas@verdnatura.es'
WHERE `name` = 'FINANZAS';

View File

@ -0,0 +1,2 @@
UPDATE `vn`.`supplier`
SET isPayMethodChecked = TRUE;

View File

@ -0,0 +1,33 @@
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` TRIGGER `vn`.`payment_afterInsert` AFTER INSERT ON `payment` FOR EACH ROW
BEGIN
DECLARE vIsPayMethodChecked BOOLEAN;
DECLARE vEmail VARCHAR(150);
SELECT isPayMethodChecked INTO vIsPayMethodChecked
FROM supplier
WHERE id = NEW.supplierFk;
IF vIsPayMethodChecked = FALSE THEN
SELECT notificationEmail INTO vEmail
FROM department
WHERE name = 'FINANZAS';
CALL mail_insert(
vEmail,
NULL,
'Pago con método sin verificar',
CONCAT(
'Se ha realizado el pago ',
NEW.id,
' al proveedor ',
NEW.supplierFk,
' con el método de pago sin verificar.'
)
);
END IF;
END$$
DELIMITER ;

View File

@ -0,0 +1,26 @@
DROP TRIGGER IF EXISTS `vn`.`supplier_beforeUpdate`;
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` TRIGGER `vn`.`supplier_beforeUpdate`
BEFORE UPDATE ON `vn`.`supplier` FOR EACH ROW
BEGIN
DECLARE vHasChange BOOL DEFAULT FALSE;
DECLARE vPayMethodHasVerified BOOL;
SELECT hasVerified INTO vPayMethodHasVerified
FROM payMethod
WHERE id = NEW.payMethodFk;
SET vHasChange = (NEW.payDemFk <> OLD.payDemFk) OR (NEW.payDay <> OLD.payDay);
IF vPayMethodHasVerified AND !vHasChange THEN
SET vHasChange = (NEW.payMethodFk <> OLD.payMethodFk);
END IF;
IF vHasChange THEN
SET NEW.isPayMethodChecked = FALSE;
END IF;
END$$
DELIMITER ;

View File

@ -1 +0,0 @@
Delete me!

View File

@ -217,14 +217,14 @@ UPDATE `vn`.`agencyMode` SET `web` = 1, `reportMail` = 'no-reply@gothamcity.com'
UPDATE `vn`.`agencyMode` SET `code` = 'refund' WHERE `id` = 23; UPDATE `vn`.`agencyMode` SET `code` = 'refund' WHERE `id` = 23;
INSERT INTO `vn`.`payMethod`(`id`,`code`, `name`, `graceDays`, `outstandingDebt`, `isIbanRequiredForClients`, `isIbanRequiredForSuppliers`) INSERT INTO `vn`.`payMethod`(`id`,`code`, `name`, `graceDays`, `outstandingDebt`, `isIbanRequiredForClients`, `isIbanRequiredForSuppliers`, `hasVerified`)
VALUES VALUES
(1, NULL, 'PayMethod one', 0, 001, 0, 0), (1, NULL, 'PayMethod one', 0, 001, 0, 0, 0),
(2, NULL, 'PayMethod two', 10, 001, 0, 0), (2, NULL, 'PayMethod two', 10, 001, 0, 0, 1),
(3, 'compensation', 'PayMethod three', 0, 001, 0, 0), (3, 'compensation', 'PayMethod three', 0, 001, 0, 0, 0),
(4, NULL, 'PayMethod with IBAN', 0, 001, 1, 0), (4, NULL, 'PayMethod with IBAN', 0, 001, 1, 0, 0),
(5, NULL, 'PayMethod five', 10, 001, 0, 0), (5, NULL, 'PayMethod five', 10, 001, 0, 0, 0),
(8,'wireTransfer', 'WireTransfer', 5, 001, 1, 1); (8,'wireTransfer', 'WireTransfer', 5, 001, 1, 1, 0);
INSERT INTO `vn`.`payDem`(`id`, `payDem`) INSERT INTO `vn`.`payDem`(`id`, `payDem`)
VALUES VALUES

View File

@ -524,7 +524,7 @@ export default {
saturdayButton: '.vn-popup.shown vn-tool-bar > vn-button:nth-child(6)', saturdayButton: '.vn-popup.shown vn-tool-bar > vn-button:nth-child(6)',
acceptDialog: '.vn-dialog.shown button[response="accept"]', acceptDialog: '.vn-dialog.shown button[response="accept"]',
acceptChangeHourButton: '.vn-dialog.shown button[response="accept"]', acceptChangeHourButton: '.vn-dialog.shown button[response="accept"]',
descriptorDeliveryDate: 'vn-ticket-descriptor slot-body > .attributes > vn-label-value:nth-child(3) > section > span', descriptorDeliveryDate: 'vn-ticket-descriptor slot-body > .attributes > vn-label-value:nth-child(4) > section > span',
acceptInvoiceOutButton: '.vn-confirm.shown button[response="accept"]', acceptInvoiceOutButton: '.vn-confirm.shown button[response="accept"]',
acceptDeleteStowawayButton: '.vn-dialog.shown button[response="accept"]' acceptDeleteStowawayButton: '.vn-dialog.shown button[response="accept"]'
}, },
@ -558,6 +558,7 @@ export default {
moreMenuUnmarkReseved: 'vn-item[name="unreserve"]', moreMenuUnmarkReseved: 'vn-item[name="unreserve"]',
moreMenuUpdateDiscount: 'vn-item[name="discount"]', moreMenuUpdateDiscount: 'vn-item[name="discount"]',
moreMenuRecalculatePrice: 'vn-item[name="calculatePrice"]', moreMenuRecalculatePrice: 'vn-item[name="calculatePrice"]',
moreMenuPayBack: 'vn-item[name="payBack"]',
moreMenuUpdateDiscountInput: 'vn-input-number[ng-model="$ctrl.edit.discount"] input', 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', 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', transferQuantityCell: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable',

View File

@ -206,7 +206,16 @@ describe('Ticket Edit sale path', () => {
expect(message.text).toContain('Data saved!'); expect(message.text).toContain('Data saved!');
}); });
it('should select the third sale and create a pay back', async() => {
await page.waitToClick(selectors.ticketSales.firstSaleCheckbox);
await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuPayBack);
await page.waitForState('ticket.card.sale');
});
it('should select the third sale and create a claim of it', async() => { it('should select the third sale and create a claim of it', async() => {
await page.accessToSearchResult('16');
await page.accessToSection('ticket.card.sale');
await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox); await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox);
await page.waitToClick(selectors.ticketSales.moreMenu); await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuCreateClaim); await page.waitToClick(selectors.ticketSales.moreMenuCreateClaim);

View File

@ -8,7 +8,7 @@ describe('Supplier basic data path', () => {
beforeAll(async() => { beforeAll(async() => {
browser = await getBrowser(); browser = await getBrowser();
page = browser.page; page = browser.page;
await page.loginAndModule('administrative', 'supplier'); await page.loginAndModule('financial', 'supplier');
await page.accessToSearchResult('1'); await page.accessToSearchResult('1');
await page.accessToSection('supplier.card.basicData'); await page.accessToSection('supplier.card.basicData');
}); });

View File

@ -44,16 +44,10 @@ export default class SmartTable extends Component {
set model(value) { set model(value) {
this._model = value; this._model = value;
if (value) if (value) {
this.$.model = value; this.$.model = value;
this.defaultOrder();
} }
get viewConfigId() {
return this._viewConfigId;
}
set viewConfigId(value) {
this._viewConfigId = value;
} }
getDefaultViewConfig() { getDefaultViewConfig() {
@ -163,6 +157,40 @@ export default class SmartTable extends Component {
} }
} }
defaultOrder() {
const order = this.model.order;
if (!order) return;
const orderFields = order.split(', ');
for (const fieldString of orderFields) {
const field = fieldString.split(' ');
const fieldName = field[0];
let sortType = 'ASC';
if (field.length === 2)
sortType = field[1];
const column = this.columns.find(column => column.field == fieldName);
if (column) {
this.sortCriteria.push({field: fieldName, sortType: sortType});
const isASC = sortType == 'ASC';
const isDESC = sortType == 'DESC';
if (isDESC) {
column.element.classList.remove('asc');
column.element.classList.add('desc');
}
if (isASC) {
column.element.classList.remove('desc');
column.element.classList.add('asc');
}
}
}
}
registerColumns() { registerColumns() {
const header = this.element.querySelector('thead > tr'); const header = this.element.querySelector('thead > tr');
if (!header) return; if (!header) return;
@ -175,7 +203,7 @@ export default class SmartTable extends Component {
const columnElement = angular.element(column); const columnElement = angular.element(column);
const caption = columnElement.text().trim(); const caption = columnElement.text().trim();
this.columns.push({field, caption, index}); this.columns.push({field, caption, index, element: column});
column.addEventListener('click', () => this.orderHandler(column)); column.addEventListener('click', () => this.orderHandler(column));
} }

View File

@ -80,6 +80,45 @@ describe('Component smartTable', () => {
}); });
}); });
describe('defaultOrder', () => {
it('should insert a new object to the controller sortCriteria with a sortType value of "ASC"', () => {
const element = document.createElement('div');
controller.model = {order: 'id'};
controller.columns = [
{field: 'id', element: element},
{field: 'test1', element: element},
{field: 'test2', element: element}
];
controller.defaultOrder();
const firstSortCriteria = controller.sortCriteria[0];
expect(firstSortCriteria.field).toEqual('id');
expect(firstSortCriteria.sortType).toEqual('ASC');
});
it('should insert two new objects to the controller sortCriteria with a sortType values of "ASC" and "DESC"', () => {
const element = document.createElement('div');
controller.model = {order: 'test1, id DESC'};
controller.columns = [
{field: 'id', element: element},
{field: 'test1', element: element},
{field: 'test2', element: element}
];
controller.defaultOrder();
const firstSortCriteria = controller.sortCriteria[0];
const secondSortCriteria = controller.sortCriteria[1];
expect(firstSortCriteria.field).toEqual('test1');
expect(firstSortCriteria.sortType).toEqual('ASC');
expect(secondSortCriteria.field).toEqual('id');
expect(secondSortCriteria.sortType).toEqual('DESC');
});
});
describe('addFilter()', () => { describe('addFilter()', () => {
it('should call the model addFilter() with a basic where filter if exprBuilder() was not received', () => { it('should call the model addFilter() with a basic where filter if exprBuilder() was not received', () => {
controller.model = {addFilter: jest.fn()}; controller.model = {addFilter: jest.fn()};

View File

@ -214,5 +214,6 @@
"The type of business must be filled in basic data": "El tipo de negocio debe estar rellenado en datos básicos", "The type of business must be filled in basic data": "El tipo de negocio debe estar rellenado en datos básicos",
"You can't create a claim from a ticket delivered more than seven days ago": "No puedes crear una reclamación de un ticket entregado hace más de siete días", "You can't create a claim from a ticket delivered more than seven days ago": "No puedes crear una reclamación de un ticket entregado hace más de siete días",
"The worker has hours recorded that day": "El trabajador tiene horas fichadas ese día", "The worker has hours recorded that day": "El trabajador tiene horas fichadas ese día",
"The worker has a marked absence that day": "El trabajador tiene marcada una ausencia ese día" "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"
} }

View File

@ -82,7 +82,7 @@ class Controller extends Dialog {
} }
set amountToReturn(value) { set amountToReturn(value) {
if (!value) return; if (isNaN(value)) return;
value = value.toFixed(2); value = value.toFixed(2);

View File

@ -1,6 +1,7 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="Buys/latestBuysFilter" url="Buys/latestBuysFilter"
order="itemFk DESC"
limit="20" limit="20"
data="$ctrl.buys" data="$ctrl.buys"
auto-load="true"> auto-load="true">
@ -34,8 +35,8 @@
</vn-multi-check> </vn-multi-check>
</th> </th>
<th translate>Picture</th> <th translate>Picture</th>
<th field="id"> <th field="itemFk">
<span translate>Identifier</span> <span translate>Item ID</span>
</th> </th>
<th field="packing" number> <th field="packing" number>
<span translate>Packing</span> <span translate>Packing</span>

View File

@ -112,7 +112,10 @@ module.exports = Self => {
case 'search': case 'search':
return /^\d+$/.test(value) return /^\d+$/.test(value)
? {or: [{'i.id': value}, codeWhere]} ? {or: [{'i.id': value}, codeWhere]}
: {or: [{'i.name': {like: `%${value}%`}}, codeWhere]}; : {or: [
{'i.name': {like: `%${value}%`}},
{'i.longName': {like: `%${value}%`}},
codeWhere]};
case 'id': case 'id':
case 'isActive': case 'isActive':
case 'typeFk': case 'typeFk':

View File

@ -2,7 +2,7 @@
vn-id="model" vn-id="model"
url="SalesMonitors/salesFilter" url="SalesMonitors/salesFilter"
limit="20" limit="20"
order="shippedDate DESC, theoreticalhour, id"> order="shipped DESC, theoreticalHour, id">
</vn-crud-model> </vn-crud-model>
<vn-portal slot="topbar"> <vn-portal slot="topbar">
<vn-searchbar <vn-searchbar
@ -27,11 +27,13 @@
view-config-id="ticketsMonitor" view-config-id="ticketsMonitor"
options="$ctrl.smartTableOptions" options="$ctrl.smartTableOptions"
expr-builder="$ctrl.exprBuilder(param, value)"> expr-builder="$ctrl.exprBuilder(param, value)">
<slot-actions><vn-check <slot-actions>
<vn-check
label="Auto-refresh" label="Auto-refresh"
vn-tooltip="Toggle auto-refresh every 2 minutes" vn-tooltip="Toggle auto-refresh every 2 minutes"
on-change="$ctrl.autoRefresh(value)"> on-change="$ctrl.autoRefresh(value)">
</vn-check></slot-actions> </vn-check>
</slot-actions>
<slot-table> <slot-table>
<table> <table>
<thead> <thead>

View File

@ -29,7 +29,7 @@ vn-monitor-sales-tickets {
height: 736px height: 736px
} }
vn-tbody a[ng-repeat].vn-tr:focus { tbody tr[ng-repeat]:focus {
background-color: $color-primary-light background-color: $color-primary-light
} }

View File

@ -8,6 +8,19 @@ describe('loopback model Supplier', () => {
beforeAll(async() => { beforeAll(async() => {
supplierOne = await models.Supplier.findById(1); supplierOne = await models.Supplier.findById(1);
supplierTwo = await models.Supplier.findById(442); supplierTwo = await models.Supplier.findById(442);
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
}); });
afterAll(async() => { afterAll(async() => {
@ -32,19 +45,6 @@ describe('loopback model Supplier', () => {
}); });
it('should not throw if the payMethod id is valid', async() => { it('should not throw if the payMethod id is valid', async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
let error; let error;
const supplier = await models.Supplier.findById(442); const supplier = await models.Supplier.findById(442);
await supplier.updateAttribute('payMethodFk', 4) await supplier.updateAttribute('payMethodFk', 4)
@ -54,5 +54,40 @@ describe('loopback model Supplier', () => {
expect(error).not.toBeDefined(); expect(error).not.toBeDefined();
}); });
it('should have checked isPayMethodChecked for payMethod hasVerfified is false', async() => {
const supplier = await models.Supplier.findById(442);
await supplier.updateAttribute('isPayMethodChecked', true);
await supplier.updateAttribute('payMethodFk', 5);
const result = await models.Supplier.findById(442);
expect(result.isPayMethodChecked).toEqual(true);
});
it('should have unchecked isPayMethodChecked for payMethod hasVerfified is true', async() => {
const supplier = await models.Supplier.findById(442);
await supplier.updateAttribute('isPayMethodChecked', true);
await supplier.updateAttribute('payMethodFk', 2);
const result = await models.Supplier.findById(442);
expect(result.isPayMethodChecked).toEqual(false);
});
it('should have unchecked isPayMethodChecked for payDay and peyDemFk', async() => {
const supplier = await models.Supplier.findById(442);
await supplier.updateAttribute('isPayMethodChecked', true);
await supplier.updateAttribute('payDay', 5);
const firstResult = await models.Supplier.findById(442);
await supplier.updateAttribute('isPayMethodChecked', true);
await supplier.updateAttribute('payDemFk', 1);
const secondResult = await models.Supplier.findById(442);
expect(firstResult.isPayMethodChecked).toEqual(false);
expect(secondResult.isPayMethodChecked).toEqual(false);
});
}); });
}); });

View File

@ -7,12 +7,14 @@ module.exports = Self => {
}); });
async function ibanValidation(err, done) { async function ibanValidation(err, done) {
let filter = { const supplier = await Self.app.models.Supplier.findById(this.supplierFk);
const filter = {
fields: ['code'], fields: ['code'],
where: {id: this.countryFk} where: {id: supplier.countryFk}
}; };
let country = await Self.app.models.Country.findOne(filter);
let code = country ? country.code.toLowerCase() : null; const country = await Self.app.models.Country.findOne(filter);
const code = country ? country.code.toLowerCase() : null;
if (code != 'es') if (code != 'es')
return done(); return done();

View File

@ -1,5 +1,6 @@
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
const validateTin = require('vn-loopback/util/validateTin'); const validateTin = require('vn-loopback/util/validateTin');
const LoopBackContext = require('loopback-context');
module.exports = Self => { module.exports = Self => {
require('../methods/supplier/filter')(Self); require('../methods/supplier/filter')(Self);
@ -88,8 +89,24 @@ module.exports = Self => {
} }
Self.observe('before save', async function(ctx) { Self.observe('before save', async function(ctx) {
let changes = ctx.data || ctx.instance; const loopbackContext = LoopBackContext.getCurrentContext();
let orgData = ctx.currentInstance; const changes = ctx.data || ctx.instance;
const orgData = ctx.currentInstance;
const userId = loopbackContext.active.accessToken.userId;
const isNotFinancial = !await Self.app.models.Account.hasRole(userId, 'financial');
const isPayMethodChecked = changes.isPayMethodChecked || orgData.isPayMethodChecked;
const hasChanges = orgData && changes;
const isPayMethodCheckedChanged = hasChanges
&& orgData.isPayMethodChecked != isPayMethodChecked;
if (isNotFinancial && isPayMethodCheckedChanged)
throw new UserError('You can not modify is pay method checked');
});
Self.observe('before save', async function(ctx) {
const changes = ctx.data || ctx.instance;
const orgData = ctx.currentInstance;
const socialName = changes.name || orgData.name; const socialName = changes.name || orgData.name;
const hasChanges = orgData && changes; const hasChanges = orgData && changes;

View File

@ -38,7 +38,8 @@
</vn-check> </vn-check>
<vn-check <vn-check
label="PayMethodChecked" label="PayMethodChecked"
ng-model="$ctrl.supplier.isPayMethodChecked"> ng-model="$ctrl.supplier.isPayMethodChecked"
vn-acl="financial">
</vn-check> </vn-check>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>

View File

@ -11,7 +11,8 @@ export default class Controller extends Section {
} }
onSubmit() { onSubmit() {
return this.$.watcher.submit(); this.$.watcher.submit()
.then(() => this.card.reload());
} }
} }
@ -20,5 +21,8 @@ ngModule.vnComponent('vnSupplierBillingData', {
controller: Controller, controller: Controller,
bindings: { bindings: {
supplier: '<' supplier: '<'
},
require: {
card: '^vnSupplierCard'
} }
}); });

View File

@ -0,0 +1,70 @@
module.exports = Self => {
Self.remoteMethodCtx('payBack', {
description: 'Create ticket with the selected lines changing the sign to the quantites',
accessType: 'WRITE',
accepts: [{
arg: 'sales',
description: 'The sales',
type: ['object'],
required: true
},
{
arg: 'ticketId',
type: 'number',
required: true,
description: 'The ticket id'
}],
returns: {
type: 'number',
root: true
},
http: {
path: `/payBack`,
verb: 'post'
}
});
Self.payBack = async(ctx, sales, 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 salesIds = [];
const params = [];
sales.forEach(sale => {
salesIds.push(sale.id);
params.push('?');
});
const paramsString = params.join();
const query = `
DROP TEMPORARY TABLE IF EXISTS tmp.sale;
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 (${paramsString});
CALL vn.ticket_doRefund(${ticketId}, @newTicket);
DROP TEMPORARY TABLE tmp.sale;`;
await Self.rawSql(query, salesIds, 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

@ -0,0 +1,26 @@
const models = require('vn-loopback/server/server').models;
describe('sale payBack()', () => {
it('should create ticket with the selected lines changing the sign to the quantites', async() => {
const tx = await models.Sale.beginTransaction({});
const ticketId = 11;
const sales = [
{id: 7, ticketFk: 11},
{id: 8, ticketFk: 11}
];
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 9}}};
const response = await models.Sale.payBack(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);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -83,6 +83,11 @@ module.exports = Self => {
type: 'boolean', type: 'boolean',
description: `Whether to show only tickets with problems` description: `Whether to show only tickets with problems`
}, },
{
arg: 'hasRoute',
type: 'boolean',
description: `Whether to show only tickets with route`
},
{ {
arg: 'pending', arg: 'pending',
type: 'boolean', type: 'boolean',
@ -188,6 +193,10 @@ module.exports = Self => {
case 'alertLevel': case 'alertLevel':
return {'ts.alertLevel': value}; return {'ts.alertLevel': value};
case 'hasRoute':
if (value == true)
return {'t.routeFk': {neq: null}};
return {'t.routeFk': null};
case 'pending': case 'pending':
if (value) { if (value) {
return {and: [ return {and: [
@ -266,7 +275,8 @@ module.exports = Self => {
LEFT JOIN state st ON st.id = ts.stateFk LEFT JOIN state st ON st.id = ts.stateFk
LEFT JOIN client c ON c.id = t.clientFk LEFT JOIN client c ON c.id = t.clientFk
LEFT JOIN worker wk ON wk.id = c.salesPersonFk LEFT JOIN worker wk ON wk.id = c.salesPersonFk
LEFT JOIN account.user u ON u.id = wk.userFk`); LEFT JOIN account.user u ON u.id = wk.userFk
LEFT JOIN route r ON r.id = t.routeFk`);
if (args.orderFk) { if (args.orderFk) {
stmt.merge({ stmt.merge({

View File

@ -221,4 +221,61 @@ describe('ticket filter()', () => {
throw e; throw e;
} }
}); });
it('should return the tickets matching the route on true', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 9}}, args: {hasRoute: true}};
const filter = {};
const result = await models.Ticket.filter(ctx, filter, options);
expect(result.length).toEqual(22);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the tickets matching the route on false', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 9}}, args: {hasRoute: false}};
const filter = {};
const result = await models.Ticket.filter(ctx, filter, options);
expect(result.length).toEqual(5);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the tickets matching the route on null', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 9}}, args: {hasRoute: null}};
const filter = {};
const result = await models.Ticket.filter(ctx, filter, options);
expect(result.length).toEqual(27);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
}); });

View File

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

View File

@ -490,4 +490,9 @@
ng-if="$ctrl.isEditable && $ctrl.hasReserves()"> ng-if="$ctrl.isEditable && $ctrl.hasReserves()">
Unmark as reserved Unmark as reserved
</vn-item> </vn-item>
<vn-item translate
name="payBack"
ng-click="$ctrl.createPayBack()">
Pay Back
</vn-item>
</vn-menu> </vn-menu>

View File

@ -460,6 +460,18 @@ class Controller extends Section {
}); });
} }
createPayBack() {
const sales = this.selectedValidSales();
if (!sales) return;
const params = {sales: sales, ticketId: this.ticket.id};
const query = `Sales/payBack`;
this.resetChanges();
this.$http.post(query, params).then(res => {
this.$state.go('ticket.card.sale', {id: res.data});
});
}
itemSearchFunc($search) { itemSearchFunc($search) {
return /^\d+$/.test($search) return /^\d+$/.test($search)
? {id: $search} ? {id: $search}

View File

@ -701,6 +701,22 @@ describe('Ticket', () => {
}); });
}); });
describe('createPayBack()', () => {
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.flush();
expect(controller.resetChanges).toHaveBeenCalledWith();
expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: expectedId});
});
});
describe('itemSearchFunc()', () => { describe('itemSearchFunc()', () => {
it('should return the filter by id property for an input of a number', () => { it('should return the filter by id property for an input of a number', () => {
const itemId = 1; const itemId = 1;

View File

@ -36,3 +36,4 @@ Warehouse: Almacen
Agency: Agencia Agency: Agencia
Shipped: F. envio Shipped: F. envio
Packaging: Encajado Packaging: Encajado
Pay Back: Abono

View File

@ -135,6 +135,12 @@
ng-model="filter.pending" ng-model="filter.pending"
triple-state="true"> triple-state="true">
</vn-check> </vn-check>
<vn-check
vn-one
label="Has route"
ng-model="filter.hasRoute"
triple-state="true">
</vn-check>
</vn-horizontal> </vn-horizontal>
<vn-horizontal class="vn-px-lg vn-pb-lg vn-mt-lg"> <vn-horizontal class="vn-px-lg vn-pb-lg vn-mt-lg">
<vn-submit label="Search"></vn-submit> <vn-submit label="Search"></vn-submit>

View File

@ -12,6 +12,7 @@ Order id: Id cesta
Grouped States: Estado agrupado Grouped States: Estado agrupado
Days onward: Días adelante Days onward: Días adelante
With problems: Con problemas With problems: Con problemas
Has route: Con ruta
Pending: Pendiente Pending: Pendiente
FREE: Libre FREE: Libre
DELIVERED: Servido DELIVERED: Servido

View File

@ -23,6 +23,10 @@
<td class="font gray uppercase">{{$t('clientId')}}</td> <td class="font gray uppercase">{{$t('clientId')}}</td>
<th>{{client.id}}</th> <th>{{client.id}}</th>
</tr> </tr>
<tr>
<td class="font gray uppercase">{{$t('phone')}}</td>
<th>{{client.phone}}</th>
</tr>
<tr> <tr>
<td class="font gray uppercase">{{$t('date')}}</td> <td class="font gray uppercase">{{$t('date')}}</td>
<th>{{dated}}</th> <th>{{dated}}</th>

View File

@ -9,6 +9,7 @@ reference: Referencia
concept: Concepto concept: Concepto
clientSignature: Firma del cliente clientSignature: Firma del cliente
claim: Reclamación {0} claim: Reclamación {0}
phone: Teléfono
sections: sections:
agency: agency:
description: 'Para agilizar su recogida, por favor, póngase en contacto con la oficina description: 'Para agilizar su recogida, por favor, póngase en contacto con la oficina

View File

@ -8,7 +8,8 @@ SELECT
a.street, a.street,
a.nickname, a.nickname,
p.name AS province, p.name AS province,
ct.country ct.country,
IFNULL(c.phone, cc.phone) AS phone
FROM claim cl FROM claim cl
JOIN client c ON c.id = cl.clientFk JOIN client c ON c.id = cl.clientFk
JOIN account.user u ON u.id = c.id JOIN account.user u ON u.id = c.id
@ -17,4 +18,6 @@ FROM claim cl
LEFT JOIN province p ON p.id = a.provinceFk LEFT JOIN province p ON p.id = a.provinceFk
LEFT JOIN autonomy amy ON amy.id = p.autonomyFk LEFT JOIN autonomy amy ON amy.id = p.autonomyFk
LEFT JOIN country ct ON ct.id = amy.countryFk LEFT JOIN country ct ON ct.id = amy.countryFk
LEFT JOIN clientContact cc ON cc.clientFk = c.id
WHERE cl.id = ? WHERE cl.id = ?
LIMIT 1;