Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into puppeteer
gitea/salix/puppeteer There was a failure building this commit Details

This commit is contained in:
Carlos Jimenez Ruiz 2020-01-09 13:10:27 +01:00
commit cb2f94cdaf
66 changed files with 1525 additions and 579 deletions

View File

@ -34,5 +34,5 @@ COPY \
CMD ["pm2-runtime", "./back/process.yml"] CMD ["pm2-runtime", "./back/process.yml"]
HEALTHCHECK --interval=1m --timeout=10s \ HEALTHCHECK --interval=15s --timeout=10s \
CMD curl -f http://localhost:3000/api/Applications/status || exit 1 CMD curl -f http://localhost:3000/api/Applications/status || exit 1

33
Jenkinsfile vendored
View File

@ -6,20 +6,13 @@ pipeline {
disableConcurrentBuilds() disableConcurrentBuilds()
} }
environment { environment {
PROJECT_NAME = 'salix' PROJECT_NAME = 'salix'
REGISTRY = 'registry.verdnatura.es' STACK_NAME = "${env.PROJECT_NAME}-${env.BRANCH_NAME}"
PORT_MASTER_FRONT = '5002'
PORT_MASTER_BACK = '3001'
PORT_TEST_FRONT = '5001'
PORT_TEST_BACK = '4001'
TAG = "${env.BRANCH_NAME}"
} }
stages { stages {
stage('Checkout') { stage('Checkout') {
steps { steps {
script { script {
env.STACK_NAME = "${env.PROJECT_NAME}-${env.BRANCH_NAME}"
if (!env.GIT_COMMITTER_EMAIL) { if (!env.GIT_COMMITTER_EMAIL) {
env.COMMITTER_EMAIL = sh( env.COMMITTER_EMAIL = sh(
script: 'git --no-pager show -s --format="%ae"', script: 'git --no-pager show -s --format="%ae"',
@ -29,16 +22,6 @@ pipeline {
env.COMMITTER_EMAIL = env.GIT_COMMITTER_EMAIL; env.COMMITTER_EMAIL = env.GIT_COMMITTER_EMAIL;
} }
switch (env.BRANCH_NAME) {
case 'master':
env.PORT_FRONT = PORT_MASTER_FRONT
env.PORT_BACK = PORT_MASTER_BACK
break
case 'test':
env.PORT_FRONT = PORT_TEST_FRONT
env.PORT_BACK = PORT_TEST_BACK
break
}
switch (env.BRANCH_NAME) { switch (env.BRANCH_NAME) {
case 'master': case 'master':
env.NODE_ENV = 'production' env.NODE_ENV = 'production'
@ -48,6 +31,14 @@ pipeline {
break break
} }
} }
configFileProvider([
configFile(fileId: "salix.groovy",
variable: 'GROOVY_FILE')
]) {
load env.GROOVY_FILE
}
sh 'printenv' sh 'printenv'
} }
} }
@ -57,8 +48,8 @@ pipeline {
} }
steps { steps {
nodejs('node-lts') { nodejs('node-lts') {
sh 'npm install --no-audit' sh 'npm install --no-audit --prefer-offline'
sh 'gulp install' sh 'gulp install --ci'
} }
} }
} }

View File

@ -2,4 +2,5 @@ apps:
- script: ./loopback/server/server.js - script: ./loopback/server/server.js
name: salix-back name: salix-back
instances: 1 instances: 1
max_restarts: 5 max_restarts: 3
restart_delay: 15000

View File

@ -125,13 +125,13 @@ proc: BEGIN
INSERT INTO tmp.ticketComponent INSERT INTO tmp.ticketComponent
SELECT tcb.warehouseFk, SELECT tcb.warehouseFk,
tcb.itemFk, tcb.itemFk,
cr.id, c.id,
GREATEST(IFNULL(ROUND(tcb.base * cr.tax, 4), 0), tcc.minPrice - tcc.rate3) GREATEST(IFNULL(ROUND(tcb.base * c.tax, 4), 0), tcc.minPrice - tcc.rate3)
FROM tmp.ticketComponentBase tcb FROM tmp.ticketComponentBase tcb
JOIN componentRate cr JOIN component c
JOIN tmp.ticketComponentCalculate tcc ON tcc.itemFk = tcb.itemFk AND tcc.warehouseFk = tcb.warehouseFk JOIN tmp.ticketComponentCalculate tcc ON tcc.itemFk = tcb.itemFk AND tcc.warehouseFk = tcb.warehouseFk
LEFT JOIN specialPrice sp ON sp.clientFk = vClientFk AND sp.itemFk = tcc.itemFk LEFT JOIN specialPrice sp ON sp.clientFk = vClientFk AND sp.itemFk = tcc.itemFk
WHERE cr.id = vDiscountLastItemComponent AND cr.tax <> 0 AND tcc.minPrice < tcc.rate3 AND sp.value IS NULL; WHERE c.id = vDiscountLastItemComponent AND c.tax <> 0 AND tcc.minPrice < tcc.rate3 AND sp.value IS NULL;
INSERT INTO tmp.ticketComponent INSERT INTO tmp.ticketComponent
SELECT tcc.warehouseFk, tcc.itemFk, vSellByPacketComponent, tcc.rate2 - tcc.rate3 SELECT tcc.warehouseFk, tcc.itemFk, vSellByPacketComponent, tcc.rate2 - tcc.rate3
@ -178,9 +178,9 @@ proc: BEGIN
vSpecialPriceComponent, vSpecialPriceComponent,
sp.value - SUM(tcc.cost) sumCost sp.value - SUM(tcc.cost) sumCost
FROM tmp.ticketComponentCopy tcc FROM tmp.ticketComponentCopy tcc
JOIN componentRate cr ON cr.id = tcc.componentFk JOIN component c ON c.id = tcc.componentFk
JOIN specialPrice sp ON sp.clientFk = vClientFK AND sp.itemFk = tcc.itemFk JOIN specialPrice sp ON sp.clientFk = vClientFK AND sp.itemFk = tcc.itemFk
WHERE cr.classRate IS NULL WHERE c.classRate IS NULL
GROUP BY tcc.itemFk, tcc.warehouseFk GROUP BY tcc.itemFk, tcc.warehouseFk
HAVING ABS(sumCost) > 0.001; HAVING ABS(sumCost) > 0.001;
@ -188,10 +188,10 @@ proc: BEGIN
CREATE TEMPORARY TABLE tmp.ticketComponentSum CREATE TEMPORARY TABLE tmp.ticketComponentSum
(INDEX (itemFk, warehouseFk)) (INDEX (itemFk, warehouseFk))
ENGINE = MEMORY ENGINE = MEMORY
SELECT SUM(cost) sumCost, tc.itemFk, tc.warehouseFk, cr.classRate SELECT SUM(cost) sumCost, tc.itemFk, tc.warehouseFk, c.classRate
FROM tmp.ticketComponent tc FROM tmp.ticketComponent tc
JOIN componentRate cr ON cr.id = tc.componentFk JOIN component c ON c.id = tc.componentFk
GROUP BY tc.itemFk, tc.warehouseFk, cr.classRate; GROUP BY tc.itemFk, tc.warehouseFk, c.classRate;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketComponentRate; DROP TEMPORARY TABLE IF EXISTS tmp.ticketComponentRate;
CREATE TEMPORARY TABLE tmp.ticketComponentRate ENGINE = MEMORY CREATE TEMPORARY TABLE tmp.ticketComponentRate ENGINE = MEMORY

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Thermograph', '*', '*', 'ALLOW', 'ROLE', 'buyer');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('TravelThermograph', '*', '*', 'ALLOW', 'ROLE', 'buyer');

File diff suppressed because one or more lines are too long

View File

@ -53,20 +53,20 @@ INSERT INTO `hedera`.`tpvConfig`(`id`, `currency`, `terminal`, `transactionType`
VALUES VALUES
(1, 978, 1, 0, 2000, 9, 0); (1, 978, 1, 0, 2000, 9, 0);
INSERT INTO `account`.`user`(`id`,`name`,`password`,`role`,`active`,`email`,`lang`) INSERT INTO `account`.`user`(`id`,`name`,`nickname`, `password`,`role`,`active`,`email`,`lang`)
VALUES VALUES
(101, 'BruceWayne', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'BruceWayne@mydomain.com', 'es'), (101, 'BruceWayne', 'Bruce Wayne', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'BruceWayne@mydomain.com', 'es'),
(102, 'PetterParker', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'PetterParker@mydomain.com', 'en'), (102, 'PetterParker', 'Petter Parker', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'PetterParker@mydomain.com', 'en'),
(103, 'ClarkKent', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'ClarkKent@mydomain.com', 'fr'), (103, 'ClarkKent', 'Clark Kent', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'ClarkKent@mydomain.com', 'fr'),
(104, 'TonyStark', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'TonyStark@mydomain.com', 'es'), (104, 'TonyStark', 'Tony Stark', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'TonyStark@mydomain.com', 'es'),
(105, 'MaxEisenhardt', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'MaxEisenhardt@mydomain.com', 'pt'), (105, 'MaxEisenhardt', 'Max Eisenhardt', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'MaxEisenhardt@mydomain.com', 'pt'),
(106, 'DavidCharlesHaller', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'DavidCharlesHaller@mydomain.com', 'es'), (106, 'DavidCharlesHaller', 'David Charles Haller', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'DavidCharlesHaller@mydomain.com', 'es'),
(107, 'HankPym', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'HankPym@mydomain.com', 'es'), (107, 'HankPym', 'Hank Pym', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'HankPym@mydomain.com', 'es'),
(108, 'CharlesXavier', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'CharlesXavier@mydomain.com', 'es'), (108, 'CharlesXavier', 'Charles Xavier', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'CharlesXavier@mydomain.com', 'es'),
(109, 'BruceBanner', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'BruceBanner@mydomain.com', 'es'), (109, 'BruceBanner', 'Bruce Banner', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'BruceBanner@mydomain.com', 'es'),
(110, 'JessicaJones', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'JessicaJones@mydomain.com', 'es'), (110, 'JessicaJones', 'Jessica Jones', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'JessicaJones@mydomain.com', 'es'),
(111, 'Missing', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'es'), (111, 'Missing', 'Missing', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'es'),
(112, 'Trash', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'es'); (112, 'Trash', 'Trash', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'es');
INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`) INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`)
VALUES VALUES
@ -643,7 +643,8 @@ INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `life`,`workerFk
(2, 'ITG', 'Anthurium', 1, 31, 5, 0), (2, 'ITG', 'Anthurium', 1, 31, 5, 0),
(3, 'WPN', 'Paniculata', 2, 31, 5, 0), (3, 'WPN', 'Paniculata', 2, 31, 5, 0),
(4, 'PRT', 'Delivery ports', 3, NULL, 5, 1), (4, 'PRT', 'Delivery ports', 3, NULL, 5, 1),
(5, 'CON', 'Container', 3, NULL, 5, 1); (5, 'CON', 'Container', 3, NULL, 5, 1),
(6, 'ALS', 'Alstroemeria', 1, 31, 5, 0);
INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`) INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`)
VALUES VALUES
@ -1549,7 +1550,7 @@ INSERT INTO `postgresql`.`profile`(`profile_id`, `person_id`, `profile_type_id`)
FROM `postgresql`.`person` `p`; FROM `postgresql`.`person` `p`;
INSERT INTO `postgresql`.`business`(`business_id`, `client_id`, `provider_id`, `date_start`, `date_end`, `workerBusiness`, `reasonEndFk`) INSERT INTO `postgresql`.`business`(`business_id`, `client_id`, `provider_id`, `date_start`, `date_end`, `workerBusiness`, `reasonEndFk`)
SELECT p.profile_id, p.profile_id, 1000, CONCAT(YEAR(DATE_ADD(CURDATE(), INTERVAL -1 YEAR)), '-12-31'), CONCAT(YEAR(DATE_ADD(CURDATE(), INTERVAL +1 YEAR)), '-01-25'), CONCAT('E-46-',RPAD(CONCAT(p.profile_id,9),8,p.profile_id)), NULL SELECT p.profile_id, p.profile_id, 1000, CONCAT(YEAR(DATE_ADD(CURDATE(), INTERVAL -1 YEAR)), '-12-25'), CONCAT(YEAR(DATE_ADD(CURDATE(), INTERVAL +1 YEAR)), '-01-25'), CONCAT('E-46-',RPAD(CONCAT(p.profile_id,9),8,p.profile_id)), NULL
FROM `postgresql`.`profile` `p`; FROM `postgresql`.`profile` `p`;
INSERT INTO `postgresql`.`business_labour`(`business_id`, `notes`, `department_id`, `professional_category_id`, `incentivo`, `calendar_labour_type_id`, `porhoras`, `labour_agreement_id`, `workcenter_id`) INSERT INTO `postgresql`.`business_labour`(`business_id`, `notes`, `department_id`, `professional_category_id`, `incentivo`, `calendar_labour_type_id`, `porhoras`, `labour_agreement_id`, `workcenter_id`)
@ -1586,20 +1587,20 @@ INSERT INTO `postgresql`.`calendar_state` (`calendar_state_id`, `type`, `rgb`, `
INSERT INTO `postgresql`.`calendar_employee` (`business_id`, `calendar_state_id`, `date`) INSERT INTO `postgresql`.`calendar_employee` (`business_id`, `calendar_state_id`, `date`)
VALUES VALUES
(106, 1, DATE_ADD(CURDATE(), INTERVAL 10 DAY)), (106, 1, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -10 DAY), DATE_ADD(CURDATE(), INTERVAL 10 DAY))),
(106, 1, DATE_ADD(CURDATE(), INTERVAL 11 DAY)), (106, 1, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -11 DAY), DATE_ADD(CURDATE(), INTERVAL 11 DAY))),
(106, 1, DATE_ADD(CURDATE(), INTERVAL 12 DAY)), (106, 1, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -12 DAY), DATE_ADD(CURDATE(), INTERVAL 12 DAY))),
(106, 1, DATE_ADD(CURDATE(), INTERVAL 20 DAY)), (106, 1, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -20 DAY), DATE_ADD(CURDATE(), INTERVAL 20 DAY))),
(106, 2, DATE_ADD(CURDATE(), INTERVAL -10 DAY)), (106, 2, IF(MONTH(CURDATE()) >= 1 AND DAY(CURDATE()) > 20, DATE_ADD(CURDATE(), INTERVAL -13 DAY), DATE_ADD(CURDATE(), INTERVAL 13 DAY))),
(106, 1, DATE_ADD(CURDATE(), INTERVAL -12 DAY)), (106, 1, IF(MONTH(CURDATE()) >= 1 AND DAY(CURDATE()) > 20, DATE_ADD(CURDATE(), INTERVAL -14 DAY), DATE_ADD(CURDATE(), INTERVAL 14 DAY))),
(106, 2, DATE_ADD(CURDATE(), INTERVAL -20 DAY)), (106, 2, IF(MONTH(CURDATE()) >= 1 AND DAY(CURDATE()) > 20, DATE_ADD(CURDATE(), INTERVAL -15 DAY), DATE_ADD(CURDATE(), INTERVAL 15 DAY))),
(107, 1, DATE_ADD(CURDATE(), INTERVAL 15 DAY)), (107, 1, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -10 DAY), DATE_ADD(CURDATE(), INTERVAL 10 DAY))),
(107, 1, DATE_ADD(CURDATE(), INTERVAL 16 DAY)), (107, 1, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -11 DAY), DATE_ADD(CURDATE(), INTERVAL 11 DAY))),
(107, 1, DATE_ADD(CURDATE(), INTERVAL 20 DAY)), (107, 1, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -12 DAY), DATE_ADD(CURDATE(), INTERVAL 12 DAY))),
(107, 1, DATE_ADD(CURDATE(), INTERVAL 30 DAY)), (107, 1, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -20 DAY), DATE_ADD(CURDATE(), INTERVAL 20 DAY))),
(107, 2, DATE_ADD(CURDATE(), INTERVAL -10 DAY)), (107, 2, IF(MONTH(CURDATE()) >= 1 AND DAY(CURDATE()) > 20, DATE_ADD(CURDATE(), INTERVAL -13 DAY), DATE_ADD(CURDATE(), INTERVAL 13 DAY))),
(107, 1, DATE_ADD(CURDATE(), INTERVAL -12 DAY)), (107, 1, IF(MONTH(CURDATE()) >= 1 AND DAY(CURDATE()) > 20, DATE_ADD(CURDATE(), INTERVAL -14 DAY), DATE_ADD(CURDATE(), INTERVAL 14 DAY))),
(107, 2, DATE_ADD(CURDATE(), INTERVAL -20 DAY)); (107, 2, IF(MONTH(CURDATE()) >= 1 AND DAY(CURDATE()) > 20, DATE_ADD(CURDATE(), INTERVAL -15 DAY), DATE_ADD(CURDATE(), INTERVAL 15 DAY)));
INSERT INTO `vn`.`smsConfig` (`id`, `uri`, `title`) INSERT INTO `vn`.`smsConfig` (`id`, `uri`, `title`)
VALUES VALUES
@ -1901,7 +1902,8 @@ INSERT INTO `vn`.`dms`(`id`, `dmsTypeFk`, `file`, `contentType`, `workerFk`, `wa
(1, 14, '1.txt', 'text/plain', 5, 1, 442, NULL, FALSE, 'Ticket:11', 'Ticket:11 dms for the ticket', CURDATE()), (1, 14, '1.txt', 'text/plain', 5, 1, 442, NULL, FALSE, 'Ticket:11', 'Ticket:11 dms for the ticket', CURDATE()),
(2, 5, '2.txt', 'text/plain', 5, 1, 442, 1, TRUE, 'Client:104', 'Client:104 dms for the client', CURDATE()), (2, 5, '2.txt', 'text/plain', 5, 1, 442, 1, TRUE, 'Client:104', 'Client:104 dms for the client', CURDATE()),
(3, 5, '3.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Client: 104', 'Client:104 readme', CURDATE()), (3, 5, '3.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Client: 104', 'Client:104 readme', CURDATE()),
(4, 3, '3.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Worker: 106', 'Worker:106 readme', CURDATE()); (4, 3, '4.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Worker: 106', 'Worker:106 readme', CURDATE()),
(5, 5, '5.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'travel: 1', 'dmsForThermograph', CURDATE());
INSERT INTO `vn`.`ticketDms`(`ticketFk`, `dmsFk`) INSERT INTO `vn`.`ticketDms`(`ticketFk`, `dmsFk`)
VALUES VALUES
@ -1998,3 +2000,16 @@ INSERT INTO `vn`.`workerTimeControlParams` (`id`, `dayBreak`, `weekBreak`, `week
(1, 43200, 129600, 734400, 43200, 50400); (1, 43200, 129600, 734400, 43200, 50400);
INSERT IGNORE INTO `vn`.`greugeConfig` (`id`, `freightPickUpPrice`) VALUES ('1', '11'); INSERT IGNORE INTO `vn`.`greugeConfig` (`id`, `freightPickUpPrice`) VALUES ('1', '11');
INSERT INTO `vn`.`thermograph`(`id`, `model`)
VALUES
('TMM190901395', 'TEMPMATE'),
('TL.BBA85422', 'TL30'),
('TZ1905012010', 'DISPOSABLE');
INSERT INTO `vn`.`travelThermograph`(`thermographFk`, `created`, `warehouseFk`, `travelFk`, `temperature`, `result`, `dmsFk`)
VALUES
('TMM190901395', CURDATE(), 1, 1, 'WARM', 'Ok', NULL),
('TL.BBA85422', DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 2, 2, 'COOL', 'Ok', NULL),
('TL.BBA85422', CURDATE(), 2, 1, 'COOL', 'can not read the temperature', NULL),
('TZ1905012010', CURDATE(), 1, 1, 'WARM', 'Temperature in range', 5);

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +1,45 @@
version: '3.5' version: '3.7'
services: services:
front: front:
image: registry.verdnatura.es/salix-front:${TAG} image: registry.verdnatura.es/salix-front:${BRANCH_NAME:?}
restart: unless-stopped build:
build: context: .
context: . dockerfile: front/Dockerfile
dockerfile: front/Dockerfile ports:
ports: - ${FRONT_PORT:?}:80
- ${PORT_FRONT}:80 deploy:
links: replicas: 3
- back back:
deploy: image: registry.verdnatura.es/salix-back:${BRANCH_NAME:?}
replicas: 3 build: .
back: ports:
image: registry.verdnatura.es/salix-back:${TAG} - ${BACK_PORT:?}:3000
restart: unless-stopped environment:
build: . - NODE_ENV
ports: configs:
- ${PORT_BACK}:3000 - source: datasources
environment: target: /etc/salix/datasources.json
- NODE_ENV - source: datasources_local
volumes: target: /etc/salix/datasources.local.json
- /mnt/storage/containers/salix:/etc/salix - source: print
- /mnt/storage/pdfs:/var/lib/salix/pdfs target: /etc/salix/print.json
- /mnt/storage/dms:/var/lib/salix/dms - source: print_local
deploy: target: /etc/salix/print.local.json
replicas: 6 volumes:
- /mnt/storage/pdfs:/var/lib/salix/pdfs
- /mnt/storage/dms:/var/lib/salix/dms
deploy:
replicas: 6
configs:
datasources:
external: true
name: salix_datasources
datasources_local:
external: true
name: salix-${BRANCH_NAME:?}_datasources
print:
external: true
name: salix_print
print_local:
external: true
name: salix-${BRANCH_NAME:?}_print

View File

@ -164,8 +164,8 @@ export default {
}, },
clientBalance: { clientBalance: {
balanceButton: 'vn-left-menu a[ui-sref="client.card.balance.index"]', balanceButton: 'vn-left-menu a[ui-sref="client.card.balance.index"]',
companyAutocomplete: 'vn-client-balance-index vn-autocomplete[ng-model="$ctrl.companyFk"]', companyAutocomplete: 'vn-client-balance-index vn-autocomplete[ng-model="$ctrl.companyId"]',
newPaymentButton: 'vn-float-button', newPaymentButton: `vn-float-button`,
newPaymentBank: '.vn-dialog.shown vn-autocomplete[ng-model="$ctrl.receipt.bankFk"]', newPaymentBank: '.vn-dialog.shown vn-autocomplete[ng-model="$ctrl.receipt.bankFk"]',
newPaymentAmountInput: '.vn-dialog.shown [ng-model="$ctrl.receipt.amountPaid"]', newPaymentAmountInput: '.vn-dialog.shown [ng-model="$ctrl.receipt.amountPaid"]',
saveButton: '.vn-dialog.shown vn-button[label="Save"]', saveButton: '.vn-dialog.shown vn-button[label="Save"]',
@ -603,8 +603,8 @@ export default {
orderByAutocomplete: 'vn-autocomplete[label="Order by"]', orderByAutocomplete: 'vn-autocomplete[label="Order by"]',
plantRealmButton: 'vn-order-catalog > vn-side-menu vn-icon[icon="icon-plant"]', plantRealmButton: 'vn-order-catalog > vn-side-menu vn-icon[icon="icon-plant"]',
typeAutocomplete: 'vn-autocomplete[data="$ctrl.itemTypes"]', typeAutocomplete: 'vn-autocomplete[data="$ctrl.itemTypes"]',
itemIdInput: 'vn-catalog-filter [ng-model="$ctrl.itemFk"]', itemIdInput: 'vn-catalog-filter vn-textfield[ng-model="$ctrl.itemId"]',
itemTagValueInput: 'vn-catalog-filter [ng-model="$ctrl.value"]', itemTagValueInput: 'vn-catalog-filter vn-textfield[ng-model="$ctrl.value"]',
openTagSearch: 'vn-catalog-filter > div > vn-vertical > vn-textfield[ng-model="$ctrl.value"] .append i', openTagSearch: 'vn-catalog-filter > div > vn-vertical > vn-textfield[ng-model="$ctrl.value"] .append i',
tagAutocomplete: 'vn-order-catalog-search-panel vn-autocomplete[ng-model="filter.tagFk"]', tagAutocomplete: 'vn-order-catalog-search-panel vn-autocomplete[ng-model="filter.tagFk"]',
tagValueInput: 'vn-order-catalog-search-panel [ng-model="filter.value"]', tagValueInput: 'vn-order-catalog-search-panel [ng-model="filter.value"]',

View File

@ -34,7 +34,7 @@ describe('Route basic Data path', () => {
.waitToGetProperty(`${selectors.routeBasicData.workerAutoComplete} input`, 'value'); .waitToGetProperty(`${selectors.routeBasicData.workerAutoComplete} input`, 'value');
expect(worker).toEqual('adminBossNick'); expect(worker).toEqual('adminBoss - adminBossNick');
}); });
it('should confirm the vehicle was edited', async() => { it('should confirm the vehicle was edited', async() => {

View File

@ -0,0 +1 @@
No service for the specified zone: No hay servicio para la zona especificada

View File

@ -11,11 +11,9 @@
.vn-droppable, .vn-droppable,
[vn-droppable] { [vn-droppable] {
display: block;
&.dropping { &.dropping {
background-color: $color-hover-cd; background-color: $color-hover-cd;
border-color: $color-bg-dark; border-color: $color-font-secondary;
} }
} }

View File

@ -1,4 +1,5 @@
module.exports = { const crudModel = {
_data: [1, 2, 3],
data: [], data: [],
filter: {}, filter: {},
order: {}, order: {},
@ -31,7 +32,28 @@ module.exports = {
} }
}; };
}, },
refresh: () => {}, refresh: () => {
addFilter: () => {}, return {
applyFilter: () => {}, then: callback => {
return callback({data: {id: 1234}});
}
};
},
addFilter: () => {
return {
then: callback => {
return callback({data: {id: 1234}});
}
};
},
applyFilter: () => {
crudModel.data = crudModel._data;
return {
then: callback => {
return callback({data: {id: 1234}});
}
};
},
}; };
module.exports = crudModel;

