merge
gitea/salix/test This commit looks good Details

This commit is contained in:
Joan Sanchez 2019-09-02 09:57:46 +02:00
commit 67733e6e08
179 changed files with 3925 additions and 4081 deletions

1
Jenkinsfile vendored
View File

@ -68,7 +68,6 @@ pipeline {
environment { environment {
NODE_ENV = "" NODE_ENV = ""
FIREFOX_BIN = "/opt/firefox/firefox-bin" FIREFOX_BIN = "/opt/firefox/firefox-bin"
DB_HOST = "${env.DOCKER_HOST_2}"
} }
steps { steps {
nodejs('node-lts') { nodejs('node-lts') {

View File

@ -19,8 +19,8 @@ module.exports = Self => {
next(); next();
}); });
Self.remoteMethod('getCurrentUserName', { Self.remoteMethod('getCurrentUserData', {
description: 'Gets the current user name', description: 'Gets the current user data',
accepts: [ accepts: [
{ {
arg: 'context', arg: 'context',
@ -31,21 +31,22 @@ module.exports = Self => {
} }
], ],
returns: { returns: {
type: 'string', type: 'object',
root: true root: true
}, },
http: { http: {
verb: 'GET', verb: 'GET',
path: '/getCurrentUserName' path: '/getCurrentUserData'
} }
}); });
Self.getCurrentUserName = async function(ctx) { Self.getCurrentUserData = async function(ctx) {
let filter = {fields: ['name']}; let filter = {fields: ['name']};
let userId = ctx.req.accessToken.userId; let userId = ctx.req.accessToken.userId;
let account = await Self.findById(userId, filter); let account = await Self.findById(userId, filter);
let worker = await Self.app.models.Worker.findOne({where: {userFk: userId}, fields: ['id']});
return account.name; return {accountName: account.name, workerId: worker.id};
}; };
/** /**

View File

@ -0,0 +1,6 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Dms', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
('ClaimDms', 'removeFile', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('ClaimDms', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Claim', 'uploadFile', 'WRITE', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,15 @@
CREATE TABLE `vn`.`claimDms` (
`claimFk` INT UNSIGNED NOT NULL,
`dmsFk` INT NOT NULL,
PRIMARY KEY (`claimFk`, `dmsFk`),
INDEX `dmsFk_idx` (`dmsFk` ASC),
CONSTRAINT `claimFk`
FOREIGN KEY (`claimFk`)
REFERENCES `vn2008`.`cl_main` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT `dmsFk`
FOREIGN KEY (`dmsFk`)
REFERENCES `vn2008`.`gestdoc` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE);

View File

@ -0,0 +1,2 @@
ALTER TABLE `vn`.`claimLog`
CHANGE COLUMN `id` `id` INT(11) NOT NULL AUTO_INCREMENT ;

File diff suppressed because one or more lines are too long

View File

@ -133,19 +133,13 @@ INSERT INTO `vn`.`payDem`(`id`, `payDem`)
(1, 10), (1, 10),
(2, 20); (2, 20);
INSERT INTO `vn2008`.`zones`(`zone_id`, `name`, `printingOrder`) INSERT INTO `vn`.`province`(`id`, `name`, `countryFk`, `warehouseFk`)
VALUES VALUES
(1, 'zone one', 1), (1, 'Province one', 1, NULL),
(2, 'zone two', 2), (2, 'Province two', 1, NULL),
(3, 'zone three', 3); (3, 'Province three', 1, NULL),
(4, 'Province four', 1, NULL),
INSERT INTO `vn`.`province`(`id`, `name`, `countryFk`, `warehouseFk`, `zoneFk`) (5, 'Province five', 1, NULL);
VALUES
(1, 'Province one', 1, NULL, 1),
(2, 'Province two', 1, NULL, 2),
(3, 'Province three', 1, NULL, 3),
(4, 'Province four', 1, NULL, 2),
(5, 'Province five', 1, NULL, 1);
INSERT INTO `vn`.`town`(`id`, `name`, `provinceFk`) INSERT INTO `vn`.`town`(`id`, `name`, `provinceFk`)
VALUES VALUES
@ -404,6 +398,18 @@ INSERT INTO `vn`.`invoiceOutTax` (`invoiceOutFk`, `taxableBase`, `vat`, `pgcFk`)
(4, 8.07, 0.81, 4770000010), (4, 8.07, 0.81, 4770000010),
(5, 8.07, 0.81, 4770000010); (5, 8.07, 0.81, 4770000010);
INSERT INTO `vn`.`expence`(`id`, `taxTypeFk`, `name`, `isWithheld`)
VALUES
(2000000000, 1, 'Inmovilizado pendiente', 0),
(2000000000, 3, 'Compra de bienes de inmovilizado', 0),
(4751000000, 0, 'Retenciones', 1),
(4751000000, 1, 'Retenciones', 1),
(4751000000, 6, 'Retencion', 0),
(6210000567, 0, 'Alquiler VNH', 0),
(7001000000, 1, 'Mercaderia', 0);
INSERT INTO `vn`.`invoiceOutExpence`(`id`, `invoiceOutFk`, `amount`, `expenceFk`, `created`) INSERT INTO `vn`.`invoiceOutExpence`(`id`, `invoiceOutFk`, `amount`, `expenceFk`, `created`)
VALUES VALUES
(1, 1, 813.06, 2000000000, CURDATE()), (1, 1, 813.06, 2000000000, CURDATE()),
@ -435,8 +441,8 @@ INSERT INTO `vn`.`zone` (`id`, `name`, `hour`, `warehouseFk`, `agencyModeFk`, `t
(4, 'Zone 247 B', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 2, 7, 1, 2, 0), (4, 'Zone 247 B', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 2, 7, 1, 2, 0),
(5, 'Zone expensive A', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 1, 8, 1, 1000, 0), (5, 'Zone expensive A', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 1, 8, 1, 1000, 0),
(6, 'Zone expensive B', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 2, 8, 1, 1000, 0), (6, 'Zone expensive B', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 2, 8, 1, 1000, 0),
(7, 'Zone refund', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 1, 10, 0, 0, 0), (7, 'Zone refund', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 1, 23, 0, 0, 0),
(8, 'Zone others', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 1, 23, 0, 0, 0), (8, 'Zone others', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 1, 10, 0, 0, 0),
(9, 'Zone superMan', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 1, 2, 0, 0, 0), (9, 'Zone superMan', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 1, 2, 0, 0, 0),
(10, 'Zone teleportation', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 3, 3, 0, 0, 0), (10, 'Zone teleportation', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 3, 3, 0, 0, 0),
(11, 'Zone pickup C', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 5, 1, 0, 0, 0), (11, 'Zone pickup C', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 5, 1, 0, 0, 0),
@ -467,8 +473,8 @@ INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeF
(20, 1, 5, 5, 3, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)), (20, 1, 5, 5, 3, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(21, NULL, 5, 5, NULL, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Holland', 102, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)), (21, NULL, 5, 5, NULL, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Holland', 102, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(22, NULL, 5, 5, NULL, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Japan', 103, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)), (22, NULL, 5, 5, NULL, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Japan', 103, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(23, NULL, 23, 1, NULL, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 101, 'address 21', 121, NULL, 0, 8, CURDATE()), (23, NULL, 10, 1, NULL, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 101, 'address 21', 121, NULL, 0, 8, CURDATE()),
(24 ,NULL, 23, 1, NULL, CURDATE(), CURDATE(), 101, 'Bruce Wayne', 1, NULL, 0, 8, CURDATE()); (24 ,NULL, 10, 1, NULL, CURDATE(), CURDATE(), 101, 'Bruce Wayne', 1, NULL, 0, 8, CURDATE());
INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`) INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`)
VALUES VALUES
@ -1082,14 +1088,6 @@ INSERT INTO `vn2008`.`tblContadores`(`id`,`FechaInventario`)
VALUES VALUES
(1,DATE_ADD(CURDATE(),INTERVAL -1 MONTH)); (1,DATE_ADD(CURDATE(),INTERVAL -1 MONTH));
INSERT INTO `vn2008`.`Estados` (`Id_Estado`, `Estado`)
VALUES
('1', 'En Espera');
INSERT INTO `vn2008`.`Informes` (`Id_Informe`, `Informe`)
VALUES
('30', 'Generar factura PDF');
INSERT INTO `vn`.`deliveryMethod`(`id`, `code`, `description`) INSERT INTO `vn`.`deliveryMethod`(`id`, `code`, `description`)
VALUES VALUES
(1, 'AGENCY', 'Agencia'), (1, 'AGENCY', 'Agencia'),
@ -1508,7 +1506,7 @@ INSERT INTO `vn`.`workCenter` (`id`, `name`, `warehouseFk`)
('1', 'Silla', '1'), ('1', 'Silla', '1'),
('5', 'Madrid', '5'); ('5', 'Madrid', '5');
INSERT INTO `vn2008`.`workcenter_holiday` (`workcenter_id`, `day`, `year`) INSERT INTO `vn`.`workCenterHoliday` (`workCenterFk`, `days`, `year`)
VALUES VALUES
('1', '27.5', YEAR(CURDATE())), ('1', '27.5', YEAR(CURDATE())),
('5', '22', YEAR(CURDATE())), ('5', '22', YEAR(CURDATE())),
@ -1551,6 +1549,17 @@ INSERT INTO `vn`.`sharingCart`(`id`, `workerFk`, `started`, `ended`, `workerSubs
VALUES VALUES
(1, 18, DATE_ADD(CURDATE(), INTERVAL -5 DAY), DATE_ADD(CURDATE(), INTERVAL +15 DAY), 19, DATE_ADD(CURDATE(), INTERVAL -5 DAY)); (1, 18, DATE_ADD(CURDATE(), INTERVAL -5 DAY), DATE_ADD(CURDATE(), INTERVAL +15 DAY), 19, DATE_ADD(CURDATE(), INTERVAL -5 DAY));
INSERT INTO `vn`.`zoneGeo`(`id`, `name`, `lft`, `rgt`, `depth`, `sons`)
VALUES
(1, 'Origin', 1, 16, 0, 5),
(2, 'España', 2, 9, 1, 3),
(6, 'Valencia', 3, 8, 2, 2),
(7, 'Silla', 4, 7, 3, 1),
(8, '46460', 5, 6, 4, 0),
(3, 'Francia', 10, 11, 1, 0),
(4, 'Holanda', 12, 13, 1, 0),
(5, 'Portugal', 14, 15, 1, 0);
INSERT INTO `vn`.`zoneIncluded` (`zoneFk`, `geoFk`, `isIncluded`) INSERT INTO `vn`.`zoneIncluded` (`zoneFk`, `geoFk`, `isIncluded`)
VALUES VALUES
(1, 3, 0), (1, 3, 0),
@ -1828,13 +1837,14 @@ INSERT INTO `vn`.`dmsType`(`id`, `name`, `path`, `readRoleFk`, `writeRoleFk`, `c
(16, 'Logistica', 'logistica', NULL, NULL, 'logistics'), (16, 'Logistica', 'logistica', NULL, NULL, 'logistics'),
(17, 'cmr', 'cmr', NULL, NULL, 'cmr'), (17, 'cmr', 'cmr', NULL, NULL, 'cmr'),
(18, 'dua', 'dua', NULL, NULL, 'dua'), (18, 'dua', 'dua', NULL, NULL, 'dua'),
(19, 'inmovilizado', 'inmovilizado', NULL, NULL, 'fixedAssets'); (19, 'inmovilizado', 'inmovilizado', NULL, NULL, 'fixedAssets'),
(20, 'Reclamación', 'reclamacion', 1, 1, 'claim');
INSERT INTO `vn`.`dms`(`id`, `dmsTypeFk`, `file`, `contentType`, `workerFk`, `warehouseFk`, `companyFk`, `hardCopyNumber`, `hasFile`, `reference`, `description`, `created`) INSERT INTO `vn`.`dms`(`id`, `dmsTypeFk`, `file`, `contentType`, `workerFk`, `warehouseFk`, `companyFk`, `hardCopyNumber`, `hasFile`, `reference`, `description`, `created`)
VALUES VALUES
(1, 14, '1.txt', 'text/plain', 5, 1, 442, NULL, FALSE, 'Ticket:11', 'Ticket:11 dms for the ticket', CURDATE()), (1, 14, '1.txt', 'text/plain', 5, 1, 442, NULL, FALSE, 'Ticket:11', 'Ticket:11 dms for the ticket', CURDATE()),
(2, 5, '2.txt', 'text/plain', 5, 1, 442, 1, TRUE, 'Client:101', 'Client:101 dms for the client', CURDATE()), (2, 5, '2.txt', 'text/plain', 5, 1, 442, 1, TRUE, 'Client:104', 'Client:104 dms for the client', CURDATE()),
(3, 5, '3.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Client: 101', 'Client:101 readme', CURDATE()); (3, 5, '3.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Client: 104', 'Client:104 readme', CURDATE());
INSERT INTO `vn`.`ticketDms`(`ticketFk`, `dmsFk`) INSERT INTO `vn`.`ticketDms`(`ticketFk`, `dmsFk`)
VALUES VALUES
@ -1842,8 +1852,8 @@ INSERT INTO `vn`.`ticketDms`(`ticketFk`, `dmsFk`)
INSERT INTO `vn`.`clientDms`(`clientFk`, `dmsFk`) INSERT INTO `vn`.`clientDms`(`clientFk`, `dmsFk`)
VALUES VALUES
(101, 2), (104, 2),
(101, 3); (104, 3);
INSERT INTO `vn`.`device` (`sn`, `model`, `userFk`) INSERT INTO `vn`.`device` (`sn`, `model`, `userFk`)
VALUES VALUES

File diff suppressed because it is too large Load Diff

View File

@ -41,7 +41,6 @@ TABLES=(
cplusTaxBreak cplusTaxBreak
pgc pgc
tag tag
zoneGeo
) )
dump_tables ${TABLES[@]} dump_tables ${TABLES[@]}
@ -57,14 +56,12 @@ TABLES=(
container container
department department
escritos escritos
Gastos
Grupos Grupos
iva_group_codigo iva_group_codigo
Monedas Monedas
state state
tarifa_componentes tarifa_componentes
tarifa_componentes_series tarifa_componentes_series
Tintas
) )
dump_tables ${TABLES[@]} dump_tables ${TABLES[@]}

View File

@ -14,9 +14,10 @@ module.exports = function createNightmare(width = 1280, height = 720) {
}).viewport(width, height); }).viewport(width, height);
nightmare.on('console', (type, message, ...args) => { nightmare.on('console', (type, message, ...args) => {
if (type === 'error') if (type === 'error') {
console[type](message, ...args);
throw new Error(message); throw new Error(message);
else } else
console[type](message, ...args); console[type](message, ...args);
}); });

View File

@ -106,10 +106,10 @@ export default {
agencyAutocomplete: 'vn-autocomplete[field="$ctrl.address.agencyModeFk"]', agencyAutocomplete: 'vn-autocomplete[field="$ctrl.address.agencyModeFk"]',
phoneInput: `${components.vnTextfield}[name="phone"]`, phoneInput: `${components.vnTextfield}[name="phone"]`,
mobileInput: `${components.vnTextfield}[name="mobile"]`, mobileInput: `${components.vnTextfield}[name="mobile"]`,
defaultAddress: 'vn-client-address-index vn-horizontal:nth-child(1) div[name="street"]', defaultAddress: 'vn-client-address-index div:nth-child(1) div[name="street"]',
secondMakeDefaultStar: 'vn-client-address-index vn-card vn-horizontal:nth-child(2) vn-icon-button[icon="star_border"]', secondMakeDefaultStar: 'vn-client-address-index vn-card div:nth-child(2) vn-icon-button[icon="star_border"]',
firstEditButton: 'vn-client-address-index vn-icon-button[icon="edit"]', firstEditAddress: 'vn-client-address-index div:nth-child(1) > a',
secondEditButton: 'vn-client-address-index vn-horizontal:nth-child(2) vn-icon-button[icon="edit"]', secondEditAddress: 'vn-client-address-index div:nth-child(2) > a',
activeCheckbox: 'vn-check[label="Enabled"] md-checkbox', activeCheckbox: 'vn-check[label="Enabled"] md-checkbox',
equalizationTaxCheckbox: 'vn-client-address-edit vn-check[label="Is equalizated"] md-checkbox', equalizationTaxCheckbox: 'vn-client-address-edit vn-check[label="Is equalizated"] md-checkbox',
firstObservationTypeAutocomplete: 'vn-client-address-edit [name=observations] :nth-child(1) [field="observation.observationTypeFk"]', firstObservationTypeAutocomplete: 'vn-client-address-edit [name=observations] :nth-child(1) [field="observation.observationTypeFk"]',
@ -174,6 +174,12 @@ export default {
confirmFirstPaymentButton: 'vn-client-web-payment vn-tr:nth-child(1) vn-icon-button[icon="done_all"]', confirmFirstPaymentButton: 'vn-client-web-payment vn-tr:nth-child(1) vn-icon-button[icon="done_all"]',
firstPaymentConfirmed: 'vn-client-web-payment vn-tr:nth-child(1) vn-icon[icon="check"][aria-hidden="false"]' firstPaymentConfirmed: 'vn-client-web-payment vn-tr:nth-child(1) vn-icon[icon="check"][aria-hidden="false"]'
}, },
dms: {
deleteFileButton: 'vn-client-dms-index vn-table vn-tr:nth-child(1) vn-icon-button[icon="delete"]',
firstDocWorker: 'vn-client-dms-index vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(8) > span',
firstDocWorkerDescriptor: 'vn-client-dms-index > vn-worker-descriptor-popover > vn-popover',
acceptDeleteButton: 'vn-client-dms-index > vn-confirm button[response="ACCEPT"]'
},
itemsIndex: { itemsIndex: {
searchIcon: 'vn-item-index vn-searchbar vn-icon[icon="search"]', searchIcon: 'vn-item-index vn-searchbar vn-icon[icon="search"]',
createItemButton: `${components.vnFloatButton}`, createItemButton: `${components.vnFloatButton}`,
@ -320,16 +326,20 @@ export default {
advancedSearchInvoiceOut: 'vn-ticket-index vn-searchbar vn-ticket-search-panel vn-textfield[model="filter.refFk"] input', advancedSearchInvoiceOut: 'vn-ticket-index vn-searchbar vn-ticket-search-panel vn-textfield[model="filter.refFk"] input',
newTicketButton: 'vn-ticket-index > a', newTicketButton: 'vn-ticket-index > a',
searchResult: 'vn-ticket-index vn-card > div > vn-table > div > vn-tbody > a.vn-tr', searchResult: 'vn-ticket-index vn-card > div > vn-table > div > vn-tbody > a.vn-tr',
searchWeeklyResult: 'vn-ticket-weekly-index vn-table vn-tbody > vn-tr',
searchResultDate: 'vn-ticket-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(5)', searchResultDate: 'vn-ticket-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(5)',
searchTicketInput: `vn-ticket-index ${components.vnTextfield}`, searchTicketInput: `vn-ticket-index ${components.vnTextfield}`,
searchWeeklyTicketInput: `vn-ticket-weekly-index ${components.vnTextfield}`,
searchWeeklyClearInput: 'vn-ticket-weekly-index vn-searchbar i[class="material-icons clear"]',
advancedSearchButton: 'vn-ticket-index vn-searchbar > vn-popover vn-ticket-search-panel vn-submit[label="Search"] input', advancedSearchButton: 'vn-ticket-index vn-searchbar > vn-popover vn-ticket-search-panel vn-submit[label="Search"] input',
searchButton: 'vn-ticket-index vn-searchbar vn-icon[icon="search"]', searchButton: 'vn-ticket-index vn-searchbar vn-icon[icon="search"]',
searchWeeklyButton: 'vn-ticket-weekly-index vn-searchbar vn-icon[icon="search"]',
moreMenu: 'vn-ticket-index vn-icon-menu[vn-id="more-button"] > div > vn-icon', moreMenu: 'vn-ticket-index vn-icon-menu[vn-id="more-button"] > div > vn-icon',
moreMenuTurns: 'vn-ticket-index vn-icon-menu vn-drop-down > vn-popover li:nth-child(2)', moreMenuWeeklyTickets: 'vn-ticket-index vn-icon-menu vn-drop-down > vn-popover li:nth-child(2)',
sixthWeeklyTicketTurn: 'vn-ticket-weekly vn-table vn-tr:nth-child(6) vn-autocomplete[field="weekly.weekDay"] input', sixthWeeklyTicket: 'vn-ticket-weekly-index vn-table vn-tr:nth-child(6) vn-autocomplete[field="weekly.weekDay"] input',
weeklyTicket: 'vn-ticket-weekly vn-table > div > vn-tbody > vn-tr', weeklyTicket: 'vn-ticket-weekly-index vn-table > div > vn-tbody > vn-tr',
sixthWeeklyTicketDeleteIcon: 'vn-ticket-weekly vn-tr:nth-child(6) vn-icon-button[icon="delete"]', firstWeeklyTicketDeleteIcon: 'vn-ticket-weekly-index vn-tr:nth-child(1) vn-icon-button[icon="delete"]',
acceptDeleteTurn: 'vn-ticket-weekly > vn-confirm[vn-id="deleteWeekly"] button[response="ACCEPT"]' acceptDeleteTurn: 'vn-ticket-weekly-index > vn-confirm[vn-id="deleteWeekly"] button[response="ACCEPT"]'
}, },
createTicketView: { createTicketView: {
clientAutocomplete: 'vn-ticket-create vn-autocomplete[field="$ctrl.clientFk"]', clientAutocomplete: 'vn-ticket-create vn-autocomplete[field="$ctrl.clientFk"]',
@ -349,12 +359,16 @@ export default {
moreMenuAddToTurn: 'vn-ticket-descriptor vn-drop-down > vn-popover ul > li[name="Add turn"]', moreMenuAddToTurn: 'vn-ticket-descriptor vn-drop-down > vn-popover ul > li[name="Add turn"]',
moreMenuDeleteTicket: 'vn-ticket-descriptor vn-drop-down > vn-popover ul > li[name="Delete ticket"]', moreMenuDeleteTicket: 'vn-ticket-descriptor vn-drop-down > vn-popover ul > li[name="Delete ticket"]',
moreMenuMakeInvoice: 'vn-ticket-descriptor vn-drop-down > vn-popover ul > li[name="Make invoice"]', moreMenuMakeInvoice: 'vn-ticket-descriptor vn-drop-down > vn-popover ul > li[name="Make invoice"]',
moreMenuChangeShippedHour: 'vn-ticket-descriptor vn-drop-down > vn-popover ul > li[name="Change shipped hour"]',
changeShippedHourInput: 'vn-ticket-descriptor > vn-dialog.ng-isolate-scope.vn-dialog.shown vn-input-time input',
addStowawayDialogFirstTicket: 'vn-ticket-descriptor > vn-add-stowaway > vn-dialog vn-table vn-tbody vn-tr', addStowawayDialogFirstTicket: 'vn-ticket-descriptor > vn-add-stowaway > vn-dialog vn-table vn-tbody vn-tr',
shipButton: 'vn-ticket-descriptor > div > div.body > div.quicklinks vn-icon[icon="icon-stowaway"]', shipButton: 'vn-ticket-descriptor > div > div.body > div.quicklinks vn-icon[icon="icon-stowaway"]',
thursdayButton: 'vn-ticket-descriptor > vn-dialog > div > form > div.body > tpl-body > div > vn-tool-bar > vn-button:nth-child(4)', thursdayButton: 'vn-ticket-descriptor > vn-dialog > div > form > div.body > tpl-body > div > vn-tool-bar > vn-button:nth-child(4)',
saturdayButton: 'vn-ticket-descriptor > vn-dialog > div > form > div.body > tpl-body > div > vn-tool-bar > vn-button:nth-child(6)', saturdayButton: 'vn-ticket-descriptor > vn-dialog > div > form > div.body > tpl-body > div > vn-tool-bar > vn-button:nth-child(6)',
closeStowawayDialog: 'vn-ticket-descriptor > vn-add-stowaway > vn-dialog > div > button[class="close"]', closeStowawayDialog: 'vn-ticket-descriptor > vn-add-stowaway > vn-dialog > div > button[class="close"]',
acceptDeleteButton: 'vn-ticket-descriptor button[response="ACCEPT"]', acceptDeleteButton: 'vn-ticket-descriptor button[response="ACCEPT"]',
acceptChangeHourButton: 'vn-ticket-descriptor vn-dialog[vn-id="changeShippedDialog"] button[response="ACCEPT"]',
descriptorDeliveryDate: 'vn-ticket-descriptor > div > div.body > div.attributes > vn-label-value:nth-child(6) > section > span',
acceptInvoiceOutButton: 'vn-ticket-descriptor vn-confirm[vn-id="makeInvoiceConfirmation"] button[response="ACCEPT"]', acceptInvoiceOutButton: 'vn-ticket-descriptor vn-confirm[vn-id="makeInvoiceConfirmation"] button[response="ACCEPT"]',
acceptDeleteStowawayButton: 'vn-ticket-descriptor > vn-remove-stowaway button[response="ACCEPT"]' acceptDeleteStowawayButton: 'vn-ticket-descriptor > vn-remove-stowaway button[response="ACCEPT"]'
}, },
@ -393,6 +407,8 @@ export default {
moreMenuUnmarkReseved: 'vn-ticket-sale vn-tool-bar > vn-button-menu[vn-id="more-button"] vn-drop-down > vn-popover ul > li[name="Unmark as reserved"]', moreMenuUnmarkReseved: 'vn-ticket-sale vn-tool-bar > vn-button-menu[vn-id="more-button"] vn-drop-down > vn-popover ul > li[name="Unmark as reserved"]',
moreMenuUpdateDiscount: 'vn-ticket-sale vn-tool-bar > vn-button-menu[vn-id="more-button"] vn-drop-down > vn-popover ul > li[name="Update discount"]', moreMenuUpdateDiscount: 'vn-ticket-sale vn-tool-bar > vn-button-menu[vn-id="more-button"] vn-drop-down > vn-popover ul > li[name="Update discount"]',
moreMenuUpdateDiscountInput: 'vn-ticket-sale vn-dialog form vn-ticket-sale-edit-discount vn-input-number[model="$ctrl.newDiscount"] input', moreMenuUpdateDiscountInput: 'vn-ticket-sale vn-dialog form vn-ticket-sale-edit-discount vn-input-number[model="$ctrl.newDiscount"] input',
transferQuantityInput: 'vn-ticket-sale vn-popover.transfer.ng-isolate-scope.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable > span > text',
transferQuantityCell: 'vn-ticket-sale vn-popover.transfer.ng-isolate-scope.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable',
firstSaleClaimIcon: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(1) vn-icon[icon="icon-claims"]', firstSaleClaimIcon: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(1) vn-icon[icon="icon-claims"]',
firstSaleDescriptorImage: 'vn-ticket-sale vn-item-descriptor-popover > vn-popover vn-item-descriptor img', firstSaleDescriptorImage: 'vn-ticket-sale vn-item-descriptor-popover > vn-popover vn-item-descriptor img',
firstSaleText: 'vn-table div > vn-tbody > vn-tr:nth-child(1)', firstSaleText: 'vn-table div > vn-tbody > vn-tr:nth-child(1)',
@ -416,13 +432,15 @@ export default {
secondSaleDiscount: 'vn-ticket-sale vn-tr:nth-child(2) vn-td:nth-child(8)', secondSaleDiscount: 'vn-ticket-sale vn-tr:nth-child(2) vn-td:nth-child(8)',
secondSaleImport: 'vn-ticket-sale vn-tr:nth-child(2) vn-td:nth-child(9)', secondSaleImport: 'vn-ticket-sale vn-tr:nth-child(2) vn-td:nth-child(9)',
secondSaleText: 'vn-table div > vn-tbody > vn-tr:nth-child(2)', secondSaleText: 'vn-table div > vn-tbody > vn-tr:nth-child(2)',
secondSaleQuantity: 'vn-input-number[model="sale.quantity"]:nth-child(2) input',
secondSaleQuantityCell: 'vn-ticket-sale vn-tr:nth-child(2) > vn-td-editable:nth-child(5)',
totalImport: 'vn-ticket-sale > vn-vertical > vn-card > div > vn-vertical > vn-horizontal > vn-one > p:nth-child(3) > strong', totalImport: 'vn-ticket-sale > vn-vertical > vn-card > div > vn-vertical > vn-horizontal > vn-one > p:nth-child(3) > strong',
selectAllSalesCheckbox: 'vn-ticket-sale vn-thead vn-check md-checkbox', selectAllSalesCheckbox: 'vn-ticket-sale vn-thead vn-check md-checkbox',
secondSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(2) vn-check[field="sale.checked"] md-checkbox', secondSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(2) vn-check[field="sale.checked"] md-checkbox',
thirdSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(3) vn-check[field="sale.checked"] md-checkbox', thirdSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(3) vn-check[field="sale.checked"] md-checkbox',
deleteSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="delete"]', deleteSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="delete"]',
transferSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="call_split"]', transferSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="call_split"]',
moveToTicketInput: 'vn-ticket-sale vn-popover.transfer vn-textfield[model="$ctrl.receiverTicketId"] input', moveToTicketInput: 'vn-ticket-sale vn-popover.transfer vn-textfield[model="$ctrl.transfer.ticketId"] input',
moveToTicketInputClearButton: 'vn-popover.shown i[title="Clear"]', moveToTicketInputClearButton: 'vn-popover.shown i[title="Clear"]',
moveToTicketButton: 'vn-ticket-sale vn-popover.transfer vn-icon[icon="arrow_forward_ios"]', moveToTicketButton: 'vn-ticket-sale vn-popover.transfer vn-icon[icon="arrow_forward_ios"]',
moveToNewTicketButton: 'vn-ticket-sale vn-popover.transfer vn-button[label="New ticket"]', moveToNewTicketButton: 'vn-ticket-sale vn-popover.transfer vn-button[label="New ticket"]',
@ -520,7 +538,7 @@ export default {
saveButton: `${components.vnSubmit}` saveButton: `${components.vnSubmit}`
}, },
claimDetail: { claimDetail: {
secondItemDiscount: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(7) > span', secondItemDiscount: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(6) > span',
discountInput: 'vn-claim-detail vn-popover vn-input-number[model="$ctrl.newDiscount"] > div > div > div.infix > input', discountInput: 'vn-claim-detail vn-popover vn-input-number[model="$ctrl.newDiscount"] > div > div > div.infix > input',
discoutPopoverMana: 'vn-claim-detail > vn-popover > div > div.content > div > vn-horizontal > h5', discoutPopoverMana: 'vn-claim-detail > vn-popover > div > div.content > div > vn-horizontal > h5',
addItemButton: 'vn-claim-detail a vn-float-button', addItemButton: 'vn-claim-detail a vn-float-button',
@ -528,7 +546,7 @@ export default {
claimDetailLine: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr', claimDetailLine: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr',
firstItemQuantityInput: 'vn-claim-detail vn-tr:nth-child(1) vn-input-number[model="saleClaimed.quantity"] input', firstItemQuantityInput: 'vn-claim-detail vn-tr:nth-child(1) vn-input-number[model="saleClaimed.quantity"] input',
totalClaimed: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-horizontal > div > vn-label-value:nth-child(2) > section > span', totalClaimed: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-horizontal > div > vn-label-value:nth-child(2) > section > span',
secondItemDeleteButton: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(9) > vn-icon-button > button > vn-icon > i' secondItemDeleteButton: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(8) > vn-icon-button > button > vn-icon > i'
}, },
claimDevelopment: { claimDevelopment: {
addDevelopmentButton: 'vn-claim-development > vn-vertical > vn-card > div > vn-vertical > vn-one > vn-icon-button > button > vn-icon', addDevelopmentButton: 'vn-claim-development > vn-vertical > vn-card > div > vn-vertical > vn-one > vn-icon-button > button > vn-icon',
@ -562,6 +580,10 @@ export default {
searchButton: 'vn-order-index vn-searchbar vn-icon[icon="search"]', searchButton: 'vn-order-index vn-searchbar vn-icon[icon="search"]',
createOrderButton: `${components.vnFloatButton}`, createOrderButton: `${components.vnFloatButton}`,
}, },
orderDescriptor: {
returnToModuleIndexButton: 'vn-order-descriptor a[ui-sref="order.index"]',
acceptNavigationButton: 'vn-order-basic-data vn-confirm button[response=ACCEPT]'
},
createOrderView: { createOrderView: {
clientAutocomplete: 'vn-autocomplete[label="Client"]', clientAutocomplete: 'vn-autocomplete[label="Client"]',
addressAutocomplete: 'vn-autocomplete[label="Address"]', addressAutocomplete: 'vn-autocomplete[label="Address"]',

View File

@ -14,7 +14,7 @@ describe('Client Edit fiscalData path', () => {
// Confirms all addresses have EQtax false for future propagation test step 1 // Confirms all addresses have EQtax false for future propagation test step 1
it(`should click on the 1st edit icon to check EQtax isnt checked`, async() => { it(`should click on the 1st edit icon to check EQtax isnt checked`, async() => {
const result = await nightmare const result = await nightmare
.waitToClick(selectors.clientAddresses.firstEditButton) .waitToClick(selectors.clientAddresses.firstEditAddress)
.checkboxState(selectors.clientAddresses.equalizationTaxCheckbox); .checkboxState(selectors.clientAddresses.equalizationTaxCheckbox);
expect(result).toBe('unchecked'); expect(result).toBe('unchecked');
@ -24,7 +24,7 @@ describe('Client Edit fiscalData path', () => {
it(`should go back to addresses then select the second one and confirm the EQtax isnt checked`, async() => { it(`should go back to addresses then select the second one and confirm the EQtax isnt checked`, async() => {
const result = await nightmare const result = await nightmare
.waitToClick(selectors.clientAddresses.addressesButton) .waitToClick(selectors.clientAddresses.addressesButton)
.waitToClick(selectors.clientAddresses.secondEditButton) .waitToClick(selectors.clientAddresses.secondEditAddress)
.checkboxState(selectors.clientAddresses.equalizationTaxCheckbox); .checkboxState(selectors.clientAddresses.equalizationTaxCheckbox);
expect(result).toBe('unchecked'); expect(result).toBe('unchecked');
@ -67,6 +67,7 @@ describe('Client Edit fiscalData path', () => {
.write(selectors.clientFiscalData.fiscalIdInput, 'INVALID!') .write(selectors.clientFiscalData.fiscalIdInput, 'INVALID!')
.clearInput(selectors.clientFiscalData.addressInput) .clearInput(selectors.clientFiscalData.addressInput)
.write(selectors.clientFiscalData.addressInput, 'Somewhere edited') .write(selectors.clientFiscalData.addressInput, 'Somewhere edited')
.autocompleteSearch(selectors.clientFiscalData.cityAutocomplete, 'Valencia')
.autocompleteSearch(selectors.clientFiscalData.postcodeAutocomplete, '46000') .autocompleteSearch(selectors.clientFiscalData.postcodeAutocomplete, '46000')
.waitToClick(selectors.clientFiscalData.activeCheckbox) .waitToClick(selectors.clientFiscalData.activeCheckbox)
.waitToClick(selectors.clientFiscalData.frozenCheckbox) .waitToClick(selectors.clientFiscalData.frozenCheckbox)
@ -80,7 +81,7 @@ describe('Client Edit fiscalData path', () => {
.waitForLastSnackbar(); .waitForLastSnackbar();
expect(result).toEqual('Invalid Tax number'); expect(result).toEqual('Invalid Tax number');
}, 15000); });
it(`should edit the fiscal this time with a valid fiscal id`, async() => { it(`should edit the fiscal this time with a valid fiscal id`, async() => {
const result = await nightmare const result = await nightmare
@ -134,7 +135,7 @@ describe('Client Edit fiscalData path', () => {
// confirm all addresses have now EQtax checked step 2 // confirm all addresses have now EQtax checked step 2
it(`should click on the 1st edit icon to confirm EQtax is checked`, async() => { it(`should click on the 1st edit icon to confirm EQtax is checked`, async() => {
const result = await nightmare const result = await nightmare
.waitToClick(selectors.clientAddresses.firstEditButton) .waitToClick(selectors.clientAddresses.firstEditAddress)
.checkboxState(selectors.clientAddresses.equalizationTaxCheckbox); .checkboxState(selectors.clientAddresses.equalizationTaxCheckbox);
expect(result).toBe('checked'); expect(result).toBe('checked');
@ -144,7 +145,7 @@ describe('Client Edit fiscalData path', () => {
it(`should go back to addresses then select the second one and confirm the EQtax is checked`, async() => { it(`should go back to addresses then select the second one and confirm the EQtax is checked`, async() => {
const result = await nightmare const result = await nightmare
.waitToClick(selectors.clientAddresses.addressesButton) .waitToClick(selectors.clientAddresses.addressesButton)
.waitToClick(selectors.clientAddresses.secondEditButton) .waitToClick(selectors.clientAddresses.secondEditAddress)
.checkboxState(selectors.clientAddresses.equalizationTaxCheckbox); .checkboxState(selectors.clientAddresses.equalizationTaxCheckbox);
expect(result).toBe('checked'); expect(result).toBe('checked');
@ -289,7 +290,7 @@ describe('Client Edit fiscalData path', () => {
// confirm invoice by address checkbox gets checked if the EQtax differs between addresses step 2 // confirm invoice by address checkbox gets checked if the EQtax differs between addresses step 2
it(`should click on the 1st edit icon to access the address details and uncheck EQtax checkbox`, async() => { it(`should click on the 1st edit icon to access the address details and uncheck EQtax checkbox`, async() => {
const result = await nightmare const result = await nightmare
.waitToClick(selectors.clientAddresses.firstEditButton) .waitToClick(selectors.clientAddresses.firstEditAddress)
.waitToClick(selectors.clientAddresses.equalizationTaxCheckbox) .waitToClick(selectors.clientAddresses.equalizationTaxCheckbox)
.waitToClick(selectors.clientAddresses.saveButton) .waitToClick(selectors.clientAddresses.saveButton)
.waitForLastSnackbar(); .waitForLastSnackbar();

View File

@ -68,7 +68,7 @@ describe('Client Add address path', () => {
it(`should click on the addresses button confirm the new address exists and it's the default one`, async() => { it(`should click on the addresses button confirm the new address exists and it's the default one`, async() => {
const result = await nightmare const result = await nightmare
.waitToClick(selectors.clientAddresses.addressesButton) // .waitToClick(selectors.clientAddresses.addressesButton)
.waitToGetProperty(selectors.clientAddresses.defaultAddress, 'innerText'); .waitToGetProperty(selectors.clientAddresses.defaultAddress, 'innerText');
expect(result).toContain('320 Park Avenue New York'); expect(result).toContain('320 Park Avenue New York');
@ -86,7 +86,7 @@ describe('Client Add address path', () => {
it(`should click on the edit icon of the default address`, async() => { it(`should click on the edit icon of the default address`, async() => {
const url = await nightmare const url = await nightmare
.waitForTextInElement(selectors.clientAddresses.defaultAddress, 'Somewhere in Thailand') .waitForTextInElement(selectors.clientAddresses.defaultAddress, 'Somewhere in Thailand')
.waitToClick(selectors.clientAddresses.firstEditButton) .waitToClick(selectors.clientAddresses.firstEditAddress)
.waitForURL('/edit') .waitForURL('/edit')
.parsedUrl(); .parsedUrl();

View File

@ -14,7 +14,7 @@ describe('Client add address notes path', () => {
it(`should click on the edit icon of the default address`, async() => { it(`should click on the edit icon of the default address`, async() => {
const url = await nightmare const url = await nightmare
.waitForTextInElement(selectors.clientAddresses.defaultAddress, '20 Ingram Street') .waitForTextInElement(selectors.clientAddresses.defaultAddress, '20 Ingram Street')
.waitToClick(selectors.clientAddresses.firstEditButton) .waitToClick(selectors.clientAddresses.firstEditAddress)
.waitForURL('/edit') .waitForURL('/edit')
.parsedUrl(); .parsedUrl();

View File

@ -0,0 +1,33 @@
import selectors from '../../helpers/selectors.js';
import createNightmare from '../../helpers/nightmare';
describe('Client DMS', () => {
const nightmare = createNightmare();
describe('as salesPerson', () => {
beforeAll(() => {
nightmare
.loginAndModule('salesPerson', 'client')
.accessToSearchResult('Tony Stark')
.accessToSection('client.card.dms.index');
});
it('should delete de first file', async() => {
let result = await nightmare
.waitToClick(selectors.dms.deleteFileButton)
.waitToClick(selectors.dms.acceptDeleteButton)
.waitForLastSnackbar();
expect(result).toEqual('Data saved!');
});
it(`should click on the first document line worker name making the descriptor visible`, async() => {
const visible = await nightmare
.waitToClick(selectors.dms.firstDocWorker)
.waitForClassPresent(selectors.dms.firstDocWorkerDescriptor, 'shown')
.isVisible(selectors.dms.firstDocWorkerDescriptor);
expect(visible).toBeTruthy();
});
});
});

View File

@ -11,6 +11,7 @@ describe('Item summary path', () => {
it('should search for an item', async() => { it('should search for an item', async() => {
const result = await nightmare const result = await nightmare
.clearInput(selectors.itemsIndex.searchItemInput)
.write(selectors.itemsIndex.searchItemInput, 'Ranged weapon longbow 2m') .write(selectors.itemsIndex.searchItemInput, 'Ranged weapon longbow 2m')
.waitToClick(selectors.itemsIndex.searchButton) .waitToClick(selectors.itemsIndex.searchButton)
.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1) .waitForNumberOfElements(selectors.itemsIndex.searchResult, 1)

View File

@ -11,6 +11,7 @@ describe('Item Create/Clone path', () => {
it(`should search for the item Infinity Gauntlet to confirm it isn't created yet`, async() => { it(`should search for the item Infinity Gauntlet to confirm it isn't created yet`, async() => {
const result = await nightmare const result = await nightmare
.clearInput(selectors.itemsIndex.searchItemInput)
.write(selectors.itemsIndex.searchItemInput, 'Infinity Gauntlet') .write(selectors.itemsIndex.searchItemInput, 'Infinity Gauntlet')
.waitToClick(selectors.itemsIndex.searchButton) .waitToClick(selectors.itemsIndex.searchButton)
.waitForNumberOfElements(selectors.itemsIndex.searchResult, 0) .waitForNumberOfElements(selectors.itemsIndex.searchResult, 0)
@ -95,6 +96,7 @@ describe('Item Create/Clone path', () => {
it(`should search for the item Infinity Gauntlet`, async() => { it(`should search for the item Infinity Gauntlet`, async() => {
const result = await nightmare const result = await nightmare
.clearInput(selectors.itemsIndex.searchItemInput)
.write(selectors.itemsIndex.searchItemInput, 'Infinity Gauntlet') .write(selectors.itemsIndex.searchItemInput, 'Infinity Gauntlet')
.waitToClick(selectors.itemsIndex.searchButton) .waitToClick(selectors.itemsIndex.searchButton)
.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1) .waitForNumberOfElements(selectors.itemsIndex.searchResult, 1)
@ -117,6 +119,7 @@ describe('Item Create/Clone path', () => {
it('should search for the item Infinity Gauntlet and find two', async() => { it('should search for the item Infinity Gauntlet and find two', async() => {
const result = await nightmare const result = await nightmare
.waitToClick(selectors.itemTags.goToItemIndexButton) .waitToClick(selectors.itemTags.goToItemIndexButton)
.clearInput(selectors.itemsIndex.searchItemInput)
.write(selectors.itemsIndex.searchItemInput, 'Infinity Gauntlet') .write(selectors.itemsIndex.searchItemInput, 'Infinity Gauntlet')
.waitToClick(selectors.itemsIndex.searchButton) .waitToClick(selectors.itemsIndex.searchButton)
.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2) .waitForNumberOfElements(selectors.itemsIndex.searchResult, 2)

View File

@ -11,6 +11,7 @@ describe('Item regularize path', () => {
it('should edit the user local warehouse', async() => { it('should edit the user local warehouse', async() => {
let result = await nightmare let result = await nightmare
.waitForSpinnerLoad()
.waitToClick(selectors.globalItems.userMenuButton) .waitToClick(selectors.globalItems.userMenuButton)
.autocompleteSearch(selectors.globalItems.userLocalWarehouse, 'Warehouse Four') .autocompleteSearch(selectors.globalItems.userLocalWarehouse, 'Warehouse Four')
.waitForLastSnackbar(); .waitForLastSnackbar();
@ -28,6 +29,7 @@ describe('Item regularize path', () => {
it('should search for the item', async() => { it('should search for the item', async() => {
const resultCount = await nightmare const resultCount = await nightmare
.clearInput(selectors.itemsIndex.searchItemInput)
.write(selectors.itemsIndex.searchItemInput, 'Ranged weapon pistol 9mm') .write(selectors.itemsIndex.searchItemInput, 'Ranged weapon pistol 9mm')
.waitToClick(selectors.itemsIndex.searchButton) .waitToClick(selectors.itemsIndex.searchButton)
.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1) .waitForNumberOfElements(selectors.itemsIndex.searchResult, 1)
@ -133,6 +135,7 @@ describe('Item regularize path', () => {
it('should search for the item once again', async() => { it('should search for the item once again', async() => {
const resultCount = await nightmare const resultCount = await nightmare
.clearInput(selectors.itemsIndex.searchItemInput)
.write(selectors.itemsIndex.searchItemInput, 'Ranged weapon pistol 9mm') .write(selectors.itemsIndex.searchItemInput, 'Ranged weapon pistol 9mm')
.waitToClick(selectors.itemsIndex.searchButton) .waitToClick(selectors.itemsIndex.searchButton)
.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1) .waitForNumberOfElements(selectors.itemsIndex.searchResult, 1)

View File

@ -30,17 +30,6 @@ describe('Ticket Create new tracking state path', () => {
expect(result).toEqual('State cannot be blank'); expect(result).toEqual('State cannot be blank');
}); });
it(`should attempt create a new state then clear and save it`, async() => {
let result = await nightmare
.autocompleteSearch(selectors.createStateView.stateAutocomplete, '¿Fecha?')
.waitToClick(selectors.createStateView.clearStateInputButton)
.waitToClick(selectors.createStateView.saveStateButton)
.waitForLastSnackbar();
expect(result).toEqual('State cannot be blank');
});
it(`should create a new state`, async() => { it(`should create a new state`, async() => {
let result = await nightmare let result = await nightmare
.autocompleteSearch(selectors.createStateView.stateAutocomplete, '¿Fecha?') .autocompleteSearch(selectors.createStateView.stateAutocomplete, '¿Fecha?')
@ -77,18 +66,16 @@ describe('Ticket Create new tracking state path', () => {
expect(result).toEqual(`You don't have enough privileges`); expect(result).toEqual(`You don't have enough privileges`);
}); });
it(`should attempt to create an state for the type salesPerson has rights but fail as worker is blank`, async() => { it(`should make sure the worker gets autocomplete uppon selecting the assigned state`, async() => {
let result = await nightmare let result = await nightmare
.autocompleteSearch(selectors.createStateView.stateAutocomplete, 'asignado') .autocompleteSearch(selectors.createStateView.stateAutocomplete, 'asignado')
.waitToClick(selectors.createStateView.saveStateButton) .waitToGetProperty(`${selectors.createStateView.workerAutocomplete} input`, 'value');
.waitForLastSnackbar();
expect(result).toEqual(`Worker cannot be blank`); expect(result).toEqual('salesPersonNick');
}); });
it(`should create a new state with all it's data`, async() => { it(`should succesfully create a valid state`, async() => {
let result = await nightmare let result = await nightmare
.autocompleteSearch(selectors.createStateView.workerAutocomplete, 'replenisher')
.waitToClick(selectors.createStateView.saveStateButton) .waitToClick(selectors.createStateView.saveStateButton)
.waitForLastSnackbar(); .waitForLastSnackbar();

View File

@ -11,10 +11,58 @@ describe('Ticket Edit basic data path', () => {
.accessToSection('ticket.card.basicData.stepOne'); .accessToSection('ticket.card.basicData.stepOne');
}); });
it(`should edit the ticket agency then click next`, async() => { it(`should confirm the zone autocomplete is disabled unless your role is productionBoss`, async() => {
const disabled = await nightmare
.wait(selectors.ticketBasicData.zoneAutocomplete)
.evaluate(selector => {
return document.querySelector(selector).disabled;
}, `${selectors.ticketBasicData.zoneAutocomplete} input`);
expect(disabled).toBeTruthy();
});
it(`should now log as productionBoss to perform the rest of the tests`, async() => {
await nightmare
.loginAndModule('productionBoss', 'ticket')
.accessToSearchResult(11)
.accessToSection('ticket.card.basicData.stepOne');
});
it(`should confirm the zone autocomplete is enabled for the role productionBoss`, async() => {
const disabled = await nightmare
.wait(selectors.ticketBasicData.zoneAutocomplete)
.evaluate(selector => {
return document.querySelector(selector).disabled;
}, `${selectors.ticketBasicData.zoneAutocomplete} input`);
expect(disabled).toBeFalsy();
});
it(`should check the zone is for Silla247`, async() => {
let zone = await nightmare
.waitToGetProperty(`${selectors.ticketBasicData.zoneAutocomplete} input`, 'value');
expect(zone).toContain('Zone 247 A');
});
it(`should edit the ticket agency then check there are no zones for it`, async() => {
let zone = await nightmare
.autocompleteSearch(selectors.ticketBasicData.agencyAutocomplete, 'Entanglement')
.getProperty(`${selectors.ticketBasicData.zoneAutocomplete} input`, 'value');
expect(zone.length).toEqual(0);
});
it(`should edit the ticket zone then check the agency is for the new zone`, async() => {
let zone = await nightmare
.autocompleteSearch(selectors.ticketBasicData.zoneAutocomplete, 'Zone expensive A')
.waitToGetProperty(`${selectors.ticketBasicData.agencyAutocomplete} input`, 'value');
expect(zone).toContain('Silla247Expensive');
});
it(`should click next`, async() => {
let url = await nightmare let url = await nightmare
.autocompleteSearch(selectors.ticketBasicData.agencyAutocomplete, 'Silla247Expensive')
.waitToGetProperty(`${selectors.ticketBasicData.zoneAutocomplete} input`, 'value')
.waitToClick(selectors.ticketBasicData.nextStepButton) .waitToClick(selectors.ticketBasicData.nextStepButton)
.waitForURL('data/step-two') .waitForURL('data/step-two')
.parsedUrl(); .parsedUrl();

View File

@ -1,7 +1,8 @@
import selectors from '../../helpers/selectors.js'; import selectors from '../../helpers/selectors.js';
import createNightmare from '../../helpers/nightmare'; import createNightmare from '../../helpers/nightmare';
describe('Ticket Edit sale path', () => { // #1632 [e2e] ticket.sale - Transferir líneas
xdescribe('Ticket Edit sale path', () => {
const nightmare = createNightmare(); const nightmare = createNightmare();
beforeAll(() => { beforeAll(() => {
@ -257,12 +258,14 @@ describe('Ticket Edit sale path', () => {
expect(result).toEqual(3); expect(result).toEqual(3);
}); });
it('should select the third sale and transfer it to a valid ticket', async() => { it('should select the second sale and transfer it to a valid ticket', async() => {
const targetTicketId = 12; const targetTicketId = 12;
const result = await nightmare const result = await nightmare
.waitToClick(selectors.ticketSales.thirdSaleCheckbox) .waitToClick(selectors.ticketSales.secondSaleCheckbox)
.waitToClick(selectors.ticketSales.transferSaleButton) .waitToClick(selectors.ticketSales.transferSaleButton)
.focusElement(selectors.ticketSales.transferQuantityCell)
.write(selectors.ticketSales.transferQuantityInput, '10\u000d')
.write(selectors.ticketSales.moveToTicketInput, targetTicketId) .write(selectors.ticketSales.moveToTicketInput, targetTicketId)
.waitToClick(selectors.ticketSales.moveToTicketButton) .waitToClick(selectors.ticketSales.moveToTicketButton)
.waitForURL(`ticket/${targetTicketId}/sale`) .waitForURL(`ticket/${targetTicketId}/sale`)
@ -276,7 +279,14 @@ describe('Ticket Edit sale path', () => {
.wait(selectors.ticketSales.secondSaleText) .wait(selectors.ticketSales.secondSaleText)
.waitToGetProperty(selectors.ticketSales.secondSaleText, 'innerText'); .waitToGetProperty(selectors.ticketSales.secondSaleText, 'innerText');
expect(result).toContain(`Ranged weapon longbow 2m`); expect(result).toContain(`Melee weapon heavy shield`);
});
it('should confirm the transfered quantity is the correct one', async() => {
const result = await nightmare
.waitToGetProperty(selectors.ticketSales.secondSaleQuantityCell, 'innerText');
expect(result).toContain('10');
}); });
it('should go back to the original ticket sales section', async() => { it('should go back to the original ticket sales section', async() => {
@ -290,12 +300,19 @@ describe('Ticket Edit sale path', () => {
expect(url.hash).toContain('/sale'); expect(url.hash).toContain('/sale');
}); });
it(`should confirm the original ticket has only two lines now`, async() => { it(`should confirm the original ticket has still three lines`, async() => {
const result = await nightmare const result = await nightmare
.wait(selectors.ticketSales.saleLine) .wait(selectors.ticketSales.saleLine)
.countElement(selectors.ticketSales.saleLine); .countElement(selectors.ticketSales.saleLine);
expect(result).toEqual(2); expect(result).toEqual(3);
});
it(`should confirm the second sale quantity is now half of it's original value after the transfer`, async() => {
const result = await nightmare
.waitToGetProperty(selectors.ticketSales.secondSaleQuantityCell, 'innerText');
expect(result).toContain('10');
}); });
it('should go back to the receiver ticket sales section', async() => { it('should go back to the receiver ticket sales section', async() => {
@ -324,11 +341,12 @@ describe('Ticket Edit sale path', () => {
}); });
it('should confirm the original ticket received the line', async() => { it('should confirm the original ticket received the line', async() => {
const expectedLines = 4;
const result = await nightmare const result = await nightmare
.waitForNumberOfElements(selectors.ticketSales.saleLine, 3) .waitForNumberOfElements(selectors.ticketSales.saleLine, expectedLines)
.countElement(selectors.ticketSales.saleLine); .countElement(selectors.ticketSales.saleLine);
expect(result).toEqual(3); expect(result).toEqual(expectedLines);
}); });
it(`should throw an error when attempting to create a ticket for an inactive client`, async() => { it(`should throw an error when attempting to create a ticket for an inactive client`, async() => {
@ -357,7 +375,7 @@ describe('Ticket Edit sale path', () => {
const senderTicketId = 13; const senderTicketId = 13;
const url = await nightmare const url = await nightmare
.waitToClick(selectors.ticketSales.firstSaleCheckbox) .waitToClick(selectors.ticketSales.selectAllSalesCheckbox)
.waitToClick(selectors.ticketSales.transferSaleButton) .waitToClick(selectors.ticketSales.transferSaleButton)
.waitToClick(selectors.ticketSales.moveToNewTicketButton) .waitToClick(selectors.ticketSales.moveToNewTicketButton)
.waitToClick(selectors.ticketSales.acceptDeleteTicketButton) .waitToClick(selectors.ticketSales.acceptDeleteTicketButton)

View File

@ -9,10 +9,10 @@ describe('Ticket descriptor path', () => {
.loginAndModule('employee', 'ticket'); .loginAndModule('employee', 'ticket');
}); });
it('should count the mount of tickets in the turns section', async() => { it('should count the amount of tickets in the turns section', async() => {
const result = await nightmare const result = await nightmare
.waitToClick(selectors.ticketsIndex.moreMenu) .waitToClick(selectors.ticketsIndex.moreMenu)
.waitToClick(selectors.ticketsIndex.moreMenuTurns) .waitToClick(selectors.ticketsIndex.moreMenuWeeklyTickets)
.wait(selectors.ticketsIndex.weeklyTicket) .wait(selectors.ticketsIndex.weeklyTicket)
.countElement(selectors.ticketsIndex.weeklyTicket); .countElement(selectors.ticketsIndex.weeklyTicket);
@ -32,7 +32,7 @@ describe('Ticket descriptor path', () => {
it('should search for the ticket 11', async() => { it('should search for the ticket 11', async() => {
const result = await nightmare const result = await nightmare
.write(selectors.ticketsIndex.searchTicketInput, 'id:11') .write(selectors.ticketsIndex.searchTicketInput, 11)
.waitToClick(selectors.ticketsIndex.searchButton) .waitToClick(selectors.ticketsIndex.searchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) .waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1)
.countElement(selectors.ticketsIndex.searchResult); .countElement(selectors.ticketsIndex.searchResult);
@ -73,8 +73,8 @@ describe('Ticket descriptor path', () => {
it('should confirm the ticket 11 was added on thursday', async() => { it('should confirm the ticket 11 was added on thursday', async() => {
const result = await nightmare const result = await nightmare
.waitToClick(selectors.ticketsIndex.moreMenu) .waitToClick(selectors.ticketsIndex.moreMenu)
.waitToClick(selectors.ticketsIndex.moreMenuTurns) .waitToClick(selectors.ticketsIndex.moreMenuWeeklyTickets)
.waitToGetProperty(selectors.ticketsIndex.sixthWeeklyTicketTurn, 'value'); .waitToGetProperty(selectors.ticketsIndex.sixthWeeklyTicket, 'value');
expect(result).toEqual('Thursday'); expect(result).toEqual('Thursday');
}); });
@ -92,7 +92,7 @@ describe('Ticket descriptor path', () => {
it('should now search for the ticket 11', async() => { it('should now search for the ticket 11', async() => {
const result = await nightmare const result = await nightmare
.write(selectors.ticketsIndex.searchTicketInput, 'id:11') .write(selectors.ticketsIndex.searchTicketInput, 11)
.waitToClick(selectors.ticketsIndex.searchButton) .waitToClick(selectors.ticketsIndex.searchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) .waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1)
.countElement(selectors.ticketsIndex.searchResult); .countElement(selectors.ticketsIndex.searchResult);
@ -133,17 +133,25 @@ describe('Ticket descriptor path', () => {
it('should confirm the ticket 11 was added on saturday', async() => { it('should confirm the ticket 11 was added on saturday', async() => {
const result = await nightmare const result = await nightmare
.waitToClick(selectors.ticketsIndex.moreMenu) .waitToClick(selectors.ticketsIndex.moreMenu)
.waitToClick(selectors.ticketsIndex.moreMenuTurns) .waitToClick(selectors.ticketsIndex.moreMenuWeeklyTickets)
.waitToGetProperty(selectors.ticketsIndex.sixthWeeklyTicketTurn, 'value'); .waitToGetProperty(selectors.ticketsIndex.sixthWeeklyTicket, 'value');
expect(result).toEqual('Saturday'); expect(result).toEqual('Saturday');
}); });
// Test #1450 here it('should now search for the weekly ticket 11', async() => {
const result = await nightmare
.write(selectors.ticketsIndex.searchWeeklyTicketInput, 11)
.waitToClick(selectors.ticketsIndex.searchWeeklyButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchWeeklyResult, 1)
.countElement(selectors.ticketsIndex.searchWeeklyResult);
expect(result).toEqual(1);
});
it('should delete the weekly ticket 11', async() => { it('should delete the weekly ticket 11', async() => {
const result = await nightmare const result = await nightmare
.waitToClick(selectors.ticketsIndex.sixthWeeklyTicketDeleteIcon) .waitToClick(selectors.ticketsIndex.firstWeeklyTicketDeleteIcon)
.waitToClick(selectors.ticketsIndex.acceptDeleteTurn) .waitToClick(selectors.ticketsIndex.acceptDeleteTurn)
.waitForLastSnackbar(); .waitForLastSnackbar();
@ -152,7 +160,10 @@ describe('Ticket descriptor path', () => {
it('should confirm the sixth weekly ticket was deleted', async() => { it('should confirm the sixth weekly ticket was deleted', async() => {
const result = await nightmare const result = await nightmare
.countElement(selectors.ticketsIndex.weeklyTicket); .waitToClick('vn-ticket-weekly-index vn-searchbar i[class="material-icons clear"]')
.waitToClick(selectors.ticketsIndex.searchWeeklyButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchWeeklyResult, 5)
.countElement(selectors.ticketsIndex.searchWeeklyResult);
expect(result).toEqual(5); expect(result).toEqual(5);
}); });

View File

@ -30,6 +30,33 @@ describe('Ticket descriptor path', () => {
expect(url.hash).toContain('/summary'); expect(url.hash).toContain('/summary');
}); });
it('should open the change shipped hours dialog by using the more menu option', async() => {
const visible = await nightmare
.waitToClick(selectors.ticketDescriptor.moreMenu)
.waitToClick(selectors.ticketDescriptor.moreMenuChangeShippedHour)
.wait(selectors.ticketDescriptor.changeShippedHourInput)
.isVisible(selectors.ticketDescriptor.changeShippedHourInput);
expect(visible).toBeTruthy();
});
it(`should update the shipped hour`, async() => {
const result = await nightmare
.write(selectors.ticketDescriptor.changeShippedHourInput, '0815')
.waitToClick(selectors.ticketDescriptor.acceptChangeHourButton)
.waitForLastSnackbar();
expect(result).toEqual('Shipped hour updated');
});
it(`should confirm the ticket descriptor shows the correct shipping hour`, async() => {
const result = await nightmare
.waitToGetProperty(selectors.ticketDescriptor.descriptorDeliveryDate, 'innerText');
expect(result).toContain('08:15');
});
it('should delete the ticket using the descriptor more menu', async() => { it('should delete the ticket using the descriptor more menu', async() => {
const result = await nightmare const result = await nightmare
.waitToClick(selectors.ticketDescriptor.moreMenu) .waitToClick(selectors.ticketDescriptor.moreMenu)

View File

@ -69,14 +69,6 @@ describe('Ticket Summary path', () => {
expect(exists).toBeTruthy(); expect(exists).toBeTruthy();
}); });
it('should click on the SET OK button and throw a privileges error', async() => {
let result = await nightmare
.waitToClick(selectors.ticketSummary.setOk)
.waitForLastSnackbar();
expect(result).toEqual(`You don't have enough privileges`);
});
it('should log in as production then navigate to the summary of the same ticket', async() => { it('should log in as production then navigate to the summary of the same ticket', async() => {
let url = await nightmare let url = await nightmare
.loginAndModule('production', 'ticket') .loginAndModule('production', 'ticket')

View File

@ -7,7 +7,7 @@ describe('Claim action path', () => {
beforeAll(() => { beforeAll(() => {
nightmare nightmare
.loginAndModule('administrative', 'claim') .loginAndModule('administrative', 'claim')
.accessToSearchResult(4) .accessToSearchResult(2)
.accessToSection('claim.card.action'); .accessToSection('claim.card.action');
}); });

View File

@ -28,8 +28,8 @@ describe('Order edit basic data path', () => {
it('should now navigate to order index', async() => { it('should now navigate to order index', async() => {
const orderId = 16; const orderId = 16;
const url = await nightmare const url = await nightmare
.waitToClick(selectors.globalItems.returnToModuleIndexButton) .waitToClick(selectors.orderDescriptor.returnToModuleIndexButton)
.waitToClick(selectors.globalItems.acceptButton) .waitToClick(selectors.orderDescriptor.acceptNavigationButton)
.wait(selectors.ordersIndex.createOrderButton) .wait(selectors.ordersIndex.createOrderButton)
.accessToSearchResult(orderId) .accessToSearchResult(orderId)
.accessToSection('order.card.basicData') .accessToSection('order.card.basicData')

View File

@ -18,7 +18,10 @@
ng-blur="$ctrl.hasFocus = false" ng-blur="$ctrl.hasFocus = false"
tabindex="{{$ctrl.input.tabindex}}" tabindex="{{$ctrl.input.tabindex}}"
accept="{{$ctrl.accept}}"/> accept="{{$ctrl.accept}}"/>
<label class="label" translate>{{$ctrl.label}}</label> <label class="label">
<span translate>{{::$ctrl.label}}</span>
<span translate ng-show="::$ctrl.required">*</span>
</label>
</div> </div>
<div class="underline"></div> <div class="underline"></div>
<div class="selected underline"></div> <div class="selected underline"></div>

View File

@ -122,6 +122,7 @@ ngModule.component('vnInputFile', {
name: '@?', name: '@?',
disabled: '<?', disabled: '<?',
multiple: '<?', multiple: '<?',
required: '@?',
accept: '@?', accept: '@?',
rule: '@?', rule: '@?',
files: '=model', files: '=model',

View File

@ -73,6 +73,14 @@ export default class Popover extends Component {
if (this._shown) return; if (this._shown) return;
if (parent) this.parent = parent; if (parent) this.parent = parent;
let isDescriptorMoreMenu = parent && parent.attributes[0].nodeValue == 'more-button';
const leftMenu = this.document.querySelector('div[class="menu left"]');
if (isDescriptorMoreMenu && leftMenu) {
leftMenu.style.overflow = 'hidden';
this.restoreOverflow = true;
}
this._shown = true; this._shown = true;
this.element.style.display = 'block'; this.element.style.display = 'block';
this.$timeout.cancel(this.showTimeout); this.$timeout.cancel(this.showTimeout);
@ -96,6 +104,11 @@ export default class Popover extends Component {
hide() { hide() {
if (!this._shown) return; if (!this._shown) return;
if (this.restoreOverflow) {
const leftMenu = this.document.querySelector('div[class="menu left"]');
leftMenu.style.overflow = 'auto';
this.restoreOverflow = false;
}
this._shown = false; this._shown = false;
this.$element.removeClass('shown'); this.$element.removeClass('shown');
this.$timeout.cancel(this.showTimeout); this.$timeout.cancel(this.showTimeout);

View File

@ -28,7 +28,6 @@ export default class Controller extends Component {
() => this.onStateChange()); () => this.onStateChange());
this._filter = null; this._filter = null;
this.searchString = '';
this.autoLoad = false; this.autoLoad = false;
} }
@ -58,6 +57,10 @@ export default class Controller extends Component {
this.doSearch(); this.doSearch();
} }
get shownFilter() {
return this._filter != null ? this._filter : this.suggestedFilter;
}
openPanel(event) { openPanel(event) {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
event.preventDefault(); event.preventDefault();
@ -65,7 +68,8 @@ export default class Controller extends Component {
this.$panelScope = this.$.$new(); this.$panelScope = this.$.$new();
this.$panel = this.$compile(`<${this.panel}/>`)(this.$panelScope); this.$panel = this.$compile(`<${this.panel}/>`)(this.$panelScope);
let panel = this.$panel.isolateScope().$ctrl; let panel = this.$panel.isolateScope().$ctrl;
panel.filter = this._filter; if (this.shownFilter)
panel.filter = JSON.parse(JSON.stringify(this.shownFilter));
panel.onSubmit = filter => this.onPanelSubmit(filter); panel.onSubmit = filter => this.onPanelSubmit(filter);
this.$.popover.parent = this.element; this.$.popover.parent = this.element;
@ -94,13 +98,12 @@ export default class Controller extends Component {
} }
doSearch() { doSearch() {
let filter = this._filter; this.searchString = this.getStringFromObject(this.shownFilter);
let filter = this._filter;
if (filter == null && this.autoload) if (filter == null && this.autoload)
filter = {}; filter = {};
this.searchString = this.getStringFromObject(filter);
if (this.onSearch) if (this.onSearch)
this.onSearch({$params: filter}); this.onSearch({$params: filter});
@ -214,6 +217,7 @@ ngModule.component('vnSearchbar', {
template: require('./searchbar.html'), template: require('./searchbar.html'),
bindings: { bindings: {
filter: '<?', filter: '<?',
suggestedFilter: '<?',
onSearch: '&?', onSearch: '&?',
panel: '@', panel: '@',
model: '<?', model: '<?',

View File

@ -16,7 +16,7 @@
tabindex="{{$ctrl.input.tabindex}}"/> tabindex="{{$ctrl.input.tabindex}}"/>
<label class="label"> <label class="label">
<span translate>{{::$ctrl.label}}</span> <span translate>{{::$ctrl.label}}</span>
<span translate vn-tooltip="Required" ng-show="::$ctrl.required">*</span> <span translate ng-show="::$ctrl.required">*</span>
</label> </label>
</div> </div>
<div class="underline"></div> <div class="underline"></div>

View File

@ -16,9 +16,10 @@ export default class MainMenu {
} }
getCurrentUserName() { getCurrentUserName() {
this.$http.get('/api/Accounts/getCurrentUserName') this.$http.get('/api/Accounts/getCurrentUserData')
.then(json => { .then(json => {
this.$.currentUserName = json.data; this.$.currentUserName = json.data.accountName;
window.localStorage.currentUserWorkerId = json.data.workerId;
}); });
} }

View File

@ -14,8 +14,8 @@ describe('Component vnMainMenu', () => {
describe('getCurrentUserName()', () => { describe('getCurrentUserName()', () => {
it(`should set the user name property in the controller`, () => { it(`should set the user name property in the controller`, () => {
$httpBackend.when('GET', `/api/Accounts/getCurrentUserName`).respond('Batman'); $httpBackend.when('GET', `/api/Accounts/getCurrentUserData`).respond({accountName: 'Batman'});
$httpBackend.expect('GET', `/api/Accounts/getCurrentUserName`); $httpBackend.expect('GET', `/api/Accounts/getCurrentUserData`);
controller.getCurrentUserName(); controller.getCurrentUserName();
$httpBackend.flush(); $httpBackend.flush();

View File

@ -23,9 +23,6 @@ let containerId = 'salix-db';
let dataSources = require('./loopback/server/datasources.json'); let dataSources = require('./loopback/server/datasources.json');
let dbConf = dataSources.vn; let dbConf = dataSources.vn;
if (process.env.DB_HOST)
dbConf.host = process.env.DB_HOST;
let backSources = [ let backSources = [
'!node_modules', '!node_modules',
'loopback', 'loopback',
@ -423,9 +420,11 @@ async function docker() {
containerId = result.stdout; containerId = result.stdout;
if (argv['random']) { if (argv['random']) {
let inspect = await execP(`docker inspect -f "{{json .NetworkSettings.Ports}}" ${containerId}`); let inspect = await execP(`docker inspect -f "{{json .NetworkSettings}}" ${containerId}`);
let ports = JSON.parse(inspect.stdout); let netSettings = JSON.parse(inspect.stdout);
dbConf.port = ports['3306/tcp'][0]['HostPort'];
dbConf.host = netSettings.Gateway;
dbConf.port = netSettings.Ports['3306/tcp'][0]['HostPort'];
} }
if (runChown) await dockerWait(); if (runChown) await dockerWait();

View File

@ -49,5 +49,9 @@
"This client can't be invoiced": "This client can't be invoiced", "This client can't be invoiced": "This client can't be invoiced",
"The introduced hour already exists": "The introduced hour already exists", "The introduced hour already exists": "The introduced hour already exists",
"Invalid parameters to create a new ticket": "Invalid parameters to create a new ticket", "Invalid parameters to create a new ticket": "Invalid parameters to create a new ticket",
"Concept cannot be blank": "Concept cannot be blank" "Concept cannot be blank": "Concept cannot be blank",
"Ticket id cannot be blank": "Ticket id cannot be blank",
"Weekday cannot be blank": "Weekday cannot be blank",
"This ticket can not be modified": "This ticket can not be modified",
"You can't delete a confirmed order": "You can't delete a confirmed order"
} }

View File

@ -97,5 +97,9 @@
"This postcode already exists": "Este código postal ya existe", "This postcode already exists": "Este código postal ya existe",
"Concept cannot be blank": "El concepto no puede quedar en blanco", "Concept cannot be blank": "El concepto no puede quedar en blanco",
"File doesn't exists": "El archivo no existe", "File doesn't exists": "El archivo no existe",
"You don't have privileges to change the zone": "No tienes permisos para cambiar la zona" "You don't have privileges to change the zone": "No tienes permisos para cambiar la zona",
"This ticket is already on weekly tickets": "Este ticket ya está en tickets programados",
"Ticket id cannot be blank": "El id de ticket no puede quedar en blanco",
"Weekday cannot be blank": "El día de la semana no puede quedar en blanco",
"You can't delete a confirmed order": "No puedes borrar un pedido confirmado"
} }

View File

@ -20,11 +20,16 @@
"connector": "loopback-component-storage", "connector": "loopback-component-storage",
"provider": "filesystem", "provider": "filesystem",
"root": "./e2e/dms", "root": "./e2e/dms",
"maxFileSize": "10485760", "maxFileSize": "52428800",
"allowedContentTypes": [ "allowedContentTypes": [
"application/x-7z-compressed",
"application/x-zip-compressed",
"application/x-rar-compressed",
"application/octet-stream",
"application/pdf", "application/pdf",
"application/zip", "application/zip",
"application/rar", "application/rar",
"multipart/x-zip",
"image/png", "image/png",
"image/jpeg", "image/jpeg",
"image/jpg" "image/jpg"

View File

@ -0,0 +1,23 @@
module.exports = Self => {
Self.remoteMethodCtx('allowedContentTypes', {
description: 'Returns a list of allowed contentTypes',
accessType: 'READ',
returns: {
type: ['Object'],
root: true
},
http: {
path: `/allowedContentTypes`,
verb: 'GET'
}
});
Self.allowedContentTypes = async() => {
const storageConnector = Self.app.dataSources.storage.connector;
const allowedContentTypes = storageConnector.allowedContentTypes;
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;
return modelAllowedContentTypes || allowedContentTypes;
};
};

View File

@ -0,0 +1,33 @@
module.exports = Self => {
Self.remoteMethodCtx('removeFile', {
description: 'Removes a ticket document',
accessType: 'WRITE',
accepts: {
arg: 'id',
type: 'Number',
description: 'The document id',
http: {source: 'path'}
},
returns: {
type: 'Object',
root: true
},
http: {
path: `/:id/removeFile`,
verb: 'POST'
}
});
Self.removeFile = async(ctx, id) => {
const models = Self.app.models;
const targetClaimDms = await models.ClaimDms.findById(id);
const targetDms = await models.Dms.findById(targetClaimDms.dmsFk);
const trashDmsType = await models.DmsType.findOne({where: {code: 'trash'}});
await models.Dms.removeFile(ctx, targetClaimDms.dmsFk);
await targetClaimDms.destroy();
return targetDms.updateAttribute('dmsTypeFk', trashDmsType.id);
};
};

View File

@ -0,0 +1,18 @@
const app = require('vn-loopback/server/server');
describe('TicketDms removeFile()', () => {
const ticketDmsId = 1;
it(`should return an error for a user without enough privileges`, async() => {
let clientId = 101;
let ctx = {req: {accessToken: {userId: clientId}}};
let error;
await app.models.TicketDms.removeFile(ctx, ticketDmsId).catch(e => {
error = e;
}).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`);
});
expect(error).toBeDefined();
});
});

View File

@ -0,0 +1,78 @@
module.exports = Self => {
Self.remoteMethodCtx('uploadFile', {
description: 'Upload and attach a document',
accessType: 'WRITE',
accepts: [{
arg: 'id',
type: 'Number',
description: 'The claim id',
http: {source: 'path'}
},
{
arg: 'warehouseId',
type: 'Number',
description: ''
},
{
arg: 'companyId',
type: 'Number',
description: ''
},
{
arg: 'dmsTypeId',
type: 'Number',
description: ''
},
{
arg: 'reference',
type: 'String',
description: ''
},
{
arg: 'description',
type: 'String',
description: ''
},
{
arg: 'hasFile',
type: 'Boolean',
description: ''
}],
returns: {
type: 'Object',
root: true
},
http: {
path: `/:id/uploadFile`,
verb: 'POST'
}
});
Self.uploadFile = async(ctx, id) => {
const models = Self.app.models;
const promises = [];
const tx = await Self.beginTransaction({});
try {
const options = {transaction: tx};
const uploadedFiles = await models.Dms.uploadFile(ctx, options);
uploadedFiles.forEach(dms => {
const newClaimDms = models.ClaimDms.create({
claimFk: id,
dmsFk: dms.id
}, options);
promises.push(newClaimDms);
});
const resolvedPromises = await Promise.all(promises);
await tx.commit();
return resolvedPromises;
} catch (err) {
await tx.rollback();
throw err;
}
};
};

View File

@ -31,5 +31,11 @@
}, },
"ClaimState": { "ClaimState": {
"dataSource": "vn" "dataSource": "vn"
},
"ClaimDms": {
"dataSource": "vn"
},
"ClaimLog": {
"dataSource": "vn"
} }
} }

View File

@ -0,0 +1,4 @@
module.exports = Self => {
require('../methods/claim-dms/removeFile')(Self);
require('../methods/claim-dms/allowedContentTypes')(Self);
};

View File

@ -0,0 +1,37 @@
{
"name": "ClaimDms",
"base": "Loggable",
"log": {
"model": "ClaimLog",
"relation": "claim"
},
"options": {
"mysql": {
"table": "claimDms"
}
},
"allowedContentTypes": [
"image/png",
"image/jpeg",
"image/jpg"
],
"properties": {
"dmsFk": {
"type": "Number",
"id": true,
"required": true
}
},
"relations": {
"claim": {
"type": "belongsTo",
"model": "Claim",
"foreignKey": "claimFk"
},
"dms": {
"type": "belongsTo",
"model": "Dms",
"foreignKey": "dmsFk"
}
}
}

View File

@ -0,0 +1,58 @@
{
"name": "ClaimLog",
"base": "VnModel",
"options": {
"mysql": {
"table": "claimLog"
}
},
"properties": {
"id": {
"id": true,
"type": "Number",
"forceId": false
},
"originFk": {
"type": "Number",
"required": true
},
"userFk": {
"type": "Number"
},
"action": {
"type": "String",
"required": true
},
"changedModel": {
"type": "String"
},
"oldInstance": {
"type": "Object"
},
"newInstance": {
"type": "Object"
},
"creationDate": {
"type": "Date"
},
"changedModelId": {
"type": "Number"
},
"changedModelValue": {
"type": "String"
},
"description": {
"type": "String"
}
},
"relations": {
"user": {
"type": "belongsTo",
"model": "Account",
"foreignKey": "userFk"
}
},
"scope": {
"order": ["creationDate DESC", "id DESC"]
}
}

View File

@ -1,7 +1,8 @@
module.exports = Self => { module.exports = Self => {
require('../methods/claim/filter')(Self);
require('../methods/claim/getSummary')(Self); require('../methods/claim/getSummary')(Self);
require('../methods/claim/createFromSales')(Self); require('../methods/claim/createFromSales')(Self);
require('../methods/claim/updateClaim')(Self); require('../methods/claim/updateClaim')(Self);
require('../methods/claim/regularizeClaim')(Self); require('../methods/claim/regularizeClaim')(Self);
require('../methods/claim/filter')(Self); require('../methods/claim/uploadFile')(Self);
}; };

View File

@ -18,11 +18,13 @@
<vn-tool-bar margin-medium-bottom> <vn-tool-bar margin-medium-bottom>
<vn-button <vn-button
label="Import claim" label="Import claim"
vn-http-click="$ctrl.importToNewRefundTicket()" disabled="$ctrl.claim.claimStateFk == $ctrl.resolvedState"
vn-http-click="$ctrl.importToNewRefundTicket()"p
vn-tooltip="Imports claim details"> vn-tooltip="Imports claim details">
</vn-button> </vn-button>
<vn-button <vn-button
label="Import ticket" label="Import ticket"
disabled="$ctrl.claim.claimStateFk == $ctrl.resolvedState"
ng-click="$ctrl.showLastTickets($event)" ng-click="$ctrl.showLastTickets($event)"
vn-tooltip="Imports ticket lines"> vn-tooltip="Imports ticket lines">
</vn-button> </vn-button>

View File

@ -22,7 +22,6 @@
<vn-table model="model"> <vn-table model="model">
<vn-thead> <vn-thead>
<vn-tr> <vn-tr>
<vn-th number>Id</vn-th>
<vn-th center>Landed</vn-th> <vn-th center>Landed</vn-th>
<vn-th number>Quantity</vn-th> <vn-th number>Quantity</vn-th>
<vn-th>Claimed</vn-th> <vn-th>Claimed</vn-th>
@ -35,13 +34,6 @@
</vn-thead> </vn-thead>
<vn-tbody> <vn-tbody>
<vn-tr ng-repeat="saleClaimed in $ctrl.salesClaimed" vn-repeat-last> <vn-tr ng-repeat="saleClaimed in $ctrl.salesClaimed" vn-repeat-last>
<vn-td number>
<span
ng-click="$ctrl.showDescriptor($event, saleClaimed.sale.itemFk)"
class="link">
{{::saleClaimed.sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td center>{{::saleClaimed.sale.ticket.landed | dateTime:'dd/MM/yyyy'}}</vn-td> <vn-td center>{{::saleClaimed.sale.ticket.landed | dateTime:'dd/MM/yyyy'}}</vn-td>
<vn-td number>{{::saleClaimed.sale.quantity}}</vn-td> <vn-td number>{{::saleClaimed.sale.quantity}}</vn-td>
<vn-td> <vn-td>
@ -50,7 +42,13 @@
on-change="$ctrl.setClaimedQuantity(saleClaimed.id, saleClaimed.quantity)"> on-change="$ctrl.setClaimedQuantity(saleClaimed.id, saleClaimed.quantity)">
</vn-input-number> </vn-input-number>
</vn-td> </vn-td>
<vn-td expand>{{::saleClaimed.sale.concept}}</vn-td> <vn-td expand title="{{::saleClaimed.sale.concept}}">
<span
class="link"
ng-click="$ctrl.showDescriptor($event, saleClaimed.sale.itemFk)">
{{::saleClaimed.sale.concept}}
</span>
</vn-td>
<vn-td number>{{::saleClaimed.sale.price | currency: 'EUR':2}}</vn-td> <vn-td number>{{::saleClaimed.sale.price | currency: 'EUR':2}}</vn-td>
<vn-td number> <vn-td number>
<span class="link" <span class="link"

View File

@ -0,0 +1,71 @@
<vn-watcher
vn-id="watcher"
data="$ctrl.dms">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" margin-medium enctype="multipart/form-data">
<div compact>
<vn-card pad-large>
<vn-horizontal>
<vn-textfield vn-one vn-focus
label="Reference"
field="$ctrl.dms.reference">
</vn-textfield>
<vn-autocomplete vn-one
label="Company"
field="$ctrl.dms.companyId"
url="/api/Companies"
show-field="code"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
label="Warehouse"
field="$ctrl.dms.warehouseId"
url="/api/Warehouses"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete vn-one
label="Type"
field="$ctrl.dms.dmsTypeId"
url="/api/DmsTypes"
show-field="name"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textarea vn-one
label="Description"
field="$ctrl.dms.description">
</vn-textarea>
</vn-horizontal>
<vn-horizontal>
<vn-input-file vn-one
label="File"
model="$ctrl.dms.files"
on-change="$ctrl.onFileChange(files)"
accept="{{$ctrl.allowedContentTypes}}"
multiple="true">
<t-right-icons>
<vn-icon vn-none
color-secondary
title="{{$ctrl.contentTypesInfo}}"
icon="info">
</vn-icon>
</t-right-icons>
</vn-input-file>
</vn-horizontal>
<vn-vertical>
<vn-check
label="Generate identifier for original file"
field="$ctrl.dms.hasFile">
</vn-check>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Upload"></vn-submit>
<vn-button ui-sref="claim.card.dms.index" label="Cancel"></vn-button>
</vn-button-bar>
</div>
</form>

View File

@ -0,0 +1,115 @@
import ngModule from '../../module';
import './style.scss';
class Controller {
constructor($scope, $http, $state, $translate, vnApp) {
this.$ = $scope;
this.$http = $http;
this.$state = $state;
this.$translate = $translate;
this.vnApp = vnApp;
this.dms = {
files: [],
hasFile: false,
hasFileAttached: false
};
}
get claim() {
return this._claim;
}
set claim(value) {
this._claim = value;
if (value) {
this.setDefaultParams();
this.getAllowedContentTypes();
}
}
getAllowedContentTypes() {
this.$http.get('/api/claimDms/allowedContentTypes').then(res => {
const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes;
});
}
get contentTypesInfo() {
return this.$translate.instant('ContentTypesInfo', {
allowedContentTypes: this.allowedContentTypes
});
}
setDefaultParams() {
const params = {filter: {
where: {code: 'claim'}
}};
this.$http.get('/api/DmsTypes/findOne', {params}).then(res => {
const dmsTypeId = res.data && res.data.id;
const companyId = window.localStorage.defaultCompanyFk;
const warehouseId = window.localStorage.defaultWarehouseFk;
const defaultParams = {
reference: this.claim.id,
warehouseId: warehouseId,
companyId: companyId,
dmsTypeId: dmsTypeId,
description: this.$translate.instant('FileDescription', {
claimId: this.claim.id,
clientId: this.claim.client.id,
clientName: this.claim.client.name
}).toUpperCase()
};
this.dms = Object.assign(this.dms, defaultParams);
});
}
onSubmit() {
const query = `/api/claims/${this.claim.id}/uploadFile`;
const options = {
method: 'POST',
url: query,
params: this.dms,
headers: {
'Content-Type': undefined
},
transformRequest: files => {
const formData = new FormData();
for (let i = 0; i < files.length; i++)
formData.append(files[i].name, files[i]);
return formData;
},
data: this.dms.files
};
this.$http(options).then(res => {
if (res) {
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
this.$.watcher.updateOriginalData();
this.$state.go('claim.card.dms.index');
}
});
}
onFileChange(files) {
let hasFileAttached = false;
if (files.length > 0)
hasFileAttached = true;
this.$.$applyAsync(() => {
this.dms.hasFileAttached = hasFileAttached;
});
}
}
Controller.$inject = ['$scope', '$http', '$state', '$translate', 'vnApp'];
ngModule.component('vnClaimDmsCreate', {
template: require('./index.html'),
controller: Controller,
bindings: {
claim: '<'
}
});

View File

@ -0,0 +1,81 @@
import './index';
describe('Claim', () => {
describe('Component vnClaimDmsCreate', () => {
let controller;
let $scope;
let $httpBackend;
let $httpParamSerializer;
beforeEach(ngModule('claim'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
controller = $componentController('vnClaimDmsCreate', {$scope});
controller._claim = {
id: 15,
client: {id: 101, name: 'Bruce wayne'},
ticketFk: 16
};
}));
describe('claim() setter', () => {
it('should set the claim data and then call setDefaultParams() and getAllowedContentTypes()', () => {
spyOn(controller, 'setDefaultParams');
spyOn(controller, 'getAllowedContentTypes');
controller._claim = undefined;
controller.claim = {
id: 15,
client: {id: 101, name: 'Bruce wayne'},
ticketFk: 16
};
expect(controller.claim).toBeDefined();
expect(controller.setDefaultParams).toHaveBeenCalledWith();
expect(controller.getAllowedContentTypes).toHaveBeenCalledWith();
});
});
describe('setDefaultParams()', () => {
it('should perform a GET query and define the dms property on controller', () => {
const params = {filter: {
where: {code: 'claim'}
}};
let serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `/api/DmsTypes/findOne?${serializedParams}`).respond({id: 14, code: 'claim'});
$httpBackend.expect('GET', `/api/DmsTypes/findOne?${serializedParams}`);
controller.setDefaultParams();
$httpBackend.flush();
expect(controller.dms).toBeDefined();
expect(controller.dms.reference).toEqual(15);
expect(controller.dms.dmsTypeId).toEqual(14);
});
});
describe('onFileChange()', () => {
it('should set dms hasFileAttached property to true if has any files', () => {
const files = [{id: 1, name: 'MyFile'}];
controller.onFileChange(files);
$scope.$apply();
expect(controller.dms.hasFileAttached).toBeTruthy();
});
});
describe('getAllowedContentTypes()', () => {
it('should make an HTTP GET request to get the allowed content types', () => {
const expectedResponse = ['image/png', 'image/jpg'];
$httpBackend.when('GET', `/api/claimDms/allowedContentTypes`).respond(expectedResponse);
$httpBackend.expect('GET', `/api/claimDms/allowedContentTypes`);
controller.getAllowedContentTypes();
$httpBackend.flush();
expect(controller.allowedContentTypes).toBeDefined();
expect(controller.allowedContentTypes).toEqual('image/png, image/jpg');
});
});
});
});

View File

@ -0,0 +1,7 @@
vn-ticket-request {
vn-textfield {
margin: 0!important;
max-width: 100px;
}
}

View File

@ -0,0 +1,71 @@
<vn-watcher
vn-id="watcher"
data="$ctrl.dms">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" margin-medium enctype="multipart/form-data">
<div compact>
<vn-card pad-large>
<vn-horizontal>
<vn-textfield vn-one vn-focus
label="Reference"
field="$ctrl.dms.reference">
</vn-textfield>
<vn-autocomplete vn-one required="true"
label="Company"
field="$ctrl.dms.companyId"
url="/api/Companies"
show-field="code"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one required="true"
label="Warehouse"
field="$ctrl.dms.warehouseId"
url="/api/Warehouses"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete vn-one required="true"
label="Type"
field="$ctrl.dms.dmsTypeId"
url="/api/DmsTypes"
show-field="name"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textarea vn-one required="true"
label="Description"
field="$ctrl.dms.description">
</vn-textarea>
</vn-horizontal>
<vn-horizontal>
<vn-input-file vn-one
label="File"
model="$ctrl.dms.files"
on-change="$ctrl.onFileChange(files)"
accept="{{$ctrl.allowedContentTypes}}"
multiple="true">
<t-right-icons>
<vn-icon vn-none
color-secondary
title="{{$ctrl.contentTypesInfo}}"
icon="info">
</vn-icon>
</t-right-icons>
</vn-input-file>
</vn-horizontal>
<vn-vertical>
<vn-check disabled="true"
label="Generate identifier for original file"
field="$ctrl.dms.hasFile">
</vn-check>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
<vn-button ui-sref="claim.card.dms.index" label="Cancel"></vn-button>
</vn-button-bar>
</div>
</form>

View File

@ -0,0 +1,104 @@
import ngModule from '../../module';
import './style.scss';
class Controller {
constructor($scope, $http, $state, $translate, vnApp) {
this.$ = $scope;
this.$http = $http;
this.$state = $state;
this.$stateParams = $state.params;
this.$translate = $translate;
this.vnApp = vnApp;
}
get claim() {
return this._claim;
}
set claim(value) {
this._claim = value;
if (value) {
this.setDefaultParams();
this.getAllowedContentTypes();
}
}
getAllowedContentTypes() {
this.$http.get('/api/claimDms/allowedContentTypes').then(res => {
const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes;
});
}
get contentTypesInfo() {
return this.$translate.instant('ContentTypesInfo', {
allowedContentTypes: this.allowedContentTypes
});
}
setDefaultParams() {
const path = `/api/Dms/${this.$stateParams.dmsId}`;
this.$http.get(path).then(res => {
const dms = res.data && res.data;
this.dms = {
reference: dms.reference,
warehouseId: dms.warehouseFk,
companyId: dms.companyFk,
dmsTypeId: dms.dmsTypeFk,
description: dms.description,
hasFile: dms.hasFile,
hasFileAttached: false,
files: []
};
});
}
onSubmit() {
const query = `/api/dms/${this.$stateParams.dmsId}/updateFile`;
const options = {
method: 'POST',
url: query,
params: this.dms,
headers: {
'Content-Type': undefined
},
transformRequest: files => {
const formData = new FormData();
for (let i = 0; i < files.length; i++)
formData.append(files[i].name, files[i]);
return formData;
},
data: this.dms.files
};
this.$http(options).then(res => {
if (res) {
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
this.$.watcher.updateOriginalData();
this.$state.go('claim.card.dms.index');
}
});
}
onFileChange(files) {
let hasFileAttached = false;
if (files.length > 0)
hasFileAttached = true;
this.$.$applyAsync(() => {
this.dms.hasFileAttached = hasFileAttached;
});
}
}
Controller.$inject = ['$scope', '$http', '$state', '$translate', 'vnApp'];
ngModule.component('vnClaimDmsEdit', {
template: require('./index.html'),
controller: Controller,
bindings: {
claim: '<'
}
});

View File

@ -0,0 +1,84 @@
import './index';
describe('Claim', () => {
describe('Component vnClaimDmsEdit', () => {
let controller;
let $scope;
let $httpBackend;
let $state;
beforeEach(ngModule('claim'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$state = {params: {dmsId: 1}};
controller = $componentController('vnClaimDmsEdit', {$scope, $state});
controller._claim = {id: 1, ticketFk: 16};
}));
describe('claim() setter', () => {
it('should set the claim data and then call setDefaultParams() and getAllowedContentTypes()', () => {
spyOn(controller, 'setDefaultParams');
spyOn(controller, 'getAllowedContentTypes');
controller._claim = undefined;
controller.claim = {
id: 15,
ticketFk: 16
};
expect(controller.setDefaultParams).toHaveBeenCalledWith();
expect(controller.claim).toBeDefined();
expect(controller.getAllowedContentTypes).toHaveBeenCalledWith();
});
});
describe('setDefaultParams()', () => {
it('should perform a GET query and define the dms property on controller', () => {
const dmsId = 1;
const expectedResponse = {
reference: 101,
warehouseFk: 1,
companyFk: 442,
dmsTypeFk: 20,
description: 'Test',
hasFile: false,
hasFileAttached: false
};
$httpBackend.when('GET', `/api/Dms/${dmsId}`).respond(expectedResponse);
$httpBackend.expect('GET', `/api/Dms/${dmsId}`).respond(expectedResponse);
controller.setDefaultParams();
$httpBackend.flush();
expect(controller.dms).toBeDefined();
expect(controller.dms.reference).toEqual(101);
expect(controller.dms.dmsTypeId).toEqual(20);
});
});
describe('onFileChange()', () => {
it('should set dms hasFileAttached property to true if has any files', () => {
const files = [{id: 1, name: 'MyFile'}];
controller.dms = {hasFileAttached: false};
controller.onFileChange(files);
$scope.$apply();
expect(controller.dms.hasFileAttached).toBeTruthy();
});
});
describe('getAllowedContentTypes()', () => {
it('should make an HTTP GET request to get the allowed content types', () => {
const expectedResponse = ['image/png', 'image/jpg'];
$httpBackend.when('GET', `/api/claimDms/allowedContentTypes`).respond(expectedResponse);
$httpBackend.expect('GET', `/api/claimDms/allowedContentTypes`);
controller.getAllowedContentTypes();
$httpBackend.flush();
expect(controller.allowedContentTypes).toBeDefined();
expect(controller.allowedContentTypes).toEqual('image/png, image/jpg');
});
});
});
});

View File

@ -0,0 +1,7 @@
vn-ticket-request {
vn-textfield {
margin: 0!important;
max-width: 100px;
}
}

View File

@ -0,0 +1,110 @@
<vn-crud-model
vn-id="model"
url="/api/ClaimDms"
link="{claimFk: $ctrl.$stateParams.id}"
filter="::$ctrl.filter"
limit="20"
data="$ctrl.claimDms">
</vn-crud-model>
<vn-vertical>
<vn-card pad-large>
<vn-vertical>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="dmsFk" default-order="DESC" shrink>Id</vn-th>
<vn-th shrink>Type</vn-th>
<vn-th shrink number>Order</vn-th>
<vn-th shrink>Reference</vn-th>
<vn-th>Description</vn-th>
<vn-th shrink>Original</vn-th>
<vn-th>File</vn-th>
<vn-th shrink>Employee</vn-th>
<vn-th>Created</vn-th>
<vn-th number></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="document in $ctrl.claimDms">
<vn-td number shrink>{{::document.dmsFk}}</vn-td>
<vn-td shrink>
<span title="{{::document.dms.dmsType.name}}">
{{::document.dms.dmsType.name}}
</span>
</vn-td>
<vn-td shrink number>
<span title="{{::document.dms.hardCopyNumber}}">
{{::document.dms.hardCopyNumber}}
</span>
</vn-td>
<vn-td shrink>
<span title="{{::document.dms.reference}}">
{{::document.dms.reference}}
</span>
</vn-td>
<vn-td>
<span title="{{::document.dms.description}}">
{{::document.dms.description}}
</span>
</vn-td>
<vn-td shrink>
<vn-check disabled="true"
field="document.dms.hasFile">
</vn-check>
</vn-td>
<vn-td>
<a target="_blank"
title="{{'Download file' | translate}}"
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}">
{{::document.dms.file}}
</a>
</vn-td>
<vn-td shrink>
<span class="link"
ng-click="$ctrl.showWorkerDescriptor($event, document.dms.workerFk)">
{{::document.dms.worker.user.nickname | dashIfEmpty}}
</span></vn-td>
<vn-td>
{{::document.dms.created | dateTime:'dd/MM/yyyy HH:mm'}}
</vn-td>
<vn-td number>
<a target="_blank"
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}">
<vn-icon-button
icon="cloud_download"
title="{{'Download file' | translate}}">
</vn-icon-button>
</a>
<vn-icon-button ui-sref="claim.card.dms.edit({dmsId: {{::document.dmsFk}}})"
icon="edit"
title="{{'Edit file' | translate}}">
</vn-icon-button>
<vn-icon-button
icon="delete"
ng-click="$ctrl.showDeleteConfirm($index)"
title="{{'Remove file' | translate}}"
tabindex="-1">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-vertical>
<vn-pagination model="model"></vn-pagination>
</vn-card>
</vn-vertical>
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<a ui-sref="claim.card.dms.create"
vn-tooltip="Upload file"
vn-bind="+"
fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>
<vn-confirm
vn-id="confirm"
message="This file will be deleted"
question="Are you sure you want to continue?"
on-response="$ctrl.deleteDms(response)">
</vn-confirm>

View File

@ -0,0 +1,79 @@
import ngModule from '../../module';
import './style.scss';
class Controller {
constructor($stateParams, $scope, $http, $translate, vnToken, vnApp) {
this.$stateParams = $stateParams;
this.$ = $scope;
this.$http = $http;
this.$translate = $translate;
this.accessToken = vnToken.token;
this.vnApp = vnApp;
this.filter = {
include: {
relation: 'dms',
scope: {
fields: [
'dmsTypeFk',
'workerFk',
'hardCopyNumber',
'reference',
'description',
'hasFile',
'file',
'created',
],
include: [{
relation: 'dmsType',
scope: {
fields: ['name']
}
},
{
relation: 'worker',
scope: {
fields: ['userFk'],
include: {
relation: 'user',
scope: {
fields: ['nickname']
}
},
}
}]
},
}
};
}
showWorkerDescriptor(event, workerFk) {
event.preventDefault();
event.stopImmediatePropagation();
this.$.workerDescriptor.parent = event.target;
this.$.workerDescriptor.workerFk = workerFk;
this.$.workerDescriptor.show();
}
showDeleteConfirm(index) {
this.dmsIndex = index;
this.$.confirm.show();
}
deleteDms(response) {
if (response === 'ACCEPT') {
const dmsFk = this.claimDms[this.dmsIndex].dmsFk;
const query = `/api/claimDms/${dmsFk}/removeFile`;
this.$http.post(query).then(() => {
this.$.model.remove(this.dmsIndex);
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
});
}
}
}
Controller.$inject = ['$stateParams', '$scope', '$http', '$translate', 'vnToken', 'vnApp'];
ngModule.component('vnClaimDmsIndex', {
template: require('./index.html'),
controller: Controller,
});

View File

@ -0,0 +1,40 @@
import './index';
import crudModel from 'core/mocks/crud-model';
describe('Claim', () => {
describe('Component vnClaimDmsIndex', () => {
let $componentController;
let $scope;
let $httpBackend;
let controller;
beforeEach(ngModule('claim'));
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_) => {
$componentController = _$componentController_;
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
controller = $componentController('vnClaimDmsIndex', {$: $scope});
controller.$.model = crudModel;
}));
describe('deleteDms()', () => {
it('should make an HTTP Post query', () => {
const dmsId = 1;
const dmsIndex = 0;
spyOn(controller.vnApp, 'showSuccess');
spyOn(controller.$.model, 'remove');
controller.claimDms = [{dmsFk: 1}];
controller.dmsIndex = dmsIndex;
$httpBackend.when('POST', `/api/claimDms/${dmsId}/removeFile`).respond({});
$httpBackend.expect('POST', `/api/claimDms/${dmsId}/removeFile`);
controller.deleteDms('ACCEPT');
$httpBackend.flush();
expect(controller.$.model.remove).toHaveBeenCalledWith(dmsIndex);
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
});
});
});
});

View File

@ -0,0 +1,6 @@
vn-client-risk-index {
.totalBox {
display: table;
float: right;
}
}

View File

@ -0,0 +1,2 @@
FileDescription: Ticket id {{ticketId}} from client {{clientName}} id {{clientId}}
ContentTypesInfo: Allowed file types {{allowedContentTypes}}

View File

@ -0,0 +1,8 @@
Upload file: Subir fichero
Edit file: Editar fichero
Upload: Subir
File: Fichero
FileDescription: Reclamación id {{claimId}} del cliente "{{clientName}}" id {{clientId}}
Generate identifier for original file: Generar identificador para archivo original
ContentTypesInfo: "Tipos de archivo permitidos: {{allowedContentTypes}}"
Are you sure you want to continue?: ¿Seguro que quieres continuar?

View File

@ -9,3 +9,6 @@ import './descriptor';
import './development'; import './development';
import './search-panel'; import './search-panel';
import './summary'; import './summary';
import './dms/index';
import './dms/create';
import './dms/edit';

View File

@ -8,7 +8,8 @@
{"state": "claim.card.basicData", "icon": "settings"}, {"state": "claim.card.basicData", "icon": "settings"},
{"state": "claim.card.detail", "icon": "icon-details"}, {"state": "claim.card.detail", "icon": "icon-details"},
{"state": "claim.card.development", "icon": "icon-traceability"}, {"state": "claim.card.development", "icon": "icon-traceability"},
{"state": "claim.card.action", "icon": "icon-actions"} {"state": "claim.card.action", "icon": "icon-actions"},
{"state": "claim.card.dms.index", "icon": "image"}
], ],
"keybindings": [ "keybindings": [
{"key": "r", "state": "claim.index"} {"key": "r", "state": "claim.index"}
@ -74,6 +75,35 @@
"claim": "$ctrl.claim" "claim": "$ctrl.claim"
}, },
"acl": ["salesAssistant"] "acl": ["salesAssistant"]
}, {
"url": "/dms",
"state": "claim.card.dms",
"abstract": true,
"component": "ui-view"
}, {
"url" : "/index",
"state": "claim.card.dms.index",
"component": "vn-claim-dms-index",
"description": "Pictures",
"params": {
"claim": "$ctrl.claim"
}
}, {
"url" : "/create",
"state": "claim.card.dms.create",
"component": "vn-claim-dms-create",
"description": "Upload file",
"params": {
"claim": "$ctrl.claim"
}
}, {
"url": "/:dmsId/edit",
"state": "claim.card.dms.edit",
"component": "vn-claim-dms-edit",
"description": "Edit file",
"params": {
"claim": "$ctrl.claim"
}
} }
] ]
} }

View File

@ -1,53 +0,0 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethod('filter', {
description: 'Find all instances of the model matched by filter from the data source.',
accessType: 'READ',
accepts: [
{
arg: 'filter',
type: 'Object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'}
}
],
returns: {
type: ['Object'],
root: true
},
http: {
path: `/filter`,
verb: 'GET'
}
});
Self.filter = async filter => {
let conn = Self.dataSource.connector;
let stmts = [];
let stmt;
filter.order = [
'c.defaultAddressFk DESC',
'a.isActive DESC',
'a.nickname ASC'
];
stmt = new ParameterizedSQL(
`SELECT a.*
FROM vn.address a
LEFT JOIN vn.client c ON c.defaultAddressFk = a.id`
);
stmt.merge(conn.makeSuffix(filter));
let itemsIndex = stmts.push(stmt) - 1;
let sql = ParameterizedSQL.join(stmts, ';');
let result = await conn.executeStmt(sql);
return itemsIndex === 0 ? result : result[itemsIndex];
};
};

View File

@ -0,0 +1,23 @@
module.exports = Self => {
Self.remoteMethodCtx('allowedContentTypes', {
description: 'Returns a list of allowed contentTypes',
accessType: 'READ',
returns: {
type: ['Object'],
root: true
},
http: {
path: `/allowedContentTypes`,
verb: 'GET'
}
});
Self.allowedContentTypes = async() => {
const storageConnector = Self.app.dataSources.storage.connector;
const allowedContentTypes = storageConnector.allowedContentTypes;
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;
return modelAllowedContentTypes || allowedContentTypes;
};
};

View File

@ -1,34 +1,39 @@
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('threeLastActive', { Self.remoteMethod('lastActiveTickets', {
description: 'Returns the last three tickets of a client that have the alertLevel at 0 and the shiped day is gt today', description: 'Returns the last three tickets of a client that have the alertLevel at 0 and the shiped day is gt today',
accessType: 'READ', accessType: 'READ',
accepts: [{ accepts: [{
arg: 'filter', arg: 'id',
type: 'object', type: 'Number',
required: true, required: true,
description: 'client id, ticketFk' description: 'Client id',
http: {source: 'path'}
}, {
arg: 'ticketId',
type: 'Number',
required: true
}], }],
returns: { returns: {
type: [this.modelName], type: ['Object'],
root: true root: true
}, },
http: { http: {
path: `/threeLastActive`, path: `/:id/lastActiveTickets`,
verb: 'GET' verb: 'GET'
} }
}); });
Self.threeLastActive = async params => { Self.lastActiveTickets = async(id, ticketId) => {
let query = ` const query = `
SELECT t.id, t.shipped, a.name AS agencyName, w.name AS warehouseName SELECT t.id, t.shipped, a.name AS agencyName, w.name AS warehouseName
FROM vn.ticket t FROM vn.ticket t
JOIN vn.ticketState ts ON t.id = ts.ticketFk JOIN vn.ticketState ts ON t.id = ts.ticketFk
JOIN vn.agencyMode a ON t.agencyModeFk = a.id JOIN vn.agencyMode a ON t.agencyModeFk = a.id
JOIN vn.warehouse w ON t.warehouseFk = w.id JOIN vn.warehouse w ON t.warehouseFk = w.id
WHERE t.shipped > CURDATE() AND t.clientFk = ? AND ts.alertLevel = 0 AND t.id <> ? WHERE t.shipped >= CURDATE() AND t.clientFk = ? AND ts.alertLevel = 0 AND t.id <> ?
ORDER BY t.shipped ORDER BY t.shipped
LIMIT 3`; LIMIT 3`;
let tickets = await Self.rawSql(query, [params.clientFk, params.ticketFk]);
return tickets; return Self.rawSql(query, [id, ticketId]);
}; };
}; };

View File

@ -0,0 +1,12 @@
const app = require('vn-loopback/server/server');
describe('client lastActiveTickets()', () => {
it('should return the last three active tickets', async() => {
const clientId = 109;
const ticketId = 19;
let result = await app.models.Client.lastActiveTickets(clientId, ticketId);
expect(result.length).toEqual(3);
});
});

View File

@ -5,7 +5,6 @@ let isMultiple = require('vn-loopback/util/hook').isMultiple;
module.exports = Self => { module.exports = Self => {
// Methods // Methods
require('../methods/address/createDefaultAddress')(Self); require('../methods/address/createDefaultAddress')(Self);
require('../methods/address/filter')(Self);
Self.validateAsync('isEqualizated', cannotHaveET, { Self.validateAsync('isEqualizated', cannotHaveET, {
message: 'Cannot check Equalization Tax in this NIF/CIF' message: 'Cannot check Equalization Tax in this NIF/CIF'

View File

@ -1,3 +1,4 @@
module.exports = Self => { module.exports = Self => {
require('../methods/client-dms/removeFile')(Self); require('../methods/client-dms/removeFile')(Self);
require('../methods/client-dms/allowedContentTypes')(Self);
}; };

View File

@ -21,6 +21,7 @@ module.exports = Self => {
require('../methods/client/confirmTransaction')(Self); require('../methods/client/confirmTransaction')(Self);
require('../methods/client/canBeInvoiced')(Self); require('../methods/client/canBeInvoiced')(Self);
require('../methods/client/uploadFile')(Self); require('../methods/client/uploadFile')(Self);
require('../methods/client/lastActiveTickets')(Self);
// Validations // Validations

View File

@ -61,17 +61,11 @@
fields="['code', 'townFk']" fields="['code', 'townFk']"
field="$ctrl.address.postalCode" field="$ctrl.address.postalCode"
where="{townFk: town.selection.id}" where="{townFk: town.selection.id}"
selection="$ctrl.postcodeSelection"
search-function="{code: $search}" search-function="{code: $search}"
fetch-function="{townFk: town.selection.id}"
order="code, townFk" order="code, townFk"
show-field="code" show-field="code"
value-field="code" value-field="code"
label="Postcode"> label="Postcode">
<tpl-item>
{{code}}, {{town.name}} - {{town.province.name}}
({{town.province.country.country}})
</tpl-item>
</vn-autocomplete> </vn-autocomplete>
<vn-icon-button vn-auto margin-medium-v <vn-icon-button vn-auto margin-medium-v
icon="add_circle" icon="add_circle"

View File

@ -20,23 +20,6 @@ export default class Controller {
this.$state.go('client.card.address.index'); this.$state.go('client.card.address.index');
} }
get postcodeSelection() {
return this._postcodeSelection;
}
set postcodeSelection(selection) {
const hasValue = this._postcodeSelection;
this._postcodeSelection = selection;
if (!selection || !hasValue) return;
const town = selection.town;
const province = town.province;
this.address.city = town.name;
this.address.provinceFk = province.id;
}
onResponse(response) { onResponse(response) {
this.address.postalCode = response.code; this.address.postalCode = response.code;
} }

View File

@ -55,31 +55,5 @@ describe('Client', () => {
expect(controller.$state.go).toHaveBeenCalledWith('client.card.address.index'); expect(controller.$state.go).toHaveBeenCalledWith('client.card.address.index');
}); });
}); });
describe('postcodeSelection() setter', () => {
it(`should set the town, province and contry properties`, () => {
controller.address = {};
controller._postcodeSelection = {townFk: 2};
controller.postcodeSelection = {
townFk: 1,
code: 46001,
town: {
id: 1,
name: 'New York',
province: {
id: 1,
name: 'New york',
country: {
id: 2,
name: 'USA'
}
}
}
};
expect(controller.address.city).toEqual('New York');
expect(controller.address.provinceFk).toEqual(1);
});
});
}); });
}); });

View File

@ -1,26 +1,28 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="/client/api/Addresses/filter" url="/api/Clients/{{$ctrl.$stateParams.id}}/addresses"
filter="$ctrl.filter"
limit="10" limit="10"
link="{clientFk: $ctrl.$stateParams.id}"
data="$ctrl.addresses" data="$ctrl.addresses"
auto-load="true"> auto-load="true">
</vn-crud-model> </vn-crud-model>
<div compact> <div compact>
<vn-card pad-large> <vn-card pad-large>
<vn-horizontal <div
ng-repeat="address in $ctrl.addresses" ng-repeat="address in $ctrl.addresses"
class="pad-medium-top" class="address">
style="align-items: center;"> <a
<vn-one ui-sref="client.card.address.edit({addressId: {{::address.id}}})"
border-radius
class="pad-small border-solid" class="pad-small border-solid"
ng-class="{ ng-class="{
'item-hightlight': $ctrl.isDefaultAddress(address), 'item-hightlight': $ctrl.isDefaultAddress(address),
'item-disabled': !address.isActive && !$ctrl.isDefaultAddress(address) 'item-disabled': !address.isActive && !$ctrl.isDefaultAddress(address)
}"> }"
<vn-horizontal style="align-items: center;"> translate-attr="{title: 'Edit address'}"
<vn-none pad-medium-h> border-radius>
<vn-none
pad-small-right
ng-click="$ctrl.onStarClick($event)">
<vn-icon-button <vn-icon-button
icon="star" icon="star"
ng-if="$ctrl.isDefaultAddress(address)"> ng-if="$ctrl.isDefaultAddress(address)">
@ -37,36 +39,34 @@
ng-click="$ctrl.setDefault(address)"> ng-click="$ctrl.setDefault(address)">
</vn-icon-button> </vn-icon-button>
</vn-none> </vn-none>
<vn-one border-solid-right> <vn-one
<vn-horizontal> style="overflow: hidden; min-width: 14em;">
<vn-one> <div class="ellipsize"><b>{{::address.nickname}}</b></div>
<div><b>{{::address.nickname}}</b></div> <div class="ellipsize" name="street">{{::address.street}}</div>
<div name="street">{{::address.street}}</div> <div class="ellipsize">{{::address.city}}, {{::address.province.name}}</div>
<div>{{::address.city}}, {{::address.province}}</div> <div class="ellipsize">
<div>{{::address.phone}}, {{::address.mobile}}</div> {{::address.phone}}<span ng-if="::address.mobile">, </span>
</vn-one> {{::address.mobile}}
<vn-one> </div>
<vn-check <vn-check
vn-one label="Is equalizated" vn-one label="Is equalizated"
field="address.isEqualizated" field="address.isEqualizated"
disabled="true"> disabled="true">
</vn-check> </vn-check>
</vn-one> </vn-one>
</vn-horizontal> <vn-vertical
</vn-one> vn-one
<vn-vertical vn-one pad-medium-h> ng-if="address.observations.length"
border-solid-left
pad-medium-h
style="height: 6em; overflow: auto;">
<vn-one ng-repeat="observation in address.observations track by $index" ng-class="{'pad-small-top': $index}"> <vn-one ng-repeat="observation in address.observations track by $index" ng-class="{'pad-small-top': $index}">
<b margin-medium-right>{{::observation.observationType.description}}:</b> <b>{{::observation.observationType.description}}:</b>
<span>{{::observation.description}}</span> <span>{{::observation.description}}</span>
</vn-one> </vn-one>
</vn-vertical> </vn-vertical>
<a pad-medium-h vn-tooltip="Edit address"
vn-auto ui-sref="client.card.address.edit({addressId: {{::address.id}}})">
<vn-icon-button icon="edit"></vn-icon-button>
</a> </a>
</vn-horizontal> </div>
</vn-one>
</vn-horizontal>
</vn-card> </vn-card>
<vn-float-button <vn-float-button
vn-bind="+" vn-bind="+"

View File

@ -1,10 +1,47 @@
import ngModule from '../../module'; import ngModule from '../../module';
import './style.scss';
class Controller { class Controller {
constructor($http, $scope, $stateParams) { constructor($http, $scope, $stateParams) {
this.$http = $http; this.$http = $http;
this.$scope = $scope; this.$scope = $scope;
this.$stateParams = $stateParams; this.$stateParams = $stateParams;
this.filter = {
fields: [
'id',
'isDefaultAddress',
'isActive',
'nickname',
'street',
'city',
'provinceFk',
'phone',
'mobile',
'isEqualizated'
],
order: [
'isDefaultAddress DESC',
'isActive DESC',
'nickname ASC'],
include: [
{
relation: 'observations',
scope: {
include: 'observationType'
}
}, {
relation: 'province',
scope: {
fields: ['id', 'name']
}
}
]
};
}
onStarClick(event) {
event.stopPropagation();
event.preventDefault();
} }
setDefault(address) { setDefault(address) {

View File

@ -0,0 +1,21 @@
@import "variables";
@import "./effects";
vn-client-address-index {
.address {
padding-bottom: $pad-medium;
&:last-child {
padding-bottom: 0;
}
& > a {
@extend %clickable;
box-sizing: border-box;
display: flex;
align-items: center;
width: 100%;
color: inherit;
overflow: hidden;
}
}
}

View File

@ -18,7 +18,7 @@
value-field="id" value-field="id"
where="{role: 'employee'}" where="{role: 'employee'}"
label="Salesperson"> label="Salesperson">
<tpl-item>{{firstName}} {{name}}</tpl-item> <tpl-item>{{firstName}} {{lastName}}</tpl-item>
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>

View File

@ -46,7 +46,16 @@
label="File" label="File"
model="$ctrl.dms.files" model="$ctrl.dms.files"
on-change="$ctrl.onFileChange(files)" on-change="$ctrl.onFileChange(files)"
accept=".pdf, .png, .jpg, .jpeg, application/zip, application/rar, application/x-7z-compressed"> accept="{{$ctrl.allowedContentTypes}}"
required="true"
multiple="true">
<t-right-icons>
<vn-icon vn-none
color-secondary
title="{{$ctrl.contentTypesInfo}}"
icon="info">
</vn-icon>
</t-right-icons>
</vn-input-file> </vn-input-file>
</vn-horizontal> </vn-horizontal>
<vn-vertical> <vn-vertical>

View File

@ -22,8 +22,23 @@ class Controller {
set client(value) { set client(value) {
this._client = value; this._client = value;
if (value) if (value) {
this.setDefaultParams(); this.setDefaultParams();
this.getAllowedContentTypes();
}
}
getAllowedContentTypes() {
this.$http.get('/api/clientDms/allowedContentTypes').then(res => {
const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes;
});
}
get contentTypesInfo() {
return this.$translate.instant('ContentTypesInfo', {
allowedContentTypes: this.allowedContentTypes
});
} }
setDefaultParams() { setDefaultParams() {

View File

@ -18,8 +18,9 @@ describe('Client', () => {
})); }));
describe('client() setter', () => { describe('client() setter', () => {
it('should set the client data and then call setDefaultParams()', () => { it('should set the client data and then call setDefaultParams() and getAllowedContentTypes()', () => {
spyOn(controller, 'setDefaultParams'); spyOn(controller, 'setDefaultParams');
spyOn(controller, 'getAllowedContentTypes');
controller.client = { controller.client = {
id: 15, id: 15,
name: 'Bruce wayne' name: 'Bruce wayne'
@ -27,6 +28,7 @@ describe('Client', () => {
expect(controller.client).toBeDefined(); expect(controller.client).toBeDefined();
expect(controller.setDefaultParams).toHaveBeenCalledWith(); expect(controller.setDefaultParams).toHaveBeenCalledWith();
expect(controller.getAllowedContentTypes).toHaveBeenCalledWith();
}); });
}); });
@ -56,5 +58,18 @@ describe('Client', () => {
expect(controller.dms.hasFileAttached).toBeTruthy(); expect(controller.dms.hasFileAttached).toBeTruthy();
}); });
}); });
describe('getAllowedContentTypes()', () => {
it('should make an HTTP GET request to get the allowed content types', () => {
const expectedResponse = ['image/png', 'image/jpg'];
$httpBackend.when('GET', `/api/clientDms/allowedContentTypes`).respond(expectedResponse);
$httpBackend.expect('GET', `/api/clientDms/allowedContentTypes`);
controller.getAllowedContentTypes();
$httpBackend.flush();
expect(controller.allowedContentTypes).toBeDefined();
expect(controller.allowedContentTypes).toEqual('image/png, image/jpg');
});
});
}); });
}); });

View File

@ -1 +0,0 @@
ClientFileDescription: "{{dmsTypeName}} from client {{clientName}} id {{clientId}}"

View File

@ -1,5 +0,0 @@
Upload file: Subir fichero
Upload: Subir
File: Fichero
ClientFileDescription: "{{dmsTypeName}} del cliente {{clientName}} id {{clientId}}"
Generate identifier for original file: Generar identificador para archivo original

View File

@ -18,8 +18,23 @@ class Controller {
set client(value) { set client(value) {
this._client = value; this._client = value;
if (value) if (value) {
this.setDefaultParams(); this.setDefaultParams();
this.getAllowedContentTypes();
}
}
getAllowedContentTypes() {
this.$http.get('/api/clientDms/allowedContentTypes').then(res => {
const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes;
});
}
get contentTypesInfo() {
return this.$translate.instant('ContentTypesInfo', {
allowedContentTypes: this.allowedContentTypes
});
} }
setDefaultParams() { setDefaultParams() {

View File

@ -1,43 +1,52 @@
import './index'; import './index';
describe('Client', () => { describe('Client', () => {
describe('Component vnClientDmsCreate', () => { describe('Component vnClientDmsEdit', () => {
let controller; let controller;
let $scope; let $scope;
let $httpBackend; let $httpBackend;
let $httpParamSerializer; let $state;
beforeEach(ngModule('client')); beforeEach(ngModule('client'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => { beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
$scope = $rootScope.$new(); $scope = $rootScope.$new();
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_; $state = {params: {dmsId: 1}};
controller = $componentController('vnClientDmsCreate', {$scope}); controller = $componentController('vnClientDmsEdit', {$scope, $state});
controller._client = {id: 101, name: 'Bruce wayne'}; controller._client = {id: 1};
})); }));
describe('client() setter', () => { describe('client() setter', () => {
it('should set the client data and then call setDefaultParams()', () => { it('should set the client data and then call setDefaultParams() and getAllowedContentTypes()', () => {
spyOn(controller, 'setDefaultParams'); spyOn(controller, 'setDefaultParams');
spyOn(controller, 'getAllowedContentTypes');
controller._client = undefined;
controller.client = { controller.client = {
id: 15, id: 15
name: 'Bruce wayne'
}; };
expect(controller.client).toBeDefined();
expect(controller.setDefaultParams).toHaveBeenCalledWith(); expect(controller.setDefaultParams).toHaveBeenCalledWith();
expect(controller.client).toBeDefined();
expect(controller.getAllowedContentTypes).toHaveBeenCalledWith();
}); });
}); });
describe('setDefaultParams()', () => { describe('setDefaultParams()', () => {
it('should perform a GET query and define the dms property on controller', () => { it('should perform a GET query and define the dms property on controller', () => {
const params = {filter: { const dmsId = 1;
where: {code: 'paymentsLaw'} const expectedResponse = {
}}; reference: 101,
let serializedParams = $httpParamSerializer(params); warehouseFk: 1,
$httpBackend.when('GET', `/api/DmsTypes/findOne?${serializedParams}`).respond({id: 12, code: 'paymentsLaw'}); companyFk: 442,
$httpBackend.expect('GET', `/api/DmsTypes/findOne?${serializedParams}`); dmsTypeFk: 12,
description: 'Test',
hasFile: false,
hasFileAttached: false
};
$httpBackend.when('GET', `/api/Dms/${dmsId}`).respond(expectedResponse);
$httpBackend.expect('GET', `/api/Dms/${dmsId}`).respond(expectedResponse);
controller.setDefaultParams(); controller.setDefaultParams();
$httpBackend.flush(); $httpBackend.flush();
@ -50,11 +59,25 @@ describe('Client', () => {
describe('onFileChange()', () => { describe('onFileChange()', () => {
it('should set dms hasFileAttached property to true if has any files', () => { it('should set dms hasFileAttached property to true if has any files', () => {
const files = [{id: 1, name: 'MyFile'}]; const files = [{id: 1, name: 'MyFile'}];
controller.dms = {hasFileAttached: false};
controller.onFileChange(files); controller.onFileChange(files);
$scope.$apply(); $scope.$apply();
expect(controller.dms.hasFileAttached).toBeTruthy(); expect(controller.dms.hasFileAttached).toBeTruthy();
}); });
}); });
describe('getAllowedContentTypes()', () => {
it('should make an HTTP GET request to get the allowed content types', () => {
const expectedResponse = ['image/png', 'image/jpg'];
$httpBackend.when('GET', `/api/clientDms/allowedContentTypes`).respond(expectedResponse);
$httpBackend.expect('GET', `/api/clientDms/allowedContentTypes`);
controller.getAllowedContentTypes();
$httpBackend.flush();
expect(controller.allowedContentTypes).toBeDefined();
expect(controller.allowedContentTypes).toEqual('image/png, image/jpg');
});
});
}); });
}); });

View File

@ -1,3 +0,0 @@
Edit file: Editar fichero
File: Fichero
Generate identifier for original file: Generar identificador para archivo original

View File

@ -13,14 +13,14 @@
<vn-thead> <vn-thead>
<vn-tr> <vn-tr>
<vn-th field="dmsFk" default-order="DESC" shrink>Id</vn-th> <vn-th field="dmsFk" default-order="DESC" shrink>Id</vn-th>
<vn-th field="dmsTypeFk" shrink>Type</vn-th> <vn-th shrink>Type</vn-th>
<vn-th field="hardCopyNumber" shrink number>Order</vn-th> <vn-th shrink number>Order</vn-th>
<vn-th field="reference" shrink>Reference</vn-th> <vn-th shrink>Reference</vn-th>
<vn-th>Description</vn-th> <vn-th>Description</vn-th>
<vn-th field="hasFile" shrink>Original</vn-th> <vn-th shrink>Original</vn-th>
<vn-th>File</vn-th> <vn-th>File</vn-th>
<vn-th shrink>Employee</vn-th> <vn-th shrink>Employee</vn-th>
<vn-th field="created">Created</vn-th> <vn-th>Created</vn-th>
<vn-th number></vn-th> <vn-th number></vn-th>
</vn-tr> </vn-tr>
</vn-thead> </vn-thead>
@ -54,7 +54,7 @@
</vn-td> </vn-td>
<vn-td> <vn-td>
<a target="_blank" <a target="_blank"
vn-tooltip="Download file" title="{{'Download file' | translate}}"
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}">{{::document.dms.file}} href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}">{{::document.dms.file}}
</a> </a>
</vn-td> </vn-td>
@ -68,22 +68,20 @@
</vn-td> </vn-td>
<vn-td number> <vn-td number>
<a target="_blank" <a target="_blank"
vn-tooltip="Download file"
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}"> href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}">
<vn-icon-button <vn-icon-button
icon="cloud_download" icon="cloud_download"
title="{{'Download PDF' | translate}}"> title="{{'Download file' | translate}}">
</vn-icon-button> </vn-icon-button>
</a> </a>
<vn-icon-button ui-sref="client.card.dms.edit({dmsId: {{::document.dmsFk}}})" <vn-icon-button ui-sref="client.card.dms.edit({dmsId: {{::document.dmsFk}}})"
vn-tooltip="Edit file"
icon="edit" icon="edit"
title="{{'Edit file' | translate}}"> title="{{'Edit file' | translate}}">
</vn-icon-button> </vn-icon-button>
<vn-icon-button <vn-icon-button
vn-tooltip="Remove file"
icon="delete" icon="delete"
ng-click="$ctrl.showDeleteConfirm($index)" ng-click="$ctrl.showDeleteConfirm($index)"
title="{{'Remove file' | translate}}"
tabindex="-1"> tabindex="-1">
</vn-icon-button> </vn-icon-button>
</vn-td> </vn-td>

View File

@ -62,7 +62,7 @@ class Controller {
deleteDms(response) { deleteDms(response) {
if (response === 'ACCEPT') { if (response === 'ACCEPT') {
const dmsFk = this.clientDms[this.dmsIndex].dmsFk; const dmsFk = this.clientDms[this.dmsIndex].dmsFk;
const query = `/api/ClientDms/${dmsFk}/removeFile`; const query = `/api/clientDms/${dmsFk}/removeFile`;
this.$http.post(query).then(() => { this.$http.post(query).then(() => {
this.$.model.remove(this.dmsIndex); this.$.model.remove(this.dmsIndex);
this.vnApp.showSuccess(this.$translate.instant('Data saved!')); this.vnApp.showSuccess(this.$translate.instant('Data saved!'));

View File

@ -0,0 +1,40 @@
import './index';
import crudModel from 'core/mocks/crud-model';
describe('Client', () => {
describe('Component vnClientDmsIndex', () => {
let $componentController;
let $scope;
let $httpBackend;
let controller;
beforeEach(ngModule('client'));
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_) => {
$componentController = _$componentController_;
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
controller = $componentController('vnClientDmsIndex', {$: $scope});
controller.$.model = crudModel;
}));
describe('deleteDms()', () => {
it('should make an HTTP Post query', () => {
const dmsId = 1;
const dmsIndex = 0;
spyOn(controller.vnApp, 'showSuccess');
spyOn(controller.$.model, 'remove');
controller.clientDms = [{dmsFk: 1}];
controller.dmsIndex = dmsIndex;
$httpBackend.when('POST', `/api/clientDms/${dmsId}/removeFile`).respond({});
$httpBackend.expect('POST', `/api/clientDms/${dmsId}/removeFile`);
controller.deleteDms('ACCEPT');
$httpBackend.flush();
expect(controller.$.model.remove).toHaveBeenCalledWith(dmsIndex);
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
});
});
});
});

View File

@ -0,0 +1,2 @@
ClientFileDescription: "{{dmsTypeName}} from client {{clientName}} id {{clientId}}"
ContentTypesInfo: Allowed file types {{allowedContentTypes}}

View File

@ -0,0 +1,14 @@
Upload file: Subir fichero
Edit file: Editar fichero
Upload: Subir
File: Fichero
ClientFileDescription: "{{dmsTypeName}} del cliente {{clientName}} id {{clientId}}"
ContentTypesInfo: "Tipos de archivo permitidos: {{allowedContentTypes}}"
Generate identifier for original file: Generar identificador para archivo original
File management: Gestión documental
Hard copy: Copia
This file will be deleted: Este fichero va a ser borrado
Are you sure?: Estas seguro?
File deleted: Fichero eliminado
Remove file: Eliminar fichero
Download file: Descargar fichero

View File

@ -58,16 +58,12 @@
url="/api/Postcodes/location" url="/api/Postcodes/location"
fields="['code', 'townFk']" fields="['code', 'townFk']"
field="$ctrl.client.postcode" field="$ctrl.client.postcode"
selection="$ctrl.postcodeSelection"
search-function="{code: $search}" search-function="{code: $search}"
where="{townFk: town.selection.id}" where="{townFk: town.selection.id}"
order="code, townFk"
show-field="code" show-field="code"
value-field="code" value-field="code"
label="Postcode"> label="Postcode">
<tpl-item>
{{code}}, {{town.name}} - {{town.province.name}}
({{town.province.country.country}})
</tpl-item>
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal pad-small-v> <vn-horizontal pad-small-v>

View File

@ -56,24 +56,8 @@ export default class Controller {
); );
} }
} }
get postcodeSelection() {
return this._postcodeSelection;
}
set postcodeSelection(selection) {
const hasValue = this._postcodeSelection;
this._postcodeSelection = selection;
if (!selection || !hasValue) return;
const town = selection.town;
const province = town.province;
this.address.city = town.name;
this.address.provinceFk = province.id;
}
} }
Controller.$inject = ['$scope', '$http', 'vnApp', '$translate']; Controller.$inject = ['$scope', '$http', 'vnApp', '$translate'];
ngModule.component('vnClientFiscalData', { ngModule.component('vnClientFiscalData', {

View File

@ -21,9 +21,9 @@ module.exports = Self => {
}); });
Self.delete = async id => { Self.delete = async id => {
const tx = await Self.beginTransaction({}); const transaction = await Self.beginTransaction({});
try { try {
let options = {transaction: tx}; let options = {transaction: transaction};
let invoiceOut = await Self.findById(id); let invoiceOut = await Self.findById(id);
let tickets = await Self.app.models.Ticket.find({where: {refFk: invoiceOut.ref}}); let tickets = await Self.app.models.Ticket.find({where: {refFk: invoiceOut.ref}});
@ -35,10 +35,10 @@ module.exports = Self => {
await Promise.all(promises); await Promise.all(promises);
await invoiceOut.destroy(options); await invoiceOut.destroy(options);
await tx.commit(); await transaction.commit();
return tickets; return tickets;
} catch (e) { } catch (e) {
await tx.rollback(); await transaction.rollback();
throw e; throw e;
} }
}; };

View File

@ -38,11 +38,6 @@ module.exports = Self => {
type: 'Integer', type: 'Integer',
description: 'Type id', description: 'Type id',
http: {source: 'query'} http: {source: 'query'}
}, {
arg: 'hasVisible',
type: 'Boolean',
description: 'Whether the the item has visible or not',
http: {source: 'query'}
}, { }, {
arg: 'isActive', arg: 'isActive',
type: 'Boolean', type: 'Boolean',

View File

@ -15,6 +15,7 @@
panel="vn-item-search-panel" panel="vn-item-search-panel"
on-search="$ctrl.onSearch($params)" on-search="$ctrl.onSearch($params)"
info="Search items by id, name or barcode" info="Search items by id, name or barcode"
suggested-filter="{isActive: true}"
vn-focus> vn-focus>
</vn-searchbar> </vn-searchbar>
<vn-icon-menu <vn-icon-menu

View File

@ -38,9 +38,6 @@ class Controller {
} }
onSearch(params) { onSearch(params) {
if (params && params.hasVisible === undefined && params.isActive === undefined)
Object.assign(params, {hasVisible: true, isActive: true});
if (params) if (params)
this.$.model.applyFilter(null, params); this.$.model.applyFilter(null, params);
else else

View File

@ -0,0 +1,57 @@
const app = require('vn-loopback/server/server');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethod('getItemTypeAvailable', {
description: 'Gets the item types available for an rder and item category ',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'order id',
http: {source: 'path'}
},
{
arg: 'itemCategoryId',
type: 'number',
required: true
}],
returns: {
type: 'number',
root: true
},
http: {
path: `/:id/getItemTypeAvailable`,
verb: 'GET'
}
});
Self.getItemTypeAvailable = async(orderId, itemCategoryId) => {
let stmts = [];
let stmt;
let order = await app.models.Order.findById(orderId);
stmt = new ParameterizedSQL('call vn.available_calc(?, ?, ?)', [
order.landed,
order.addressFk,
order.agencyModeFk
]);
stmts.push(stmt);
stmt = new ParameterizedSQL(`SELECT it.id, it.name
FROM tmp.availableCalc ac
JOIN cache.available a ON a.calc_id = ac.calcFk
JOIN item i ON i.id = a.item_id
JOIN itemType it ON it.id = i.typeFk
WHERE it.categoryFk = ?
GROUP BY it.id`, [
itemCategoryId
]);
let categoriesIndex = stmts.push(stmt) - 1;
let sql = ParameterizedSQL.join(stmts, ';');
let result = await Self.rawStmt(sql);
return result[categoriesIndex];
};
};

View File

@ -0,0 +1,19 @@
const app = require('vn-loopback/server/server');
describe('order getItemTypeAvailable()', () => {
it('should call the getItemTypeAvailable method with a valid order and item category', async() => {
let orderId = 11;
let itemCategoryId = 1;
let result = await app.models.Order.getItemTypeAvailable(orderId, itemCategoryId);
expect(result.length).toEqual(1);
});
it('should call the getItemTypeAvailable method with the same order and different item category', async() => {
let orderId = 11;
let itemCategoryId = 4;//
let result = await app.models.Order.getItemTypeAvailable(orderId, itemCategoryId);
expect(result.length).toEqual(0);
});
});

View File

@ -1,3 +1,5 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
require('../methods/order/new')(Self); require('../methods/order/new')(Self);
require('../methods/order/getTotalVolume')(Self); require('../methods/order/getTotalVolume')(Self);
@ -13,4 +15,12 @@ module.exports = Self => {
require('../methods/order/updateBasicData')(Self); require('../methods/order/updateBasicData')(Self);
require('../methods/order/confirm')(Self); require('../methods/order/confirm')(Self);
require('../methods/order/filter')(Self); require('../methods/order/filter')(Self);
require('../methods/order/getItemTypeAvailable')(Self);
Self.beforeRemote('deleteById', async function(ctx) {
const targetOrder = await Self.findById(ctx.args.id);
if (targetOrder.isConfirmed === 1)
throw new UserError(`You can't delete a confirmed order`);
});
}; };

Some files were not shown because too many files have changed in this diff Show More