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 {
NODE_ENV = ""
FIREFOX_BIN = "/opt/firefox/firefox-bin"
DB_HOST = "${env.DOCKER_HOST_2}"
}
steps {
nodejs('node-lts') {

View File

@ -19,8 +19,8 @@ module.exports = Self => {
next();
});
Self.remoteMethod('getCurrentUserName', {
description: 'Gets the current user name',
Self.remoteMethod('getCurrentUserData', {
description: 'Gets the current user data',
accepts: [
{
arg: 'context',
@ -31,21 +31,22 @@ module.exports = Self => {
}
],
returns: {
type: 'string',
type: 'object',
root: true
},
http: {
verb: 'GET',
path: '/getCurrentUserName'
path: '/getCurrentUserData'
}
});
Self.getCurrentUserName = async function(ctx) {
Self.getCurrentUserData = async function(ctx) {
let filter = {fields: ['name']};
let userId = ctx.req.accessToken.userId;
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),
(2, 20);
INSERT INTO `vn2008`.`zones`(`zone_id`, `name`, `printingOrder`)
VALUES
(1, 'zone one', 1),
(2, 'zone two', 2),
(3, 'zone three', 3);
INSERT INTO `vn`.`province`(`id`, `name`, `countryFk`, `warehouseFk`, `zoneFk`)
INSERT INTO `vn`.`province`(`id`, `name`, `countryFk`, `warehouseFk`)
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);
(1, 'Province one', 1, NULL),
(2, 'Province two', 1, NULL),
(3, 'Province three', 1, NULL),
(4, 'Province four', 1, NULL),
(5, 'Province five', 1, NULL);
INSERT INTO `vn`.`town`(`id`, `name`, `provinceFk`)
VALUES
@ -397,12 +391,24 @@ UPDATE `vn`.`invoiceOut` SET ref = 'A1111111' WHERE id = 5;
INSERT INTO `vn`.`invoiceOutTax` (`invoiceOutFk`, `taxableBase`, `vat`, `pgcFk`)
VALUES
(1, 895.76, 89.58, 4722000010),
(1, 33.80, 7.10, 4722000021),
(2, 110.33, 11.03, 4770000010),
(3, 8.07, 0.81, 4770000010),
(4, 8.07, 0.81, 4770000010),
(5, 8.07, 0.81, 4770000010);
(1, 895.76, 89.58, 4722000010),
(1, 33.80, 7.10, 4722000021),
(2, 110.33, 11.03, 4770000010),
(3, 8.07, 0.81, 4770000010),
(4, 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`)
VALUES
@ -427,7 +433,7 @@ INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaF
('T', 'Española rapida', 1, 'NATIONAL', 0),
('V', 'Intracomunitaria global', 0, 'CEE', 1);
INSERT INTO `vn`.`zone` (`id`, `name`, `hour`, `warehouseFk`, `agencyModeFk`, `travelingDays`, `price`, `bonus`)
INSERT INTO `vn`.`zone` (`id`, `name`, `hour`, `warehouseFk`, `agencyModeFk`, `travelingDays`, `price`, `bonus`)
VALUES
(1, 'Zone pickup A', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 1, 1, 0, 0, 0),
(2, 'Zone pickup B', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 2, 1, 0, 0, 0),
@ -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),
(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),
(7, 'Zone refund', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 1, 10, 0, 0, 0),
(8, 'Zone others', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 1, 23, 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, 10, 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),
(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)),
(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)),
(23, NULL, 23, 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());
(23, NULL, 10, 1, NULL, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 101, 'address 21', 121, 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`)
VALUES
@ -1056,7 +1062,7 @@ INSERT INTO `vn`.`entry`(`id`, `supplierFk`, `created`, `travelFk`, `companyFk`,
INSERT INTO `bi`.`claims_ratio`(`id_Cliente`, `Consumo`, `Reclamaciones`, `Ratio`, `recobro`, `inflacion`)
VALUES
(101, 500, NULL, 0.00, 0.00, 1.00),
(102, 1000, 2.00, 0.01, 0.05, 1.00),
(102, 1000, 2.00, 0.01, 0.05, 1.00),
(103, 2000, 0.00, 0.00, 0.02, 1.00),
(104, 2500, 150.00, 0.02, 0.10, 1.00);
@ -1082,14 +1088,6 @@ INSERT INTO `vn2008`.`tblContadores`(`id`,`FechaInventario`)
VALUES
(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`)
VALUES
(1, 'AGENCY', 'Agencia'),
@ -1508,7 +1506,7 @@ INSERT INTO `vn`.`workCenter` (`id`, `name`, `warehouseFk`)
('1', 'Silla', '1'),
('5', 'Madrid', '5');
INSERT INTO `vn2008`.`workcenter_holiday` (`workcenter_id`, `day`, `year`)
INSERT INTO `vn`.`workCenterHoliday` (`workCenterFk`, `days`, `year`)
VALUES
('1', '27.5', YEAR(CURDATE())),
('5', '22', YEAR(CURDATE())),
@ -1551,6 +1549,17 @@ INSERT INTO `vn`.`sharingCart`(`id`, `workerFk`, `started`, `ended`, `workerSubs
VALUES
(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`)
VALUES
(1, 3, 0),
@ -1828,13 +1837,14 @@ INSERT INTO `vn`.`dmsType`(`id`, `name`, `path`, `readRoleFk`, `writeRoleFk`, `c
(16, 'Logistica', 'logistica', NULL, NULL, 'logistics'),
(17, 'cmr', 'cmr', NULL, NULL, 'cmr'),
(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`)
VALUES
(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()),
(3, 5, '3.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Client: 101', 'Client:101 readme', 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:104', 'Client:104 dms for the client', CURDATE()),
(3, 5, '3.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Client: 104', 'Client:104 readme', CURDATE());
INSERT INTO `vn`.`ticketDms`(`ticketFk`, `dmsFk`)
VALUES
@ -1842,8 +1852,8 @@ INSERT INTO `vn`.`ticketDms`(`ticketFk`, `dmsFk`)
INSERT INTO `vn`.`clientDms`(`clientFk`, `dmsFk`)
VALUES
(101, 2),
(101, 3);
(104, 2),
(104, 3);
INSERT INTO `vn`.`device` (`sn`, `model`, `userFk`)
VALUES

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -106,10 +106,10 @@ export default {
agencyAutocomplete: 'vn-autocomplete[field="$ctrl.address.agencyModeFk"]',
phoneInput: `${components.vnTextfield}[name="phone"]`,
mobileInput: `${components.vnTextfield}[name="mobile"]`,
defaultAddress: 'vn-client-address-index vn-horizontal:nth-child(1) div[name="street"]',
secondMakeDefaultStar: 'vn-client-address-index vn-card vn-horizontal:nth-child(2) vn-icon-button[icon="star_border"]',
firstEditButton: 'vn-client-address-index vn-icon-button[icon="edit"]',
secondEditButton: 'vn-client-address-index vn-horizontal:nth-child(2) vn-icon-button[icon="edit"]',
defaultAddress: 'vn-client-address-index div:nth-child(1) div[name="street"]',
secondMakeDefaultStar: 'vn-client-address-index vn-card div:nth-child(2) vn-icon-button[icon="star_border"]',
firstEditAddress: 'vn-client-address-index div:nth-child(1) > a',
secondEditAddress: 'vn-client-address-index div:nth-child(2) > a',
activeCheckbox: 'vn-check[label="Enabled"] 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"]',
@ -174,6 +174,12 @@ export default {
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"]'
},
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: {
searchIcon: 'vn-item-index vn-searchbar vn-icon[icon="search"]',
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',
newTicketButton: 'vn-ticket-index > a',
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)',
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',
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',
moreMenuTurns: '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',
weeklyTicket: 'vn-ticket-weekly vn-table > div > vn-tbody > vn-tr',
sixthWeeklyTicketDeleteIcon: 'vn-ticket-weekly vn-tr:nth-child(6) vn-icon-button[icon="delete"]',
acceptDeleteTurn: 'vn-ticket-weekly > vn-confirm[vn-id="deleteWeekly"] button[response="ACCEPT"]'
moreMenuWeeklyTickets: 'vn-ticket-index vn-icon-menu vn-drop-down > vn-popover li:nth-child(2)',
sixthWeeklyTicket: 'vn-ticket-weekly-index vn-table vn-tr:nth-child(6) vn-autocomplete[field="weekly.weekDay"] input',
weeklyTicket: 'vn-ticket-weekly-index vn-table > div > vn-tbody > vn-tr',
firstWeeklyTicketDeleteIcon: 'vn-ticket-weekly-index vn-tr:nth-child(1) vn-icon-button[icon="delete"]',
acceptDeleteTurn: 'vn-ticket-weekly-index > vn-confirm[vn-id="deleteWeekly"] button[response="ACCEPT"]'
},
createTicketView: {
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"]',
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"]',
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',
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)',
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"]',
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"]',
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"]',
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',
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"]',
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)',
@ -416,13 +432,15 @@ export default {
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)',
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',
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',
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"]',
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"]',
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"]',
@ -520,7 +538,7 @@ export default {
saveButton: `${components.vnSubmit}`
},
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',
discoutPopoverMana: 'vn-claim-detail > vn-popover > div > div.content > div > vn-horizontal > h5',
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',
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',
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: {
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"]',
createOrderButton: `${components.vnFloatButton}`,
},
orderDescriptor: {
returnToModuleIndexButton: 'vn-order-descriptor a[ui-sref="order.index"]',
acceptNavigationButton: 'vn-order-basic-data vn-confirm button[response=ACCEPT]'
},
createOrderView: {
clientAutocomplete: 'vn-autocomplete[label="Client"]',
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
it(`should click on the 1st edit icon to check EQtax isnt checked`, async() => {
const result = await nightmare
.waitToClick(selectors.clientAddresses.firstEditButton)
.waitToClick(selectors.clientAddresses.firstEditAddress)
.checkboxState(selectors.clientAddresses.equalizationTaxCheckbox);
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() => {
const result = await nightmare
.waitToClick(selectors.clientAddresses.addressesButton)
.waitToClick(selectors.clientAddresses.secondEditButton)
.waitToClick(selectors.clientAddresses.secondEditAddress)
.checkboxState(selectors.clientAddresses.equalizationTaxCheckbox);
expect(result).toBe('unchecked');
@ -67,6 +67,7 @@ describe('Client Edit fiscalData path', () => {
.write(selectors.clientFiscalData.fiscalIdInput, 'INVALID!')
.clearInput(selectors.clientFiscalData.addressInput)
.write(selectors.clientFiscalData.addressInput, 'Somewhere edited')
.autocompleteSearch(selectors.clientFiscalData.cityAutocomplete, 'Valencia')
.autocompleteSearch(selectors.clientFiscalData.postcodeAutocomplete, '46000')
.waitToClick(selectors.clientFiscalData.activeCheckbox)
.waitToClick(selectors.clientFiscalData.frozenCheckbox)
@ -80,7 +81,7 @@ describe('Client Edit fiscalData path', () => {
.waitForLastSnackbar();
expect(result).toEqual('Invalid Tax number');
}, 15000);
});
it(`should edit the fiscal this time with a valid fiscal id`, async() => {
const result = await nightmare
@ -134,7 +135,7 @@ describe('Client Edit fiscalData path', () => {
// confirm all addresses have now EQtax checked step 2
it(`should click on the 1st edit icon to confirm EQtax is checked`, async() => {
const result = await nightmare
.waitToClick(selectors.clientAddresses.firstEditButton)
.waitToClick(selectors.clientAddresses.firstEditAddress)
.checkboxState(selectors.clientAddresses.equalizationTaxCheckbox);
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() => {
const result = await nightmare
.waitToClick(selectors.clientAddresses.addressesButton)
.waitToClick(selectors.clientAddresses.secondEditButton)
.waitToClick(selectors.clientAddresses.secondEditAddress)
.checkboxState(selectors.clientAddresses.equalizationTaxCheckbox);
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
it(`should click on the 1st edit icon to access the address details and uncheck EQtax checkbox`, async() => {
const result = await nightmare
.waitToClick(selectors.clientAddresses.firstEditButton)
.waitToClick(selectors.clientAddresses.firstEditAddress)
.waitToClick(selectors.clientAddresses.equalizationTaxCheckbox)
.waitToClick(selectors.clientAddresses.saveButton)
.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() => {
const result = await nightmare
.waitToClick(selectors.clientAddresses.addressesButton)
// .waitToClick(selectors.clientAddresses.addressesButton)
.waitToGetProperty(selectors.clientAddresses.defaultAddress, 'innerText');
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() => {
const url = await nightmare
.waitForTextInElement(selectors.clientAddresses.defaultAddress, 'Somewhere in Thailand')
.waitToClick(selectors.clientAddresses.firstEditButton)
.waitToClick(selectors.clientAddresses.firstEditAddress)
.waitForURL('/edit')
.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() => {
const url = await nightmare
.waitForTextInElement(selectors.clientAddresses.defaultAddress, '20 Ingram Street')
.waitToClick(selectors.clientAddresses.firstEditButton)
.waitToClick(selectors.clientAddresses.firstEditAddress)
.waitForURL('/edit')
.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() => {
const result = await nightmare
.clearInput(selectors.itemsIndex.searchItemInput)
.write(selectors.itemsIndex.searchItemInput, 'Ranged weapon longbow 2m')
.waitToClick(selectors.itemsIndex.searchButton)
.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() => {
const result = await nightmare
.clearInput(selectors.itemsIndex.searchItemInput)
.write(selectors.itemsIndex.searchItemInput, 'Infinity Gauntlet')
.waitToClick(selectors.itemsIndex.searchButton)
.waitForNumberOfElements(selectors.itemsIndex.searchResult, 0)
@ -95,6 +96,7 @@ describe('Item Create/Clone path', () => {
it(`should search for the item Infinity Gauntlet`, async() => {
const result = await nightmare
.clearInput(selectors.itemsIndex.searchItemInput)
.write(selectors.itemsIndex.searchItemInput, 'Infinity Gauntlet')
.waitToClick(selectors.itemsIndex.searchButton)
.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() => {
const result = await nightmare
.waitToClick(selectors.itemTags.goToItemIndexButton)
.clearInput(selectors.itemsIndex.searchItemInput)
.write(selectors.itemsIndex.searchItemInput, 'Infinity Gauntlet')
.waitToClick(selectors.itemsIndex.searchButton)
.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2)

View File

@ -11,6 +11,7 @@ describe('Item regularize path', () => {
it('should edit the user local warehouse', async() => {
let result = await nightmare
.waitForSpinnerLoad()
.waitToClick(selectors.globalItems.userMenuButton)
.autocompleteSearch(selectors.globalItems.userLocalWarehouse, 'Warehouse Four')
.waitForLastSnackbar();
@ -28,6 +29,7 @@ describe('Item regularize path', () => {
it('should search for the item', async() => {
const resultCount = await nightmare
.clearInput(selectors.itemsIndex.searchItemInput)
.write(selectors.itemsIndex.searchItemInput, 'Ranged weapon pistol 9mm')
.waitToClick(selectors.itemsIndex.searchButton)
.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1)
@ -133,6 +135,7 @@ describe('Item regularize path', () => {
it('should search for the item once again', async() => {
const resultCount = await nightmare
.clearInput(selectors.itemsIndex.searchItemInput)
.write(selectors.itemsIndex.searchItemInput, 'Ranged weapon pistol 9mm')
.waitToClick(selectors.itemsIndex.searchButton)
.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');
});
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() => {
let result = await nightmare
.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`);
});
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
.autocompleteSearch(selectors.createStateView.stateAutocomplete, 'asignado')
.waitToClick(selectors.createStateView.saveStateButton)
.waitForLastSnackbar();
.waitToGetProperty(`${selectors.createStateView.workerAutocomplete} input`, 'value');
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
.autocompleteSearch(selectors.createStateView.workerAutocomplete, 'replenisher')
.waitToClick(selectors.createStateView.saveStateButton)
.waitForLastSnackbar();

View File

@ -11,10 +11,58 @@ describe('Ticket Edit basic data path', () => {
.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
.autocompleteSearch(selectors.ticketBasicData.agencyAutocomplete, 'Silla247Expensive')
.waitToGetProperty(`${selectors.ticketBasicData.zoneAutocomplete} input`, 'value')
.waitToClick(selectors.ticketBasicData.nextStepButton)
.waitForURL('data/step-two')
.parsedUrl();

View File

@ -1,7 +1,8 @@
import selectors from '../../helpers/selectors.js';
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();
beforeAll(() => {
@ -257,12 +258,14 @@ describe('Ticket Edit sale path', () => {
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 result = await nightmare
.waitToClick(selectors.ticketSales.thirdSaleCheckbox)
.waitToClick(selectors.ticketSales.secondSaleCheckbox)
.waitToClick(selectors.ticketSales.transferSaleButton)
.focusElement(selectors.ticketSales.transferQuantityCell)
.write(selectors.ticketSales.transferQuantityInput, '10\u000d')
.write(selectors.ticketSales.moveToTicketInput, targetTicketId)
.waitToClick(selectors.ticketSales.moveToTicketButton)
.waitForURL(`ticket/${targetTicketId}/sale`)
@ -276,7 +279,14 @@ describe('Ticket Edit sale path', () => {
.wait(selectors.ticketSales.secondSaleText)
.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() => {
@ -290,12 +300,19 @@ describe('Ticket Edit sale path', () => {
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
.wait(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() => {
@ -324,11 +341,12 @@ describe('Ticket Edit sale path', () => {
});
it('should confirm the original ticket received the line', async() => {
const expectedLines = 4;
const result = await nightmare
.waitForNumberOfElements(selectors.ticketSales.saleLine, 3)
.waitForNumberOfElements(selectors.ticketSales.saleLine, expectedLines)
.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() => {
@ -357,7 +375,7 @@ describe('Ticket Edit sale path', () => {
const senderTicketId = 13;
const url = await nightmare
.waitToClick(selectors.ticketSales.firstSaleCheckbox)
.waitToClick(selectors.ticketSales.selectAllSalesCheckbox)
.waitToClick(selectors.ticketSales.transferSaleButton)
.waitToClick(selectors.ticketSales.moveToNewTicketButton)
.waitToClick(selectors.ticketSales.acceptDeleteTicketButton)

View File

@ -9,10 +9,10 @@ describe('Ticket descriptor path', () => {
.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
.waitToClick(selectors.ticketsIndex.moreMenu)
.waitToClick(selectors.ticketsIndex.moreMenuTurns)
.waitToClick(selectors.ticketsIndex.moreMenuWeeklyTickets)
.wait(selectors.ticketsIndex.weeklyTicket)
.countElement(selectors.ticketsIndex.weeklyTicket);
@ -32,7 +32,7 @@ describe('Ticket descriptor path', () => {
it('should search for the ticket 11', async() => {
const result = await nightmare
.write(selectors.ticketsIndex.searchTicketInput, 'id:11')
.write(selectors.ticketsIndex.searchTicketInput, 11)
.waitToClick(selectors.ticketsIndex.searchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1)
.countElement(selectors.ticketsIndex.searchResult);
@ -73,8 +73,8 @@ describe('Ticket descriptor path', () => {
it('should confirm the ticket 11 was added on thursday', async() => {
const result = await nightmare
.waitToClick(selectors.ticketsIndex.moreMenu)
.waitToClick(selectors.ticketsIndex.moreMenuTurns)
.waitToGetProperty(selectors.ticketsIndex.sixthWeeklyTicketTurn, 'value');
.waitToClick(selectors.ticketsIndex.moreMenuWeeklyTickets)
.waitToGetProperty(selectors.ticketsIndex.sixthWeeklyTicket, 'value');
expect(result).toEqual('Thursday');
});
@ -92,7 +92,7 @@ describe('Ticket descriptor path', () => {
it('should now search for the ticket 11', async() => {
const result = await nightmare
.write(selectors.ticketsIndex.searchTicketInput, 'id:11')
.write(selectors.ticketsIndex.searchTicketInput, 11)
.waitToClick(selectors.ticketsIndex.searchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1)
.countElement(selectors.ticketsIndex.searchResult);
@ -133,17 +133,25 @@ describe('Ticket descriptor path', () => {
it('should confirm the ticket 11 was added on saturday', async() => {
const result = await nightmare
.waitToClick(selectors.ticketsIndex.moreMenu)
.waitToClick(selectors.ticketsIndex.moreMenuTurns)
.waitToGetProperty(selectors.ticketsIndex.sixthWeeklyTicketTurn, 'value');
.waitToClick(selectors.ticketsIndex.moreMenuWeeklyTickets)
.waitToGetProperty(selectors.ticketsIndex.sixthWeeklyTicket, 'value');
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() => {
const result = await nightmare
.waitToClick(selectors.ticketsIndex.sixthWeeklyTicketDeleteIcon)
.waitToClick(selectors.ticketsIndex.firstWeeklyTicketDeleteIcon)
.waitToClick(selectors.ticketsIndex.acceptDeleteTurn)
.waitForLastSnackbar();
@ -152,7 +160,10 @@ describe('Ticket descriptor path', () => {
it('should confirm the sixth weekly ticket was deleted', async() => {
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);
});

View File

@ -30,6 +30,33 @@ describe('Ticket descriptor path', () => {
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() => {
const result = await nightmare
.waitToClick(selectors.ticketDescriptor.moreMenu)

View File

@ -69,14 +69,6 @@ describe('Ticket Summary path', () => {
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() => {
let url = await nightmare
.loginAndModule('production', 'ticket')

View File

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

View File

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

View File

@ -18,7 +18,10 @@
ng-blur="$ctrl.hasFocus = false"
tabindex="{{$ctrl.input.tabindex}}"
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 class="underline"></div>
<div class="selected underline"></div>

View File

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

View File

@ -73,6 +73,14 @@ export default class Popover extends Component {
if (this._shown) return;
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.element.style.display = 'block';
this.$timeout.cancel(this.showTimeout);
@ -96,6 +104,11 @@ export default class Popover extends Component {
hide() {
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.$element.removeClass('shown');
this.$timeout.cancel(this.showTimeout);

View File

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

View File

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

View File

@ -16,9 +16,10 @@ export default class MainMenu {
}
getCurrentUserName() {
this.$http.get('/api/Accounts/getCurrentUserName')
this.$http.get('/api/Accounts/getCurrentUserData')
.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()', () => {
it(`should set the user name property in the controller`, () => {
$httpBackend.when('GET', `/api/Accounts/getCurrentUserName`).respond('Batman');
$httpBackend.expect('GET', `/api/Accounts/getCurrentUserName`);
$httpBackend.when('GET', `/api/Accounts/getCurrentUserData`).respond({accountName: 'Batman'});
$httpBackend.expect('GET', `/api/Accounts/getCurrentUserData`);
controller.getCurrentUserName();
$httpBackend.flush();

View File

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

View File

@ -49,5 +49,9 @@
"This client can't be invoiced": "This client can't be invoiced",
"The introduced hour already exists": "The introduced hour already exists",
"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",
"Concept cannot be blank": "El concepto no puede quedar en blanco",
"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",
"provider": "filesystem",
"root": "./e2e/dms",
"maxFileSize": "10485760",
"maxFileSize": "52428800",
"allowedContentTypes": [
"application/x-7z-compressed",
"application/x-zip-compressed",
"application/x-rar-compressed",
"application/octet-stream",
"application/pdf",
"application/zip",
"application/rar",
"multipart/x-zip",
"image/png",
"image/jpeg",
"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": {
"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 => {
require('../methods/claim/filter')(Self);
require('../methods/claim/getSummary')(Self);
require('../methods/claim/createFromSales')(Self);
require('../methods/claim/updateClaim')(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-button
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-button>
<vn-button
label="Import ticket"
disabled="$ctrl.claim.claimStateFk == $ctrl.resolvedState"
ng-click="$ctrl.showLastTickets($event)"
vn-tooltip="Imports ticket lines">
</vn-button>

View File

@ -22,7 +22,6 @@
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th number>Id</vn-th>
<vn-th center>Landed</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th>Claimed</vn-th>
@ -35,13 +34,6 @@
</vn-thead>
<vn-tbody>
<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 number>{{::saleClaimed.sale.quantity}}</vn-td>
<vn-td>
@ -50,7 +42,13 @@
on-change="$ctrl.setClaimedQuantity(saleClaimed.id, saleClaimed.quantity)">
</vn-input-number>
</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>
<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 './search-panel';
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.detail", "icon": "icon-details"},
{"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": [
{"key": "r", "state": "claim.index"}
@ -74,6 +75,35 @@
"claim": "$ctrl.claim"
},
"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 => {
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',
accessType: 'READ',
accepts: [{
arg: 'filter',
type: 'object',
arg: 'id',
type: 'Number',
required: true,
description: 'client id, ticketFk'
description: 'Client id',
http: {source: 'path'}
}, {
arg: 'ticketId',
type: 'Number',
required: true
}],
returns: {
type: [this.modelName],
type: ['Object'],
root: true
},
http: {
path: `/threeLastActive`,
path: `/:id/lastActiveTickets`,
verb: 'GET'
}
});
Self.threeLastActive = async params => {
let query = `
Self.lastActiveTickets = async(id, ticketId) => {
const query = `
SELECT t.id, t.shipped, a.name AS agencyName, w.name AS warehouseName
FROM vn.ticket t
JOIN vn.ticketState ts ON t.id = ts.ticketFk
JOIN vn.agencyMode a ON t.agencyModeFk = a.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
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 => {
// Methods
require('../methods/address/createDefaultAddress')(Self);
require('../methods/address/filter')(Self);
Self.validateAsync('isEqualizated', cannotHaveET, {
message: 'Cannot check Equalization Tax in this NIF/CIF'

View File

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

View File

@ -5,7 +5,7 @@
"model":"ClientLog",
"relation": "client",
"showField": "dmsFk"
},
},
"options": {
"mysql": {
"table": "clientDms"

View File

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

View File

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

View File

@ -20,23 +20,6 @@ export default class Controller {
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) {
this.address.postalCode = response.code;
}

View File

@ -55,31 +55,5 @@ describe('Client', () => {
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,72 +1,72 @@
<vn-crud-model
vn-id="model"
url="/client/api/Addresses/filter"
url="/api/Clients/{{$ctrl.$stateParams.id}}/addresses"
filter="$ctrl.filter"
limit="10"
link="{clientFk: $ctrl.$stateParams.id}"
data="$ctrl.addresses"
auto-load="true">
</vn-crud-model>
<div compact>
<vn-card pad-large>
<vn-horizontal
<div
ng-repeat="address in $ctrl.addresses"
class="pad-medium-top"
style="align-items: center;">
<vn-one
border-radius
class="address">
<a
ui-sref="client.card.address.edit({addressId: {{::address.id}}})"
class="pad-small border-solid"
ng-class="{
'item-hightlight': $ctrl.isDefaultAddress(address),
'item-disabled': !address.isActive && !$ctrl.isDefaultAddress(address)
}">
<vn-horizontal style="align-items: center;">
<vn-none pad-medium-h>
<vn-icon-button
icon="star"
ng-if="$ctrl.isDefaultAddress(address)">
</vn-icon-button>
<vn-icon-button
ng-if="!address.isActive"
icon="star_border"
vn-tooltip="Active first to set as default">
</vn-icon-button>
<vn-icon-button
ng-if="address.isActive && !$ctrl.isDefaultAddress(address)"
icon="star_border"
vn-tooltip="Set as default"
ng-click="$ctrl.setDefault(address)">
</vn-icon-button>
</vn-none>
<vn-one border-solid-right>
<vn-horizontal>
<vn-one>
<div><b>{{::address.nickname}}</b></div>
<div name="street">{{::address.street}}</div>
<div>{{::address.city}}, {{::address.province}}</div>
<div>{{::address.phone}}, {{::address.mobile}}</div>
</vn-one>
<vn-one>
<vn-check
vn-one label="Is equalizated"
field="address.isEqualizated"
disabled="true">
</vn-check>
</vn-one>
</vn-horizontal>
}"
translate-attr="{title: 'Edit address'}"
border-radius>
<vn-none
pad-small-right
ng-click="$ctrl.onStarClick($event)">
<vn-icon-button
icon="star"
ng-if="$ctrl.isDefaultAddress(address)">
</vn-icon-button>
<vn-icon-button
ng-if="!address.isActive"
icon="star_border"
vn-tooltip="Active first to set as default">
</vn-icon-button>
<vn-icon-button
ng-if="address.isActive && !$ctrl.isDefaultAddress(address)"
icon="star_border"
vn-tooltip="Set as default"
ng-click="$ctrl.setDefault(address)">
</vn-icon-button>
</vn-none>
<vn-one
style="overflow: hidden; min-width: 14em;">
<div class="ellipsize"><b>{{::address.nickname}}</b></div>
<div class="ellipsize" name="street">{{::address.street}}</div>
<div class="ellipsize">{{::address.city}}, {{::address.province.name}}</div>
<div class="ellipsize">
{{::address.phone}}<span ng-if="::address.mobile">, </span>
{{::address.mobile}}
</div>
<vn-check
vn-one label="Is equalizated"
field="address.isEqualizated"
disabled="true">
</vn-check>
</vn-one>
<vn-vertical
vn-one
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}">
<b>{{::observation.observationType.description}}:</b>
<span>{{::observation.description}}</span>
</vn-one>
<vn-vertical vn-one pad-medium-h>
<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>
<span>{{::observation.description}}</span>
</vn-one>
</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>
</vn-horizontal>
</vn-one>
</vn-horizontal>
</vn-vertical>
</a>
</div>
</vn-card>
<vn-float-button
vn-bind="+"

View File

@ -1,10 +1,47 @@
import ngModule from '../../module';
import './style.scss';
class Controller {
constructor($http, $scope, $stateParams) {
this.$http = $http;
this.$scope = $scope;
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) {

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"
where="{role: 'employee'}"
label="Salesperson">
<tpl-item>{{firstName}} {{name}}</tpl-item>
<tpl-item>{{firstName}} {{lastName}}</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>

View File

@ -46,7 +46,16 @@
label="File"
model="$ctrl.dms.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-horizontal>
<vn-vertical>

View File

@ -22,8 +22,23 @@ class Controller {
set client(value) {
this._client = value;
if (value)
if (value) {
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() {

View File

@ -18,8 +18,9 @@ describe('Client', () => {
}));
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, 'getAllowedContentTypes');
controller.client = {
id: 15,
name: 'Bruce wayne'
@ -27,6 +28,7 @@ describe('Client', () => {
expect(controller.client).toBeDefined();
expect(controller.setDefaultParams).toHaveBeenCalledWith();
expect(controller.getAllowedContentTypes).toHaveBeenCalledWith();
});
});
@ -56,5 +58,18 @@ describe('Client', () => {
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) {
this._client = value;
if (value)
if (value) {
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() {

View File

@ -1,43 +1,52 @@
import './index';
describe('Client', () => {
describe('Component vnClientDmsCreate', () => {
describe('Component vnClientDmsEdit', () => {
let controller;
let $scope;
let $httpBackend;
let $httpParamSerializer;
let $state;
beforeEach(ngModule('client'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
controller = $componentController('vnClientDmsCreate', {$scope});
controller._client = {id: 101, name: 'Bruce wayne'};
$state = {params: {dmsId: 1}};
controller = $componentController('vnClientDmsEdit', {$scope, $state});
controller._client = {id: 1};
}));
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, 'getAllowedContentTypes');
controller._client = undefined;
controller.client = {
id: 15,
name: 'Bruce wayne'
id: 15
};
expect(controller.client).toBeDefined();
expect(controller.setDefaultParams).toHaveBeenCalledWith();
expect(controller.client).toBeDefined();
expect(controller.getAllowedContentTypes).toHaveBeenCalledWith();
});
});
describe('setDefaultParams()', () => {
it('should perform a GET query and define the dms property on controller', () => {
const params = {filter: {
where: {code: 'paymentsLaw'}
}};
let serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `/api/DmsTypes/findOne?${serializedParams}`).respond({id: 12, code: 'paymentsLaw'});
$httpBackend.expect('GET', `/api/DmsTypes/findOne?${serializedParams}`);
const dmsId = 1;
const expectedResponse = {
reference: 101,
warehouseFk: 1,
companyFk: 442,
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();
$httpBackend.flush();
@ -50,11 +59,25 @@ describe('Client', () => {
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/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-tr>
<vn-th field="dmsFk" default-order="DESC" shrink>Id</vn-th>
<vn-th field="dmsTypeFk" shrink>Type</vn-th>
<vn-th field="hardCopyNumber" shrink number>Order</vn-th>
<vn-th field="reference" shrink>Reference</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 field="hasFile" shrink>Original</vn-th>
<vn-th shrink>Original</vn-th>
<vn-th>File</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-tr>
</vn-thead>
@ -54,7 +54,7 @@
</vn-td>
<vn-td>
<a target="_blank"
vn-tooltip="Download file"
title="{{'Download file' | translate}}"
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}">{{::document.dms.file}}
</a>
</vn-td>
@ -68,22 +68,20 @@
</vn-td>
<vn-td number>
<a target="_blank"
vn-tooltip="Download file"
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}">
<vn-icon-button
icon="cloud_download"
title="{{'Download PDF' | translate}}">
title="{{'Download file' | translate}}">
</vn-icon-button>
</a>
<vn-icon-button ui-sref="client.card.dms.edit({dmsId: {{::document.dmsFk}}})"
vn-tooltip="Edit file"
icon="edit"
title="{{'Edit file' | translate}}">
</vn-icon-button>
<vn-icon-button
vn-tooltip="Remove file"
icon="delete"
ng-click="$ctrl.showDeleteConfirm($index)"
title="{{'Remove file' | translate}}"
tabindex="-1">
</vn-icon-button>
</vn-td>

View File

@ -62,7 +62,7 @@ class Controller {
deleteDms(response) {
if (response === 'ACCEPT') {
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.$.model.remove(this.dmsIndex);
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"
fields="['code', 'townFk']"
field="$ctrl.client.postcode"
selection="$ctrl.postcodeSelection"
search-function="{code: $search}"
where="{townFk: town.selection.id}"
order="code, townFk"
show-field="code"
value-field="code"
label="Postcode">
<tpl-item>
{{code}}, {{town.name}} - {{town.province.name}}
({{town.province.country.country}})
</tpl-item>
label="Postcode">
</vn-autocomplete>
</vn-horizontal>
<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'];
ngModule.component('vnClientFiscalData', {

View File

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

View File

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

View File

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

View File

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

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