View File

@ -7,7 +7,7 @@ vn-app {
ui-view { ui-view {
display: block; display: block;
box-sizing: border-box; box-sizing: border-box;
height: inherit; height: 100%;
&.ng-enter { &.ng-enter {
animation-name: nothing, slideIn; animation-name: nothing, slideIn;

View File

@ -87,6 +87,8 @@ vn-layout {
& > * { & > * {
display: block; display: block;
padding: $spacing-md; padding: $spacing-md;
box-sizing: border-box;
height: 100%
} }
&.ng-enter { &.ng-enter {
vn-side-menu { vn-side-menu {

View File

@ -11,7 +11,6 @@
transition: all 0.5s; transition: all 0.5s;
padding: $spacing-sm; padding: $spacing-sm;
position: relative; position: relative;
opacity: 0.7;
width: 28em; width: 28em;
.image { .image {
@ -20,6 +19,7 @@
0 1px 5px 0 rgba(0,0,0,.12); 0 1px 5px 0 rgba(0,0,0,.12);
background: no-repeat center center fixed; background: no-repeat center center fixed;
background-size: cover !important; background-size: cover !important;
border: 2px solid transparent;
overflow: hidden; overflow: hidden;
cursor: zoom-in; cursor: zoom-in;
height: 100%; height: 100%;
@ -34,7 +34,7 @@
top: 1em top: 1em
} }
} }
.photo:hover { .photo:hover .image {
opacity: 1 border: 2px solid $color-primary
} }
} }

View File

@ -101,6 +101,7 @@ async function backTestOnce() {
gulp.src(backSpecFiles) gulp.src(backSpecFiles)
.pipe(jasmine(options)) .pipe(jasmine(options))
.on('end', resolve) .on('end', resolve)
.on('error', reject)
.resume(); .resume();
}); });
@ -110,27 +111,33 @@ backTestOnce.description = `Runs the backend tests once, can receive --junit arg
async function backTestDockerOnce() { async function backTestDockerOnce() {
let containerId = await docker(); let containerId = await docker();
let err;
try { try {
await backTestOnce(); await backTestOnce();
} catch (e) { } catch (e) {
throw e; err = e;
} finally {
if (argv['random'])
await execP(`docker rm -fv ${containerId}`);
} }
if (argv['random'])
await execP(`docker rm -fv ${containerId}`);
if (err) throw err;
} }
backTestDockerOnce.description = `Runs backend tests using in site container once`; backTestDockerOnce.description = `Runs backend tests using in site container once`;
async function backTestDocker() { async function backTestDocker() {
let containerId = await docker(); let containerId = await docker();
let err;
try { try {
await backTest(); await backTest();
} catch (e) { } catch (e) {
throw e; err = e;
} finally {
if (argv['random'])
await execP(`docker rm -fv ${containerId}`);
} }
if (argv['random'])
await execP(`docker rm -fv ${containerId}`);
if (err) throw err;
} }
backTestDocker.description = `Runs backend tests restoring fixtures first`; backTestDocker.description = `Runs backend tests restoring fixtures first`;
@ -283,14 +290,15 @@ function install() {
const install = require('gulp-install'); const install = require('gulp-install');
const print = require('gulp-print'); const print = require('gulp-print');
let npmArgs = [];
if (argv.ci) npmArgs = ['--no-audit', '--prefer-offline'];
let packageFiles = ['front/package.json', 'print/package.json']; let packageFiles = ['front/package.json', 'print/package.json'];
return gulp.src(packageFiles) return gulp.src(packageFiles)
.pipe(print(filepath => { .pipe(print(filepath => {
return `Installing packages in ${filepath}`; return `Installing packages in ${filepath}`;
})) }))
.pipe(install({ .pipe(install({npm: npmArgs}));
npm: ['--no-package-lock']
}));
} }
install.description = `Installs node dependencies in all directories`; install.description = `Installs node dependencies in all directories`;
@ -472,15 +480,22 @@ async function docker() {
let result = await execP(`docker run --env RUN_CHOWN=${runChown} -d ${dockerArgs} salix-db`); let result = await execP(`docker run --env RUN_CHOWN=${runChown} -d ${dockerArgs} salix-db`);
containerId = result.stdout; containerId = result.stdout;
if (argv['random']) { try {
let inspect = await execP(`docker inspect -f "{{json .NetworkSettings}}" ${containerId}`); if (argv['random']) {
let netSettings = JSON.parse(inspect.stdout); let inspect = await execP(`docker inspect -f "{{json .NetworkSettings}}" ${containerId}`);
let netSettings = JSON.parse(inspect.stdout);
dbConf.host = netSettings.Gateway; dbConf.host = netSettings.Gateway;
dbConf.port = netSettings.Ports['3306/tcp'][0]['HostPort']; dbConf.port = netSettings.Ports['3306/tcp'][0]['HostPort'];
}
if (runChown) await dockerWait();
} catch (err) {
if (argv['random'])
await execP(`docker rm -fv ${containerId}`);
throw err;
} }
if (runChown) await dockerWait();
return containerId; return containerId;
} }
docker.description = `Builds the database image and runs a container`; docker.description = `Builds the database image and runs a container`;

View File

@ -14,7 +14,7 @@
"multipleStatements": true, "multipleStatements": true,
"legacyUtcDateProcessing": false, "legacyUtcDateProcessing": false,
"timezone": "local", "timezone": "local",
"connectTimeout": 20000, "connectTimeout": 40000,
"acquireTimeout": 20000 "acquireTimeout": 20000
}, },
"storage": { "storage": {

View File

@ -9,7 +9,7 @@ module.exports = Self => {
description: 'The province id', description: 'The province id',
required: true required: true
}, { }, {
arg: 'search', arg: 'postCode',
type: 'String', type: 'String',
description: 'The postcode' description: 'The postcode'
}, { }, {

View File

@ -8,8 +8,14 @@ class Controller extends Section {
} }
onSubmit() { onSubmit() {
this.$.data = null;
this.$http.get(`Zones/getEvents`, {params: this.$.params}) this.$http.get(`Zones/getEvents`, {params: this.$.params})
.then(res => this.$.data = res.data); .then(res => {
let data = res.data;
this.$.data = data;
if (!data.events.length)
this.vnApp.showMessage(this.$t('No service for the specified zone'));
});
} }
} }

View File

@ -160,12 +160,12 @@
response="cancel" response="cancel"
translate-attr="{value: 'Cancel'}"> translate-attr="{value: 'Cancel'}">
</input> </input>
<button <input
type="button"
ng-if="!$ctrl.isNew" ng-if="!$ctrl.isNew"
response="delete" response="delete"
translate> translate-attr="{value: 'Delete'}">
Delete </input>
</button>
<button response="accept"> <button response="accept">
<span ng-if="$ctrl.isNew" translate>Add</span> <span ng-if="$ctrl.isNew" translate>Add</span>
<span ng-if="!$ctrl.isNew" translate>Save</span> <span ng-if="!$ctrl.isNew" translate>Save</span>

View File

@ -54,8 +54,8 @@
</vn-td> </vn-td>
<vn-td number>{{::saleClaimed.sale.price | currency: 'EUR':2}}</vn-td> <vn-td number>{{::saleClaimed.sale.price | currency: 'EUR':2}}</vn-td>
<vn-td number> <vn-td number>
<span class="link" <span ng-class="{'link': $ctrl.isEditable}"
vn-tooltip="Edit discount" title="{{$ctrl.isEditable ? 'Edit discount' : ''}}"
ng-click="$ctrl.showEditPopover($event, saleClaimed)"> ng-click="$ctrl.showEditPopover($event, saleClaimed)">
{{saleClaimed.sale.discount}} % {{saleClaimed.sale.discount}} %
</span> </span>

View File

@ -29,8 +29,10 @@ class Controller {
set salesClaimed(value) { set salesClaimed(value) {
this._salesClaimed = value; this._salesClaimed = value;
if (value) if (value) {
this.calculateTotals(); this.calculateTotals();
this.isClaimEditable();
}
} }
get salesClaimed() { get salesClaimed() {
@ -119,13 +121,14 @@ class Controller {
} }
showEditPopover(event, saleClaimed) { showEditPopover(event, saleClaimed) {
this.saleClaimed = saleClaimed; if (this.isEditable) {
if (!this.aclService.hasAny(['salesAssistant']))
return this.vnApp.showError(this.$translate.instant('Insuficient permisos'));
if (!this.aclService.hasAny(['salesAssistant'])) this.saleClaimed = saleClaimed;
return this.vnApp.showError(this.$translate.instant('Insuficient permisos')); this.$.editPopover.parent = event.target;
this.$.editPopover.show();
this.$.editPopover.parent = event.target; }
this.$.editPopover.show();
} }
getSalespersonMana() { getSalespersonMana() {
@ -134,6 +137,12 @@ class Controller {
}); });
} }
isClaimEditable() {
this.$http.get(`Tickets/${this.claim.ticketFk}/isEditable`).then(res => {
this.isEditable = res.data;
});
}
updateDiscount() { updateDiscount() {
const claimedSale = this.saleClaimed.sale; const claimedSale = this.saleClaimed.sale;
if (this.newDiscount != claimedSale.discount) { if (this.newDiscount != claimedSale.discount) {

View File

@ -17,13 +17,14 @@ describe('claim', () => {
show: () => {} show: () => {}
}; };
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', 'Claims/ClaimBeginnings').respond({}); $httpBackend.whenGET('Claims/ClaimBeginnings').respond({});
$httpBackend.whenGET(`Tickets/1/isEditable`).respond(true);
$state = _$state_; $state = _$state_;
aclService = {hasAny: () => true}; aclService = {hasAny: () => true};
controller = $componentController('vnClaimDetail', {$state, aclService, $scope}); controller = $componentController('vnClaimDetail', {$state, aclService, $scope});
controller.claim = {ticketFk: 1};
controller.salesToClaim = [{saleFk: 1}, {saleFk: 2}]; controller.salesToClaim = [{saleFk: 1}, {saleFk: 2}];
controller.salesClaimed = [{id: 1, sale: {}}]; controller.salesClaimed = [{id: 1, sale: {}}];
controller.claim = {ticketFk: 1};
controller.$.model = crudModel; controller.$.model = crudModel;
controller.$.addSales = { controller.$.addSales = {
hide: () => {}, hide: () => {},
@ -36,7 +37,6 @@ describe('claim', () => {
describe('openAddSalesDialog()', () => { describe('openAddSalesDialog()', () => {
it('should call getClaimableFromTicket and $.addSales.show', () => { it('should call getClaimableFromTicket and $.addSales.show', () => {
controller.$ = {addSales: {show: () => {}}};
spyOn(controller, 'getClaimableFromTicket'); spyOn(controller, 'getClaimableFromTicket');
spyOn(controller.$.addSales, 'show'); spyOn(controller.$.addSales, 'show');
controller.openAddSalesDialog(); controller.openAddSalesDialog();
@ -146,5 +146,14 @@ describe('claim', () => {
expect(controller.$.descriptor.show).toHaveBeenCalledWith(); expect(controller.$.descriptor.show).toHaveBeenCalledWith();
}); });
}); });
describe('isClaimEditable()', () => {
it('should check if the claim is editable', () => {
controller.isClaimEditable();
$httpBackend.flush();
expect(controller.isEditable).toBeTruthy();
});
});
}); });
}); });

View File

@ -5,31 +5,28 @@
data="$ctrl.photos"> data="$ctrl.photos">
</vn-crud-model> </vn-crud-model>
<section class="drop-zone" vn-droppable="$ctrl.onDrop($event)"> <vn-horizontal class="photo-list drop-zone" vn-droppable="$ctrl.onDrop($event)">
<section><vn-icon icon="add_circle"></vn-icon></section> <section class="empty-rows" ng-if="!$ctrl.photos.length">
<section translate>Drag & Drop files here...</section> <section><vn-icon icon="image"></vn-icon></section>
</section> <section translate>Drag & Drop photos here...</section>
<vn-data-viewer </section>
model="model" <section class="photo" ng-repeat="photo in $ctrl.photos">
class="vn-w-xl"> <section class="image vn-shadow" on-error-src
<vn-horizontal class="photo-list"> ng-style="{'background': 'url(/api/dms/' + photo.dmsFk + '/downloadFile?access_token=' + $ctrl.accessToken + ')'}"
<section class="photo" ng-repeat="photo in $ctrl.photos"> zoom-image="/api/dms/{{::photo.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}">
<section class="image vn-shadow" on-error-src
ng-style="{'background': 'url(/api/dms/' + photo.dmsFk + '/downloadFile?access_token=' + $ctrl.accessToken + ')'}"
zoom-image="/api/dms/{{::photo.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}">
</section>
<section class="actions">
<vn-button
class="round"
ng-click="$ctrl.showDeleteConfirm($index)"
title="{{'Remove file' | translate}}"
tabindex="-1"
icon="delete">
</vn-button>
</section>
</section> </section>
</vn-horizontal> <section class="actions">
</vn-data-viewer> <vn-button
class="round"
ng-click="$ctrl.showDeleteConfirm($index)"
title="{{'Remove file' | translate}}"
tabindex="-1"
icon="delete">
</vn-button>
</section>
</section>
</vn-horizontal>
<vn-worker-descriptor-popover <vn-worker-descriptor-popover
vn-id="workerDescriptor"> vn-id="workerDescriptor">
</vn-worker-descriptor-popover> </vn-worker-descriptor-popover>
@ -38,4 +35,10 @@
message="This file will be deleted" message="This file will be deleted"
question="Are you sure you want to continue?" question="Are you sure you want to continue?"
on-response="$ctrl.deleteDms($response)"> on-response="$ctrl.deleteDms($response)">
</vn-confirm> </vn-confirm>
<vn-float-button fixed-bottom-right
icon="add"
vn-tooltip="Select photo"
vn-bind="+"
ng-click="$ctrl.openUploadDialog()">
</vn-float-button>

View File

@ -68,6 +68,19 @@ class Controller {
}); });
} }
openUploadDialog() {
const element = document.createElement('input');
element.setAttribute('type', 'file');
element.setAttribute('multiple', true);
element.click();
element.addEventListener('change', () =>
this.setDefaultParams().then(() => {
this.dms.files = element.files;
this.create();
})
);
}
create() { create() {
const query = `claims/${this.claim.id}/uploadFile`; const query = `claims/${this.claim.id}/uploadFile`;

View File

@ -2,13 +2,16 @@
vn-claim-dms-index { vn-claim-dms-index {
.drop-zone { .drop-zone {
border: 2px dashed $color-font-secondary;
color: $color-font-secondary; color: $color-font-secondary;
box-sizing: border-box; box-sizing: border-box;
padding: 2em $spacing-md;
border-radius: 0.5em; border-radius: 0.5em;
text-align: center; text-align: center;
font-size: 1.4em; min-height: 100%;
.empty-rows {
padding: 5em $spacing-md;
font-size: 1.4em
}
vn-icon { vn-icon {
font-size: 3em font-size: 3em

View File

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

View File

@ -1,4 +1,5 @@
Are you sure you want to continue?: ¿Seguro que quieres continuar? Are you sure you want to continue?: ¿Seguro que quieres continuar?
Drag & Drop files here...: Arrastra y suelta archivos aquí... Drag & Drop photos here...: Arrastra y suelta fotos aquí...
Photo deleted: Foto eliminada Photo deleted: Foto eliminada
Photo uploaded!: Foto subida! Photo uploaded!: Foto subida!
Select photo: Seleccionar foto

View File

@ -1,12 +1,10 @@
@import "./variables"; @import "./variables";
vn-claim-summary { vn-claim-summary {
.photo { section.photo {
height: 15.5em; height: 15.5em
} }
.photo .image { .photo .image {
border: 2px solid $color-bg-dark; border-radius: 0.2em
border-radius: 0.2em;
} }
} }

View File

@ -7,7 +7,7 @@ describe('Client activeWorkersWithRole', () => {
let isSalesPerson = await app.models.Account.hasRole(result[0].id, 'salesPerson'); let isSalesPerson = await app.models.Account.hasRole(result[0].id, 'salesPerson');
expect(result.length).toEqual(13); expect(result.length).toEqual(14);
expect(isSalesPerson).toBeTruthy(); expect(isSalesPerson).toBeTruthy();
}); });

View File

@ -6,7 +6,7 @@ describe('Client listWorkers', () => {
.then(result => { .then(result => {
let amountOfEmployees = Object.keys(result).length; let amountOfEmployees = Object.keys(result).length;
expect(amountOfEmployees).toEqual(48); expect(amountOfEmployees).toEqual(49);
done(); done();
}) })
.catch(done.fail); .catch(done.fail);

View File

@ -11,10 +11,15 @@ module.exports = Self => {
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string', description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'} http: {source: 'query'}
}, { }, {
arg: 'params', arg: 'clientId',
type: 'Object', type: 'Number',
description: 'clientFk', description: 'The client id',
http: {source: 'query'} required: true,
}, {
arg: 'companyId',
type: 'Number',
description: 'The company id',
required: true,
} }
], ],
returns: { returns: {
@ -27,7 +32,7 @@ module.exports = Self => {
} }
}); });
Self.filter = async(filter, params) => { Self.filter = async(filter, clientId, companyId) => {
let stmt = new ParameterizedSQL( let stmt = new ParameterizedSQL(
`SELECT * FROM ( `SELECT * FROM (
SELECT SELECT
@ -72,10 +77,10 @@ module.exports = Self => {
ORDER BY payed DESC, created DESC ORDER BY payed DESC, created DESC
) t ) t
ORDER BY payed DESC, created DESC`, [ ORDER BY payed DESC, created DESC`, [
params.clientFk, clientId,
params.companyFk, companyId,
params.clientFk, clientId,
params.companyFk, companyId,
] ]
); );

View File

@ -2,12 +2,10 @@ const app = require('vn-loopback/server/server');
describe('receipt filter()', () => { describe('receipt filter()', () => {
it('should return the receipts', async() => { it('should return the receipts', async() => {
let filter = {limit: 20}; const filter = {limit: 20};
let params = { const clientId = 101;
clientFk: 101, const companyId = 442;
companyFk: 442 let result = await app.models.Receipt.filter(filter, clientId, companyId);
};
let result = await app.models.Receipt.filter(filter, params);
expect(result.length).toBeGreaterThan(0); expect(result.length).toBeGreaterThan(0);
}); });

View File

@ -2,10 +2,12 @@ import ngModule from '../../module';
import './style.scss'; import './style.scss';
class Controller { class Controller {
constructor($http, $scope, $stateParams) { constructor($http, $scope, $stateParams, $translate, vnApp) {
this.$http = $http; this.$http = $http;
this.$scope = $scope; this.$scope = $scope;
this.$stateParams = $stateParams; this.$stateParams = $stateParams;
this.$translate = $translate;
this.vnApp = vnApp;
this.filter = { this.filter = {
fields: [ fields: [
'id', 'id',
@ -51,6 +53,7 @@ class Controller {
if (res.data) { if (res.data) {
this.client.defaultAddressFk = res.data.defaultAddressFk; this.client.defaultAddressFk = res.data.defaultAddressFk;
this.sortAddresses(); this.sortAddresses();
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
} }
}); });
} }
@ -69,7 +72,7 @@ class Controller {
}); });
} }
} }
Controller.$inject = ['$http', '$scope', '$stateParams']; Controller.$inject = ['$http', '$scope', '$stateParams', '$translate', 'vnApp'];
ngModule.component('vnClientAddressIndex', { ngModule.component('vnClientAddressIndex', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -1,10 +1,8 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="receipts/filter" url="receipts/filter"
params="$ctrl.params"
limit="20" limit="20"
data="$ctrl.balances" data="$ctrl.balances">
auto-load="true">
</vn-crud-model> </vn-crud-model>
<vn-crud-model <vn-crud-model
vn-id="riskModel" vn-id="riskModel"
@ -21,8 +19,7 @@
<vn-autocomplete <vn-autocomplete
vn-id="company" vn-id="company"
class="dense" class="dense"
ng-model="$ctrl.companyFk" ng-model="$ctrl.companyId"
on-change="$ctrl.setOrder(value)"
url="Companies" url="Companies"
show-field="code" show-field="code"
value-field="id" value-field="id"

View File

@ -16,75 +16,53 @@ class Controller {
scope: { scope: {
fields: ['code'], fields: ['code'],
}, },
},
where: {
clientFk: $stateParams.id,
companyFk: this.companyFk
},
};
this.params = {
params: {
clientFk: this.$stateParams.id,
companyFk: this.companyFk,
},
};
}
get companyFk() {
if (!this._companyFk)
return this.vnConfig.companyFk;
return this._companyFk;
}
set companyFk(id) {
this._companyFk = id;
}
setOrder(value) {
this.params.params.companyFk = value;
this.filter.where.companyFk = value;
this.refresh();
}
refresh() {
this.$.model.refresh();
this.$.riskModel.refresh();
}
set balances(value) {
this._balances = value;
if (!value) return;
const params = {filter: this.filter};
this.$http.get(`ClientRisks`, {params}).then(response => {
if (response.data) {
this.clientRisks = response.data;
this.getBalances();
} }
}); };
} }
get balances() { get companyId() {
return this._balances; if (!this._companyId)
this.companyId = this.vnConfig.companyFk;
return this._companyId;
} }
set companyId(value) {
this._companyId = value;
if (value) this.getData();
}
getData() {
return this.$.model.applyFilter(null, {
clientId: this.$stateParams.id,
companyId: this.companyId
}).then(() => this.$.riskModel.applyFilter({
where: {
clientFk: this.$stateParams.id,
companyFk: this.companyId
}
})).then(() => this.getBalances());
}
getCurrentBalance() { getCurrentBalance() {
const selectedCompany = this.$.company.selection; const clientRisks = this.$.riskModel.data;
const currentBalance = this.clientRisks.find(balance => { const selectedCompany = this.companyId;
return balance.companyFk === selectedCompany.id; const currentBalance = clientRisks.find(balance => {
return balance.companyFk === selectedCompany;
}); });
return currentBalance.amount; return currentBalance.amount;
} }
getBalances() { getBalances() {
this.balances.forEach((balance, index) => { const balances = this.$.model.data;
balances.forEach((balance, index) => {
if (index === 0) if (index === 0)
balance.balance = this.getCurrentBalance(); balance.balance = this.getCurrentBalance();
if (index > 0) { if (index > 0) {
let previousBalance = this.balances[index - 1]; let previousBalance = balances[index - 1];
balance.balance = previousBalance.balance - (previousBalance.debit - previousBalance.credit); balance.balance = previousBalance.balance - (previousBalance.debit - previousBalance.credit);
} }
}); });
@ -92,10 +70,8 @@ class Controller {
openCreateDialog() { openCreateDialog() {
this.$.balanceCreateDialog.companyFk = this.companyFk; this.$.balanceCreateDialog.companyFk = this.companyId;
this.$.balanceCreateDialog.onResponse = () => { this.$.balanceCreateDialog.onResponse = () => this.getData();
this.refresh();
};
this.$.balanceCreateDialog.show(); this.$.balanceCreateDialog.show();
} }

View File

@ -3,41 +3,98 @@ import './index';
describe('Client', () => { describe('Client', () => {
describe('Component vnClientBalanceIndex', () => { describe('Component vnClientBalanceIndex', () => {
let $componentController; let $componentController;
let $scope;
let $httpBackend;
let $httpParamSerializer;
let controller; let controller;
beforeEach(ngModule('client')); beforeEach(ngModule('client'));
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_, _$httpParamSerializer_) => { beforeEach(angular.mock.inject((_$componentController_, $rootScope) => {
$componentController = _$componentController_; $componentController = _$componentController_;
$httpBackend = _$httpBackend_; let $scope = $rootScope.$new();
$httpParamSerializer = _$httpParamSerializer_;
$scope = $rootScope.$new();
controller = $componentController('vnClientBalanceIndex', {$scope}); controller = $componentController('vnClientBalanceIndex', {$scope});
controller.$.model = {applyFilter: () => {}};
controller.$.riskModel = {
applyFilter: () => {},
data:
[{
clientFk: 101,
companyFk: 442,
amount: 713.24,
company: {
id: 442,
code: 'VNL'
}
}]
};
})); }));
describe('balances() setter', () => { describe('getData()', () => {
it('should calculate the balance for each line from the oldest date to the newest', () => { it('should apply the filters on he models and get the client balance', () => {
controller.getCurrentBalance = jasmine.createSpy(controller, 'getCurrentBalance').and.returnValue(1000); controller._companyId = 442;
let balances = [ controller.$stateParams.id = 101;
{credit: -100, debit: 0}, spyOn(controller, 'getBalances');
{credit: 0, debit: 300}, spyOn(controller.$.model, 'applyFilter').and.returnValue(Promise.resolve());
{credit: 100, debit: 0}, spyOn(controller.$.riskModel, 'applyFilter').and.returnValue(Promise.resolve());
{credit: 0, debit: -300}
];
const params = {filter: controller.filter};
let serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `ClientRisks?${serializedParams}`).respond(balances);
$httpBackend.expect('GET', `ClientRisks?${serializedParams}`);
controller.balances = balances;
$httpBackend.flush();
expect(controller.balances[0].balance).toEqual(1000); controller.getData().then(() => {
expect(controller.balances[1].balance).toEqual(900); expect(controller.$.model.applyFilter).toHaveBeenCalledWith(null, {'clientId': 101, 'companyId': 442});
expect(controller.balances[2].balance).toEqual(600); expect(controller.$.riskModel.applyFilter).toHaveBeenCalledWith({'where': {'clientFk': 101, 'companyFk': 442}});
expect(controller.balances[3].balance).toEqual(700); expect(controller.getBalances).toHaveBeenCalledWith();
});
});
});
describe('company setter/getter', () => {
it('should return the company', () => {
controller.companyId = null;
expect(controller._companyId).toEqual(jasmine.any(Object));
});
it('should return the company and then call getData()', () => {
spyOn(controller, 'getData');
controller.companyId = 442;
expect(controller._companyId).toEqual(442);
expect(controller.getData).toHaveBeenCalledWith();
});
});
describe('getCurrentBalance()', () => {
it('should return the client balance amount', () => {
controller._companyId = 442;
let result = controller.getCurrentBalance();
expect(result).toEqual(713.24);
});
});
describe('getBalances()', () => {
it('should return the total client balance amount', () => {
spyOn(controller, 'getCurrentBalance').and.callThrough();
controller._companyId = 442;
controller.$.model = {data:
[{
id: 1,
debit: 1000,
credit: null
},
{
id: 2,
debit: null,
credit: 500
},
{
id: 3,
debit: null,
credit: 300
}
]};
controller.getBalances();
const expectedBalances = controller.$.model.data;
expect(expectedBalances[0].balance).toEqual(713.24);
expect(expectedBalances[1].balance).toEqual(-286.76);
expect(expectedBalances[2].balance).toEqual(213.24);
}); });
}); });
}); });

View File

@ -29,13 +29,24 @@ describe('Component VnClientWebAccess', () => {
}); });
describe('isCustomer()', () => { describe('isCustomer()', () => {
it(`should perform a query if client is defined with an ID`, () => { it('should return true if the password can be modified', () => {
controller.client = {id: '1234'}; controller.client = {id: '1234'};
controller.isCustomer();
$httpBackend.when('GET', `Clients/${controller.client.id}/hasCustomerRole`).respond('ok'); $httpBackend.expectGET(`Clients/${controller.client.id}/hasCustomerRole`).respond({isCustomer: true});
$httpBackend.expectGET(`Clients/${controller.client.id}/hasCustomerRole`); controller.isCustomer();
$httpBackend.flush(); $httpBackend.flush();
expect(controller.canChangePassword).toBeTruthy();
});
it(`should return a false if the password can't be modified`, () => {
controller.client = {id: '1234'};
$httpBackend.expectGET(`Clients/${controller.client.id}/hasCustomerRole`).respond({isCustomer: false});
controller.isCustomer();
$httpBackend.flush();
expect(controller.canChangePassword).toBeFalsy();
}); });
}); });

