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

This commit is contained in:
Vicent Llopis 2023-01-12 07:32:44 +00:00
commit 352fba9acf
27 changed files with 563 additions and 245 deletions

View File

@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [Artículo](Datos Básicos) Añadido campo Unidades/Caja
### Changed
- [Tickets](Líneas preparadas) Actualizada sección para que sea más visual
### Fixed
- [General] Al utilizar el traductor de Google se descuadraban los iconos

View File

@ -0,0 +1,2 @@
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`)
VALUES ('ItemShelvingSale','*','*','ALLOW','employee');

View File

@ -0,0 +1,4 @@
SET FOREIGN_KEY_CHECKS = 0;
ALTER TABLE `vn`.`report` MODIFY COLUMN id tinyint(3) unsigned NOT NULL AUTO_INCREMENT;
ALTER TABLE `vn`.`printer` MODIFY COLUMN id tinyint(3) unsigned NOT NULL AUTO_INCREMENT;
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -1143,10 +1143,10 @@ INSERT INTO `vn`.`itemShelving` (`itemFk`, `shelvingFk`, `visible`, `grouping`,
INSERT INTO `vn`.`itemShelvingSale` (`itemShelvingFk`, `saleFk`, `quantity`, `created`, `userFk`)
VALUES
('1', '1', '1', '', '1106'),
('2', '2', '5', '', '1106'),
('1', '7', '1', '', '1106'),
('2', '8', '5', '', '1106');
('1', '1', '1', util.VN_CURDATE(), '1106'),
('2', '2', '5', util.VN_CURDATE(), '1106'),
('1', '7', '1', util.VN_CURDATE(), '1106'),
('2', '8', '5', util.VN_CURDATE(), '1106');
INSERT INTO `vncontrol`.`accion`(`accion_id`, `accion`)
VALUES
@ -1779,7 +1779,7 @@ INSERT INTO `vn`.`claimDestination`(`id`, `description`, `addressFk`)
INSERT INTO `vn`.`claimDevelopment`(`id`, `claimFk`, `claimResponsibleFk`, `workerFk`, `claimReasonFk`, `claimResultFk`, `claimRedeliveryFk`, `claimDestinationFk`)
VALUES
(1, 1, 1, 21, 1, 1, 2, 5),
(2, 1, 1, 21, 7, 2, 2, 5),
(2, 1, 2, 21, 7, 2, 2, 5),
(3, 2, 7, 21, 9, 3, 2, 5),
(4, 3, 7, 21, 15, 8, 2, 5),
(5, 4, 7, 21, 7, 8, 2, 5);

View File

@ -2,68 +2,32 @@ import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker summary path', () => {
const workerId = 3;
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('employee', 'worker');
const httpDataResponse = page.waitForResponse(response => {
return response.status() === 200 && response.url().includes(`Workers/${workerId}`);
});
await page.accessToSearchResult('agencyNick');
await httpDataResponse;
});
afterAll(async() => {
await browser.close();
});
it('should reach the employee summary section', async() => {
await page.waitForState('worker.card.summary');
});
it('should check the summary contains the name and userName on the header', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.header, 'innerText');
expect(result).toEqual('agency agency');
});
it('should check the summary contains the basic data id', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.id, 'innerText');
expect(result).toEqual('3');
});
it('should check the summary contains the basic data email', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.email, 'innerText');
expect(result).toEqual('agency@verdnatura.es');
});
it('should check the summary contains the basic data department', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.department, 'innerText');
expect(result).toEqual('CAMARA');
});
it('should check the summary contains the user data id', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.userId, 'innerText');
expect(result).toEqual('3');
});
it('should check the summary contains the user data name', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.userName, 'innerText');
expect(result).toEqual('agency');
});
it('should check the summary contains the user data role', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.role, 'innerText');
expect(result).toEqual('agency');
});
it('should check the summary contains the user data extension', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.extension, 'innerText');
expect(result).toEqual('1101');
it('should reach the employee summary section and check all properties', async() => {
expect(await page.getProperty(selectors.workerSummary.header, 'innerText')).toEqual('agency agency');
expect(await page.getProperty(selectors.workerSummary.id, 'innerText')).toEqual('3');
expect(await page.getProperty(selectors.workerSummary.email, 'innerText')).toEqual('agency@verdnatura.es');
expect(await page.getProperty(selectors.workerSummary.department, 'innerText')).toEqual('CAMARA');
expect(await page.getProperty(selectors.workerSummary.userId, 'innerText')).toEqual('3');
expect(await page.getProperty(selectors.workerSummary.userName, 'innerText')).toEqual('agency');
expect(await page.getProperty(selectors.workerSummary.role, 'innerText')).toEqual('agency');
expect(await page.getProperty(selectors.workerSummary.extension, 'innerText')).toEqual('1101');
});
});

View File

@ -2,13 +2,18 @@ import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker basic data path', () => {
const workerId = 1106;
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('hr', 'worker');
const httpDataResponse = page.waitForResponse(response => {
return response.status() === 200 && response.url().includes(`Workers/${workerId}`);
});
await page.accessToSearchResult('David Charles Haller');
await httpDataResponse;
await page.accessToSection('worker.card.basicData');
});
@ -16,35 +21,20 @@ describe('Worker basic data path', () => {
await browser.close();
});
it('should edit the form', async() => {
await page.clearInput(selectors.workerBasicData.name);
await page.write(selectors.workerBasicData.name, 'David C.');
await page.clearInput(selectors.workerBasicData.surname);
await page.write(selectors.workerBasicData.surname, 'H.');
await page.clearInput(selectors.workerBasicData.phone);
await page.write(selectors.workerBasicData.phone, '444332211');
await page.waitToClick(selectors.workerBasicData.saveButton);
it('should edit the form and then reload the section and check the data was edited', async() => {
await page.overwrite(selectors.workerBasicData.name, 'David C.');
await page.overwrite(selectors.workerBasicData.surname, 'H.');
await page.overwrite(selectors.workerBasicData.phone, '444332211');
await page.click(selectors.workerBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should reload the section then check the name was edited', async() => {
await page.reloadSection('worker.card.basicData');
const result = await page.waitToGetProperty(selectors.workerBasicData.name, 'value');
expect(result).toEqual('David C.');
});
it('should the surname was edited', async() => {
const result = await page.waitToGetProperty(selectors.workerBasicData.surname, 'value');
expect(result).toEqual('H.');
});
it('should the phone was edited', async() => {
const result = await page.waitToGetProperty(selectors.workerBasicData.phone, 'value');
expect(result).toEqual('444332211');
expect(await page.waitToGetProperty(selectors.workerBasicData.name, 'value')).toEqual('David C.');
expect(await page.waitToGetProperty(selectors.workerBasicData.surname, 'value')).toEqual('H.');
expect(await page.waitToGetProperty(selectors.workerBasicData.phone, 'value')).toEqual('444332211');
});
});

View File

@ -16,19 +16,16 @@ describe('Worker pbx path', () => {
await browser.close();
});
it('should receive an error when the extension exceeds 4 characters', async() => {
it('should receive an error when the extension exceeds 4 characters and then sucessfully save the changes', async() => {
await page.write(selectors.workerPbx.extension, '55555');
await page.waitToClick(selectors.workerPbx.saveButton);
const message = await page.waitForSnackbar();
await page.click(selectors.workerPbx.saveButton);
let message = await page.waitForSnackbar();
expect(message.text).toContain('Extension format is invalid');
});
it('should sucessfully save the changes', async() => {
await page.clearInput(selectors.workerPbx.extension);
await page.write(selectors.workerPbx.extension, '4444');
await page.waitToClick(selectors.workerPbx.saveButton);
const message = await page.waitForSnackbar();
await page.overwrite(selectors.workerPbx.extension, '4444');
await page.click(selectors.workerPbx.saveButton);
message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved! User must access web');
});

View File

@ -21,38 +21,34 @@ describe('Worker time control path', () => {
const fourPm = '16:00';
const hankPymId = 1107;
it('should go to the next month', async() => {
const date = new Date();
it('should go to the next month, go to current month and go 1 month in the past', async() => {
let date = new Date();
date.setMonth(date.getMonth() + 1);
const month = date.toLocaleString('default', {month: 'long'});
let month = date.toLocaleString('default', {month: 'long'});
await page.waitToClick(selectors.workerTimeControl.nextMonthButton);
const result = await page.waitToGetProperty(selectors.workerTimeControl.monthName, 'innerText');
await page.click(selectors.workerTimeControl.nextMonthButton);
let result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month);
});
it('should go to current month', async() => {
const date = new Date();
const month = date.toLocaleString('default', {month: 'long'});
date = new Date();
month = date.toLocaleString('default', {month: 'long'});
await page.waitToClick(selectors.workerTimeControl.previousMonthButton);
const result = await page.waitToGetProperty(selectors.workerTimeControl.monthName, 'innerText');
await page.click(selectors.workerTimeControl.previousMonthButton);
result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month);
});
it('should go 1 month in the past', async() => {
const date = new Date();
date = new Date();
date.setMonth(date.getMonth() - 1);
const timestamp = Math.round(date.getTime() / 1000);
const month = date.toLocaleString('default', {month: 'long'});
month = date.toLocaleString('default', {month: 'long'});
await page.loginAndModule('salesBoss', 'worker');
await page.goto(`http://localhost:5000/#!/worker/${hankPymId}/time-control?timestamp=${timestamp}`);
await page.waitToClick(selectors.workerTimeControl.secondWeekDay);
await page.click(selectors.workerTimeControl.secondWeekDay);
const result = await page.waitToGetProperty(selectors.workerTimeControl.monthName, 'innerText');
result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month);
});
@ -115,7 +111,9 @@ describe('Worker time control path', () => {
});
it('should change week of month', async() => {
await page.waitToClick(selectors.workerTimeControl.thrirdWeekDay);
await page.waitForTextInElement(selectors.workerTimeControl.mondayWorkedHours, '00:00 h.');
await page.click(selectors.workerTimeControl.thrirdWeekDay);
const result = await page.getProperty(selectors.workerTimeControl.mondayWorkedHours, 'innerText');
expect(result).toEqual('00:00 h.');
});
});

