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"]
HEALTHCHECK --interval=1m --timeout=10s \
HEALTHCHECK --interval=15s --timeout=10s \
CMD curl -f http://localhost:3000/api/Applications/status || exit 1

31
Jenkinsfile vendored
View File

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

View File

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

View File

@ -125,13 +125,13 @@ proc: BEGIN
INSERT INTO tmp.ticketComponent
SELECT tcb.warehouseFk,
tcb.itemFk,
cr.id,
GREATEST(IFNULL(ROUND(tcb.base * cr.tax, 4), 0), tcc.minPrice - tcc.rate3)
c.id,
GREATEST(IFNULL(ROUND(tcb.base * c.tax, 4), 0), tcc.minPrice - tcc.rate3)
FROM tmp.ticketComponentBase tcb
JOIN componentRate cr
JOIN component c
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
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
SELECT tcc.warehouseFk, tcc.itemFk, vSellByPacketComponent, tcc.rate2 - tcc.rate3
@ -178,9 +178,9 @@ proc: BEGIN
vSpecialPriceComponent,
sp.value - SUM(tcc.cost) sumCost
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
WHERE cr.classRate IS NULL
WHERE c.classRate IS NULL
GROUP BY tcc.itemFk, tcc.warehouseFk
HAVING ABS(sumCost) > 0.001;
@ -188,10 +188,10 @@ proc: BEGIN
CREATE TEMPORARY TABLE tmp.ticketComponentSum
(INDEX (itemFk, warehouseFk))
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
JOIN componentRate cr ON cr.id = tc.componentFk
GROUP BY tc.itemFk, tc.warehouseFk, cr.classRate;
JOIN component c ON c.id = tc.componentFk
GROUP BY tc.itemFk, tc.warehouseFk, c.classRate;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketComponentRate;
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
(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
(101, 'BruceWayne', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'BruceWayne@mydomain.com', 'es'),
(102, 'PetterParker', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'PetterParker@mydomain.com', 'en'),
(103, 'ClarkKent', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'ClarkKent@mydomain.com', 'fr'),
(104, 'TonyStark', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'TonyStark@mydomain.com', 'es'),
(105, 'MaxEisenhardt', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'MaxEisenhardt@mydomain.com', 'pt'),
(106, 'DavidCharlesHaller', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'DavidCharlesHaller@mydomain.com', 'es'),
(107, 'HankPym', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'HankPym@mydomain.com', 'es'),
(108, 'CharlesXavier', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'CharlesXavier@mydomain.com', 'es'),
(109, 'BruceBanner', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'BruceBanner@mydomain.com', 'es'),
(110, 'JessicaJones', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'JessicaJones@mydomain.com', 'es'),
(111, 'Missing', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'es'),
(112, 'Trash', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'es');
(101, 'BruceWayne', 'Bruce Wayne', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'BruceWayne@mydomain.com', 'es'),
(102, 'PetterParker', 'Petter Parker', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'PetterParker@mydomain.com', 'en'),
(103, 'ClarkKent', 'Clark Kent', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'ClarkKent@mydomain.com', 'fr'),
(104, 'TonyStark', 'Tony Stark', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'TonyStark@mydomain.com', 'es'),
(105, 'MaxEisenhardt', 'Max Eisenhardt', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'MaxEisenhardt@mydomain.com', 'pt'),
(106, 'DavidCharlesHaller', 'David Charles Haller', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'DavidCharlesHaller@mydomain.com', 'es'),
(107, 'HankPym', 'Hank Pym', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'HankPym@mydomain.com', 'es'),
(108, 'CharlesXavier', 'Charles Xavier', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'CharlesXavier@mydomain.com', 'es'),
(109, 'BruceBanner', 'Bruce Banner', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'BruceBanner@mydomain.com', 'es'),
(110, 'JessicaJones', 'Jessica Jones', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'JessicaJones@mydomain.com', 'es'),
(111, 'Missing', 'Missing', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'es'),
(112, 'Trash', 'Trash', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'es');
INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`)
VALUES
@ -643,7 +643,8 @@ INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `life`,`workerFk
(2, 'ITG', 'Anthurium', 1, 31, 5, 0),
(3, 'WPN', 'Paniculata', 2, 31, 5, 0),
(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`)
VALUES
@ -1549,7 +1550,7 @@ INSERT INTO `postgresql`.`profile`(`profile_id`, `person_id`, `profile_type_id`)
FROM `postgresql`.`person` `p`;
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`;
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`)
VALUES
(106, 1, DATE_ADD(CURDATE(), INTERVAL 10 DAY)),
(106, 1, DATE_ADD(CURDATE(), INTERVAL 11 DAY)),
(106, 1, DATE_ADD(CURDATE(), INTERVAL 12 DAY)),
(106, 1, DATE_ADD(CURDATE(), INTERVAL 20 DAY)),
(106, 2, DATE_ADD(CURDATE(), INTERVAL -10 DAY)),
(106, 1, DATE_ADD(CURDATE(), INTERVAL -12 DAY)),
(106, 2, DATE_ADD(CURDATE(), INTERVAL -20 DAY)),
(107, 1, DATE_ADD(CURDATE(), INTERVAL 15 DAY)),
(107, 1, DATE_ADD(CURDATE(), INTERVAL 16 DAY)),
(107, 1, DATE_ADD(CURDATE(), INTERVAL 20 DAY)),
(107, 1, DATE_ADD(CURDATE(), INTERVAL 30 DAY)),
(107, 2, DATE_ADD(CURDATE(), INTERVAL -10 DAY)),
(107, 1, DATE_ADD(CURDATE(), INTERVAL -12 DAY)),
(107, 2, DATE_ADD(CURDATE(), INTERVAL -20 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, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -11 DAY), DATE_ADD(CURDATE(), INTERVAL 11 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, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -20 DAY), DATE_ADD(CURDATE(), INTERVAL 20 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, IF(MONTH(CURDATE()) >= 1 AND DAY(CURDATE()) > 20, DATE_ADD(CURDATE(), INTERVAL -14 DAY), DATE_ADD(CURDATE(), INTERVAL 14 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, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -10 DAY), DATE_ADD(CURDATE(), INTERVAL 10 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, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -12 DAY), DATE_ADD(CURDATE(), INTERVAL 12 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, IF(MONTH(CURDATE()) >= 1 AND DAY(CURDATE()) > 20, DATE_ADD(CURDATE(), INTERVAL -13 DAY), DATE_ADD(CURDATE(), INTERVAL 13 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, 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`)
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()),
(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()),
(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`)
VALUES
@ -1998,3 +2000,16 @@ INSERT INTO `vn`.`workerTimeControlParams` (`id`, `dayBreak`, `weekBreak`, `week
(1, 43200, 129600, 734400, 43200, 50400);
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:
front:
image: registry.verdnatura.es/salix-front:${TAG}
restart: unless-stopped
image: registry.verdnatura.es/salix-front:${BRANCH_NAME:?}
build:
context: .
dockerfile: front/Dockerfile
ports:
- ${PORT_FRONT}:80
links:
- back
- ${FRONT_PORT:?}:80
deploy:
replicas: 3
back:
image: registry.verdnatura.es/salix-back:${TAG}
restart: unless-stopped
image: registry.verdnatura.es/salix-back:${BRANCH_NAME:?}
build: .
ports:
- ${PORT_BACK}:3000
- ${BACK_PORT:?}:3000
environment:
- NODE_ENV
configs:
- source: datasources
target: /etc/salix/datasources.json
- source: datasources_local
target: /etc/salix/datasources.local.json
- source: print
target: /etc/salix/print.json
- source: print_local
target: /etc/salix/print.local.json
volumes:
- /mnt/storage/containers/salix:/etc/salix
- /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: {
balanceButton: 'vn-left-menu a[ui-sref="client.card.balance.index"]',
companyAutocomplete: 'vn-client-balance-index vn-autocomplete[ng-model="$ctrl.companyFk"]',
newPaymentButton: 'vn-float-button',
companyAutocomplete: 'vn-client-balance-index vn-autocomplete[ng-model="$ctrl.companyId"]',
newPaymentButton: `vn-float-button`,
newPaymentBank: '.vn-dialog.shown vn-autocomplete[ng-model="$ctrl.receipt.bankFk"]',
newPaymentAmountInput: '.vn-dialog.shown [ng-model="$ctrl.receipt.amountPaid"]',
saveButton: '.vn-dialog.shown vn-button[label="Save"]',
@ -603,8 +603,8 @@ export default {
orderByAutocomplete: 'vn-autocomplete[label="Order by"]',
plantRealmButton: 'vn-order-catalog > vn-side-menu vn-icon[icon="icon-plant"]',
typeAutocomplete: 'vn-autocomplete[data="$ctrl.itemTypes"]',
itemIdInput: 'vn-catalog-filter [ng-model="$ctrl.itemFk"]',
itemTagValueInput: 'vn-catalog-filter [ng-model="$ctrl.value"]',
itemIdInput: 'vn-catalog-filter vn-textfield[ng-model="$ctrl.itemId"]',
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',
tagAutocomplete: 'vn-order-catalog-search-panel vn-autocomplete[ng-model="filter.tagFk"]',
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');
expect(worker).toEqual('adminBossNick');
expect(worker).toEqual('adminBoss - adminBossNick');
});
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] {
display: block;
&.dropping {
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: [],
filter: {},
order: {},
@ -31,7 +32,28 @@ module.exports = {
}
};
},
refresh: () => {},
addFilter: () => {},
applyFilter: () => {},
refresh: () => {
return {
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 {
display: block;
box-sizing: border-box;
height: inherit;
height: 100%;
&.ng-enter {
animation-name: nothing, slideIn;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,8 +8,14 @@ class Controller extends Section {
}
onSubmit() {
this.$.data = null;
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"
translate-attr="{value: 'Cancel'}">
</input>
<button
<input
type="button"
ng-if="!$ctrl.isNew"
response="delete"
translate>
Delete
</button>
translate-attr="{value: 'Delete'}">
</input>
<button response="accept">
<span ng-if="$ctrl.isNew" translate>Add</span>
<span ng-if="!$ctrl.isNew" translate>Save</span>

View File

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

View File

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

View File

@ -17,13 +17,14 @@ describe('claim', () => {
show: () => {}
};
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', 'Claims/ClaimBeginnings').respond({});
$httpBackend.whenGET('Claims/ClaimBeginnings').respond({});
$httpBackend.whenGET(`Tickets/1/isEditable`).respond(true);
$state = _$state_;
aclService = {hasAny: () => true};
controller = $componentController('vnClaimDetail', {$state, aclService, $scope});
controller.claim = {ticketFk: 1};
controller.salesToClaim = [{saleFk: 1}, {saleFk: 2}];
controller.salesClaimed = [{id: 1, sale: {}}];
controller.claim = {ticketFk: 1};
controller.$.model = crudModel;
controller.$.addSales = {
hide: () => {},
@ -36,7 +37,6 @@ describe('claim', () => {
describe('openAddSalesDialog()', () => {
it('should call getClaimableFromTicket and $.addSales.show', () => {
controller.$ = {addSales: {show: () => {}}};
spyOn(controller, 'getClaimableFromTicket');
spyOn(controller.$.addSales, 'show');
controller.openAddSalesDialog();
@ -146,5 +146,14 @@ describe('claim', () => {
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,14 +5,11 @@
data="$ctrl.photos">
</vn-crud-model>
<section class="drop-zone" vn-droppable="$ctrl.onDrop($event)">
<section><vn-icon icon="add_circle"></vn-icon></section>
<section translate>Drag & Drop files here...</section>
<vn-horizontal class="photo-list drop-zone" vn-droppable="$ctrl.onDrop($event)">
<section class="empty-rows" ng-if="!$ctrl.photos.length">
<section><vn-icon icon="image"></vn-icon></section>
<section translate>Drag & Drop photos here...</section>
</section>
<vn-data-viewer
model="model"
class="vn-w-xl">
<vn-horizontal class="photo-list">
<section class="photo" ng-repeat="photo in $ctrl.photos">
<section class="image vn-shadow" on-error-src
ng-style="{'background': 'url(/api/dms/' + photo.dmsFk + '/downloadFile?access_token=' + $ctrl.accessToken + ')'}"
@ -29,7 +26,7 @@
</section>
</section>
</vn-horizontal>
</vn-data-viewer>
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
@ -39,3 +36,9 @@
question="Are you sure you want to continue?"
on-response="$ctrl.deleteDms($response)">
</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() {
const query = `claims/${this.claim.id}/uploadFile`;

View File

@ -2,13 +2,16 @@
vn-claim-dms-index {
.drop-zone {
border: 2px dashed $color-font-secondary;
color: $color-font-secondary;
box-sizing: border-box;
padding: 2em $spacing-md;
border-radius: 0.5em;
text-align: center;
font-size: 1.4em;
min-height: 100%;
.empty-rows {
padding: 5em $spacing-md;
font-size: 1.4em
}
vn-icon {
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?
Drag & Drop files here...: Arrastra y suelta archivos aquí...
Drag & Drop photos here...: Arrastra y suelta fotos aquí...
Photo deleted: Foto eliminada
Photo uploaded!: Foto subida!
Select photo: Seleccionar foto

View File

@ -1,12 +1,10 @@
@import "./variables";
vn-claim-summary {
.photo {
height: 15.5em;
section.photo {
height: 15.5em
}
.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');
expect(result.length).toEqual(13);
expect(result.length).toEqual(14);
expect(isSalesPerson).toBeTruthy();
});

View File

@ -6,7 +6,7 @@ describe('Client listWorkers', () => {
.then(result => {
let amountOfEmployees = Object.keys(result).length;
expect(amountOfEmployees).toEqual(48);
expect(amountOfEmployees).toEqual(49);
done();
})
.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',
http: {source: 'query'}
}, {
arg: 'params',
type: 'Object',
description: 'clientFk',
http: {source: 'query'}
arg: 'clientId',
type: 'Number',
description: 'The client id',
required: true,
}, {
arg: 'companyId',
type: 'Number',
description: 'The company id',
required: true,
}
],
returns: {
@ -27,7 +32,7 @@ module.exports = Self => {
}
});
Self.filter = async(filter, params) => {
Self.filter = async(filter, clientId, companyId) => {
let stmt = new ParameterizedSQL(
`SELECT * FROM (
SELECT
@ -72,10 +77,10 @@ module.exports = Self => {
ORDER BY payed DESC, created DESC
) t
ORDER BY payed DESC, created DESC`, [
params.clientFk,
params.companyFk,
params.clientFk,
params.companyFk,
clientId,
companyId,
clientId,
companyId,
]
);

View File

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

View File

@ -2,10 +2,12 @@ import ngModule from '../../module';
import './style.scss';
class Controller {
constructor($http, $scope, $stateParams) {
constructor($http, $scope, $stateParams, $translate, vnApp) {
this.$http = $http;
this.$scope = $scope;
this.$stateParams = $stateParams;
this.$translate = $translate;
this.vnApp = vnApp;
this.filter = {
fields: [
'id',
@ -51,6 +53,7 @@ class Controller {
if (res.data) {
this.client.defaultAddressFk = res.data.defaultAddressFk;
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', {
template: require('./index.html'),

View File

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

View File

@ -16,75 +16,53 @@ class Controller {
scope: {
fields: ['code'],
},
},
}
};
}
get companyId() {
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: $stateParams.id,
companyFk: this.companyFk
},
};
this.params = {
params: {
clientFk: this.$stateParams.id,
companyFk: this.companyFk,
},
};
companyFk: this.companyId
}
})).then(() => this.getBalances());
}
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() {
return this._balances;
}
getCurrentBalance() {
const selectedCompany = this.$.company.selection;
const currentBalance = this.clientRisks.find(balance => {
return balance.companyFk === selectedCompany.id;
const clientRisks = this.$.riskModel.data;
const selectedCompany = this.companyId;
const currentBalance = clientRisks.find(balance => {
return balance.companyFk === selectedCompany;
});
return currentBalance.amount;
}
getBalances() {
this.balances.forEach((balance, index) => {
const balances = this.$.model.data;
balances.forEach((balance, index) => {
if (index === 0)
balance.balance = this.getCurrentBalance();
if (index > 0) {
let previousBalance = this.balances[index - 1];
let previousBalance = balances[index - 1];
balance.balance = previousBalance.balance - (previousBalance.debit - previousBalance.credit);
}
});
@ -92,10 +70,8 @@ class Controller {
openCreateDialog() {
this.$.balanceCreateDialog.companyFk = this.companyFk;
this.$.balanceCreateDialog.onResponse = () => {
this.refresh();
};
this.$.balanceCreateDialog.companyFk = this.companyId;
this.$.balanceCreateDialog.onResponse = () => this.getData();
this.$.balanceCreateDialog.show();
}

View File

@ -3,41 +3,98 @@ import './index';
describe('Client', () => {
describe('Component vnClientBalanceIndex', () => {
let $componentController;
let $scope;
let $httpBackend;
let $httpParamSerializer;
let controller;
beforeEach(ngModule('client'));
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
beforeEach(angular.mock.inject((_$componentController_, $rootScope) => {
$componentController = _$componentController_;
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
$scope = $rootScope.$new();
let $scope = $rootScope.$new();
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', () => {
it('should calculate the balance for each line from the oldest date to the newest', () => {
controller.getCurrentBalance = jasmine.createSpy(controller, 'getCurrentBalance').and.returnValue(1000);
let balances = [
{credit: -100, debit: 0},
{credit: 0, debit: 300},
{credit: 100, debit: 0},
{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();
describe('getData()', () => {
it('should apply the filters on he models and get the client balance', () => {
controller._companyId = 442;
controller.$stateParams.id = 101;
spyOn(controller, 'getBalances');
spyOn(controller.$.model, 'applyFilter').and.returnValue(Promise.resolve());
spyOn(controller.$.riskModel, 'applyFilter').and.returnValue(Promise.resolve());
expect(controller.balances[0].balance).toEqual(1000);
expect(controller.balances[1].balance).toEqual(900);
expect(controller.balances[2].balance).toEqual(600);
expect(controller.balances[3].balance).toEqual(700);
controller.getData().then(() => {
expect(controller.$.model.applyFilter).toHaveBeenCalledWith(null, {'clientId': 101, 'companyId': 442});
expect(controller.$.riskModel.applyFilter).toHaveBeenCalledWith({'where': {'clientFk': 101, 'companyFk': 442}});
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()', () => {
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.isCustomer();
$httpBackend.when('GET', `Clients/${controller.client.id}/hasCustomerRole`).respond('ok');
$httpBackend.expectGET(`Clients/${controller.client.id}/hasCustomerRole`);
$httpBackend.expectGET(`Clients/${controller.client.id}/hasCustomerRole`).respond({isCustomer: true});
controller.isCustomer();
$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');
describe('invoiceOut book()', () => {
const invoiceOutId = 1;
const invoiceOutId = 5;
let bookedDate;
let OriginalInvoiceOut;
let updatedInvoiceOut;

View File

@ -53,7 +53,7 @@ class Controller {
}
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');
}

View File

@ -27,7 +27,7 @@ export default class Controller {
}
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');
event.preventDefault();
event.stopImmediatePropagation();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,6 +17,9 @@
value-field="id"
where="{role: 'employee'}"
label="Worker">
<tpl-item>
<div>{{name}} - {{nickname}}</div>
</tpl-item>
</vn-autocomplete>
<vn-autocomplete
vn-one

View File

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

View File

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

View File

@ -58,4 +58,18 @@ describe('component vnTravelSummary', () => {
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
Supplier: Proveedor
Pallet: Pallet
Freight cost: Coste porte
Package cost: Coste embalaje
Freight: Porte
Package: Embalaje
Half box: Media caja

View File

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

View File

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

View File

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

View File

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