View File

@ -1,7 +1,7 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('invoiceOut book()', () => { describe('invoiceOut book()', () => {
const invoiceOutId = 1; const invoiceOutId = 5;
let bookedDate; let bookedDate;
let OriginalInvoiceOut; let OriginalInvoiceOut;
let updatedInvoiceOut; let updatedInvoiceOut;

View File

@ -53,7 +53,7 @@ class Controller {
} }
showInvoiceOutPdf() { showInvoiceOutPdf() {
let url = `InvoiceOuts/${this.invoiceOut.id}/download?access_token=${this.accessToken}`; let url = `api/InvoiceOuts/${this.invoiceOut.id}/download?access_token=${this.accessToken}`;
window.open(url, '_blank'); window.open(url, '_blank');
} }

View File

@ -27,7 +27,7 @@ export default class Controller {
} }
openPdf(id, event) { openPdf(id, event) {
let url = `InvoiceOuts/${id}/download?access_token=${this.accessToken}`; let url = `api/InvoiceOuts/${id}/download?access_token=${this.accessToken}`;
window.open(url, '_blank'); window.open(url, '_blank');
event.preventDefault(); event.preventDefault();
event.stopImmediatePropagation(); event.stopImmediatePropagation();

View File

@ -20,7 +20,7 @@ class Controller {
]; ];
this.orderFields = [].concat(this.defaultOrderFields); this.orderFields = [].concat(this.defaultOrderFields);
this._orderWay = this.orderWays[0].way; this._orderWay = this.orderWays[0].way;
this._orderField = this.orderFields[0].field; this.orderField = this.orderFields[0].field;
} }
/** /**
@ -76,15 +76,13 @@ class Controller {
if (value) this.applyOrder(); if (value) this.applyOrder();
} }
/** get orderSelection() {
* Get order fields return this._orderSelection;
*/
get orderField() {
return this._orderField;
} }
set orderField(value) { set orderSelection(value) {
this._orderField = value; this._orderSelection = value;
if (value) this.applyOrder(); if (value) this.applyOrder();
} }
@ -94,10 +92,11 @@ class Controller {
* @return {Object} - Order param * @return {Object} - Order param
*/ */
getOrderBy() { getOrderBy() {
const isTag = !!(this.orderSelection && this.orderSelection.isTag);
return { return {
field: this.orderField, field: this.orderField,
way: this.orderWay, way: this.orderWay,
isTag: !!(this.orderSelection && this.orderSelection.isTag) isTag: isTag
}; };
} }

View File

@ -6,27 +6,26 @@
</vn-crud-model> </vn-crud-model>
<div> <div>
<vn-horizontal class="item-category"> <vn-horizontal class="item-category">
<vn-autocomplete vn-id="category"
data="categories"
ng-model="$ctrl.categoryId"
show-field="name"
value-field="id"
label="Category">
</vn-autocomplete>
<vn-one ng-repeat="category in categories"> <vn-one ng-repeat="category in categories">
<vn-icon <vn-icon
ng-class="{'active': $ctrl.category.id == category.id}" ng-class="{'active': $ctrl.categoryId == category.id}"
icon="{{::category.icon}}" icon="{{::category.icon}}"
vn-tooltip="{{::category.name}}" vn-tooltip="{{::category.name}}"
ng-click="$ctrl.category = { ng-click="$ctrl.categoryId = category.id">
id: category.id,
value: category.name
}">
</vn-icon> </vn-icon>
</vn-one> </vn-one>
</vn-horizontal> </vn-horizontal>
<vn-vertical class="input"> <vn-vertical class="input">
<vn-autocomplete <vn-autocomplete vn-id="type"
vn-id="type"
data="$ctrl.itemTypes" data="$ctrl.itemTypes"
on-change="$ctrl.type = { ng-model="$ctrl.typeId"
id: value,
value: type.selection.name
}"
ng-model="$ctrl.type.id"
show-field="name" show-field="name"
value-field="id" value-field="id"
label="Type" label="Type"
@ -38,9 +37,6 @@
{{categoryName}} {{categoryName}}
</div> </div>
</tpl-item> </tpl-item>
<prepend>
<vn-icon icon="search"></vn-icon>
</prepend>
</vn-autocomplete> </vn-autocomplete>
</vn-vertical> </vn-vertical>
<vn-vertical class="input vn-pt-md"> <vn-vertical class="input vn-pt-md">
@ -73,7 +69,7 @@
<vn-textfield <vn-textfield
ng-keyUp="$ctrl.onSearchById($event)" ng-keyUp="$ctrl.onSearchById($event)"
label="Item id" label="Item id"
ng-model="$ctrl.itemFk"> ng-model="$ctrl.itemId">
<prepend> <prepend>
<vn-icon icon="icon-item"></vn-icon> <vn-icon icon="icon-item"></vn-icon>
</prepend> </prepend>
@ -106,20 +102,20 @@
</vn-popover> </vn-popover>
<div class="chips"> <div class="chips">
<vn-chip <vn-chip
ng-if="$ctrl.category" ng-if="category.selection"
removable="true" removable="true"
translate-attr="{title: 'Category'}" translate-attr="{title: 'Category'}"
on-remove="$ctrl.category = null" on-remove="$ctrl.categoryId = null"
class="colored"> class="colored">
<span translate>{{$ctrl.category.value}}</span> <span translate>{{category.selection.name}}</span>
</vn-chip> </vn-chip>
<vn-chip <vn-chip
ng-if="$ctrl.type" ng-if="type.selection"
removable="true" removable="true"
translate-attr="{title: 'Type'}" translate-attr="{title: 'Type'}"
on-remove="$ctrl.type = null" on-remove="$ctrl.typeId = null"
class="colored"> class="colored">
<span translate>{{$ctrl.type.value}}</span> <span translate>{{type.selection.name}}</span>
</vn-chip> </vn-chip>
<vn-chip <vn-chip
ng-repeat="tag in $ctrl.tags" ng-repeat="tag in $ctrl.tags"

