1781-zoneHoliday #994

Merged
joan merged 49 commits from 1781-zoneHoliday into dev 2022-08-03 06:41:31 +00:00
20 changed files with 727 additions and 100 deletions

View File

@ -0,0 +1,4 @@
INSERT INTO `salix`.`ACL`(`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('ZoneExclusionGeo', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
('ZoneExclusionGeo', '*', 'WRITE', 'ALLOW', 'ROLE', 'deliveryBoss');

View File

@ -2582,6 +2582,15 @@ INSERT INTO `vn`.`machineWorker` (`workerFk`, `machineFk`, `inTimed`, `outTimed`
(1106, 2, util.VN_CURDATE(), NULL), (1106, 2, util.VN_CURDATE(), NULL),
(1106, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL +1 DAY)); (1106, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL +1 DAY));
INSERT INTO `vn`.`zoneExclusion` (`id`, `zoneFk`, `dated`, `created`, `userFk`)
VALUES
(1, 1, DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=7, 7, 14) - DAYOFWEEK(CURDATE())) DAY), CURDATE(), 100),
(2, 1, DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=8, 8, 15) - DAYOFWEEK(CURDATE())) DAY), CURDATE(), 100);
INSERT INTO `vn`.`zoneExclusionGeo` (`zoneExclusionFk`, `geoFk`)
VALUES
(2, 1);
INSERT INTO `vn`.`mdbBranch` (`name`) INSERT INTO `vn`.`mdbBranch` (`name`)
VALUES VALUES
('test'), ('test'),

View File

@ -0,0 +1,64 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
vicent marked this conversation as resolved
Review

from

from
Self.remoteMethod('exclusionGeo', {
description: 'Exclude a geo from a zone',
accepts: [
{
arg: 'zoneFk',
type: 'number',
description: 'The zone id'
},
{
arg: 'date',
type: 'date',
description: 'The date to exclude'
},
{
arg: 'geoIds',
type: ['number'],
vicent marked this conversation as resolved Outdated

geoIds a mi entender son Identificadores de geo por ejemplo: [1, 2, 3, 4]

pero poner un nombre que reprensentaria una coleccion de ids y darle de tipo "any" no es buena practica resultando en un manejo de datos confuso.

refactoriza los argumentos para que reciba lo que necesita que aparentemente son ids a secas en una array.

geoIds a mi entender son Identificadores de geo por ejemplo: [1, 2, 3, 4] pero poner un nombre que reprensentaria una coleccion de ids y darle de tipo "any" no es buena practica resultando en un manejo de datos confuso. refactoriza los argumentos para que reciba lo que necesita que aparentemente son ids a secas en una array.
description: 'The geos id'
}
],
returns: {
type: 'object',
root: true
},
http: {
path: `/exclusionGeo`,
verb: 'POST'
}
vicent marked this conversation as resolved
Review

where are my tests and my transaction?

where are my tests and my transaction?
});
Self.exclusionGeo = async(zoneFk, date, geoIds, options) => {
const models = Self.app.models;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!geoIds[0]) throw new UserError(`You must select a location`);
const newZoneExclusion = await models.ZoneExclusion.create({
zoneFk: zoneFk,
dated: date
}, myOptions);
const promises = [];
for (const geoId of geoIds) {
const newZoneExclusionGeo = await models.ZoneExclusionGeo.create({
zoneExclusionFk: newZoneExclusion.id,
geoFk: geoId
}, myOptions);
promises.push(newZoneExclusionGeo);
}
const newZoneExclusionGeos = await Promise.all(promises);
return newZoneExclusionGeos;
};
};

View File

@ -56,12 +56,23 @@ module.exports = Self => {
[zoneFk, started, ended, started, ended, started, ended, started, ended], myOptions); [zoneFk, started, ended, started, ended, started, ended, started, ended], myOptions);
query = ` query = `
SELECT * SELECT e.*
FROM vn.zoneExclusion FROM vn.zoneExclusion e
WHERE zoneFk = ? LEFT JOIN vn.zoneExclusionGeo eg ON eg.zoneExclusionFk = e.id
AND dated BETWEEN ? AND ?;`; WHERE e.zoneFk = ?
AND e.dated BETWEEN ? AND ?
AND eg.zoneExclusionFk IS NULL;`;
const exclusions = await Self.rawSql(query, [zoneFk, started, ended], myOptions); const exclusions = await Self.rawSql(query, [zoneFk, started, ended], myOptions);
return {events, exclusions}; query = `
SELECT eg.*, e.zoneFk, e.dated, e.created, e.userFk
FROM vn.zoneExclusion e
LEFT JOIN vn.zoneExclusionGeo eg ON eg.zoneExclusionFk = e.id
WHERE e.zoneFk = ?
AND e.dated BETWEEN ? AND ?
AND eg.zoneExclusionFk IS NOT NULL;`;
const geoExclusions = await Self.rawSql(query, [zoneFk, started, ended], myOptions);
return {events, exclusions, geoExclusions};
}; };
}; };

View File

@ -0,0 +1,41 @@
const models = require('vn-loopback/server/server').models;
describe('zone exclusionGeo()', () => {
const zoneId = 1;
const today = new Date();
it(`should show an error when location isn't selected`, async() => {
const tx = await models.Zone.beginTransaction({});
try {
const options = {transaction: tx};
const geoIds = [];
await models.Zone.exclusionGeo(zoneId, today, geoIds, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error.message).toContain(`You must select a location`);
});
it('should create two exclusion by geo', async() => {
const tx = await models.Zone.beginTransaction({});
try {
const options = {transaction: tx};
const geoIds = [1, 2];
vicent marked this conversation as resolved Outdated

const geoIds = [1, 2]

const geoIds = [1, 2]
const result = await models.Zone.exclusionGeo(zoneId, today, geoIds, options);
expect(result.length).toEqual(2);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,40 @@
const models = require('vn-loopback/server/server').models;
describe('zone updateExclusionGeo()', () => {
it(`should show an error when location isn't selected`, async() => {
const tx = await models.Zone.beginTransaction({});
try {
const options = {transaction: tx};
const zoneId = 1;
const geoIds = [];
await models.Zone.updateExclusionGeo(zoneId, geoIds, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error.message).toContain(`You must select a location`);
});
it('should delete all exclusion and then create two exclusion by geo for a zone', async() => {
const tx = await models.Zone.beginTransaction({});
try {
const options = {transaction: tx};
const zoneId = 2;
const geoIds = [1, 2];
vicent marked this conversation as resolved Outdated

mas de lo mismo

mas de lo mismo
const result = await models.Zone.updateExclusionGeo(zoneId, geoIds, options);
expect(result.length).toEqual(2);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,55 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('updateExclusionGeo', {
description: 'Update the geos excluded from a zone',
accepts: [
{
arg: 'zoneExclusionFk',
type: 'number',
description: 'The zoneExclusion id'
},
{
arg: 'geoIds',
type: ['number'],
vicent marked this conversation as resolved Outdated

mala practica

mala practica
description: 'The geos id'
}
],
returns: {
type: 'object',
root: true
},
http: {
path: `/updateExclusionGeo`,
verb: 'POST'
}
});
Self.updateExclusionGeo = async(zoneExclusionFk, geoIds, options) => {
const models = Self.app.models;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!geoIds[0]) throw new UserError(`You must select a location`);
await models.ZoneExclusionGeo.destroyAll({
zoneExclusionFk: zoneExclusionFk
}, myOptions);
const promises = [];
for (const geoId of geoIds) {
const params = {
zoneExclusionFk: zoneExclusionFk,
geoFk: geoId
};
const deletedZoneExclusionGeos = models.ZoneExclusionGeo.create(params, myOptions);
promises.push(deletedZoneExclusionGeos);
}
return Promise.all(promises);
};
};

View File

@ -23,6 +23,9 @@
"ZoneExclusion": { "ZoneExclusion": {
"dataSource": "vn" "dataSource": "vn"
}, },
"ZoneExclusionGeo": {
"dataSource": "vn"
},
"ZoneGeo": { "ZoneGeo": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -0,0 +1,21 @@
{
"name": "ZoneExclusionGeo",
"base": "VnModel",
"options": {
"mysql": {
"table": "zoneExclusionGeo"
}
},
"properties": {
"id": {
"id": true,
"type": "number"
},
"zoneExclusionFk": {
"type": "number"
},
"geoFk": {
"type": "number"
}
}
}

View File

@ -8,6 +8,8 @@ module.exports = Self => {
require('../methods/zone/deleteZone')(Self); require('../methods/zone/deleteZone')(Self);
require('../methods/zone/includingExpired')(Self); require('../methods/zone/includingExpired')(Self);
require('../methods/zone/getZoneClosing')(Self); require('../methods/zone/getZoneClosing')(Self);
require('../methods/zone/exclusionGeo')(Self);
require('../methods/zone/updateExclusionGeo')(Self);
Self.validatesPresenceOf('agencyModeFk', { Self.validatesPresenceOf('agencyModeFk', {
message: `Agency cannot be blank` message: `Agency cannot be blank`

View File

@ -75,6 +75,17 @@ class Controller extends Component {
} }
} }
this.geoExclusions = {};
let geoExclusions = value.geoExclusions;
if (geoExclusions) {
vicent marked this conversation as resolved
Review

is this testes?

is this testes?
for (let geoExclusion of geoExclusions) {
let stamp = toStamp(geoExclusion.dated);
if (!this.geoExclusions[stamp]) this.geoExclusions[stamp] = [];
this.geoExclusions[stamp].push(geoExclusion);
}
}
let events = value.events; let events = value.events;
if (events) { if (events) {
@ -135,11 +146,13 @@ class Controller extends Component {
onSelection($event, $days, $type, $weekday) { onSelection($event, $days, $type, $weekday) {
let $events = []; let $events = [];
let $exclusions = []; let $exclusions = [];
let $geoExclusions = [];
for (let day of $days) { for (let day of $days) {
let stamp = day.getTime(); let stamp = day.getTime();
$events = $events.concat(this.days[stamp] || []); $events = $events.concat(this.days[stamp] || []);
$exclusions = $exclusions.concat(this.exclusions[stamp] || []); $exclusions = $exclusions.concat(this.exclusions[stamp] || []);
$geoExclusions = $geoExclusions.concat(this.geoExclusions[stamp] || []);
} }
this.emit('selection', { this.emit('selection', {
@ -148,19 +161,23 @@ class Controller extends Component {
$type, $type,
$weekday, $weekday,
$events, $events,
$exclusions $exclusions,
$geoExclusions
}); });
} }
hasEvents(day) { hasEvents(day) {
let stamp = day.getTime(); let stamp = day.getTime();
return this.days[stamp] || this.exclusions[stamp]; return this.days[stamp] || this.exclusions[stamp] || this.geoExclusions[stamp];
} }
getClass(day) { getClass(day) {
let stamp = day.getTime(); let stamp = day.getTime();
return this.exclusions[stamp] && !this.days[stamp] if (this.geoExclusions[stamp])
? 'excluded' : ''; return 'geoExcluded';
else if (this.exclusions[stamp])
return 'excluded';
else return '';
} }
} }
Controller.$inject = ['$element', '$scope', 'vnWeekDays']; Controller.$inject = ['$element', '$scope', 'vnWeekDays'];

View File

@ -15,6 +15,7 @@ describe('component vnZoneCalendar', () => {
controller.zone = {id: 1}; controller.zone = {id: 1};
controller.days = []; controller.days = [];
controller.exclusions = []; controller.exclusions = [];
controller.geoExclusions = [];
})); }));
describe('date() setter', () => { describe('date() setter', () => {
@ -57,7 +58,7 @@ describe('component vnZoneCalendar', () => {
}); });
describe('data() setter', () => { describe('data() setter', () => {
it('should set the events and exclusions and then call the refreshEvents() method', () => { it('should set the events, exclusions and geoExclusions and then call the refreshEvents() method', () => {
jest.spyOn(controller, 'refreshEvents').mockReturnThis(); jest.spyOn(controller, 'refreshEvents').mockReturnThis();
controller.data = { controller.data = {
@ -66,13 +67,17 @@ describe('component vnZoneCalendar', () => {
}], }],
events: [{ events: [{
dated: new Date() dated: new Date()
}] }],
geoExclusions: [{
dated: new Date()
}],
}; };
expect(controller.refreshEvents).toHaveBeenCalledWith(); expect(controller.refreshEvents).toHaveBeenCalledWith();
expect(controller.events).toBeDefined(); expect(controller.events).toBeDefined();
expect(controller.events.length).toEqual(1); expect(controller.events.length).toEqual(1);
expect(controller.exclusions).toBeDefined(); expect(controller.exclusions).toBeDefined();
expect(controller.geoExclusions).toBeDefined();
expect(Object.keys(controller.exclusions).length).toEqual(1); expect(Object.keys(controller.exclusions).length).toEqual(1);
}); });
}); });
@ -122,7 +127,8 @@ describe('component vnZoneCalendar', () => {
$events: [], $events: [],
$exclusions: [], $exclusions: [],
$type: 'day', $type: 'day',
$weekday: 1 $weekday: 1,
$geoExclusions: [],
} }
); );
}); });
@ -151,5 +157,16 @@ describe('component vnZoneCalendar', () => {
expect(result).toEqual('excluded'); expect(result).toEqual('excluded');
}); });
it('should return the className "geoExcluded" for a date with geo excluded', () => {
const dated = new Date();
controller.geoExclusions = [];
controller.geoExclusions[dated.getTime()] = true;
const result = controller.getClass(dated);
expect(result).toEqual('geoExcluded');
});
}); });
}); });

