Loading front/src/ActivityTimesheetSynthesisMenu.vue +259 −21 Original line number Diff line number Diff line Loading @@ -6,15 +6,110 @@ Synthèse des déclarations </button> <div class="overlay" v-if="infosPerson"> <div class="overlay-content" style="flex-basis: 90%; max-height: 90%"> <h2> Détails pour <strong>{{ infosPerson.person }}</strong> pour <em>{{ infosPerson.period | periodStrFromKey }}</em> <a href="#" @click.prevent="infosPerson = null"><i class="icon-cancel-outline"></i></a> </h2> <a :href="'/feuille-de-temps/document/person' + '?period=' + infosPerson.period +'&format=pdf&person=' +infosPerson.person_id" class="btn btn-default"> <i class="icon-download"></i> Télécharger le PDF </a> <table class="table bordered period-person-details"> <thead> <tr> <th> ~</th> <th v-for="d, i in infosPerson.days"> <small>{{ d.label }}</small> {{ d.i }} </th> <th> Total</th> </tr> </thead> <tbody v-for="activity in organize(infosPerson).activities"> <tr> <th :colspan="infosPerson.dayNbr + 2"><h3><i class="icon-cube"></i>{{ activity.acronym }}</h3> </th> </tr> <tr v-for="workpackage in activity.workpackages" class="subline"> <th>{{ workpackage.code }}</th> <td v-for="d, i in infosPerson.days" class="day" :class="{ 'off': d.locked }"> <strong v-if="workpackage.days[i]"> {{ workpackage.days[i] | hours }} </strong> <small v-else>0.0</small> </td> <th class="total">{{ workpackage.total | hours }}</th> </tr> <tr> <th>Total</th> <td :colspan="infosPerson.dayNbr"> </td> <th class="total">{{ activity.total | hours }}</th> </tr> </tbody> <tbody> <tr> <th :colspan="infosPerson.dayNbr + 2"><h3><i class="icon-tags"></i>Hors-lot</h3></th> </tr> <tr v-for="other in organize(infosPerson).others"> <th>{{ other.label }}</th> <td v-for="d, i in infosPerson.days" class="day" :class="{ 'off': d.locked }"> <strong v-if="other.days[i]"> {{ other.days[i] | hours }} </strong> <small v-else>0.0</small> </td> <th class="total">{{ other.total | hours }}</th> </tr> </tbody> <tfoot> <tr> <th>Total</th> <th v-for="d, i in infosPerson.days" class="day" :class="{'off': d.locked }"> <small>{{ d.total | hours }}</small> </th> <th>{{ infosPerson.total | hours }}</th> </tr> </tfoot> </table> </div> </div> <div class="overlay-content"> <a href="#" class="overlay-closer" @click="handlerCloseSynthesis()">Close</a> <h1> <small>Cumul de temps pour</small> <strong> <i class="icon-cube"></i> {{ mainContext }} </strong> </h1> <div v-if="synthese" class="activity-timesheet-synthesis"> <nav class="text-center"> <button v-for="p, person_id in persons" class="btn" :class="filterPersons.indexOf(parseInt(person_id)) < 0 ? 'btn-primary' : 'btn-default'" @click="handlerTooglePerson(person_id)"> {{ p }} <h2> <i class="icon-users"></i> Déclarants : </h2> <button v-for="p in persons" class="btn btn-xs" :class="filterPersons.indexOf(parseInt(p.id)) < 0 ? 'btn-primary' : 'btn-default'" @click="handlerTooglePerson(p.id)" @click.shift="handlerOnePerson(p.id)" > <i class="icon-user"></i> {{ p.displayname }} <small>( <i class="icon-clock"></i> {{ p.total }} heure(s))</small> </button> </nav> Loading @@ -25,7 +120,10 @@ <i class="icon-angle-down" v-show="y.open"></i> {{ y.label }} </strong> <em class="total" v-show="!y.open">{{ y.totalmain | hours }}</em> <em class="total" v-show="!y.open"> <i class="icon-clock"></i> {{ y.totalmain | hours }} </em> </h3> <section v-for="p in y.periods" class="month" v-show="y.open" :class="{'open': y.open }"> Loading @@ -38,10 +136,27 @@ <em class="total" v-show="!p.open">{{ p.totalmain | hours }}</em> </h4> <section v-for="pers in p.persons" class="person" v-show="p.open" :class="{'open': p.open }" @click="handlerOpenPerson(pers)"> <section v-for="pers in p.persons" class="person" v-show="p.open" :class="{'open': p.open }" @click="handlerOpenPerson(pers)"> <h5 class="line"> <strong>{{ pers.label }}</strong> <em class="total">{{ pers.totalmain | hours }}</em> <strong class="person-label"> <i class="icon-angle-right" v-show="!pers.open"></i> <i class="icon-angle-down" v-show="pers.open"></i> <i class="icon-user"></i> {{ pers.label }} <small> <a href="#" @click.stop.prevent="details(pers.key, p.key)"> <i class="icon-search-outline"></i>Voir la répartition </a> - <a :href="'/feuille-de-temps/document/person' + '?period=' + p.key +'&format=pdf&person=' +pers.key" @click.stop=""> <i class="icon-search-outline"></i>Télécharger la feuille de temps </a> </small> </strong> <em class="total"> <i class="icon-clock"></i> {{ pers.totalmain | hours }}</em> </h5> <section class="details-person" v-if="pers.open"> <article class="line"> Loading @@ -53,7 +168,6 @@ </em> </article> <section class="othersprojects" v-if="pers.othersprojects.total > 0"> <article v-for="projectDuration, projectName in pers.othersprojects.projects" class="line"> <strong> <i class="icon-cube"></i> {{ projectName }} Loading @@ -66,6 +180,7 @@ <section class="othersprojects" v-if="pers.others.total > 0"> <article v-for="otherDuration, otherName in pers.others.context" class="line"> <strong> <i :class="'icon-'+otherName"></i> {{ otherName | contextLabel }} </strong> <em> Loading @@ -75,7 +190,7 @@ </section> <article class="line totalline"> <strong> Total pour cette période : Total déclaré pour cette période : </strong> <em> {{ pers.total | hours }} Loading Loading @@ -124,6 +239,12 @@ node node_modules/.bin/gulp activityTimesheetSynthesisMenu */ let periodStrFromKey = function (key) { let year = parseInt(key.substring(0, 4)); let month = parseInt(key.substring(5, 7)); return periodFullStr(year, month); }; let periodStr = function (year, month) { return year + '-' + (month < 10 ? '0' + month : month); }; Loading Loading @@ -171,6 +292,9 @@ export default { openYear: [], openPeriod: [], openPerson: [], // infosPerson: null } }, Loading @@ -185,6 +309,9 @@ export default { return contextsLabels[contextKey]; } return contextKey + "*"; }, periodStrFromKey(periodKey) { return periodStrFromKey(periodKey); } }, Loading Loading @@ -311,6 +438,27 @@ export default { }, methods: { /** * Affiche uniquement la personne. * * @param id */ handlerOnePerson(id) { let personId = parseInt(id); this.filterPersons.splice(0, this.filterPersons.length); Object.keys(this.persons).forEach(personIdStr => { let personIdInt = parseInt(personIdStr); if (personIdInt != personId) { this.filterPersons.push(personIdInt); } }); }, /** * Affiche/Cache le personne. * * @param id */ handlerTooglePerson(id) { let personId = parseInt(id); let indexId = this.filterPersons.indexOf(personId); Loading @@ -321,6 +469,88 @@ export default { } }, details: function (personId, period) { this.$http.get('/feuille-de-temps/synthesis?person_id=' + personId + '&format=json&period=' + period).then( ok => { this.infosPerson = ok.data; } ); }, organize(personDatas) { let datas = { activities: {}, others: {} }; Object.keys(personDatas.activities).forEach(activityId => { var activity = personDatas.activities[activityId]; datas.activities[activityId] = { acronym: activity.acronym, id: activity.id, label: activity.label, total: activity.total, workpackages: {}, days: {} }; }); Object.keys(personDatas.workpackages).forEach(wpId => { var wp = personDatas.workpackages[wpId]; datas.activities[wp.activity_id].workpackages[wpId] = { code: wp.code, id: wp.id, label: wp.label, total: wp.total, days: {} }; }); Object.keys(personDatas.otherWP).forEach(otherKey => { var other = personDatas.otherWP[otherKey]; datas.others[otherKey] = { label: other.label, code: other.code, total: other.total, days: {} }; }); Object.keys(personDatas.days).forEach(date => { var day = personDatas.days[date]; if (day.declarations) { day.declarations.forEach(declaration => { var wpId = declaration.wp_id; var activityId = declaration.activity_id; var duration = declaration.duration; if (!datas.activities[activityId].days.hasOwnProperty(date)) { datas.activities[activityId].days[date] = 0.0; } datas.activities[activityId].days[date] += duration; if (!datas.activities[activityId].workpackages[wpId].days.hasOwnProperty(date)) { datas.activities[activityId].workpackages[wpId].days[date] = 0.0; } datas.activities[activityId].workpackages[wpId].days[date] += duration; }); } if (day.othersWP) { day.othersWP.forEach(otherInfos => { var otherKey = otherInfos.code; var duration = otherInfos.duration; if (!datas.others[otherKey].days.hasOwnProperty(date)) { datas.others[otherKey].days[date] = 0.0; } datas.others[otherKey].days[date] += duration; }) } }); return datas; }, handlerOpenPerson(person) { console.log(person); let indexId = this.openPerson.indexOf(person.indexOpen); Loading Loading @@ -392,11 +622,19 @@ export default { datas.synthesis.forEach(e => { if (!persons.hasOwnProperty(e.person_id)) { persons[e.person_id] = e.displayname; persons[e.person_id] = { "id": e.person_id, "displayname": e.displayname, "total": 0.0 } } if (contexts.indexOf(e.context) < 0 && e.activity_id != this.mainContextId) { contexts.push(e.context) } if (e.activity_id == this.mainContextId) { persons[e.person_id].total += parseFloat(e.duration) } }); this.periods = periods; Loading module/Oscar/config/module.config.php +1 −1 Original line number Diff line number Diff line Loading @@ -208,7 +208,7 @@ return array( 'validationActivity2', 'validationHWPPerson', 'validatePersonPeriod', 'importIcal', 'declarations', 'resumeActivity', 'resolveInvalidLabels', 'syntheseActivity', 'synthesisAll', 'synthesisActivityPeriod', 'exportActivityDates', 'checkperiod' 'exportActivityDates', 'checkperiod', 'documentPerson' ], 'roles' => ['user'] ], Loading module/Oscar/config/routes.yml +6 −0 Original line number Diff line number Diff line Loading @@ -170,6 +170,12 @@ timesheet: defaults: action: synthesisActivityPeriod document_person: type: segment options: route: /document/person defaults: action: documentPerson synthesis: type: segment Loading module/Oscar/src/Oscar/Controller/PublicController.php +0 −1 Original line number Diff line number Diff line Loading @@ -172,7 +172,6 @@ class PublicController extends AbstractOscarController implements UseTimesheetSe if( DEBUG_OSCAR ){ return []; } die("to test"); } /** Loading module/Oscar/src/Oscar/Controller/TimesheetController.php +769 −489 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
front/src/ActivityTimesheetSynthesisMenu.vue +259 −21 Original line number Diff line number Diff line Loading @@ -6,15 +6,110 @@ Synthèse des déclarations </button> <div class="overlay" v-if="infosPerson"> <div class="overlay-content" style="flex-basis: 90%; max-height: 90%"> <h2> Détails pour <strong>{{ infosPerson.person }}</strong> pour <em>{{ infosPerson.period | periodStrFromKey }}</em> <a href="#" @click.prevent="infosPerson = null"><i class="icon-cancel-outline"></i></a> </h2> <a :href="'/feuille-de-temps/document/person' + '?period=' + infosPerson.period +'&format=pdf&person=' +infosPerson.person_id" class="btn btn-default"> <i class="icon-download"></i> Télécharger le PDF </a> <table class="table bordered period-person-details"> <thead> <tr> <th> ~</th> <th v-for="d, i in infosPerson.days"> <small>{{ d.label }}</small> {{ d.i }} </th> <th> Total</th> </tr> </thead> <tbody v-for="activity in organize(infosPerson).activities"> <tr> <th :colspan="infosPerson.dayNbr + 2"><h3><i class="icon-cube"></i>{{ activity.acronym }}</h3> </th> </tr> <tr v-for="workpackage in activity.workpackages" class="subline"> <th>{{ workpackage.code }}</th> <td v-for="d, i in infosPerson.days" class="day" :class="{ 'off': d.locked }"> <strong v-if="workpackage.days[i]"> {{ workpackage.days[i] | hours }} </strong> <small v-else>0.0</small> </td> <th class="total">{{ workpackage.total | hours }}</th> </tr> <tr> <th>Total</th> <td :colspan="infosPerson.dayNbr"> </td> <th class="total">{{ activity.total | hours }}</th> </tr> </tbody> <tbody> <tr> <th :colspan="infosPerson.dayNbr + 2"><h3><i class="icon-tags"></i>Hors-lot</h3></th> </tr> <tr v-for="other in organize(infosPerson).others"> <th>{{ other.label }}</th> <td v-for="d, i in infosPerson.days" class="day" :class="{ 'off': d.locked }"> <strong v-if="other.days[i]"> {{ other.days[i] | hours }} </strong> <small v-else>0.0</small> </td> <th class="total">{{ other.total | hours }}</th> </tr> </tbody> <tfoot> <tr> <th>Total</th> <th v-for="d, i in infosPerson.days" class="day" :class="{'off': d.locked }"> <small>{{ d.total | hours }}</small> </th> <th>{{ infosPerson.total | hours }}</th> </tr> </tfoot> </table> </div> </div> <div class="overlay-content"> <a href="#" class="overlay-closer" @click="handlerCloseSynthesis()">Close</a> <h1> <small>Cumul de temps pour</small> <strong> <i class="icon-cube"></i> {{ mainContext }} </strong> </h1> <div v-if="synthese" class="activity-timesheet-synthesis"> <nav class="text-center"> <button v-for="p, person_id in persons" class="btn" :class="filterPersons.indexOf(parseInt(person_id)) < 0 ? 'btn-primary' : 'btn-default'" @click="handlerTooglePerson(person_id)"> {{ p }} <h2> <i class="icon-users"></i> Déclarants : </h2> <button v-for="p in persons" class="btn btn-xs" :class="filterPersons.indexOf(parseInt(p.id)) < 0 ? 'btn-primary' : 'btn-default'" @click="handlerTooglePerson(p.id)" @click.shift="handlerOnePerson(p.id)" > <i class="icon-user"></i> {{ p.displayname }} <small>( <i class="icon-clock"></i> {{ p.total }} heure(s))</small> </button> </nav> Loading @@ -25,7 +120,10 @@ <i class="icon-angle-down" v-show="y.open"></i> {{ y.label }} </strong> <em class="total" v-show="!y.open">{{ y.totalmain | hours }}</em> <em class="total" v-show="!y.open"> <i class="icon-clock"></i> {{ y.totalmain | hours }} </em> </h3> <section v-for="p in y.periods" class="month" v-show="y.open" :class="{'open': y.open }"> Loading @@ -38,10 +136,27 @@ <em class="total" v-show="!p.open">{{ p.totalmain | hours }}</em> </h4> <section v-for="pers in p.persons" class="person" v-show="p.open" :class="{'open': p.open }" @click="handlerOpenPerson(pers)"> <section v-for="pers in p.persons" class="person" v-show="p.open" :class="{'open': p.open }" @click="handlerOpenPerson(pers)"> <h5 class="line"> <strong>{{ pers.label }}</strong> <em class="total">{{ pers.totalmain | hours }}</em> <strong class="person-label"> <i class="icon-angle-right" v-show="!pers.open"></i> <i class="icon-angle-down" v-show="pers.open"></i> <i class="icon-user"></i> {{ pers.label }} <small> <a href="#" @click.stop.prevent="details(pers.key, p.key)"> <i class="icon-search-outline"></i>Voir la répartition </a> - <a :href="'/feuille-de-temps/document/person' + '?period=' + p.key +'&format=pdf&person=' +pers.key" @click.stop=""> <i class="icon-search-outline"></i>Télécharger la feuille de temps </a> </small> </strong> <em class="total"> <i class="icon-clock"></i> {{ pers.totalmain | hours }}</em> </h5> <section class="details-person" v-if="pers.open"> <article class="line"> Loading @@ -53,7 +168,6 @@ </em> </article> <section class="othersprojects" v-if="pers.othersprojects.total > 0"> <article v-for="projectDuration, projectName in pers.othersprojects.projects" class="line"> <strong> <i class="icon-cube"></i> {{ projectName }} Loading @@ -66,6 +180,7 @@ <section class="othersprojects" v-if="pers.others.total > 0"> <article v-for="otherDuration, otherName in pers.others.context" class="line"> <strong> <i :class="'icon-'+otherName"></i> {{ otherName | contextLabel }} </strong> <em> Loading @@ -75,7 +190,7 @@ </section> <article class="line totalline"> <strong> Total pour cette période : Total déclaré pour cette période : </strong> <em> {{ pers.total | hours }} Loading Loading @@ -124,6 +239,12 @@ node node_modules/.bin/gulp activityTimesheetSynthesisMenu */ let periodStrFromKey = function (key) { let year = parseInt(key.substring(0, 4)); let month = parseInt(key.substring(5, 7)); return periodFullStr(year, month); }; let periodStr = function (year, month) { return year + '-' + (month < 10 ? '0' + month : month); }; Loading Loading @@ -171,6 +292,9 @@ export default { openYear: [], openPeriod: [], openPerson: [], // infosPerson: null } }, Loading @@ -185,6 +309,9 @@ export default { return contextsLabels[contextKey]; } return contextKey + "*"; }, periodStrFromKey(periodKey) { return periodStrFromKey(periodKey); } }, Loading Loading @@ -311,6 +438,27 @@ export default { }, methods: { /** * Affiche uniquement la personne. * * @param id */ handlerOnePerson(id) { let personId = parseInt(id); this.filterPersons.splice(0, this.filterPersons.length); Object.keys(this.persons).forEach(personIdStr => { let personIdInt = parseInt(personIdStr); if (personIdInt != personId) { this.filterPersons.push(personIdInt); } }); }, /** * Affiche/Cache le personne. * * @param id */ handlerTooglePerson(id) { let personId = parseInt(id); let indexId = this.filterPersons.indexOf(personId); Loading @@ -321,6 +469,88 @@ export default { } }, details: function (personId, period) { this.$http.get('/feuille-de-temps/synthesis?person_id=' + personId + '&format=json&period=' + period).then( ok => { this.infosPerson = ok.data; } ); }, organize(personDatas) { let datas = { activities: {}, others: {} }; Object.keys(personDatas.activities).forEach(activityId => { var activity = personDatas.activities[activityId]; datas.activities[activityId] = { acronym: activity.acronym, id: activity.id, label: activity.label, total: activity.total, workpackages: {}, days: {} }; }); Object.keys(personDatas.workpackages).forEach(wpId => { var wp = personDatas.workpackages[wpId]; datas.activities[wp.activity_id].workpackages[wpId] = { code: wp.code, id: wp.id, label: wp.label, total: wp.total, days: {} }; }); Object.keys(personDatas.otherWP).forEach(otherKey => { var other = personDatas.otherWP[otherKey]; datas.others[otherKey] = { label: other.label, code: other.code, total: other.total, days: {} }; }); Object.keys(personDatas.days).forEach(date => { var day = personDatas.days[date]; if (day.declarations) { day.declarations.forEach(declaration => { var wpId = declaration.wp_id; var activityId = declaration.activity_id; var duration = declaration.duration; if (!datas.activities[activityId].days.hasOwnProperty(date)) { datas.activities[activityId].days[date] = 0.0; } datas.activities[activityId].days[date] += duration; if (!datas.activities[activityId].workpackages[wpId].days.hasOwnProperty(date)) { datas.activities[activityId].workpackages[wpId].days[date] = 0.0; } datas.activities[activityId].workpackages[wpId].days[date] += duration; }); } if (day.othersWP) { day.othersWP.forEach(otherInfos => { var otherKey = otherInfos.code; var duration = otherInfos.duration; if (!datas.others[otherKey].days.hasOwnProperty(date)) { datas.others[otherKey].days[date] = 0.0; } datas.others[otherKey].days[date] += duration; }) } }); return datas; }, handlerOpenPerson(person) { console.log(person); let indexId = this.openPerson.indexOf(person.indexOpen); Loading Loading @@ -392,11 +622,19 @@ export default { datas.synthesis.forEach(e => { if (!persons.hasOwnProperty(e.person_id)) { persons[e.person_id] = e.displayname; persons[e.person_id] = { "id": e.person_id, "displayname": e.displayname, "total": 0.0 } } if (contexts.indexOf(e.context) < 0 && e.activity_id != this.mainContextId) { contexts.push(e.context) } if (e.activity_id == this.mainContextId) { persons[e.person_id].total += parseFloat(e.duration) } }); this.periods = periods; Loading
module/Oscar/config/module.config.php +1 −1 Original line number Diff line number Diff line Loading @@ -208,7 +208,7 @@ return array( 'validationActivity2', 'validationHWPPerson', 'validatePersonPeriod', 'importIcal', 'declarations', 'resumeActivity', 'resolveInvalidLabels', 'syntheseActivity', 'synthesisAll', 'synthesisActivityPeriod', 'exportActivityDates', 'checkperiod' 'exportActivityDates', 'checkperiod', 'documentPerson' ], 'roles' => ['user'] ], Loading
module/Oscar/config/routes.yml +6 −0 Original line number Diff line number Diff line Loading @@ -170,6 +170,12 @@ timesheet: defaults: action: synthesisActivityPeriod document_person: type: segment options: route: /document/person defaults: action: documentPerson synthesis: type: segment Loading
module/Oscar/src/Oscar/Controller/PublicController.php +0 −1 Original line number Diff line number Diff line Loading @@ -172,7 +172,6 @@ class PublicController extends AbstractOscarController implements UseTimesheetSe if( DEBUG_OSCAR ){ return []; } die("to test"); } /** Loading
module/Oscar/src/Oscar/Controller/TimesheetController.php +769 −489 File changed.Preview size limit exceeded, changes collapsed. Show changes