View File

@ -1,16 +1,25 @@
/* eslint-disable max-len */
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker calendar path', () => {
let reasonableTimeBetweenClicks = 400;
const reasonableTimeBetweenClicks = 300;
const date = new Date();
const lastYear = (date.getFullYear() - 1).toString();
let browser;
let page;
async function accessAs(user) {
await page.loginAndModule(user, 'worker');
await page.accessToSearchResult('Charles Xavier');
await page.accessToSection('worker.card.calendar');
}
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('hr', 'worker');
await page.accessToSearchResult('Charles Xavier');
await page.accessToSection('worker.card.calendar');
accessAs('hr');
});
afterAll(async() => {
@ -21,48 +30,40 @@ describe('Worker calendar path', () => {
it('should set two days as holidays on the calendar and check the total holidays increased by 1.5', async() => {
await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.penultimateMondayOfJanuary);
await page.click(selectors.workerCalendar.penultimateMondayOfJanuary);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.absence);
await page.click(selectors.workerCalendar.absence);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.lastMondayOfMarch);
await page.click(selectors.workerCalendar.lastMondayOfMarch);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.halfHoliday);
await page.click(selectors.workerCalendar.halfHoliday);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.fistMondayOfMay);
await page.click(selectors.workerCalendar.fistMondayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.furlough);
await page.click(selectors.workerCalendar.furlough);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondTuesdayOfMay);
await page.click(selectors.workerCalendar.secondTuesdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondWednesdayOfMay);
await page.click(selectors.workerCalendar.secondWednesdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondThursdayOfMay);
await page.click(selectors.workerCalendar.secondThursdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.halfFurlough);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondFridayOfJun);
await page.click(selectors.workerCalendar.halfFurlough);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.secondFridayOfJun);
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
expect(result).toContain(' 1.5 ');
expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 1.5 ');
});
});
describe(`as salesBoss`, () => {
it(`should log in and get to Charles Xavier's calendar`, async() => {
await page.loginAndModule('salesBoss', 'worker');
await page.accessToSearchResult('Charles Xavier');
await page.accessToSection('worker.card.calendar');
});
it(`should log in, get to Charles Xavier's calendar, undo what was done here, and check the total holidays used are back to what it was`, async() => {
accessAs('salesBoss');
it('should undo what was done here', async() => {
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.penultimateMondayOfJanuary);
@ -90,45 +91,24 @@ describe('Worker calendar path', () => {
await page.waitToClick(selectors.workerCalendar.halfFurlough);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondFridayOfJun);
});
it('should check the total holidays used are back to what it was', async() => {
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
expect(result).toContain(' 0 ');
expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 0 ');
});
});
describe(`as Charles Xavier`, () => {
it(`should log in and get to his calendar`, async() => {
await page.loginAndModule('CharlesXavier', 'worker');
await page.accessToSearchResult('Charles Xavier');
await page.accessToSection('worker.card.calendar');
});
it('should make a futile attempt to add holidays', async() => {
await page.waitForTimeout(reasonableTimeBetweenClicks);
it('should log in and get to his calendar, make a futile attempt to add holidays, check the total holidays used are now the initial ones and use the year selector to go to the previous year', async() => {
accessAs('CharlesXavier');
await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.penultimateMondayOfJanuary);
});
it('should check the total holidays used are now the initial ones', async() => {
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
await page.click(selectors.workerCalendar.penultimateMondayOfJanuary);
expect(result).toContain(' 0 ');
});
it('should use the year selector to go to the previous year', async() => {
const date = new Date();
const lastYear = (date.getFullYear() - 1).toString();
expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 0 ');
await page.autocompleteSearch(selectors.workerCalendar.year, lastYear);
await page.waitForTimeout(reasonableTimeBetweenClicks);
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
expect(result).toContain(' 0 ');
expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 0 ');
});
});
});

View File

@ -29,7 +29,7 @@ vn-table {
& > tbody {
display: table-row-group;
}
& > vn-tfoot,
& > vn-tfoot,
& > .vn-tfoot,
& > tfoot {
border-top: $border;
@ -42,7 +42,7 @@ vn-table {
height: 48px;
}
vn-thead, .vn-thead,
vn-tbody, .vn-tbody,
vn-tbody, .vn-tbody,
vn-tfoot, .vn-tfoot,
thead, tbody, tfoot {
& > * {
@ -153,6 +153,18 @@ vn-table {
background-color: $color-font-bg-dark;
color: $color-font-bg;
}
&.dark-notice {
background-color: $color-notice;
color: $color-font-bg;
}
&.yellow {
background-color: $color-yellow;
color: $color-font-bg;
}
&.pink {
background-color: $color-pink;
color: $color-font-bg;
}
}
vn-icon-menu {
display: inline-block;
@ -194,7 +206,7 @@ vn-table.scrollable > .vn-table,
}
vn-thead th,
vn-thead vn-th,
vn-thead vn-th,
thead vn-th,
thead th {
border-bottom: $border;
@ -217,4 +229,4 @@ vn-table.scrollable.lg,
.tableWrapper {
overflow-x: auto;
}
}

View File

@ -101,6 +101,8 @@ $color-marginal: #222;
$color-success: #a3d131;
$color-notice: #32b1ce;
$color-alert: #fa3939;
$color-pink: #ff99cc;
$color-yellow: #ffff00;
$color-button: $color-secondary;
$color-spacer: rgba(255, 255, 255, .3);

View File

@ -23,7 +23,7 @@ module.exports = Self => {
{
arg: 'search',
type: 'string',
description: `If it's and integer searchs by id, otherwise it searchs by client name`,
description: `If it's a number searchs by id, otherwise it searchs by client name`,
http: {source: 'query'}
},
{
@ -34,31 +34,31 @@ module.exports = Self => {
},
{
arg: 'id',
type: 'integer',
type: 'number',
description: 'The claim id',
http: {source: 'query'}
},
{
arg: 'clientFk',
type: 'integer',
type: 'number',
description: 'The client id',
http: {source: 'query'}
},
{
arg: 'claimStateFk',
type: 'integer',
type: 'number',
description: 'The claim state id',
http: {source: 'query'}
},
{
arg: 'salesPersonFk',
type: 'integer',
type: 'number',
description: 'The salesPerson id',
http: {source: 'query'}
},
{
arg: 'attenderFk',
type: 'integer',
type: 'number',
description: 'The attender worker id',
http: {source: 'query'}
},
@ -67,6 +67,18 @@ module.exports = Self => {
type: 'date',
description: 'The to date filter',
http: {source: 'query'}
},
{
arg: 'itemFk',
type: 'number',
description: 'The item id',
http: {source: 'query'}
},
{
arg: 'claimResponsibleFk',
type: 'number',
description: 'The claimResponsible id',
http: {source: 'query'}
}
],
returns: {
@ -80,33 +92,58 @@ module.exports = Self => {
});
Self.filter = async(ctx, filter, options) => {
const models = Self.app.models;
const conn = Self.dataSource.connector;
const args = ctx.args;
const myOptions = {};
let to;
if (typeof options == 'object')
Object.assign(myOptions, options);
const where = buildFilter(ctx.args, (param, value) => {
let claimIdsByItemFk = [];
let claimIdsByClaimResponsibleFk = [];
if (args.itemFk) {
query = `SELECT cb.claimFk
FROM claimBeginning cb
LEFT JOIN sale s ON s.id = cb.saleFk
WHERE s.itemFk = ?`;
const claims = await Self.rawSql(query, [args.itemFk], myOptions);
claimIdsByItemFk = claims.map(claim => claim.claimFk);
}
if (args.claimResponsibleFk) {
const claims = await models.ClaimDevelopment.find({
fields: ['claimFk'],
where: {claimResponsibleFk: args.claimResponsibleFk}
}, myOptions);
claimIdsByClaimResponsibleFk = claims.map(claim => claim.claimFk);
}
const where = buildFilter(args, (param, value) => {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {'cl.id': value}
: {
or: [
{'cl.clientName': {like: `%${value}%`}}
{'c.name': {like: `%${value}%`}}
]
};
case 'clientName':
return {'cl.clientName': {like: `%${value}%`}};
return {'c.name': {like: `%${value}%`}};
case 'clientFk':
return {'cl.clientFk': value};
case 'id':
case 'claimStateFk':
case 'priority':
return {[`cl.${param}`]: value};
case 'itemFk':
return {'cl.id': {inq: claimIdsByItemFk}};
case 'claimResponsibleFk':
return {'cl.id': {inq: claimIdsByClaimResponsibleFk}};
case 'salesPersonFk':
return {'cl.salesPersonFk': value};
return {'c.salesPersonFk': value};
case 'attenderFk':
return {'cl.workerFk': value};
case 'created':
@ -118,29 +155,23 @@ module.exports = Self => {
}
});
filter = mergeFilters(ctx.args.filter, {where});
filter = mergeFilters(args.filter, {where});
const stmts = [];
const stmt = new ParameterizedSQL(
`SELECT *
FROM (
SELECT
cl.id,
cl.clientFk,
c.name AS clientName,
cl.workerFk,
u.name AS workerName,
cs.description,
cl.created,
cs.priority,
cl.claimStateFk,
c.salesPersonFk
FROM claim cl
LEFT JOIN client c ON c.id = cl.clientFk
LEFT JOIN worker w ON w.id = cl.workerFk
LEFT JOIN account.user u ON u.id = w.userFk
LEFT JOIN claimState cs ON cs.id = cl.claimStateFk ) cl`
`SELECT
cl.id,
cl.clientFk,
c.name AS clientName,
cl.workerFk,
u.name AS workerName,
cs.description,
cl.created
FROM claim cl
LEFT JOIN client c ON c.id = cl.clientFk
LEFT JOIN account.user u ON u.id = cl.workerFk
LEFT JOIN claimState cs ON cs.id = cl.claimStateFk`
);
stmt.merge(conn.makeSuffix(filter));

View File

@ -57,4 +57,44 @@ describe('claim filter()', () => {
throw e;
}
});
it('should return 3 results filtering by item id', async() => {
const tx = await app.models.Claim.beginTransaction({});
try {
const options = {transaction: tx};
const result = await app.models.Claim.filter({args: {filter: {}, itemFk: 2}}, null, options);
expect(result.length).toEqual(3);
expect(result[0].id).toEqual(1);
expect(result[1].id).toEqual(2);
expect(result[2].id).toEqual(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return 3 results filtering by claimResponsible id', async() => {
const tx = await app.models.Claim.beginTransaction({});
try {
const options = {transaction: tx};
const result = await app.models.Claim.filter({args: {filter: {}, claimResponsibleFk: 7}}, null, options);
expect(result.length).toEqual(3);
expect(result[0].id).toEqual(2);
expect(result[1].id).toEqual(3);
expect(result[2].id).toEqual(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -58,8 +58,28 @@
ng-model="filter.created">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one class="dense"
label="Item"
url="Items/withName"
ng-model="filter.itemFk"
show-field="name"
value-field="id"
search-function="$ctrl.itemSearchFunc($search)"
order="id DESC">
<tpl-item>{{::id}} - {{::name}}</tpl-item>
</vn-autocomplete>
<vn-autocomplete
vn-one
ng-model="filter.claimResponsibleFk"
url="ClaimResponsibles"
show-field="description"
value-field="id"
label="Responsible">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal class="vn-mt-lg">
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>
</div>

View File

@ -1,7 +1,14 @@
import ngModule from '../module';
import SearchPanel from 'core/components/searchbar/search-panel';
class Controller extends SearchPanel {
itemSearchFunc($search) {
return /^\d+$/.test($search)
? {id: $search}
: {name: {like: '%' + $search + '%'}};
}
}
ngModule.vnComponent('vnClaimSearchPanel', {
template: require('./index.html'),
controller: SearchPanel
controller: Controller
});

View File

@ -11,7 +11,6 @@ describe('InvoiceOut createPdf()', () => {
const ctx = {req: activeCtx};
it('should create a new PDF file and set true the hasPdf property', async() => {
pending('https://redmine.verdnatura.es/issues/4875');
const invoiceId = 1;
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx

View File

@ -13,7 +13,6 @@ describe('InvoiceOut downloadZip()', () => {
};
it('should return part of link to dowloand the zip', async() => {
pending('https://redmine.verdnatura.es/issues/4875');
const tx = await models.InvoiceOut.beginTransaction({});
try {

View File

@ -51,7 +51,6 @@ describe('InvoiceOut filter()', () => {
});
it('should return the invoice out matching hasPdf', async() => {
pending('https://redmine.verdnatura.es/issues/4875');
const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx};
@ -67,7 +66,7 @@ describe('InvoiceOut filter()', () => {
const result = await models.InvoiceOut.filter(ctx, {id: invoiceOut.id}, options);
expect(result.length).toEqual(1);
expect(result.length).toBeGreaterThanOrEqual(1);
await tx.rollback();
} catch (e) {

View File

@ -0,0 +1,49 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethod('filter', {
description: 'Returns all item shelving sale matching with the filter',
accessType: 'READ',
accepts: [{
arg: 'filter',
type: 'object',
description: 'Filter defining where and paginated data'
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/filter`,
verb: 'GET'
}
});
Self.filter = async(filter, options) => {
const conn = Self.dataSource.connector;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const stmt = new ParameterizedSQL(`
SELECT iss.created,
iss.saleFk,
iss.quantity,
iss.userFk,
ish.shelvingFk,
p.code,
u.name
FROM itemShelvingSale iss
LEFT JOIN itemShelving ish ON iss.itemShelvingFk = ish.id
LEFT JOIN shelving s ON ish.shelvingFk = s.code
LEFT JOIN parking p ON s.parkingFk = p.id
LEFT JOIN account.user u ON u.id = iss.userFk`
);
stmt.merge(conn.makeSuffix(filter));
return conn.executeStmt(stmt);
};
};

View File

@ -0,0 +1,3 @@
module.exports = Self => {
require('../methods/item-shelving-sale/filter')(Self);
};

View File

@ -12,21 +12,12 @@
"id": true,
"description": "Identifier"
},
"shelve": {
"type": "string"
},
"shelvingFk": {
"type": "string"
},
"itemFk": {
"type": "number"
},
"deep": {
"type": "number"
},
"quantity": {
"type": "number"
},
"created": {
"type": "date"
}
@ -41,6 +32,11 @@
"type": "belongsTo",
"model": "Account",
"foreignKey": "userFk"
}
},
"shelving": {
"type": "belongsTo",
"model": "Shelving",
"foreignKey": "shelvingFk"
}
}
}

View File

@ -0,0 +1,33 @@
module.exports = Self => {
Self.remoteMethodCtx('salePreparingList', {
description: 'Returns a list with the lines of a ticket and its different states of preparation',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The ticket id',
http: {source: 'path'}
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/:id/salePreparingList`,
verb: 'GET'
}
});
Self.salePreparingList = async(ctx, id, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
query = `CALL vn.salePreparingList(?)`;
const [sales] = await Self.rawSql(query, [id], myOptions);
return sales;
};
};

View File

@ -1,5 +1,6 @@
module.exports = Self => {
require('../methods/sale/getClaimableFromTicket')(Self);
require('../methods/sale/salePreparingList')(Self);
require('../methods/sale/reserve')(Self);
require('../methods/sale/deleteSales')(Self);
require('../methods/sale/updatePrice')(Self);

View File

@ -1,10 +1,11 @@
<vn-crud-model
vn-id="model"
url="SaleTrackings/listSaleTracking"
url="sales"
filter="::$ctrl.filter"
link="{ticketFk: $ctrl.$params.id}"
limit="20"
data="sales"
order="itemFk DESC"
data="$ctrl.sales"
order="concept ASC"
auto-load="true">
</vn-crud-model>
<vn-data-viewer model="model">
@ -12,31 +13,27 @@
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th shrink></vn-th>
<vn-th field="isChecked" center>Is checked</vn-th>
<vn-th field="itemFk" number>Item</vn-th>
<vn-th expand>Description</vn-th>
<vn-th field="concept">Description</vn-th>
<vn-th field="quantity" number>Quantity</vn-th>
<vn-th field="originalQuantity" number>Original</vn-th>
<vn-th field="workerFk">Worker</vn-th>
<vn-th field="state" shrink>State</vn-th>
<vn-th field="created" expand>Created</vn-th>
<vn-th></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="sale in sales">
<vn-td shrink>
<vn-icon
class="bright"
icon="warning"
ng-if="sale.quantity != sale.originalQuantity"
vn-tooltip="The quantity do not match">
</vn-icon>
<vn-tr ng-repeat="sale in $ctrl.sales">
<vn-td center>
<span class="chip {{$ctrl.chipHasSaleGroupDetail(sale.preparingList.hasSaleGroupDetail)}} vn-mx-xs chip2" vn-tooltip="has saleGroupDetail"></span>
<span class="chip {{$ctrl.chipIsPreviousSelected(sale.preparingList.isPreviousSelected)}} vn-ml-xs" vn-tooltip="is previousSelected"></span>
<span class="chip {{$ctrl.chipIsPrevious(sale.preparingList.isPrevious)}} vn-mr-xs" vn-tooltip="is previous"></span>
<span class="chip {{$ctrl.chipIsPrepared(sale.preparingList.isPrepared)}} vn-mx-xs" vn-tooltip="is prepared"></span>
<span class="chip {{$ctrl.chipIsControled(sale.preparingList.isControled)}} vn-mx-xs" vn-tooltip="is controled"></span>
</vn-td>
<vn-td number>
<span
ng-click="itemDescriptor.show($event, sale.itemFk, sale.id)"
ng-click="$ctrl.showItemDescriptor($event, sale)"
class="link">
{{sale.itemFk | zeroFill:6}}
{{::sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td vn-fetched-tags>
@ -53,16 +50,18 @@
</vn-fetched-tags>
</vn-td>
<vn-td number>{{::sale.quantity}}</vn-td>
<vn-td number>{{::sale.originalQuantity}}</vn-td>
<vn-td expand>
<span
class="link"
ng-click="workerDescriptor.show($event, sale.workerFk)">
{{::sale.userNickname | dashIfEmpty}}
</span>
<vn-td actions>
<vn-icon-button
vn-click-stop="$ctrl.showSaleTracking(sale)"
vn-tooltip="Sale tracking"
icon="history">
</vn-icon-button>
<vn-icon-button
vn-click-stop="$ctrl.showItemShelvingSale(sale)"
vn-tooltip="ItemShelvings sale"
icon="icon-inventory">
</vn-icon-button>
</vn-td>
<vn-td shrink>{{::sale.state}}</vn-td>
<vn-td expand>{{::sale.created | date: 'dd/MM/yyyy HH:mm'}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
@ -70,8 +69,99 @@
</vn-data-viewer>
<vn-item-descriptor-popover
vn-id="item-descriptor"
warehouse-fk="$ctrl.ticket.warehouseFk">
warehouse-fk="$ctrl.ticket.warehouseFk"
ticket-fk="$ctrl.ticket.id">
</vn-item-descriptor-popover>
<vn-worker-descriptor-popover
vn-id="worker-descriptor">
</vn-worker-descriptor-popover>
<vn-popup vn-id="saleTracking">
<vn-crud-model
vn-id="modelSaleTracking"
url="SaleTrackings/listSaleTracking"
link="{saleFk: $ctrl.saleId}"
limit="20"
data="saleTrackings"
order="itemFk DESC"
auto-load="true">
</vn-crud-model>
<vn-data-viewer model="modelSaleTracking">
<vn-card class="vn-w-lg">
<vn-table model="modelSaleTracking">
<vn-thead>
<vn-tr>
<vn-th field="quantity" number>Quantity</vn-th>
<vn-th field="originalQuantity" number>Original</vn-th>
<vn-th field="workerFk">Worker</vn-th>
<vn-th field="state" shrink>State</vn-th>
<vn-th field="created" expand>Created</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="sale in saleTrackings">
<vn-td number>{{::sale.quantity}}</vn-td>
<vn-td number>{{::sale.originalQuantity}}</vn-td>
<vn-td expand>
<span
class="link"
ng-click="workerDescriptor.show($event, sale.workerFk)">
{{::sale.userNickname | dashIfEmpty}}
</span>
</vn-td>
<vn-td shrink>{{::sale.state}}</vn-td>
<vn-td expand>{{::sale.created | date: 'dd/MM/yyyy HH:mm'}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-item-descriptor-popover
vn-id="item-descriptor"
warehouse-fk="$ctrl.ticket.warehouseFk">
</vn-item-descriptor-popover>
<vn-worker-descriptor-popover
vn-id="worker-descriptor">
</vn-worker-descriptor-popover>
</vn-popup>
<vn-popup vn-id="itemShelvingSale">
<vn-crud-model
vn-id="modelSaleTracking"
url="ItemShelvingSales/filter"
link="{saleFk: $ctrl.saleId}"
limit="20"
data="$ctrl.itemShelvingSales"
auto-load="true">
</vn-crud-model>
<vn-data-viewer model="modelSaleTracking">
<vn-card class="vn-w-lg">
<vn-table model="modelSaleTracking">
<vn-thead>
<vn-tr>
<vn-th field="quantity" number>Quantity</vn-th>
<vn-th field="workerFk">Worker</vn-th>
<vn-th field="shelving" shrink>Shelving</vn-th>
<vn-th field="parking" shrink>Parking</vn-th>
<vn-th field="created" expand>Created</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="itemShelvingSale in $ctrl.itemShelvingSales">
<vn-td number>{{::itemShelvingSale.quantity}}</vn-td>
<vn-td expand>
<span
class="link"
ng-click="workerDescriptor.show($event, itemShelvingSale.userFk)">
{{::itemShelvingSale.name | dashIfEmpty}}
</span>
</vn-td>
<vn-td shrink>{{::itemShelvingSale.shelvingFk}}</vn-td>
<vn-td shrink>{{::itemShelvingSale.code}}</vn-td>
<vn-td expand>{{::itemShelvingSale.created | date: 'dd/MM/yyyy HH:mm'}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-worker-descriptor-popover
vn-id="worker-descriptor">
</vn-worker-descriptor-popover>
</vn-popup>

View File

@ -1,12 +1,100 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {}
class Controller extends Section {
constructor($element, $) {
super($element, $);
this.filter = {
include: [
{
relation: 'item'
}, {
relation: 'saleTracking',
scope: {
fields: ['isChecked']
}
}
]
};
}
get sales() {
return this._sales;
}
set sales(value) {
this._sales = value;
if (value) {
const query = `Sales/${this.$params.id}/salePreparingList`;
this.$http.get(query)
.then(res => {
this.salePreparingList = res.data;
for (const salePreparing of this.salePreparingList) {
for (const sale of this.sales) {
if (salePreparing.saleFk == sale.id)
sale.preparingList = salePreparing;
}
}
});
}
}
showItemDescriptor(event, sale) {
this.quicklinks = {
btnThree: {
icon: 'icon-transaction',
state: `item.card.diary({
id: ${sale.itemFk},
warehouseFk: ${this.ticket.warehouseFk},
lineFk: ${sale.id}
})`,
tooltip: 'Item diary'
}
};
this.$.itemDescriptor.show(event.target, sale.itemFk);
}
chipHasSaleGroupDetail(hasSaleGroupDetail) {
if (hasSaleGroupDetail) return 'pink';
else return 'message';
}
chipIsPreviousSelected(isPreviousSelected) {
if (isPreviousSelected) return 'notice';
else return 'message';
}
chipIsPrevious(isPrevious) {
if (isPrevious) return 'dark-notice';
else return 'message';
}
chipIsPrepared(isPrepared) {
if (isPrepared) return 'warning';
else return 'message';
}
chipIsControled(isControled) {
if (isControled) return 'yellow';
else return 'message';
}
showSaleTracking(sale) {
this.saleId = sale.id;
this.$.saleTracking.show();
}
showItemShelvingSale(sale) {
this.saleId = sale.id;
this.$.itemShelvingSale.show();
}
}
ngModule.vnComponent('vnTicketSaleTracking', {
template: require('./index.html'),
controller: Controller,
bindings: {
ticket: '<',
},
ticket: '<'
}
});

View File

@ -0,0 +1,6 @@
ItemShelvings sale: Carros línea
has saleGroupDetail: tiene detalle grupo lineas
is previousSelected: es previa seleccionada
is previous: es previa
is prepared: esta preparado
is controled: esta controlado

View File

@ -0,0 +1,7 @@
@import "variables";
.chip {
display: inline-block;
min-width: 15px;
min-height: 25px;
}