View File

@ -33,6 +33,9 @@ vn-zone-calendar {
&.excluded .day-number { &.excluded .day-number {
background-color: $color-alert; background-color: $color-alert;
} }
&.geoExcluded .day-number {
background-color: $color-main;
}
} }
} }
} }

View File

@ -2,7 +2,7 @@
id="calendar" id="calendar"
vn-id="calendar" vn-id="calendar"
data="data" data="data"
on-selection="$ctrl.onSelection($days, $type, $weekday, $events, $exclusions)" on-selection="$ctrl.onSelection($days, $type, $weekday, $events, $exclusions, $geoExclusions)"
on-step="$ctrl.refresh()" on-step="$ctrl.refresh()"
class="vn-w-md"> class="vn-w-md">
</vn-zone-calendar> </vn-zone-calendar>
@ -98,7 +98,7 @@
fixed-bottom-right> fixed-bottom-right>
</vn-float-button> </vn-float-button>
<vn-dialog <vn-dialog
vn-id="dialog" vn-id="includeDialog"
on-response="$ctrl.onIncludeResponse($response)" on-response="$ctrl.onIncludeResponse($response)"
message="{{$ctrl.isNew ? 'Add event' : 'Edit event'}}"> message="{{$ctrl.isNew ? 'Add event' : 'Edit event'}}">
<tpl-body> <tpl-body>
@ -198,3 +198,80 @@
message="This item will be deleted" message="This item will be deleted"
question="Are you sure you want to continue?"> question="Are you sure you want to continue?">
</vn-confirm> </vn-confirm>
<vn-dialog
vn-id="excludeDialog"
on-response="$ctrl.onExcludeResponse($response)"
message="{{$ctrl.isNew ? 'Exclusion' : 'Edit exclusion'}}"
on-open="$ctrl.onSearch($params)"
on-close="$ctrl.resetExclusions()">
<tpl-body>
<vn-date-picker
label="Day"
ng-model="$ctrl.excludeSelected.dated">
</vn-date-picker>
<vn-vertical class="width">
<vn-vertical class="vn-pb-md">
<vn-radio
ng-model="$ctrl.excludeSelected.type"
label="All"
on-change="$ctrl.test()"
val="all">
</vn-radio>
<vn-radio
ng-model="$ctrl.excludeSelected.type"
label="Specific locations"
on-change="$ctrl.onSearch($params)"
val="specificLocations">
</vn-radio>
</vn-vertical>
<vn-crud-model
vn-id="model"
url="Zones/{{$ctrl.$params.id}}/getLeaves"
filter="$ctrl.filter">
</vn-crud-model>
<div ng-if="$ctrl.excludeSelected.type == 'specificLocations'">
<vn-textfield
label="Search"
ng-keydown="$ctrl.onKeyDown($event)"
ng-model="$ctrl.excludeSearch">
<prepend>
<vn-icon icon="search"></vn-icon>
</prepend>
</vn-textfield>
<div class="treeview">
<vn-treeview
vn-id="treeview"
root-label="Locations where it is not distributed"
fetch-func="$ctrl.onFetch($item)"
sort-func="$ctrl.onSort($a, $b)">
<vn-check
ng-model="item.checked"
ng-click="$event.preventDefault()"
on-change="$ctrl.onItemCheck(item.id, value)"
label="{{::item.name}}">
</vn-check>
</vn-treeview>
</div>
</div>
</vn-vertical>
</tpl-body>
<tpl-buttons>
<input
type="button"
response="cancel"
translate-attr="{value: 'Cancel'}"
tabindex="0">
</input>
<input
type="button"
ng-if="!$ctrl.isNew"
response="delete"
translate-attr="{value: 'Delete'}"
tabindex="0">
</input>
<button response="accept">
<span ng-if="$ctrl.isNew" translate>Add</span>
<span ng-if="!$ctrl.isNew" translate>Save</span>
</button>
</tpl-buttons>
</vn-dialog>