View File

@ -24,65 +24,50 @@ class Controller {
* @param {Object} value - Order data * @param {Object} value - Order data
*/ */
set order(value) { set order(value) {
if (!value || !value.id || this._order) return;
this._order = value; this._order = value;
if (!value) return;
this.$.$applyAsync(() => { this.$.$applyAsync(() => {
let category; if (this.$stateParams.categoryId)
let type; this.categoryId = this.$stateParams.categoryId;
if (this.$stateParams.category) if (this.$stateParams.typeId)
category = JSON.parse(this.$stateParams.category); this.typeId = this.$stateParams.typeId;
if (this.$stateParams.type)
type = JSON.parse(this.$stateParams.type);
if (category && category.id)
this.category = category;
if (type && type.id)
this.type = type;
}); });
} }
get category() { get categoryId() {
return this._category; return this._categoryId;
} }
set category(value) { set categoryId(value) {
this.catalog.$scope.model.data = []; if (!value || (this.categoryId == value))
this.itemTypes = []; value = null;
this.type = null;
if (!value || (this.category && this.category.id == value.id)) this._categoryId = value;
this._category = null; this.itemTypes = [];
else this.typeId = null;
this._category = value;
this.updateStateParams(); this.updateStateParams();
if (this.tags.length > 0) if (this.tags.length > 0)
this.applyFilters(); this.applyFilters();
if (this._category) if (value)
this.updateItemTypes(); this.updateItemTypes();
} }
get type() { get typeId() {
return this._type; return this._typeId;
} }
set type(value) { set typeId(value) {
if (value && this.type && this.type.id == value.id) return; this._typeId = value;
this._type = value;
if (!value || !value.id)
this._type = null;
this.updateStateParams(); this.updateStateParams();
if ((value && value.id) || this.tags.length > 0) if ((value) || this.tags.length > 0)
this.applyFilters(); this.applyFilters();
} }
@ -91,17 +76,17 @@ class Controller {
*/ */
updateItemTypes() { updateItemTypes() {
let params = { let params = {
itemCategoryId: this.category.id itemCategoryId: this.categoryId
}; };
const query = `Orders/${this.order.id}/getItemTypeAvailable`; const query = `Orders/${this.order.id}/getItemTypeAvailable`;
this.$http.get(query, {params}).then(res => { this.$http.get(query, {params}).then(res =>
this.itemTypes = res.data; this.itemTypes = res.data);
});
} }
onSearchById(event) { onSearchById(event) {
if (event.key === 'Enter' && (this.tags.length > 0 || this.itemFk || this.type)) const hasValue = this.tags.length > 0 || this.itemId || this.typeId;
if (event.key === 'Enter' && hasValue)
this.applyFilters(); this.applyFilters();
} }
@ -117,7 +102,7 @@ class Controller {
remove(index) { remove(index) {
this.tags.splice(index, 1); this.tags.splice(index, 1);
if (this.tags.length >= 0 || this.itemFk || this.type) if (this.tags.length >= 0 || this.itemId || this.typeId)
this.applyFilters(); this.applyFilters();
} }
@ -126,14 +111,14 @@ class Controller {
let newFilter = {}; let newFilter = {};
const model = this.catalog.$scope.model; const model = this.catalog.$scope.model;
if (this.category) if (this.categoryId)
newFilter.categoryFk = this.category.id; newFilter.categoryFk = this.categoryId;
if (this.type) if (this.typeId)
newFilter.typeFk = this.type.id; newFilter.typeFk = this.typeId;
if (this.itemFk) if (this.itemId)
newFilter = {'i.id': this.itemFk}; newFilter = {'i.id': this.itemId};
newParams = { newParams = {
orderFk: this.order.id, orderFk: this.order.id,
@ -164,13 +149,13 @@ class Controller {
updateStateParams() { updateStateParams() {
const params = {}; const params = {};
if (this.category) if (this.categoryId)
params.category = JSON.stringify(this.category); params.categoryId = this.categoryId;
else params.categoryId = undefined;
if (this.type) if (this.typeId)
params.type = JSON.stringify(this.type); params.typeId = this.typeId;
else else params.typeId = undefined;
params.type = undefined;
this.$state.go(this.$state.current.name, params); this.$state.go(this.$state.current.name, params);
} }

View File

@ -16,8 +16,8 @@ describe('Order', () => {
$scope.model = crudModel; $scope.model = crudModel;
$scope.search = {}; $scope.search = {};
$state = _$state_; $state = _$state_;
$state.params.category = '{"id": 1, "value": "My Category"}'; $state.params.categoryId = 1;
$state.params.type = '{"id": 1, "value": "My type"}'; $state.params.typeId = 2;
$state.current.name = 'my.current.state'; $state.current.name = 'my.current.state';
controller = $componentController('vnCatalogFilter', {$element: null, $scope, $state}); controller = $componentController('vnCatalogFilter', {$element: null, $scope, $state});
controller.catalog = { controller.catalog = {
@ -35,15 +35,15 @@ describe('Order', () => {
$scope.$apply(); $scope.$apply();
expect(controller.category).toEqual({id: 1, value: 'My Category'}); expect(controller.categoryId).toEqual(1);
expect(controller.type).toEqual({id: 1, value: 'My type'}); expect(controller.typeId).toEqual(2);
}); });
}); });
describe('category() setter', () => { describe('categoryId() setter', () => {
it(`should set category property to null, call updateStateParams() method and not call applyFilters()`, () => { it(`should set category property to null, call updateStateParams() method and not call applyFilters()`, () => {
spyOn(controller, 'updateStateParams'); spyOn(controller, 'updateStateParams');
controller.category = null; controller.categoryId = null;
expect(controller.updateStateParams).toHaveBeenCalledWith(); expect(controller.updateStateParams).toHaveBeenCalledWith();
}); });
@ -51,17 +51,17 @@ describe('Order', () => {
it(`should set category property and then call updateStateParams() and applyFilters() methods`, () => { it(`should set category property and then call updateStateParams() and applyFilters() methods`, () => {
spyOn(controller, 'updateStateParams'); spyOn(controller, 'updateStateParams');
controller._order = {id: 4}; controller._order = {id: 4};
controller.category = {id: 2, value: 'My category'}; controller.categoryId = 2;
expect(controller.updateStateParams).toHaveBeenCalledWith(); expect(controller.updateStateParams).toHaveBeenCalledWith();
}); });
}); });
describe('type() setter', () => { describe('typeId() setter', () => {
it(`should set type property to null, call updateStateParams() method and not call applyFilters()`, () => { it(`should set type property to null, call updateStateParams() method and not call applyFilters()`, () => {
spyOn(controller, 'updateStateParams'); spyOn(controller, 'updateStateParams');
spyOn(controller, 'applyFilters'); spyOn(controller, 'applyFilters');
controller.type = null; controller.typeId = null;
expect(controller.updateStateParams).toHaveBeenCalledWith(); expect(controller.updateStateParams).toHaveBeenCalledWith();
expect(controller.applyFilters).not.toHaveBeenCalledWith(); expect(controller.applyFilters).not.toHaveBeenCalledWith();
@ -70,7 +70,7 @@ describe('Order', () => {
it(`should set category property and then call updateStateParams() and applyFilters() methods`, () => { it(`should set category property and then call updateStateParams() and applyFilters() methods`, () => {
spyOn(controller, 'updateStateParams'); spyOn(controller, 'updateStateParams');
spyOn(controller, 'applyFilters'); spyOn(controller, 'applyFilters');
controller.type = {id: 2, value: 'My type'}; controller.typeId = 2;
expect(controller.updateStateParams).toHaveBeenCalledWith(); expect(controller.updateStateParams).toHaveBeenCalledWith();
expect(controller.applyFilters).toHaveBeenCalledWith(); expect(controller.applyFilters).toHaveBeenCalledWith();
@ -101,7 +101,7 @@ describe('Order', () => {
describe('onSearchById()', () => { describe('onSearchById()', () => {
it(`should not filter by id if the event key code doesn't equals to 'Enter'`, () => { it(`should not filter by id if the event key code doesn't equals to 'Enter'`, () => {
spyOn(controller, 'applyFilters'); spyOn(controller, 'applyFilters');
controller.itemFk = 1; controller.itemId = 1;
controller.onSearchById({key: 'Tab'}); controller.onSearchById({key: 'Tab'});
expect(controller.applyFilters).not.toHaveBeenCalledWith(); expect(controller.applyFilters).not.toHaveBeenCalledWith();
@ -109,7 +109,7 @@ describe('Order', () => {
it(`should filter by id if the event key code equals to 'Enter' an then call applyFilters()`, () => { it(`should filter by id if the event key code equals to 'Enter' an then call applyFilters()`, () => {
spyOn(controller, 'applyFilters'); spyOn(controller, 'applyFilters');
controller.itemFk = 1; controller.itemId = 1;
controller.onSearchById({key: 'Enter'}); controller.onSearchById({key: 'Enter'});
@ -121,14 +121,14 @@ describe('Order', () => {
it(`should call model applyFilter() method with a new filter`, () => { it(`should call model applyFilter() method with a new filter`, () => {
let model = controller.catalog.$scope.model; let model = controller.catalog.$scope.model;
spyOn(model, 'applyFilter'); spyOn(model, 'applyFilter');
controller._category = {id: 1, value: 'My Category'}; controller._categoryId = 2;
controller._type = {id: 1, value: 'My type'}; controller._typeId = 4;
controller._order = {id: 4}; controller._order = {id: 4};
controller.applyFilters(); controller.applyFilters();
expect(model.applyFilter).toHaveBeenCalledWith( expect(model.applyFilter).toHaveBeenCalledWith(
{where: {categoryFk: 1, typeFk: 1}}, {where: {categoryFk: 2, typeFk: 4}},
{orderFk: 4, orderBy: controller.catalog.getOrderBy(), tags: []}); {orderFk: 4, orderBy: controller.catalog.getOrderBy(), tags: []});
}); });
}); });
@ -146,8 +146,8 @@ describe('Order', () => {
it(`should remove a tag from tags property and call applyFilters() if there's no more tags`, () => { it(`should remove a tag from tags property and call applyFilters() if there's no more tags`, () => {
spyOn(controller, 'applyFilters'); spyOn(controller, 'applyFilters');
controller._category = {id: 1, value: 'My Category'}; controller._categoryId = 1;
controller._type = {id: 1, value: 'My type'}; controller._typeId = 1;
controller.tags = [{tagFk: 1, value: 'Blue'}]; controller.tags = [{tagFk: 1, value: 'Blue'}];
controller.remove(0); controller.remove(0);
@ -159,9 +159,9 @@ describe('Order', () => {
describe('updateStateParams()', () => { describe('updateStateParams()', () => {
it(`should call state go() method passing category and type state params`, () => { it(`should call state go() method passing category and type state params`, () => {
spyOn(controller.$state, 'go'); spyOn(controller.$state, 'go');
controller._category = {id: 1, value: 'My Category'}; controller._categoryId = 2;
controller._type = {id: 1, value: 'My type'}; controller._typeId = 4;
let result = {category: '{"id":1,"value":"My Category"}', type: '{"id":1,"value":"My type"}'}; let result = {categoryId: 2, typeId: 4};
controller.updateStateParams(); controller.updateStateParams();
expect(controller.$state.go).toHaveBeenCalledWith('my.current.state', result); expect(controller.$state.go).toHaveBeenCalledWith('my.current.state', result);

View File

@ -14,6 +14,10 @@ vn-catalog-filter > div {
align-items: flex-start; align-items: flex-start;
flex-wrap: wrap; flex-wrap: wrap;
vn-autocomplete[vn-id="category"] {
display: none
}
& > vn-one { & > vn-one {
padding: $spacing-sm; padding: $spacing-sm;
min-width: 33.33%; min-width: 33.33%;

View File

@ -41,7 +41,7 @@
"order": "$ctrl.order" "order": "$ctrl.order"
} }
}, { }, {
"url": "/catalog?category&type", "url": "/catalog?categoryId&typeId",
"state": "order.card.catalog", "state": "order.card.catalog",
"component": "vn-order-catalog", "component": "vn-order-catalog",
"description": "Catalog", "description": "Catalog",

View File

@ -8,7 +8,7 @@
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md"> <form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg"> <vn-card class="vn-pa-lg">
<vn-horizontal> <vn-horizontal>
<vn-autocomplete <vn-autocomplete
vn-one vn-one
ng-model="$ctrl.route.workerFk" ng-model="$ctrl.route.workerFk"
url="Clients/activeWorkersWithRole" url="Clients/activeWorkersWithRole"
@ -17,6 +17,9 @@
value-field="id" value-field="id"
where="{role: 'employee'}" where="{role: 'employee'}"
label="Worker"> label="Worker">
<tpl-item>
<div>{{name}} - {{nickname}}</div>
</tpl-item>
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete <vn-autocomplete
vn-one vn-one

View File

@ -7,5 +7,9 @@
"dataSource": "vn" "dataSource": "vn"
},"Currency": { },"Currency": {
"dataSource": "vn" "dataSource": "vn"
},"Thermograph": {
"dataSource": "vn"
},"TravelThermograph": {
"dataSource": "vn"
} }
} }

View File

@ -0,0 +1,19 @@
{
"name": "Thermograph",
"base": "VnModel",
"options": {
"mysql": {
"table": "thermograph"
}
},
"properties": {
"id": {
"type": "String",
"id": true,
"description": "Identifier"
},
"model": {
"type": "String"
}
}
}

View File

@ -0,0 +1,49 @@
{
"name": "TravelThermograph",
"base": "VnModel",
"options": {
"mysql": {
"table": "travelThermograph"
}
},
"properties": {
"thermographFk": {
"type": "String",
"id": 1,
"description": "Identifier"
},
"created": {
"type": "Date",
"id": 2,
"description": "Identifier"
},
"temperature": {
"type": "String"
},
"result": {
"type": "String"
}
},
"relations": {
"travel": {
"type": "belongsTo",
"model": "Travel",
"foreignKey": "travelFk"
},
"warehouse": {
"type": "belongsTo",
"model": "Warehouse",
"foreignKey": "warehouseFk"
},
"dms": {
"type": "belongsTo",
"model": "Dms",
"foreignKey": "dmsFk"
},
"thermograph": {
"type": "belongsTo",
"model": "Thermograph",
"foreignKey": "thermographFk"
}
}
}

View File

@ -43,7 +43,7 @@
</vn-one> </vn-one>
<vn-auto> <vn-auto>
<h4 translate>Entries</h4> <h4 translate>Entries</h4>
<vn-table model="model"> <vn-table>
<vn-thead> <vn-thead>
<vn-tr> <vn-tr>
<vn-th shrink>Confirmed</vn-th> <vn-th shrink>Confirmed</vn-th>
@ -51,8 +51,8 @@
<vn-th shrink>Supplier</vn-th> <vn-th shrink>Supplier</vn-th>
<vn-th shrink>Reference</vn-th> <vn-th shrink>Reference</vn-th>
<vn-th shrink title="Half box">HB</vn-th> <vn-th shrink title="Half box">HB</vn-th>
<vn-th shrink>Freight cost</vn-th> <vn-th shrink>Freight</vn-th>
<vn-th shrink>Package cost</vn-th> <vn-th shrink>Package</vn-th>
<vn-th shrink>CC</vn-th> <vn-th shrink>CC</vn-th>
<vn-th shrink>Pallet</vn-th> <vn-th shrink>Pallet</vn-th>
<vn-th shrink>m3</vn-th> <vn-th shrink>m3</vn-th>
@ -61,16 +61,18 @@
</vn-thead> </vn-thead>
<vn-tbody> <vn-tbody>
<vn-tr ng-repeat="entry in $ctrl.entries"> <vn-tr ng-repeat="entry in $ctrl.entries">
<vn-check <vn-td shrink>
value="{{entry.isConfirmed}}" <vn-check
disabled="true"> value="{{entry.isConfirmed}}"
</vn-check> disabled="true">
</vn-check>
</vn-td>
<vn-td shrink>{{entry.id}} </vn-td> <vn-td shrink>{{entry.id}} </vn-td>
<vn-td shrink>{{entry.supplierName}}</vn-td> <vn-td shrink>{{entry.supplierName}}</vn-td>
<vn-td shrink>{{entry.ref}}</vn-td> <vn-td shrink>{{entry.ref}}</vn-td>
<vn-td shrink>{{entry.hb}}</vn-td> <vn-td shrink>{{entry.hb}}</vn-td>
<vn-td shrink>{{entry.freightValue}}</vn-td> <vn-td shrink>{{entry.freightValue | currency: 'EUR': 2}}</vn-td>
<vn-td shrink>{{entry.packageValue}}</vn-td> <vn-td shrink>{{entry.packageValue | currency: 'EUR': 2}}</vn-td>
<vn-td shrink>{{entry.cc}}</vn-td> <vn-td shrink>{{entry.cc}}</vn-td>
<vn-td shrink>{{entry.pallet}}</vn-td> <vn-td shrink>{{entry.pallet}}</vn-td>
<vn-td shrink>{{entry.m3}}</vn-td> <vn-td shrink>{{entry.m3}}</vn-td>
@ -81,13 +83,28 @@
icon="insert_drive_file"> icon="insert_drive_file">
</vn-icon> </vn-icon>
<vn-icon <vn-icon
ng-if="entry.notes.length" ng-if="entry.observation.length"
vn-tooltip="{{entry.observation}}" vn-tooltip="{{entry.observation}}"
icon="insert_drive_file"> icon="insert_drive_file">
</vn-icon> </vn-icon>
</vn-td> </vn-td>
</vn-tr> </vn-tr>
</vn-tbody> </vn-tbody>
<vn-tfoot>
<vn-tr>
<vn-td></vn-td>
<vn-td></vn-td>
<vn-td></vn-td>
<vn-td></vn-td>
<vn-td shrink><strong>{{$ctrl.total('hb')}}</strong></vn-td>
<vn-td shrink><strong>{{$ctrl.total('freightValue') | currency: 'EUR': 2}}</strong></vn-td>
<vn-td shrink><strong>{{$ctrl.total('packageValue') | currency: 'EUR': 2}}</strong></vn-td>
<vn-td shrink><strong>{{$ctrl.total('cc')}}</strong></vn-td>
<vn-td shrink><strong>{{$ctrl.total('pallet')}}</strong></vn-td>
<vn-td shrink><strong>{{$ctrl.total('m3')}}</strong></vn-td>
<vn-td></vn-td>
</vn-tr>
</vn-tfoot>
</vn-table> </vn-table>
</vn-auto> </vn-auto>
</vn-horizontal> </vn-horizontal>

View File

@ -5,6 +5,7 @@ class Controller {
constructor($scope, $http) { constructor($scope, $http) {
this.$ = $scope; this.$ = $scope;
this.$http = $http; this.$http = $http;
this.entries = [];
} }
get travel() { get travel() {
@ -31,6 +32,15 @@ class Controller {
this.entries = response.data; this.entries = response.data;
}); });
} }
total(field) {
let total = 0;
for (let entry of this.entries)
total += entry[field];
return total;
}
} }
Controller.$inject = ['$scope', '$http']; Controller.$inject = ['$scope', '$http'];

View File

@ -58,4 +58,18 @@ describe('component vnTravelSummary', () => {
expect(controller.entries).toEqual('I am the entries'); expect(controller.entries).toEqual('I am the entries');
}); });
}); });
describe('total()', () => {
it('should calculate the total amount of a given property for every row', () => {
controller.entries = [
{id: 1, freightValue: 1, packageValue: 2, cc: 0.01},
{id: 2, freightValue: 1, packageValue: 2, cc: 0.01},
{id: 3, freightValue: 1, packageValue: 2, cc: 0.01}
];
expect(controller.total('freightValue')).toEqual(3);
expect(controller.total('packageValue')).toEqual(6);
expect(controller.total('cc')).toEqual(0.03);
});
});
}); });

View File

@ -12,6 +12,6 @@ Confirmed: Confirmada
Entry Id: Entrada Id Entry Id: Entrada Id
Supplier: Proveedor Supplier: Proveedor
Pallet: Pallet Pallet: Pallet
Freight cost: Coste porte Freight: Porte
Package cost: Coste embalaje Package: Embalaje
Half box: Media caja Half box: Media caja

View File

@ -1,7 +1,6 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
// #1924 - Fix hours describe('Worker absences()', () => {
xdescribe('Worker absences()', () => {
it('should get the absence calendar for a full year contract', async() => { it('should get the absence calendar for a full year contract', async() => {
let ctx = {req: {accessToken: {userId: 106}}}; let ctx = {req: {accessToken: {userId: 106}}};
let workerFk = 106; let workerFk = 106;
@ -27,8 +26,8 @@ xdescribe('Worker absences()', () => {
let firstType = absences[0].absenceType().name; let firstType = absences[0].absenceType().name;
let sixthType = absences[5].absenceType().name; let sixthType = absences[5].absenceType().name;
expect(firstType).toEqual('Leave of absence'); expect(firstType).toMatch(/(Holidays|Leave of absence)/);
expect(sixthType).toEqual('Holidays'); expect(sixthType).toMatch(/(Holidays|Leave of absence)/);
}); });
it('should get the absence calendar for a permanent contract', async() => { it('should get the absence calendar for a permanent contract', async() => {
@ -64,8 +63,8 @@ xdescribe('Worker absences()', () => {
let firstType = absences[0].absenceType().name; let firstType = absences[0].absenceType().name;
let sixthType = absences[5].absenceType().name; let sixthType = absences[5].absenceType().name;
expect(firstType).toEqual('Leave of absence'); expect(firstType).toMatch(/(Holidays|Leave of absence)/);
expect(sixthType).toEqual('Holidays'); expect(sixthType).toMatch(/(Holidays|Leave of absence)/);
// restores the contract end date // restores the contract end date
await app.models.WorkerLabour.rawSql( await app.models.WorkerLabour.rawSql(
@ -146,8 +145,8 @@ xdescribe('Worker absences()', () => {
let firstType = absences[0].absenceType().name; let firstType = absences[0].absenceType().name;
let sixthType = absences[5].absenceType().name; let sixthType = absences[5].absenceType().name;
expect(firstType).toEqual('Leave of absence'); expect(firstType).toMatch(/(Holidays|Leave of absence)/);
expect(sixthType).toEqual('Holidays'); expect(sixthType).toMatch(/(Holidays|Leave of absence)/);
// resets the holidays per year with originalHolidaysValue and the contract starting date // resets the holidays per year with originalHolidaysValue and the contract starting date
await app.models.WorkCenterHoliday.updateAll( await app.models.WorkCenterHoliday.updateAll(

View File

@ -29,7 +29,7 @@ module.exports = Self => {
const mySubordinates = await Self.mySubordinates(ctx); const mySubordinates = await Self.mySubordinates(ctx);
const isSubordinate = mySubordinates.find(subordinate => { const isSubordinate = mySubordinates.find(subordinate => {
return subordinate.workerFk === id; return subordinate.workerFk == id;
}); });
const isHr = await models.Account.hasRole(myUserId, 'hr'); const isHr = await models.Account.hasRole(myUserId, 'hr');

View File

@ -19,7 +19,7 @@ vn-log {
} }
@media screen and (max-width: 1570px) { @media screen and (max-width: 1570px) {
.expendable { vn-table .expendable {
display: none; display: none;
} }
.changes { .changes {

View File

@ -4,8 +4,10 @@ let env = process.env.NODE_ENV ? process.env.NODE_ENV : 'development';
let configPath = `/etc/salix`; let configPath = `/etc/salix`;
let config = require('../config/print.json'); let config = require('../config/print.json');
let configFiles = [ let configFiles = [
`${appPath}/config/print.local.json`,
`${appPath}/config/print.${env}.json`, `${appPath}/config/print.${env}.json`,
`${configPath}/print.json`, `${configPath}/print.json`,
`${configPath}/print.local.json`,
`${configPath}/print.${env}.json` `${configPath}/print.${env}.json`
]; ];

View File

@ -0,0 +1,8 @@
const Stylesheet = require(`${appPath}/core/stylesheet`);
module.exports = new Stylesheet([
`${appPath}/common/css/spacing.css`,
`${appPath}/common/css/misc.css`,
`${appPath}/common/css/layout.css`,
`${appPath}/common/css/email.css`])
.mergeStyles();

View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html v-bind:lang="locale">
<head>
<meta name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no">
<title>{{ $t('subject') }}</title>
</head>
<body>
<table class="grid">
<tbody>
<tr>
<td>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
<!-- Header block -->
<div class="grid-row">
<div class="grid-block">
<email-header v-bind="$props"></email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-lg">
<h1>{{ $t('title') }}</h1>
<p>{{$t('dear')}},</p>
<p v-html="$t('description', [dated])"></p>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-lg">
<table class="column-oriented">
<thead>
<tr>
<th>{{$t('buyer')}}</th>
<th class="number">{{$t('percentage')}}</th>
<th class="number">{{$t('dwindle')}}</th>
<th class="number">{{$t('total')}}</th>
</tr>
</thead>
<tbody>
<tr v-for="waste in wastes" v-bind:key="waste.buyer">
<td class="font gray">{{waste.buyer}}</td>
<td class="number">{{(waste.percentage / 100) | percentage(4, 4, locale)}}</td>
<td class="number">{{waste.dwindle | currency('EUR', locale)}}</td>
<td class="number">{{waste.total | currency('EUR', locale)}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -0,0 +1,31 @@
const Component = require(`${appPath}/core/component`);
const db = require(`${appPath}/core/database`);
const emailHeader = new Component('email-header');
const emailFooter = new Component('email-footer');
module.exports = {
name: 'buyer-week-waste',
async serverPrefetch() {
this.wastes = await this.fetchWastes();
if (!this.wastes)
throw new Error('Something went wrong');
},
computed: {
dated: function() {
const filters = this.$options.filters;
return filters.date(new Date(), '%d-%m-%Y');
}
},
methods: {
fetchWastes() {
return db.findOne(`CALL bs.weekWaste()`);
}
},
components: {
'email-header': emailHeader.build(),
'email-footer': emailFooter.build()
},
props: {}
};

View File

@ -0,0 +1,8 @@
subject: Merma semanal
title: Merma semanal
dear: Hola
description: A continuación se muestra la merma semanal a fecha de <strong>{0}</strong>.
buyer: Comprador
percentage: Porcentaje
weakening: Mermas
total: Total

View File

@ -12,4 +12,4 @@ claim: Reclamación {0}
sections: sections:
agency: agency:
description: 'Para agilizar tu recogida, por favor, pónte en contacto con la oficina description: 'Para agilizar tu recogida, por favor, pónte en contacto con la oficina
de integrados. <br/> Tlf: 96 166 77 88 - Ana Gómez (Ext. 2133) <em>(agomezf@integra2.es)</em>' de integrados. <br/> Tlf: 96 166 77 88 - Ana Gómez (Ext. 2113) <em>(agomezf@integra2.es)</em>'