View File

@ -1,5 +1,6 @@
import ngModule from '../module'; import ngModule from '../module';
import Section from 'salix/components/section'; import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section { class Controller extends Section {
constructor($element, $, vnWeekDays) { constructor($element, $, vnWeekDays) {
@ -20,6 +21,16 @@ class Controller extends Section {
return `Zones/${this.$params.id}/exclusions`; return `Zones/${this.$params.id}/exclusions`;
} }
get checked() {
const geos = this.$.model.data || [];
const checkedLines = [];
for (let geo of geos) {
vicent marked this conversation as resolved Outdated

no need for a getter if you don't use a setter in this case.

no need for a getter if you don't use a setter in this case.
if (geo.checked)
checkedLines.push(geo);
}
return checkedLines;
}
refresh() { refresh() {
this.$.data = null; this.$.data = null;
this.$.$applyAsync(() => { this.$.$applyAsync(() => {
@ -48,33 +59,56 @@ class Controller extends Section {
: this.$t('Everyday'); : this.$t('Everyday');
} }
onSelection(days, type, weekday, events, exclusions) { onSelection(days, type, weekday, events, exclusions, exclusionGeos) {
if (this.editMode == 'include') { if (this.editMode == 'include') {
if (events.length) if (events.length)
this.edit(events[0]); return this.editInclusion(events[0]);
else return this.createInclusion(type, days, weekday);
this.create(type, days, weekday); } else if (this.editMode == 'exclude') {
} else { if (exclusions.length || exclusionGeos.length)
if (exclusions.length) return this.editExclusion(exclusions[0] || {}, exclusionGeos);
this.exclusionDelete(exclusions); return this.createExclusion(days);
else
this.exclusionCreate(days);
} }
} }
editExclusion(exclusion, exclusionGeos) {
this.isNew = false;
this.excludeSelected = angular.copy(exclusion);
this.excludeSelected.type = exclusionGeos.length ?
'specificLocations' : 'all';
this.exclusionGeos = new Set();
if (exclusionGeos.length) {
this.excludeSelected.id = exclusionGeos[0].zoneExclusionFk;
exclusionGeos.forEach(x => this.exclusionGeos.add(x.geoFk));
}
this.$.excludeDialog.show();
}
createExclusion(days) {
this.isNew = true;
this.excludeSelected = {
type: 'all',
dated: days[0]
};
this.exclusionGeos = new Set();
this.$.excludeDialog.show();
}
onEditClick(row, event) { onEditClick(row, event) {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
this.edit(row); this.editInclusion(row);
} }
edit(row) { editInclusion(row) {
this.isNew = false; this.isNew = false;
this.selected = angular.copy(row); this.selected = angular.copy(row);
this.selected.wdays = this.vnWeekDays.fromSet(row.weekDays); this.selected.wdays = this.vnWeekDays.fromSet(row.weekDays);
this.$.dialog.show(); this.$.includeDialog.show();
} }
create(type, days, weekday) { createInclusion(type, days, weekday) {
this.isNew = true; this.isNew = true;
if (type == 'weekday') { if (type == 'weekday') {
@ -92,7 +126,7 @@ class Controller extends Section {
}; };
} }
this.$.dialog.show(); this.$.includeDialog.show();
} }
onIncludeResponse(response) { onIncludeResponse(response) {
@ -132,6 +166,19 @@ class Controller extends Section {
} }
} }
onExcludeResponse(response) {
const type = this.excludeSelected.type;
switch (response) {
case 'accept': {
if (type == 'all')
return this.exclusionCreate();
return this.exclusionGeoCreate();
}
case 'delete':
return this.exclusionDelete(this.excludeSelected);
}
}
onDeleteClick(id, event) { onDeleteClick(id, event) {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
event.preventDefault(); event.preventDefault();
@ -149,31 +196,121 @@ class Controller extends Section {
.then(() => this.refresh()); .then(() => this.refresh());
} }
exclusionCreate(days) { exclusionCreate() {
let exclusions = days.map(dated => { const excludeSelected = this.excludeSelected;
return {dated}; const dated = excludeSelected.dated;
let req;
if (this.isNew)
req = this.$http.post(this.exclusionsPath, [{dated}]);
if (!this.isNew)
req = this.$http.put(`${this.exclusionsPath}/${excludeSelected.id}`, {dated});
return req.then(() => {
this.refresh();
}); });
}
this.$http.post(this.exclusionsPath, exclusions) exclusionGeoCreate() {
const excludeSelected = this.excludeSelected;
let req;
const geoIds = [];
this.exclusionGeos.forEach(id => geoIds.push(id));
if (this.isNew) {
const params = {
zoneFk: parseInt(this.$params.id),
date: excludeSelected.dated,
geoIds
};
req = this.$http.post(`Zones/exclusionGeo`, params);
} else {
const params = {
zoneExclusionFk: this.excludeSelected.id,
geoIds
};
req = this.$http.post(`Zones/updateExclusionGeo`, params);
}
return req.then(() => this.refresh());
}
exclusionDelete(exclusion) {
const path = `${this.exclusionsPath}/${exclusion.id}`;
return this.$http.delete(path)
.then(() => this.refresh()); .then(() => this.refresh());
} }
exclusionDelete(exclusions) { set excludeSearch(value) {
let reqs = []; this._excludeSearch = value;
if (!value) this.onSearch();
for (let exclusion of exclusions) {
if (!exclusion.id) continue;
let path = `${this.exclusionsPath}/${exclusion.id}`;
reqs.push(this.$http.delete(path));
} }
this.$q.all(reqs) get excludeSearch() {
.then(() => this.refresh()); return this._excludeSearch;
}
onKeyDown(event) {
if (event.key == 'Enter') {
event.preventDefault();
this.onSearch();
}
}
onSearch() {
const params = {search: this._excludeSearch};
if (this.excludeSelected.type == 'specificLocations') {
this.$.model.applyFilter({}, params).then(() => {
const data = this.$.model.data;
this.getChecked(data);
this.$.treeview.data = data;
});
}
}
onFetch(item) {
const params = item ? {parentId: item.id} : null;
return this.$.model.applyFilter({}, params).then(() => {
const data = this.$.model.data;
this.getChecked(data);
return data;
});
}
onSort(a, b) {
if (b.selected !== a.selected) {
if (a.selected == null)
return 1;
if (b.selected == null)
return -1;
return b.selected - a.selected;
}
return a.name.localeCompare(b.name);
}
getChecked(data) {
for (let geo of data) {
geo.checked = this.exclusionGeos.has(geo.id);
if (geo.childs) this.getChecked(geo.childs);
}
}
onItemCheck(geoId, checked) {
if (checked)
this.exclusionGeos.add(geoId);
else
this.exclusionGeos.delete(geoId);
} }
} }
Controller.$inject = ['$element', '$scope', 'vnWeekDays']; Controller.$inject = ['$element', '$scope', 'vnWeekDays'];
ngModule.vnComponent('vnZoneEvents', { ngModule.vnComponent('vnZoneEvents', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller controller: Controller,
bindings: {
zone: '<'
},
require: {
card: '^vnZoneCard'
}
}); });

View File

@ -1,4 +1,5 @@
import './index'; import './index';
import crudModel from 'core/mocks/crud-model';
describe('component vnZoneEvents', () => { describe('component vnZoneEvents', () => {
let $scope; let $scope;
@ -34,7 +35,8 @@ describe('component vnZoneEvents', () => {
const query = `Zones/getEventsFiltered?ended=${date}&started=${date}&zoneFk=${params.zoneFk}`; const query = `Zones/getEventsFiltered?ended=${date}&started=${date}&zoneFk=${params.zoneFk}`;
const response = { const response = {
events: 'myEvents', events: 'myEvents',
exclusions: 'myExclusions' exclusions: 'myExclusions',
geoExclusions: 'myGeoExclusions',
}; };
$httpBackend.whenGET(query).respond(response); $httpBackend.whenGET(query).respond(response);
controller.refresh(); controller.refresh();
@ -48,71 +50,129 @@ describe('component vnZoneEvents', () => {
}); });
describe('onSelection()', () => { describe('onSelection()', () => {
it('should call the edit() method', () => { it('should call the editInclusion() method', () => {
jest.spyOn(controller, 'edit').mockReturnThis(); jest.spyOn(controller, 'editInclusion').mockReturnThis();
const weekday = {}; const weekday = {};
const days = []; const days = [];
const type = 'EventType'; const type = 'EventType';
const events = [{name: 'Event'}]; const events = [{name: 'Event'}];
const exclusions = []; const exclusions = [];
const exclusionsGeo = [];
controller.editMode = 'include'; controller.editMode = 'include';
controller.onSelection(days, type, weekday, events, exclusions); controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo);
expect(controller.edit).toHaveBeenCalledWith({name: 'Event'}); expect(controller.editInclusion).toHaveBeenCalledWith({name: 'Event'});
}); });
it('should call the create() method', () => { it('should call the createInclusion() method', () => {
jest.spyOn(controller, 'create').mockReturnThis(); jest.spyOn(controller, 'createInclusion').mockReturnThis();
const weekday = {dated: new Date()}; const weekday = {dated: new Date()};
const days = [weekday]; const days = [weekday];
const type = 'EventType'; const type = 'EventType';
const events = []; const events = [];
const exclusions = []; const exclusions = [];
const exclusionsGeo = [];
controller.editMode = 'include'; controller.editMode = 'include';
controller.onSelection(days, type, weekday, events, exclusions); controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo);
expect(controller.create).toHaveBeenCalledWith(type, days, weekday); expect(controller.createInclusion).toHaveBeenCalledWith(type, days, weekday);
}); });
it('should call the exclusionDelete() method', () => { it('should call the editExclusion() method with exclusions', () => {
jest.spyOn(controller, 'exclusionDelete').mockReturnThis(); jest.spyOn(controller, 'editExclusion').mockReturnThis();
const weekday = {}; const weekday = {};
const days = []; const days = [];
const type = 'EventType'; const type = 'EventType';
const events = []; const events = [];
const exclusions = [{id: 1}]; const exclusions = [{name: 'Exclusion'}];
controller.editMode = 'delete'; const exclusionsGeo = [];
controller.onSelection(days, type, weekday, events, exclusions); controller.editMode = 'exclude';
controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo);
expect(controller.exclusionDelete).toHaveBeenCalledWith(exclusions); expect(controller.editExclusion).toHaveBeenCalled();
}); });
it('should call the exclusionCreate() method', () => { it('should call the editExclusion() method with exclusionsGeo', () => {
jest.spyOn(controller, 'exclusionCreate').mockReturnThis(); jest.spyOn(controller, 'editExclusion').mockReturnThis();
const weekday = {};
const days = [];
const type = 'EventType';
const events = [];
const exclusions = [];
const exclusionsGeo = [{name: 'GeoExclusion'}];
controller.editMode = 'exclude';
controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo);
expect(controller.editExclusion).toHaveBeenCalled();
});
it('should call the createExclusion() method', () => {
jest.spyOn(controller, 'createExclusion').mockReturnThis();
const weekday = {}; const weekday = {};
const days = [{dated: new Date()}]; const days = [{dated: new Date()}];
const type = 'EventType'; const type = 'EventType';
const events = []; const events = [];
const exclusions = []; const exclusions = [];
controller.editMode = 'delete'; const exclusionsGeo = [];
controller.onSelection(days, type, weekday, events, exclusions); controller.editMode = 'exclude';
controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo);
expect(controller.exclusionCreate).toHaveBeenCalledWith(days); expect(controller.createExclusion).toHaveBeenCalledWith(days);
}); });
}); });
describe('create()', () => { describe('editExclusion()', () => {
it('shoud set the selected property and then call the dialog show() method', () => { it('shoud set the excludeSelected.type = "specificLocations" and then call the excludeDialog show() method', () => {
controller.$.dialog = {show: jest.fn()}; controller.$.excludeDialog = {show: jest.fn()};
const exclusionGeos = [{id: 1}];
const exclusions = [];
controller.editExclusion(exclusions, exclusionGeos);
expect(controller.excludeSelected.type).toEqual('specificLocations');
expect(controller.$.excludeDialog.show).toHaveBeenCalledWith();
});
it('shoud set the excludeSelected.type = "all" and then call the excludeDialog show() method', () => {
controller.$.excludeDialog = {show: jest.fn()};
const exclusionGeos = [];
const exclusions = [{id: 1}];
controller.editExclusion(exclusions, exclusionGeos);
expect(controller.excludeSelected.type).toEqual('all');
expect(controller.$.excludeDialog.show).toHaveBeenCalledWith();
});
});
describe('createExclusion()', () => {
it('shoud set the excludeSelected property and then call the excludeDialog show() method', () => {
controller.$.excludeDialog = {show: jest.fn()};
const days = [new Date()];
controller.createExclusion(days);
expect(controller.excludeSelected).toBeDefined();
expect(controller.isNew).toBeTruthy();
expect(controller.$.excludeDialog.show).toHaveBeenCalledWith();
});
});
describe('createInclusion()', () => {
it('shoud set the selected property and then call the includeDialog show() method', () => {
controller.$.includeDialog = {show: jest.fn()};
const type = 'weekday'; const type = 'weekday';
const days = [new Date()]; const days = [new Date()];
const weekday = 1; const weekday = 1;
controller.create(type, days, weekday); controller.createInclusion(type, days, weekday);
const selection = controller.selected; const selection = controller.selected;
const firstWeekday = selection.wdays[weekday]; const firstWeekday = selection.wdays[weekday];
@ -120,23 +180,23 @@ describe('component vnZoneEvents', () => {
expect(selection.type).toEqual('indefinitely'); expect(selection.type).toEqual('indefinitely');
expect(firstWeekday).toBeTruthy(); expect(firstWeekday).toBeTruthy();
expect(controller.isNew).toBeTruthy(); expect(controller.isNew).toBeTruthy();
expect(controller.$.dialog.show).toHaveBeenCalledWith(); expect(controller.$.includeDialog.show).toHaveBeenCalledWith();
}); });
it('shoud set the selected property with the first day and then call the dialog show() method', () => { it('shoud set the selected property with the first day and then call the includeDialog show() method', () => {
controller.$.dialog = {show: jest.fn()}; controller.$.includeDialog = {show: jest.fn()};
const type = 'nonListedType'; const type = 'nonListedType';
const days = [new Date()]; const days = [new Date()];
const weekday = 1; const weekday = 1;
controller.create(type, days, weekday); controller.createInclusion(type, days, weekday);
const selection = controller.selected; const selection = controller.selected;
expect(selection.type).toEqual('day'); expect(selection.type).toEqual('day');
expect(selection.dated).toEqual(days[0]); expect(selection.dated).toEqual(days[0]);
expect(controller.isNew).toBeTruthy(); expect(controller.isNew).toBeTruthy();
expect(controller.$.dialog.show).toHaveBeenCalledWith(); expect(controller.$.includeDialog.show).toHaveBeenCalledWith();
}); });
}); });
@ -180,6 +240,35 @@ describe('component vnZoneEvents', () => {
}); });
}); });
describe('onExcludeResponse()', () => {
it('should call the exclusionCreate() method', () => {
jest.spyOn(controller, 'exclusionCreate').mockReturnThis();
controller.excludeSelected = {type: 'all'};
controller.onExcludeResponse('accept');
expect(controller.exclusionCreate).toHaveBeenCalledWith();
});
it('should call the exclusionGeoCreate() method', () => {
jest.spyOn(controller, 'exclusionGeoCreate').mockReturnThis();
controller.excludeSelected = {type: 'specificLocations'};
controller.onExcludeResponse('accept');
expect(controller.exclusionGeoCreate).toHaveBeenCalledWith();
});
it('should call the exclusionDelete() method', () => {
jest.spyOn(controller, 'exclusionDelete').mockReturnThis();
controller.excludeSelected = {id: 1, type: 'all'};
controller.onExcludeResponse('delete');
expect(controller.exclusionDelete).toHaveBeenCalledWith(controller.excludeSelected);
});
});
describe('onDeleteResponse()', () => { describe('onDeleteResponse()', () => {
it('shoud make an HTTP DELETE query and then call the refresh() method', () => { it('shoud make an HTTP DELETE query and then call the refresh() method', () => {
jest.spyOn(controller, 'refresh').mockReturnThis(); jest.spyOn(controller, 'refresh').mockReturnThis();
@ -197,9 +286,10 @@ describe('component vnZoneEvents', () => {
it('shoud make an HTTP POST query and then call the refresh() method', () => { it('shoud make an HTTP POST query and then call the refresh() method', () => {
jest.spyOn(controller, 'refresh').mockReturnThis(); jest.spyOn(controller, 'refresh').mockReturnThis();
const dates = [new Date()]; controller.excludeSelected = {};
controller.isNew = true;
$httpBackend.expect('POST', `Zones/1/exclusions`).respond({id: 1}); $httpBackend.expect('POST', `Zones/1/exclusions`).respond({id: 1});
controller.exclusionCreate(dates); controller.exclusionCreate();
$httpBackend.flush(); $httpBackend.flush();
expect(controller.refresh).toHaveBeenCalledWith(); expect(controller.refresh).toHaveBeenCalledWith();
@ -210,25 +300,41 @@ describe('component vnZoneEvents', () => {
it('shoud make an HTTP DELETE query once and then call the refresh() method', () => { it('shoud make an HTTP DELETE query once and then call the refresh() method', () => {
jest.spyOn(controller, 'refresh').mockReturnThis(); jest.spyOn(controller, 'refresh').mockReturnThis();
const exclusions = [{id: 1}]; const exclusions = {id: 1};
const firstExclusionId = 1; const firstExclusionId = 1;
$httpBackend.when('DELETE', `Zones/1/exclusions/${firstExclusionId}`).respond(200); $httpBackend.expectDELETE(`Zones/1/exclusions/${firstExclusionId}`).respond(200);
controller.exclusionDelete(exclusions); controller.exclusionDelete(exclusions);
$httpBackend.flush(); $httpBackend.flush();
expect(controller.refresh).toHaveBeenCalledWith(); expect(controller.refresh).toHaveBeenCalledWith();
}); });
});
it('shoud make an HTTP DELETE query for every event and then call the refresh() method', () => { describe('onSearch()', () => {
jest.spyOn(controller, 'refresh').mockReturnThis(); it('should call the applyFilter() method and then set the data', () => {
jest.spyOn(controller.$http, 'delete').mockReturnValue(200); jest.spyOn(controller, 'getChecked').mockReturnValue([1, 2, 3]);
const exclusions = [{id: 1}, {id: 2}, {id: 3}, {id: 4}]; controller.$.treeview = {};
controller.exclusionDelete(exclusions); controller.$.model = crudModel;
$scope.$apply(); controller.excludeSelected = {type: 'specificLocations'};
controller._excludeSearch = 'es';
expect(controller.$http.delete).toHaveBeenCalledTimes(4); controller.onSearch();
expect(controller.refresh).toHaveBeenCalledWith(); const treeviewData = controller.$.treeview.data;
expect(treeviewData).toBeDefined();
expect(treeviewData.length).toEqual(3);
});
});
describe('onFetch()', () => {
it('should call the applyFilter() method and then return the model data', () => {
jest.spyOn(controller, 'getChecked').mockReturnValue([1, 2, 3]);
controller.$.model = crudModel;
const result = controller.onFetch();
expect(result.length).toEqual(3);
}); });
}); });
}); });

View File

@ -4,3 +4,7 @@ Exclude: Excluir
Events: Eventos Events: Eventos
Add event: Añadir evento Add event: Añadir evento
Edit event: Editar evento Edit event: Editar evento
All: Todo
Specific locations: Localizaciones concretas
Locations where it is not distributed: Localizaciones en las que no se reparte
You must select a location: Debes seleccionar una localización

View File

@ -0,0 +1,11 @@
@import "variables";
.width{
width: 600px
}
.treeview{
max-height: 300px;
overflow: auto;
}

View File

@ -1,5 +1,6 @@
@import "variables"; @import "variables";
vn-zone-location {
vn-treeview-child { vn-treeview-child {
.content > .vn-check:not(.indeterminate):not(.checked) { .content > .vn-check:not(.indeterminate):not(.checked) {
color: $color-alert; color: $color-alert;
@ -17,3 +18,4 @@ vn-treeview-child {
} }
} }
} }
}

View File

@ -85,10 +85,13 @@
"description": "Warehouses" "description": "Warehouses"
}, },
{ {
"url": "/events", "url": "/events?q",
"state": "zone.card.events", "state": "zone.card.events",
"component": "vn-zone-events", "component": "vn-zone-events",
"description": "Calendar" "description": "Calendar",
"params": {
"zone": "$ctrl.zone"
}
}, },
{ {
"url": "/location?q", "url": "/location?q",