From 1af3b5fc9bb230de149d94db8f604deef4897142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20B?= <stephane.bouvry@unicaen.fr> Date: Mon, 2 Oct 2023 18:46:33 +0200 Subject: [PATCH] =?UTF-8?q?Backup=20:=20D=C3=A9penses=20/=20Recettes=20v2?= =?UTF-8?q?=20(vue=20d=C3=A9taill=C3=A9es)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Oscar/Controller/DepenseController.php | 20 +- .../Controller/ProjectGrantController.php | 15 +- .../Oscar/src/Oscar/Service/SpentService.php | 333 +++---- .../view/oscar/project-grant/spent-list.phtml | 3 - .../js/oscar/src/ActivitySpentSynthesis.vue | 13 +- .../assets/activityspentsynthesis-6325e583.js | 1 - .../assets/activityspentsynthesis-641b1fe6.js | 1 + public/js/oscar/vite/dist/manifest.json | 2 +- ui/src/ActivitySpentDetails.js | 4 +- ui/src/views/ActivitySpentSynthesis.vue | 102 +- ui/src/views/SpentLinePFI.vue | 941 +++++++++--------- 11 files changed, 713 insertions(+), 722 deletions(-) delete mode 100644 public/js/oscar/vite/dist/assets/activityspentsynthesis-6325e583.js create mode 100644 public/js/oscar/vite/dist/assets/activityspentsynthesis-641b1fe6.js diff --git a/module/Oscar/src/Oscar/Controller/DepenseController.php b/module/Oscar/src/Oscar/Controller/DepenseController.php index 5bcef65a8..d99449b01 100644 --- a/module/Oscar/src/Oscar/Controller/DepenseController.php +++ b/module/Oscar/src/Oscar/Controller/DepenseController.php @@ -214,12 +214,13 @@ class DepenseController extends AbstractOscarController implements UseServiceCon // Récupération des affectations $postedAffectations = $this->params()->fromPost('affectation'); + if( !$postedAffectations ){ return $this->getResponseBadRequest("Erreur de transmission : " . print_r($_POST, true)); } try { - $this->getSpentService()->updateAffectation($postedAffectations); + $this->getSpentService()->updateAffectation(json_decode($postedAffectations, true)); } catch (\Exception $e) { return $this->getResponseInternalError($e->getMessage()); @@ -246,15 +247,28 @@ class DepenseController extends AbstractOscarController implements UseServiceCon $this->getOscarUserContextService()->check(Privileges::DEPENSE_DETAILS, $activity); + $format = $this->params()->fromQuery('format', 'json'); + try { if( !$activity->getCodeEOTP() ){ throw new OscarException(sprintf(_("Cette activité n'a pas de Numéro financier"))); } //$spents = $this->getSpentService()->getGroupedSpentsDatas($activity->getCodeEOTP()); - $spents = $this->getSpentService()->getSpentsDatas($activity->getCodeEOTP(), SpentService::SPENT_BOTH); + $spents = $this->getSpentService()->getSpentsDatas($activity->getCodeEOTP(),SpentService::SPENT_BOTH); $spents['informations'] = $activity->toArray(); + if( $this->getOscarUserContextService()->hasPrivileges(Privileges::ACTIVITY_SHOW, $activity) ){ + $spents['url_activity'] = $this->url()->fromRoute('contract/show', ['id' => $activity->getId()]); + } + if( $this->getOscarUserContextService()->hasPrivileges(Privileges::DEPENSE_SYNC, $activity) ){ + $spents['url_sync'] = $this->url()->fromRoute('contract/list-spent', ['id' => $activity->getId()]); + } + if( $this->getOscarUserContextService()->hasPrivileges(Privileges::DEPENSE_DOWNLOAD, $activity) ){ + $spents['url_download'] = $this->url()->fromRoute('spent/activity-api', ['id' => $activity->getId()]) . '?format=excel&mode=details'; + } + if( $this->getOscarUserContextService()->hasPrivileges(Privileges::MAINTENANCE_SPENDTYPEGROUP_MANAGE) ){ + $spents['url_spentaffectation'] = $this->url()->fromRoute('spent/compte-affectation'); + } - $format = $this->params()->fromQuery('format', 'json'); switch($format){ case 'json' : $datas = $this->baseJsonResponse(); diff --git a/module/Oscar/src/Oscar/Controller/ProjectGrantController.php b/module/Oscar/src/Oscar/Controller/ProjectGrantController.php index db81fc020..e17973b1e 100644 --- a/module/Oscar/src/Oscar/Controller/ProjectGrantController.php +++ b/module/Oscar/src/Oscar/Controller/ProjectGrantController.php @@ -2003,18 +2003,6 @@ class ProjectGrantController extends AbstractOscarController implements UseNotif $out['error'] = null; // Affiche les erreurs survenue lors de la récupération/synchronisation des données $out['warning'] = null; // Affiche les avertissements -// if ($this->getOscarConfigurationService()->getAutoUpdateSpent()) { -// if (!$this->getOscarUserContextService()->hasPrivileges(Privileges::DEPENSE_SYNC, $entity)) { -// $out['warning'] = "Vous n'êtes pas autorisé à mettre à jour les dépenses, les données peuvent ne pas être à jour"; -// } else { -// try { -// $this->spentService->syncSpentsByEOTP($pfi); -// } catch (\Exception $e) { -// $out['error'] = $e->getMessage(); -// } -// } -// } - // Construction des données de dépense $out['masses'] = $masses; $out['dateUpdated'] = $entity->getDateTotalSpent(); @@ -2022,7 +2010,8 @@ class ProjectGrantController extends AbstractOscarController implements UseNotif $pfis, $this->getOscarUserContextService()->hasPrivileges( Privileges::MAINTENANCE_SPENDTYPEGROUP_MANAGE - ) + ), + 'basic' ); } catch (\Exception $e) { return $this->getResponseInternalError("Impossible de charger les dépenses pour la/les activité(s)"); diff --git a/module/Oscar/src/Oscar/Service/SpentService.php b/module/Oscar/src/Oscar/Service/SpentService.php index b0c88a19e..7fee2c212 100644 --- a/module/Oscar/src/Oscar/Service/SpentService.php +++ b/module/Oscar/src/Oscar/Service/SpentService.php @@ -847,204 +847,90 @@ class SpentService implements UseLoggerService, UseOscarConfigurationService, Us * @param false $curationNB * @return array */ - public function getSynthesisDatasPFI($pfi, $curationNB = false) :array + public function getSynthesisDatasPFI($pfis, $curationNB = false, string $mode = 'advanced' ) :array { - $clauseEffective = $this->getOscarConfigurationService()->getSpentEffectiveClauseValue(); - $clausePredicted = $this->getOscarConfigurationService()->getSpentPredictedClauseValue(); - - // Récupération des dépenses - $effectives = $spents = $this->getSpentsByPFIs($pfi, self::SPENT_EFFECTIVE); - $predicted = $this->getSpentsByPFIs($pfi, self::SPENT_PREVISIONNAL); + $spents = []; + foreach ($pfis as $pfi) { + $spents = array_merge($spents, $this->getSpentsByPFI($pfi, self::SPENT_BOTH)); + } // Récupération des Masses comptable configurées dans config $masses = $this->getOscarConfigurationService()->getMasses(); - // Structuration du tableau de retour - $out = []; - - $out['N.B'] = 0.0; - $out['entries'] = count($spents); - $out['total'] = 0.0; - - $predicted_aggregator = []; - - - // Dépenses "prévues" - $out['predicted'] = [ - 'N.B' => [] - ]; - $out['predicted_count'] = count($predicted); - $out['predicted_total'] = 0.0; - $out['details'] = []; - $out['predicted_totals'] = [ - 'N.B' => 0.0 - ]; - - // Dépenses "effectives" - $out['effective'] = [ - 'N.B' => [] - ]; - $out['effective_count'] = count($effectives); - $out['effective_total'] = 0.0; - $out['effective_totals'] = [ - 'N.B' => 0.0 - ]; - - $out['details'] = [ - 'N.B' => [] - ]; - $out['totals'] = [ - 'N.B' => 0.0 - ]; - $out['recettes'] = [ - 'total' => 0.0, - 'details' => [] + return [ + 'pfi' => $pfi, + 'masses' => $masses, + 'synthesis' => $this->getSpentDatasSynthesisBySpents($spents) ]; - if( $curationNB ){ - $out['curations'] = []; - } - - foreach ($masses as $key => $label) { - $out[$key] = 0.0; - $out['totals'][$key] = 0.0; - $out['details'][$key] = []; - - $out['predicted'][$key] = []; - $out['predicted_totals'][$key] = 0.0; - - $out['effective'][$key] = []; - $out['effective_totals'][$key] = 0.0; - } - - // On commence par traiter les données effectives - $idsEffectiveDone = []; - // Aggrégation des données - /** @var SpentLine $spent */ - foreach ($effectives as $spent) { - - $compte = $spent->getCompteGeneral(); - $compteInfos = $this->getCompte($compte); - $annexe = $compteInfos['annexe']; - $montant = floatval($spent->getMontant()); - $out['details'][] = $spent->toArray(); - $idSifac = $spent->getNumSifac(); - $idsEffectiveDone[] = $idSifac; - - if( $annexe == '' || $annexe == null ){ - $annexe = $compteInfos['masse_inherit']; - } - - if( $annexe == '0' ){ - continue; - } - - if( $annexe == '1' ){ - $out['recettes']['total'] += $montant; - $out['recettes']['details'][] = $spent->toArray(); - continue; - } - - if ($annexe == '') { - if( $curationNB ){ - $exist = $compte == $compteInfos['code']; - if( !array_key_exists($compte, $out['curations']) ){ - $out['curations'][$compte] = [ - 'compte' => $compte, - 'compteInfos' => $compteInfos, - 'label' => $compteInfos['label'], - 'montant' => 0.0, - 'totalEntries' => 0, - 'exist' => $exist ? 'true' : 'false' - ]; - } - $out['curations'][$compte]['montant'] += $montant; - $out['curations'][$compte]['totalEntries']++; - } - $annexe = 'N.B'; - if (!in_array($compte, $out['details'][$annexe])) - $out['details'][$annexe][] = $compte . ' (' . $compteInfos['label'] . ')'; - } - - - - $out[$annexe] += $montant; - $out['total'] += $montant; - $out['totals'][$annexe] += $montant; - - $out['effective_total'] += $montant; - $out['effective_totals'][$annexe] += $montant; - } - - // Puis les données prévues en évacuant les lignes déjà présentes dans les données effectives - // /** @var SpentLine $spent */ - foreach ($predicted as $spent) { - - $idSifac = $spent->getNumSifac(); - if( in_array($idSifac, $idsEffectiveDone) ){ - continue; - } - $compte = $spent->getCompteGeneral(); - $compteInfos = $this->getCompte($compte); - $annexe = $compteInfos['annexe']; - $montant = floatval($spent->getMontant()); - $out['details'][] = $spent->toArray(); - - if( $annexe == '' || $annexe == null ){ - $annexe = $compteInfos['masse_inherit']; - } - - if( $annexe == '0' ){ - continue; - } - - if( $annexe == '1' ){ - $out['recettes']['total'] += $montant; - $out['recettes']['details'][] = $spent->toArray(); - continue; - } - - if ($annexe == '') { - if( $curationNB ){ - $exist = $compte == $compteInfos['code']; - if( !array_key_exists($compte, $out['curations']) ){ - $out['curations'][$compte] = [ - 'compte' => $compte, - 'compteInfos' => $compteInfos, - 'label' => $compteInfos['label'], - 'montant' => 0.0, - 'totalEntries' => 0, - 'exist' => $exist ? 'true' : 'false' - ]; - } - $out['curations'][$compte]['montant'] += $montant; - $out['curations'][$compte]['totalEntries']++; - } - $annexe = 'N.B'; - if (!in_array($compte, $out['details'][$annexe])) - $out['details'][$annexe][] = $compte . ' (' . $compteInfos['label'] . ')'; - } - - - - $out[$annexe] += $montant; - $out['total'] += $montant; - $out['totals'][$annexe] += $montant; - - $out['predicted_total'] += $montant; - $out['predicted_totals'][$annexe] += $montant; - } - +// +// +// // Structuration du tableau de retour +// $out = []; +// +// $out['N.B'] = 0.0; +// $out['entries'] = count($spents); +// $out['total'] = 0.0; +// +// $predicted_aggregator = []; +// +// +// // Dépenses "prévues" +// $out['predicted'] = [ +// 'N.B' => [] +// ]; +// $out['predicted_count'] = count($predicted); +// $out['predicted_total'] = 0.0; +// $out['predicted_totals'] = [ +// 'N.B' => 0.0 +// ]; +// +// // Dépenses "effectives" +// $out['effective'] = [ +// 'N.B' => [] +// ]; +// $out['effective_count'] = count($effectives); +// $out['effective_total'] = 0.0; +// $out['effective_totals'] = [ +// 'N.B' => 0.0 +// ]; +// +// $out['totals'] = [ +// 'N.B' => 0.0 +// ]; +// $out['recettes'] = [ +// 'total' => 0.0, +// ]; +// +// if( $curationNB ){ +// $out['curations'] = []; +// } +// +// foreach ($masses as $key => $label) { +// $out[$key] = 0.0; +// $out['totals'][$key] = 0.0; +// +// $out['predicted'][$key] = []; +// $out['predicted_totals'][$key] = 0.0; +// +// $out['effective'][$key] = []; +// $out['effective_totals'][$key] = 0.0; +// } +// +// // On commence par traiter les données effectives +// $idsEffectiveDone = []; // // Aggrégation des données // /** @var SpentLine $spent */ -// foreach (array_merge($effectives, $predicted) as $spent) { +// foreach ($effectives as $spent) { // // $compte = $spent->getCompteGeneral(); // $compteInfos = $this->getCompte($compte); // $annexe = $compteInfos['annexe']; // $montant = floatval($spent->getMontant()); -// $out['details'][] = $spent->toArray(); +// //$out['details'][] = $spent->toArray(); +// $idSifac = $spent->getNumSifac(); +// $idsEffectiveDone[] = $idSifac; // // if( $annexe == '' || $annexe == null ){ // $annexe = $compteInfos['masse_inherit']; @@ -1056,7 +942,7 @@ class SpentService implements UseLoggerService, UseOscarConfigurationService, Us // // if( $annexe == '1' ){ // $out['recettes']['total'] += $montant; -// $out['recettes']['details'][] = $spent->toArray(); +// //$out['recettes']['details'][] = $spent->toArray(); // continue; // } // @@ -1077,8 +963,8 @@ class SpentService implements UseLoggerService, UseOscarConfigurationService, Us // $out['curations'][$compte]['totalEntries']++; // } // $annexe = 'N.B'; -// if (!in_array($compte, $out['details'][$annexe])) -// $out['details'][$annexe][] = $compte . ' (' . $compteInfos['label'] . ')'; +//// if (!in_array($compte, $out['details'][$annexe])) +//// $out['details'][$annexe][] = $compte . ' (' . $compteInfos['label'] . ')'; // } // // @@ -1087,18 +973,69 @@ class SpentService implements UseLoggerService, UseOscarConfigurationService, Us // $out['total'] += $montant; // $out['totals'][$annexe] += $montant; // -// if( $spent->getRldnr() == $clauseEffective ){ -// $out['effective_total'] += $montant; -// $out['effective_totals'][$annexe] += $montant; +// $out['effective_total'] += $montant; +// $out['effective_totals'][$annexe] += $montant; +// } +// +// // Puis les données prévues en évacuant les lignes déjà présentes dans les données effectives +// // /** @var SpentLine $spent */ +// foreach ($predicted as $spent) { +// +// $idSifac = $spent->getNumSifac(); +// if( in_array($idSifac, $idsEffectiveDone) ){ +// continue; +// } +// $compte = $spent->getCompteGeneral(); +// $compteInfos = $this->getCompte($compte); +// $annexe = $compteInfos['annexe']; +// $montant = floatval($spent->getMontant()); +// //$out['details'][] = $spent->toArray(); +// +// if( $annexe == '' || $annexe == null ){ +// $annexe = $compteInfos['masse_inherit']; +// } +// +// if( $annexe == '0' ){ +// continue; // } -// elseif ($spent->getRldnr() == $clausePredicted) { -// $out['predicted_total'] += $montant; -// $out['predicted_totals'][$annexe] += $montant; +// +// if( $annexe == '1' ){ +// $out['recettes']['total'] += $montant; +// //$out['recettes']['details'][] = $spent->toArray(); +// continue; // } -// else { -// $this->getLoggerService()->error("Une valeur de dépenses pour $spent est incohérente pour le champ RLDNR"); +// +// if ($annexe == '') { +// if( $curationNB ){ +// $exist = $compte == $compteInfos['code']; +// if( !array_key_exists($compte, $out['curations']) ){ +// $out['curations'][$compte] = [ +// 'compte' => $compte, +// 'compteInfos' => $compteInfos, +// 'label' => $compteInfos['label'], +// 'montant' => 0.0, +// 'totalEntries' => 0, +// 'exist' => $exist ? 'true' : 'false' +// ]; +// } +// $out['curations'][$compte]['montant'] += $montant; +// $out['curations'][$compte]['totalEntries']++; +// } +// $annexe = 'N.B'; +// if (!in_array($compte, $out['details'][$annexe])) +// $out['details'][$annexe][] = $compte . ' (' . $compteInfos['label'] . ')'; // } +// +// +// +// $out[$annexe] += $montant; +// $out['total'] += $montant; +// $out['totals'][$annexe] += $montant; +// +// $out['predicted_total'] += $montant; +// $out['predicted_totals'][$annexe] += $montant; // } + return $out; } @@ -1269,7 +1206,9 @@ class SpentService implements UseLoggerService, UseOscarConfigurationService, Us * @return array[] */ public function getSpentDatasSynthesisBySpents($spents){ + $pfis = []; $synthesis = [ + 'lines' => count($spents), '1' => [ 'label' => "Recettes", 'total' => 0.0, @@ -1321,6 +1260,9 @@ class SpentService implements UseLoggerService, UseOscarConfigurationService, Us $numSifacDone = []; /** @var SpentLine $spent */ foreach ($spents as $spent) { + if( !in_array($spent->getPfi(), $pfis) ){ + $pfis[] = $spent->getPfi(); + } if($spent->getBtart() == SpentLine::BTART_EFFECTUE && !in_array($spent->getNumSifac(), $numSifacDone)){ $numSifacDone[] = $spent->getNumSifac(); } @@ -1352,7 +1294,7 @@ class SpentService implements UseLoggerService, UseOscarConfigurationService, Us $synthesis[$masse]['total'] += $spent->getMontant(); $synthesis[$masse]['total_effectue'] += $totalEffectue; $synthesis[$masse]['total_engage'] += $totalEngage; - + if( in_array($masse, $massesKeys) ){ $synthesis['totaux']['effectue'] += $totalEffectue; $synthesis['totaux']['engage'] += $totalEngage; @@ -1360,6 +1302,7 @@ class SpentService implements UseLoggerService, UseOscarConfigurationService, Us $synthesis[$masse]['nbr']++; } + $synthesis['pfis'] = $pfis; return $synthesis; diff --git a/module/Oscar/view/oscar/project-grant/spent-list.phtml b/module/Oscar/view/oscar/project-grant/spent-list.phtml index d09c824c5..01e4f7749 100644 --- a/module/Oscar/view/oscar/project-grant/spent-list.phtml +++ b/module/Oscar/view/oscar/project-grant/spent-list.phtml @@ -16,11 +16,8 @@ </style> <section class="container-fluid"> - <h1>TEST</h1> <div id="depensesdetails" - data-informations="<?= json_encode($activity->toArray()) ?>" data-url="<?= $this->url('spent/activity-api', ['id' => $activity->getId()]) ?>" - > </div> <?= $this->Vite()->addJs('src/ActivitySpentDetails.js'); ?> diff --git a/public/js/oscar/src/ActivitySpentSynthesis.vue b/public/js/oscar/src/ActivitySpentSynthesis.vue index 33c1459c1..8a72586c7 100644 --- a/public/js/oscar/src/ActivitySpentSynthesis.vue +++ b/public/js/oscar/src/ActivitySpentSynthesis.vue @@ -52,6 +52,8 @@ </div> </transition> + {{ synthesis }} + <table class="table table-condensed" v-if="!pendingMsg"> <tr v-for="m,k in masses"> <th>{{ m }}</th> @@ -91,9 +93,6 @@ </section> </template> <script> - // nodejs node_modules/.bin/poi watch --format umd --moduleName ActivitySpentSynthesis --filename.js ActivitySpentSynthesis.js --dist public/js/oscar/dist public/js/oscar/src/ActivitySpentSynthesis.vue - - export default { props: ['url', 'manageDepense'], @@ -106,7 +105,13 @@ masses: {}, dateUpdated: null, showCuration: false, - affectations: {} + affectations: {}, + + // URL + url_activity: null, + url_sync: null, + url_download: null, + url_spentaffectation: null, } }, diff --git a/public/js/oscar/vite/dist/assets/activityspentsynthesis-6325e583.js b/public/js/oscar/vite/dist/assets/activityspentsynthesis-6325e583.js deleted file mode 100644 index 5efd9d572..000000000 --- a/public/js/oscar/vite/dist/assets/activityspentsynthesis-6325e583.js +++ /dev/null @@ -1 +0,0 @@ -import{a as C,c as n,b as h,w as p,T as m,F as _,f,d as e,g as r,t as i,e as u,o,h as k,v as M,l as D}from"../vendor.js";import{_ as N}from"../vendor3.js";const T={props:{url:{required:!0}},computed:{synthesis(){return this.infos}},data(){return{infos:null,pendingMsg:null,showCuration:!1,masses:[]}},methods:{fetch(){this.pendingMsg="Chargement des données financières",C.get(this.url).then(s=>{console.log("OK",s),this.infos=s.data.synthesis,this.masses=s.data.masses},s=>{console.log(s)}).then(s=>{this.pendingMsg=!1})}},mounted(){this.fetch()}},V={key:0,class:"overlay"},A={class:"overlay-content"},B=e("p",null,"Les comptes suivants ne sont pas qualifiés, vous pouvez utiliser cet écran pour les attribuer à une masse budgétaire :",-1),F={class:"card row"},L={class:"col-md-4"},q={class:"col-md-8"},U=["onUpdate:modelValue","onChange"],j=e("option",{value:"0"},"Ignorer",-1),E=e("option",{value:"1"},"Traiter comme une recette",-1),I=["value"],S=e("hr",null,null,-1),z=e("i",{class:"icon-cancel-circled"},null,-1),O=e("i",{class:"icon-floppy"},null,-1),Q={key:0,class:"alert alert-danger"},R=e("i",{class:"icon-attention-1"},null,-1),H={key:0,class:"alert-warning alert"},K=e("i",{class:"icon-warning-empty"},null,-1),P={key:0,class:"pending"},X={class:""},G=e("i",{class:"icon-spinner animate-spin"},null,-1),J={key:0,class:"table table-condensed"},W=e("tr",null,[e("th",null,"masse"),e("th",{style:{"text-align":"right"}},"Réalisées"),e("th",{style:{"text-align":"right"}},"Engagées")],-1),Y={style:{"text-align":"right","white-space":"nowrap"}},Z={style:{"text-align":"right","white-space":"nowrap"}},x={key:0,style:{"border-top":"solid #000 thin"}},$=e("br",null,null,-1),ee={style:{"font-weight":"300"},class:"error-block"},se=e("i",{class:"icon-attention"},null,-1),te={key:0},ne=e("i",{class:"icon-cog"},null,-1),oe={key:1},le={style:{"text-align":"right","white-space":"nowrap"}},ie={style:{"border-top":"solid #000 thin","font-size":"1.6em"}},ae=e("th",null,"TOTAL : ",-1),re={style:{"text-align":"right","white-space":"nowrap"}},de={style:{"text-align":"right","white-space":"nowrap"}},ue={key:1,class:"table table-condensed"},ce=e("th",null,[e("i",{class:"icon-euro"}),r("Recettes")],-1),he={style:{"text-align":"right","white-space":"nowrap"}},pe={key:0};function me(s,d,g,y,a,l){return o(),n("section",null,[h(m,{name:"fade"},{default:p(()=>[a.showCuration?(o(),n("div",V,[e("div",A,[e("h3",null,[r(" Qualification des comptes "),e("span",{class:"overlay-closer",onClick:d[0]||(d[0]=t=>a.showCuration=!1)},"X")]),B,(o(!0),n(_,null,f(l.synthesis.curations,t=>(o(),n("div",F,[e("div",L,[e("strong",null,i(t.compte),1),r(" - "),e("em",null,i(t.compteInfos.label),1)]),e("div",q,[k(e("select",{name:"",id:"",class:"form-control","onUpdate:modelValue":c=>s.affectations[t.compte]=c,onChange:c=>s.updateAffectations(t.compte,c)},[j,E,(o(!0),n(_,null,f(a.masses,(c,w)=>(o(),n("option",{value:w},i(c),9,I))),256))],40,U),[[M,s.affectations[t.compte]]])])]))),256)),S,e("button",{onClick:d[1]||(d[1]=(...t)=>s.handlerCurationCancel&&s.handlerCurationCancel(...t)),class:"btn btn-danger"},[z,r("Annuler")]),e("button",{onClick:d[2]||(d[2]=(...t)=>s.handlerCurationConfirm&&s.handlerCurationConfirm(...t)),class:"btn btn-success"},[O,r("Enregistrer")])])])):u("",!0)]),_:1}),h(m,{name:"fade"},{default:p(()=>[s.error?(o(),n("div",Q,[R,r(" Il y'a eut un problème lors de la récupération des données financières : "+i(s.error),1)])):u("",!0)]),_:1}),h(m,{name:"fade"},{default:p(()=>[s.warning?(o(),n("div",H,[K,r(" Les données affichées peuvent ne pas être à jour : "+i(s.warning),1)])):u("",!0)]),_:1}),h(m,{name:"fade"},{default:p(()=>[a.pendingMsg?(o(),n("div",P,[e("div",X,[G,r(" "+i(a.pendingMsg),1)])])):u("",!0)]),_:1}),!a.pendingMsg&&l.synthesis!=null?(o(),n("table",J,[W,(o(!0),n(_,null,f(a.masses,(t,c)=>(o(),n("tr",null,[e("th",null,i(t),1),e("td",Y,i(s.$filters.money(l.synthesis.effective_totals[c]))+" €",1),e("td",Z,i(s.$filters.money(l.synthesis.predicted_totals[c]))+" €",1)]))),256)),l.synthesis["N.B"]?(o(),n("tr",x,[e("th",null,[r(" Hors masse"),$,e("small",ee,[se,r(" Les annexes de certains comptes ne sont pas renseignés : "),e("ul",null,[(o(!0),n(_,null,f(s.getNoMasse,t=>(o(),n("li",null,[e("strong",null,i(t),1),l.synthesis.curations?(o(),n("div",te)):u("",!0)]))),256))]),s.manageDepense?(o(),n("a",{key:0,onClick:d[3]||(d[3]=(...t)=>s.handlerCuration&&s.handlerCuration(...t)),class:"btn btn-xs btn-default"},[ne,r("Qualifer les comptes")])):(o(),n("span",oe,"Merci de contacter un administrateur pour que les annexes des comptes soient configurés."))])]),e("td",le,"$filters.money("+i(l.synthesis["N.B"])+") €",1)])):u("",!0),e("tr",ie,[ae,e("td",re,i(s.$filters.money(l.synthesis.effective_total))+" €",1),e("td",de,i(s.$filters.money(l.synthesis.predicted_total))+" €",1)])])):u("",!0),l.synthesis&&l.synthesis.recettes?(o(),n("table",ue,[e("tr",null,[ce,e("td",he,i(s.$filters.money(l.synthesis.recettes.total))+" €",1)])])):u("",!0),e("small",null,[r("Données mise à jour : "),s.dateUpdated?(o(),n("strong",pe,i(s.dateUpdated.date|s.dateFull),1)):u("",!0)])])}const _e=N(T,[["render",me]]),fe={money(s){for(var d=s.toFixed(2),a="",g=!1,y=0,a=[],l=d.length-1;l>=0;l--){var t=d[l];t=="."?(a.push(","),g=!0):(a.push(t),g==!0&&t!="-"&&l>0&&(y++,y%3==0&&a.push(" ")))}return a.reverse().join("")}};let v=document.querySelector("#depenses2");const b=D(_e,{url:v.dataset.url,syncurl:v.dataset.syncurl});b.config.globalProperties.$filters={money:function(s){return fe.money(s)}};b.mount("#depenses2"); diff --git a/public/js/oscar/vite/dist/assets/activityspentsynthesis-641b1fe6.js b/public/js/oscar/vite/dist/assets/activityspentsynthesis-641b1fe6.js new file mode 100644 index 000000000..631e6f93f --- /dev/null +++ b/public/js/oscar/vite/dist/assets/activityspentsynthesis-641b1fe6.js @@ -0,0 +1 @@ +import{a as k,c as o,b as c,w as y,T as f,d as s,F as g,f as m,t as n,e as h,g as r,k as w,o as l,h as M,v as I,l as N}from"../vendor.js";import{_ as B}from"../vendor3.js";const D={props:{url:{required:!0}},computed:{synthesis(){return this.infos}},data(){return{infos:null,pendingMsg:null,showCuration:!1,masses:[],synthesis:null}},methods:{fetch(){this.pendingMsg="Chargement des données financières",k.get(this.url).then(e=>{this.synthesis=e.data.synthesis,this.masses=e.data.masses},e=>{console.log(e)}).then(e=>{this.pendingMsg=!1})}},mounted(){this.fetch()}},T={key:0,class:"overlay"},V={class:"overlay-content"},F=s("p",null,"Les comptes suivants ne sont pas qualifiés, vous pouvez utiliser cet écran pour les attribuer à une masse budgétaire :",-1),A={class:"card row"},R={class:"col-md-4"},U={class:"col-md-8"},j=["onUpdate:modelValue","onChange"],q=s("option",{value:"0"},"Ignorer",-1),E=s("option",{value:"1"},"Traiter comme une recette",-1),L=["value"],S=s("hr",null,null,-1),z=s("i",{class:"icon-cancel-circled"},null,-1),H=s("i",{class:"icon-floppy"},null,-1),P={key:0,class:"alert alert-danger"},Q=s("i",{class:"icon-attention-1"},null,-1),X={key:0,class:"alert-warning alert"},G=s("i",{class:"icon-warning-empty"},null,-1),J={key:0,class:"pending"},K={class:""},O=s("i",{class:"icon-spinner animate-spin"},null,-1),W={key:0,class:"table table-condensed card synthesis"},Y=s("thead",null,[s("tr",null,[s("th",null,"Masse"),s("th",{style:{"text-align":"right"}},"Engagé"),s("th",{style:{"text-align":"right"}},"Réalisé")])],-1),Z=["href"],$={style:{"text-align":"right"}},x={style:{"text-align":"right"}},ss={class:"total"},es=s("th",null,"Total",-1),ts={style:{"text-align":"right"}},ns={style:{"text-align":"right"}},os={key:0},ls=s("small",null,[s("i",{class:"icon-attention"}),r(" Hors-masse")],-1),is={href:"#repport-nb",class:"label label-info"},rs={style:{"text-align":"right"}},as={style:{"text-align":"right"}},ds={key:1},hs=s("h3",null,[s("i",{class:"icon-calculator"}),r("Recettes")],-1),us={key:0,class:"table table-condensed card synthesis"},cs={class:"label label-info xs",href:"#repport-1"},ys={style:{"text-align":"right"}},fs={key:2},_s={key:0},ps=s("i",{class:"icon-eye-off"},null,-1),gs={key:1},ms=s("i",{class:"icon-eye"},null,-1),bs={key:0,class:"table table-condensed card synthesis"},vs={class:"label label-info",href:"#repport-0"},Cs={style:{"text-align":"right"}},ks={key:0};function ws(e,a,_,p,t,u){return l(),o("section",null,[c(f,{name:"fade"},{default:y(()=>[t.showCuration?(l(),o("div",T,[s("div",V,[s("h3",null,[r(" Qualification des comptes "),s("span",{class:"overlay-closer",onClick:a[0]||(a[0]=i=>t.showCuration=!1)},"X")]),F,(l(!0),o(g,null,m(t.synthesis.curations,i=>(l(),o("div",A,[s("div",R,[s("strong",null,n(i.compte),1),r(" - "),s("em",null,n(i.compteInfos.label),1)]),s("div",U,[M(s("select",{name:"",id:"",class:"form-control","onUpdate:modelValue":d=>e.affectations[i.compte]=d,onChange:d=>e.updateAffectations(i.compte,d)},[q,E,(l(!0),o(g,null,m(t.masses,(d,C)=>(l(),o("option",{value:C},n(d),9,L))),256))],40,j),[[I,e.affectations[i.compte]]])])]))),256)),S,s("button",{onClick:a[1]||(a[1]=(...i)=>e.handlerCurationCancel&&e.handlerCurationCancel(...i)),class:"btn btn-danger"},[z,r("Annuler")]),s("button",{onClick:a[2]||(a[2]=(...i)=>e.handlerCurationConfirm&&e.handlerCurationConfirm(...i)),class:"btn btn-success"},[H,r("Enregistrer")])])])):h("",!0)]),_:1}),c(f,{name:"fade"},{default:y(()=>[e.error?(l(),o("div",P,[Q,r(" Il y'a eut un problème lors de la récupération des données financières : "+n(e.error),1)])):h("",!0)]),_:1}),c(f,{name:"fade"},{default:y(()=>[e.warning?(l(),o("div",X,[G,r(" Les données affichées peuvent ne pas être à jour : "+n(e.warning),1)])):h("",!0)]),_:1}),c(f,{name:"fade"},{default:y(()=>[t.pendingMsg?(l(),o("div",J,[s("div",K,[O,r(" "+n(t.pendingMsg),1)])])):h("",!0)]),_:1}),t.synthesis?(l(),o("table",W,[Y,s("tbody",null,[(l(!0),o(g,null,m(t.synthesis.masses,(i,d)=>(l(),o("tr",null,[s("th",null,[s("small",null,n(i),1),s("a",{class:"label label-info xs",href:"#repport-"+d},n(t.synthesis.synthesis[d].nbr_effectue)+" / "+n(t.synthesis.synthesis[d].nbr_engage),9,Z)]),s("td",$,n(e.$filters.money(t.synthesis.synthesis[d].total_engage)),1),s("td",x,n(e.$filters.money(t.synthesis.synthesis[d].total_effectue)),1)]))),256))]),s("tbody",null,[s("tr",ss,[es,s("td",ts,n(e.$filters.money(t.synthesis.synthesis.totaux.engage)),1),s("td",ns,n(e.$filters.money(t.synthesis.synthesis.totaux.effectue)),1)])]),s("tbody",null,[t.synthesis.synthesis["N.B"].total!=0?(l(),o("tr",os,[s("th",null,[ls,s("a",is,n(t.synthesis.synthesis["N.B"].nbr),1)]),s("td",rs,n(e.$filters.money(t.synthesis.synthesis["N.B"].total_engage)),1),s("td",as,n(e.$filters.money(t.synthesis.synthesis["N.B"].total_effectue)),1)])):h("",!0)])])):h("",!0),e.manageRecettes?(l(),o("div",ds,[hs,e.spentlines?(l(),o("table",us,[s("tbody",null,[s("tr",null,[s("th",null,[r("Recette "),s("a",cs,n(t.synthesis.synthesis[1].nbr),1)]),s("td",ys,n(e.$filters.money(t.synthesis.synthesis[1].total)),1)])])])):h("",!0)])):h("",!0),e.manageIgnored&&t.synthesis.synthesis[0].total!=0?(l(),o("div",fs,[s("a",{href:"#",onClick:a[3]||(a[3]=w(i=>e.displayIgnored=!e.displayIgnored,["prevent"]))},[e.displayIgnored?(l(),o("span",_s,[ps,r(" Cacher")])):(l(),o("span",gs,[ms,r(" Montrer")])),r(" les données ignorées ")]),e.spentlines&&e.displayIgnored?(l(),o("table",bs,[s("tbody",null,[s("tr",null,[s("th",null,[r(" Ignorées "),s("a",vs,n(t.synthesis.synthesis[0].nbr),1)]),s("td",Cs,n(e.$filters.money(t.synthesis.synthesis[0].total)),1)])])])):h("",!0)])):h("",!0),s("small",null,[r("Données mise à jour : "),e.dateUpdated?(l(),o("strong",ks,n(e.dateUpdated.date|e.dateFull),1)):h("",!0)])])}const Ms=B(D,[["render",ws]]),Is={money(e){for(var a=e.toFixed(2),t="",_=!1,p=0,t=[],u=a.length-1;u>=0;u--){var i=a[u];i=="."?(t.push(","),_=!0):(t.push(i),_==!0&&i!="-"&&u>0&&(p++,p%3==0&&t.push(" ")))}return t.reverse().join("")}};let b=document.querySelector("#depenses2");const v=N(Ms,{url:b.dataset.url,syncurl:b.dataset.syncurl});v.config.globalProperties.$filters={money:function(e){return Is.money(e)}};v.mount("#depenses2"); diff --git a/public/js/oscar/vite/dist/manifest.json b/public/js/oscar/vite/dist/manifest.json index ff98dcc35..37ae29faa 100644 --- a/public/js/oscar/vite/dist/manifest.json +++ b/public/js/oscar/vite/dist/manifest.json @@ -13,7 +13,7 @@ "file": "vendor3.js" }, "src/ActivitySpentSynthesis.js": { - "file": "assets/activityspentsynthesis-6325e583.js", + "file": "assets/activityspentsynthesis-641b1fe6.js", "imports": [ "_vendor.js", "_vendor3.js" diff --git a/ui/src/ActivitySpentDetails.js b/ui/src/ActivitySpentDetails.js index c2ea63aec..6f6df57cd 100644 --- a/ui/src/ActivitySpentDetails.js +++ b/ui/src/ActivitySpentDetails.js @@ -5,9 +5,7 @@ import MoneyFilter from "./utils/MoneyFilter"; let elemDatas = document.querySelector('#depensesdetails'); const app = createApp(SpentLinePFI, { - "url": elemDatas.dataset.url, - "syncurl": elemDatas.dataset.syncurl, - "informations": elemDatas.dataset.informations + "url": elemDatas.dataset.url }); app.config.globalProperties.$filters = { money: function (value){ diff --git a/ui/src/views/ActivitySpentSynthesis.vue b/ui/src/views/ActivitySpentSynthesis.vue index 35cb6c689..9121231d0 100644 --- a/ui/src/views/ActivitySpentSynthesis.vue +++ b/ui/src/views/ActivitySpentSynthesis.vue @@ -52,48 +52,76 @@ </div> </transition> - <table class="table table-condensed" v-if="!pendingMsg && synthesis != null"> + <table class="table table-condensed card synthesis" v-if="synthesis"> + <thead> <tr> - <th>masse</th> - <th style="text-align: right;">Réalisées</th> - <th style="text-align: right;">Engagées</th> + <th>Masse</th> + <th style="text-align: right">Engagé</th> + <th style="text-align: right">Réalisé</th> </tr> - <tr v-for="m,k in masses"> - <th>{{ m }}</th> - <td style="text-align: right; white-space: nowrap">{{ $filters.money(synthesis['effective_totals'][k]) }} €</td> - <td style="text-align: right; white-space: nowrap">{{ $filters.money(synthesis['predicted_totals'][k]) }} €</td> - </tr> - <tr style="border-top: solid #000 thin" v-if="synthesis['N.B']"> - <th> - Hors masse<br> - <small style="font-weight: 300" class="error-block"><i class="icon-attention"></i> Les annexes de certains comptes ne sont pas renseignés : - <ul> - <li v-for="c in getNoMasse"><strong>{{c}}</strong> - <div v-if="synthesis.curations"> + </thead> - </div> - </li> - </ul> - <a @click="handlerCuration" v-if="manageDepense" class="btn btn-xs btn-default"> <i class="icon-cog"></i>Qualifer les comptes</a> - <span v-else>Merci de contacter un administrateur pour que les annexes des comptes soient configurés.</span> - </small> + <tbody> + <tr v-for="dt,key in synthesis.masses"> + <th> + <small>{{ dt }}</small> + <a class="label label-info xs" :href="'#repport-' + key">{{ synthesis.synthesis[key].nbr_effectue }} / + {{ synthesis.synthesis[key].nbr_engage }}</a> </th> - <td style="text-align: right; white-space: nowrap">{{ $filters.money(synthesis['effective_totals']['N.B']) }} €</td> - <td style="text-align: right; white-space: nowrap">{{ $filters.money(synthesis['predicted_totals']['N.B']) }} €</td> + <td style="text-align: right">{{ $filters.money(synthesis.synthesis[key].total_engage) }}</td> + <td style="text-align: right">{{ $filters.money(synthesis.synthesis[key].total_effectue) }}</td> </tr> - <tr style="border-top: solid #000 thin; font-size: 1.6em"> - <th>TOTAL : </th> - <td style="text-align: right; white-space: nowrap">{{ $filters.money(synthesis['effective_total']) }} €</td> - <td style="text-align: right; white-space: nowrap">{{ $filters.money(synthesis['predicted_total']) }} €</td> + </tbody> + <tbody> + <tr class="total"> + <th>Total</th> + <td style="text-align: right">{{ $filters.money(synthesis.synthesis.totaux.engage) }}</td> + <td style="text-align: right">{{ $filters.money(synthesis.synthesis.totaux.effectue) }}</td> </tr> - </table> - - <table class="table table-condensed" v-if="synthesis && synthesis.recettes"> - <tr> - <th><i class="icon-euro"></i>Recettes</th> - <td style="text-align: right; white-space: nowrap">{{ $filters.money(synthesis.recettes.total) }} €</td> + </tbody> + <tbody> + <tr v-if="synthesis.synthesis['N.B'].total != 0"> + <th> + <small><i class="icon-attention"></i> Hors-masse</small> + <a href="#repport-nb" class="label label-info">{{ synthesis.synthesis['N.B'].nbr}}</a> + </th> + <td style="text-align: right">{{ $filters.money(synthesis.synthesis['N.B'].total_engage) }}</td> + <td style="text-align: right">{{ $filters.money(synthesis.synthesis['N.B'].total_effectue) }}</td> </tr> + </tbody> </table> + + <div v-if="manageRecettes"> + <h3><i class="icon-calculator"></i>Recettes</h3> + <table class="table table-condensed card synthesis" v-if="spentlines"> + <tbody> + <tr> + <th>Recette <a class="label label-info xs" href="#repport-1">{{ synthesis.synthesis['1'].nbr}}</a></th> + <td style="text-align: right">{{ $filters.money(synthesis.synthesis['1'].total)}}</td> + </tr> + </tbody> + </table> + </div> + + <div v-if="manageIgnored && synthesis.synthesis['0'].total != 0"> + <a href="#" @click.prevent="displayIgnored = !displayIgnored"> + <span v-if="displayIgnored"><i class="icon-eye-off"></i> Cacher</span> + <span v-else><i class="icon-eye"></i> Montrer</span> + les données ignorées + </a> + <table class="table table-condensed card synthesis" v-if="spentlines && displayIgnored"> + <tbody> + <tr> + <th> + Ignorées + <a class="label label-info" href="#repport-0">{{ synthesis.synthesis['0'].nbr}}</a> + </th> + <td style="text-align: right">{{ $filters.money(synthesis.synthesis['0'].total)}}</td> + </tr> + </tbody> + </table> + </div> + <small>Données mise à jour : <strong v-if="dateUpdated">{{ dateUpdated.date | dateFull }}</strong></small> </section> </template> @@ -118,7 +146,8 @@ export default { infos: null, pendingMsg: null, showCuration: false, - masses: [] + masses: [], + synthesis: null } }, @@ -127,8 +156,7 @@ export default { this.pendingMsg = "Chargement des données financières"; axios.get(this.url).then( ok => { - console.log("OK",ok); - this.infos = ok.data.synthesis; + this.synthesis = ok.data.synthesis; this.masses = ok.data.masses; }, ko => { diff --git a/ui/src/views/SpentLinePFI.vue b/ui/src/views/SpentLinePFI.vue index 59fa31bbb..abfb59802 100644 --- a/ui/src/views/SpentLinePFI.vue +++ b/ui/src/views/SpentLinePFI.vue @@ -1,477 +1,494 @@ <template> - <section class="spentlines"> - - <transition name="fade"> - <div class="error overlay" v-if="error"> - <div class="overlay-content"> - <i class="icon-warning-empty"></i> - {{ error }} - <br> - <a href="#" @click="error = null" class="btn btn-sm btn-default btn-xs"> - <i class="icon-cancel-circled"></i> - Fermer</a> - </div> - </div> - </transition> - - <transition name="fade"> - <div class="pending overlay" v-if="pendingMsg"> - <div class="overlay-content"> - <i class="icon-spinner animate-spin"></i> - {{ pendingMsg }} - </div> - </div> - </transition> - - <div class="overlay" v-if="editCompte"> - <div class="overlay-content"> - <h3><i class="icon-zoom-in-outline"></i>Modification de la masse : {{ editCompte.code }} - {{ editCompte.label }}</h3> - <hr> - <select name="" v-model="editCompte.annexe"> - <option value="0">Ignoré</option> - <option value="1">Recette</option> - <option :value="m" v-for="masse,m in spentlines.masses">{{ masse }}</option> - </select> - - <button class="btn btn-danger" @click="editCompte = null"><i class="icon-cancel-circled-outline"></i>Annuler</button> - <button class="btn btn-success" @click="handlerAffectationCompte(editCompte)"><i class="icon-valid"></i>Valider</button> - </div> + <section class="spentlines"> + + <transition name="fade"> + <div class="error overlay" v-if="error"> + <div class="overlay-content"> + <i class="icon-warning-empty"></i> + {{ error }} + <br> + <a href="#" @click="error = null" class="btn btn-sm btn-default btn-xs"> + <i class="icon-cancel-circled"></i> + Fermer</a> </div> + </div> + </transition> + + <transition name="fade"> + <div class="pending overlay" v-if="pendingMsg"> + <div class="overlay-content"> + <i class="icon-spinner animate-spin"></i> + {{ pendingMsg }} + </div> + </div> + </transition> + + <div class="overlay" v-if="editCompte"> + <div class="overlay-content"> + <h3><i class="icon-zoom-in-outline"></i>Modification de la masse : {{ editCompte.code }} - {{ editCompte.label + }}</h3> + <hr> + <select name="" v-model="editCompte.annexe"> + <option value="0">Ignoré</option> + <option value="1">Recette</option> + <option :value="m" v-for="masse,m in spentlines.masses">{{ masse }}</option> + </select> + + <button class="btn btn-danger" @click="editCompte = null"><i class="icon-cancel-circled-outline"></i>Annuler + </button> + <button class="btn btn-success" @click="handlerAffectationCompte(editCompte)"><i class="icon-valid"></i>Valider + </button> + </div> + </div> + + <div class="overlay" v-if="details"> + <div class="overlay-content"> + <h3><i class="icon-zoom-in-outline"></i>Détails des entrées comptables</h3> + <button class="btn btn-default" @click="details = null">Fermer</button> + + <table class="list table table-condensed table-bordered table-condensed card"> + <thead> + <tr> + <th>ID</th> + <th>N°SIFAC</th> + <th>Btart</th> + <th>Description</th> + <th>Montant engagé</th> + <th>Montant effectué</th> + <th>Compte Budgetaire</th> + <th>Centre de profit</th> + <th>Compte général</th> + <th>Masse</th> + <th>Date comptable</th> + <th>Date paiement</th> + <th>Année</th> + </tr> + </thead> + <tbody> + <tr class="text-small" v-for="d in details.details"> + <td>{{ d.syncid }}</td> + <td>{{ d.numSifac }}</td> + <td>{{ d.btart }}</td> + <td>{{ d.texteFacture|d.designation }}</td> + <td style="text-align: right">{{ $filters.money(d.montant_engage) }}</td> + <td style="text-align: right">{{ $filters.money(d.montant_effectue) }}</td> + <td>{{ d.compteBudgetaire }}</td> + <td>{{ d.centreFinancier }}</td> + <td><strong>{{ d.compteGeneral }}</strong> : {{ d.type }}</td> + <td><strong>{{ d.masse }}</strong></td> + <td>{{ d.dateComptable }}</td> + <td>{{ d.datePaiement }}</td> + <td>{{ d.dateAnneeExercice }}</td> + </tr> + </tbody> + </table> + </div> + </div> + + <div class="container-fluid"> + + <div class="row"> + <div class="col-md-3"> + <h3> + <i class="icon-help-circled"></i> + Informations + </h3> + <div class="card" v-if="informations"> + <table class="table table-condensed card synthesis" v-if="spentlines"> + <tbody> + <tr> + <th><small>PFI</small></th> + <td style="text-align: right"> + {{ informations.PFI }} + </td> + </tr> + <tr> + <th><small>N°OSCAR</small></th> + <td style="text-align: right"> + {{ informations.numOscar }} + </td> + </tr> + <tr> + <th><small>Montant</small></th> + <td style="text-align: right"> + {{ $filters.money(informations.amount) }} + </td> + </tr> + <tr> + <th><small>Projet</small></th> + <td style="text-align: right"> + <strong>{{ informations.projectacronym }}</strong><br> + <small>{{ informations.project }}</small> + </td> + </tr> + <tr> + <th><small>Activité</small></th> + <td style="text-align: right"> + <small>{{ informations.label }}</small> + </td> + </tr> + </tbody> + </table> + + <a :href="url_activity" v-if="url_activity" class="btn btn-default btn-xs"><i class="icon-cube"></i> Revenir à + l'activité</a> + + <form :action="url_sync" method="post" class="form-inline" v-if="url_sync"> + <input type="hidden" name="action" value="update"/> + <button type="submit" class="btn btn-primary btn-xs"> + <i class="icon-signal"></i> + Mettre à jour les données depuis SIFAC + </button> + </form> + <a :href="urlDownload" class="btn btn-default btn-xs" v-if="urlDownload"> + <i class="icon-download"></i> + Télécharger les données (Excel)</a> + </div> + + + <h3><i class="icon-calculator"></i>Dépenses</h3> + <table class="table table-condensed card synthesis" v-if="spentlines"> + <thead> + <tr> + <th>Masse</th> + <th style="text-align: right">Engagé</th> + <th style="text-align: right">Effectué</th> + </tr> + </thead> + + <tbody> + <tr v-for="dt,key in spentlines.masses"> + <th> + <small>{{ dt }}</small> + <a class="label label-info xs" :href="'#repport-' + key">{{ spentlines.synthesis[key].nbr_effectue }} / + {{ spentlines.synthesis[key].nbr_engage }}</a> + </th> + <td style="text-align: right">{{ $filters.money(spentlines.synthesis[key].total_engage) }}</td> + <td style="text-align: right">{{ $filters.money(spentlines.synthesis[key].total_effectue) }}</td> + </tr> + </tbody> + <tbody> + <tr class="total"> + <th>Total</th> + <td style="text-align: right">{{ $filters.money(spentlines.synthesis.totaux.engage) }}</td> + <td style="text-align: right">{{ $filters.money(spentlines.synthesis.totaux.effectue) }}</td> + </tr> + </tbody> + <tbody> + <tr v-if="spentlines.synthesis['N.B'].total != 0"> + <th> + <small><i class="icon-attention"></i> Hors-masse</small> + <a href="#repport-nb" class="label label-info">{{ spentlines.synthesis['N.B'].nbr}}</a> + </th> + <td style="text-align: right">{{ $filters.money(spentlines.synthesis['N.B'].total_engage) }}</td> + <td style="text-align: right">{{ $filters.money(spentlines.synthesis['N.B'].total_effectue) }}</td> + </tr> + </tbody> + </table> + + <div v-if="manageRecettes"> + <h3><i class="icon-calculator"></i>Recettes</h3> + <table class="table table-condensed card synthesis" v-if="spentlines"> + <tbody> + <tr> + <th>Recette <a class="label label-info xs" href="#repport-1">{{ spentlines.synthesis['1'].nbr}}</a></th> + <td style="text-align: right">{{ $filters.money(spentlines.synthesis['1'].total)}}</td> + </tr> + </tbody> + </table> + </div> + + <div v-if="manageIgnored && spentlines.synthesis['0'].total != 0"> + <a href="#" @click.prevent="displayIgnored = !displayIgnored"> + <span v-if="displayIgnored"><i class="icon-eye-off"></i> Cacher</span> + <span v-else><i class="icon-eye"></i> Montrer</span> + les données ignorées + </a> + <table class="table table-condensed card synthesis" v-if="spentlines && displayIgnored"> + <tbody> + <tr> + <th> + Ignorées + <a class="label label-info" href="#repport-0">{{ spentlines.synthesis['0'].nbr}}</a> + </th> + <td style="text-align: right">{{ $filters.money(spentlines.synthesis['0'].total)}}</td> + </tr> + </tbody> + </table> + </div> + </div> + <div class="col-md-9" style="height: 80vh; overflow-y: scroll"> + + <div v-if="spentlines != null"> + <div v-for="m, k in masses"> + <h3 :id="'repport-' + k">{{ m }}</h3> + <spent-line-p-f-i-grouped + :lines="byMasse.datas[k]" :total="spentlines.synthesis[k].total" + @editcompte="handlerEditCompte" + @detailsline="handlerDetailsLine" + /> + </div> - <div class="overlay" v-if="details"> - <div class="overlay-content"> - <h3><i class="icon-zoom-in-outline"></i>Détails des entrées comptables</h3> - <button class="btn btn-default" @click="details = null">Fermer</button> - - <table class="list table table-condensed table-bordered table-condensed card"> - <thead> - <tr> - <th>ID</th> - <th>N°SIFAC</th> - <th>Btart</th> - <th>Description</th> - <th>Montant engagé</th> - <th>Montant effectué</th> - <th>Compte Budgetaire</th> - <th>Centre de profit</th> - <th>Compte général</th> - <th>Masse</th> - <th>Date comptable</th> - <th>Date paiement</th> - <th>Année</th> - </tr> - </thead> - <tbody> - <tr class="text-small" v-for="d in details.details"> - <td>{{ d.syncid }}</td> - <td>{{ d.numSifac }}</td> - <td>{{ d.btart }}</td> - <td>{{ d.texteFacture|d.designation }}</td> - <td style="text-align: right">{{ $filters.money(d.montant_engage) }}</td> - <td style="text-align: right">{{ $filters.money(d.montant_effectue) }}</td> - <td>{{ d.compteBudgetaire }}</td> - <td>{{ d.centreFinancier }}</td> - <td><strong>{{ d.compteGeneral }}</strong> : {{ d.type }}</td> - <td><strong>{{ d.masse }}</strong></td> - <td>{{ d.dateComptable }}</td> - <td>{{ d.datePaiement }}</td> - <td>{{ d.dateAnneeExercice }}</td> - </tr> - </tbody> - </table> + <div v-if="Object.keys(byMasse.datas['N.B']).length > 0"> + <h3 :id="'repport-nb'">Hors-masse</h3> + <div class="alert alert-warning"> + <i class="icon-attention"></i> Les comptes des entrées suivantes ne sont pas qualifié. + </div> + <spent-line-p-f-i-grouped + :lines="byMasse.datas['N.B']" :total="spentlines.synthesis['N.B'].total" + @editcompte="handlerEditCompte" + @detailsline="handlerDetailsLine" + /> </div> - </div> - <div class="container-fluid"> - - <div class="row"> - <div class="col-md-3"> - <h3> - <i class="icon-help-circled"></i> - Informations - </h3> - <div class="card" v-if="informations"> - <table class="table table-condensed card synthesis" v-if="spentlines"> - <tbody> - <tr> - <th><small>PFI</small></th> - <td style="text-align: right"> - {{ informations.PFI }} - </td> - </tr> - <tr> - <th><small>N°OSCAR</small></th> - <td style="text-align: right"> - {{ informations.numOscar }} - </td> - </tr> - <tr> - <th><small>Montant</small></th> - <td style="text-align: right"> - {{ $filters.money(informations.amount) }} - </td> - </tr> - <tr> - <th><small>Projet</small></th> - <td style="text-align: right"> - <strong>{{ informations.projectacronym }}</strong><br> - <small>{{ informations.project }}</small> - </td> - </tr> - <tr> - <th><small>Activité</small></th> - <td style="text-align: right"> - <small>{{ informations.label }}</small> - </td> - </tr> - </tbody> - </table> - <a :href="urlActivity" v-if="urlActivity" class="btn btn-default btn-xs"><i class="icon-cube"></i> Revenir à l'activité</a> - <form :action="urlSync" method="post" class="form-inline" v-if="urlSync"> - <input type="hidden" name="action" value="update" /> - <button type="submit" class="btn btn-primary btn-xs"> - <i class="icon-signal"></i> - Mettre à jour les données depuis SIFAC - </button> - </form> - <a :href="urlDownload" class="btn btn-default btn-xs" v-if="urlDownload"> - <i class="icon-download"></i> - Télécharger les données (Excel)</a> - </div> - - - <h3><i class="icon-calculator"></i>Dépenses</h3> - <table class="table table-condensed card synthesis" v-if="spentlines"> - <thead> - <tr> - <th>Masse</th> - <th style="text-align: right">Engagé</th> - <th style="text-align: right">Effectué</th> - </tr> - </thead> - - <tbody> - <tr v-for="dt,key in spentlines.masses"> - <th> - <small>{{ dt }}</small> - <a class="label label-info xs" :href="'#repport-' + key">{{ spentlines.synthesis[key].nbr_effectue }} / {{ spentlines.synthesis[key].nbr_engage }}</a> - </th> - <td style="text-align: right">{{ $filters.money(spentlines.synthesis[key].total_engage) }}</td> - <td style="text-align: right">{{ $filters.money(spentlines.synthesis[key].total_effectue) }}</td> - </tr> - </tbody> - <tbody> - <tr class="total"> - <th>Total</th> - <td style="text-align: right">{{ $filters.money(spentlines.synthesis.totaux.engage) }}</td> - <td style="text-align: right">{{ $filters.money(spentlines.synthesis.totaux.effectue) }}</td> - </tr> - </tbody> - <tbody> - <tr v-if="spentlines.synthesis['N.B'].total != 0"> - <th> - <small><i class="icon-attention"></i> Hors-masse</small> - <a href="#repport-nb" class="label label-info">{{ spentlines.synthesis['N.B'].nbr}}</a> - </th> - <td style="text-align: right">{{ $filters.money(spentlines.synthesis['N.B'].total_engage) }}</td> - <td style="text-align: right">{{ $filters.money(spentlines.synthesis['N.B'].total_effectue) }}</td> - </tr> - </tbody> - </table> - - <div v-if="manageRecettes"> - <h3><i class="icon-calculator"></i>Recettes</h3> - <table class="table table-condensed card synthesis" v-if="spentlines"> - <tbody> - <tr> - <th>Recette <a class="label label-info xs" href="#repport-1">{{ spentlines.synthesis['1'].nbr}}</a></th> - <td style="text-align: right">{{ $filters.money(spentlines.synthesis['1'].total)}}</td> - </tr> - </tbody> - </table> - </div> - - <div v-if="manageIgnored && spentlines.synthesis['0'].total != 0"> - <a href="#" @click.prevent="displayIgnored = !displayIgnored"> - <span v-if="displayIgnored"><i class="icon-eye-off"></i> Cacher</span> - <span v-else><i class="icon-eye"></i> Montrer</span> - les données ignorées - </a> - <table class="table table-condensed card synthesis" v-if="spentlines && displayIgnored"> - <tbody> - <tr> - <th> - Ignorées - <a class="label label-info" href="#repport-0">{{ spentlines.synthesis['0'].nbr}}</a> - </th> - <td style="text-align: right">{{ $filters.money(spentlines.synthesis['0'].total)}}</td> - </tr> - </tbody> - </table> - </div> - </div> - <div class="col-md-9" style="height: 80vh; overflow-y: scroll"> - - <div v-if="spentlines != null"> - <div v-for="m, k in masses"> - <h3 :id="'repport-' + k">{{ m }}</h3> - <spent-line-p-f-i-grouped - :lines="byMasse.datas[k]" :total="spentlines.synthesis[k].total" - @editcompte="handlerEditCompte" - @detailsline="handlerDetailsLine" - /> - </div> - - <div v-if="Object.keys(byMasse.datas['N.B']).length > 0"> - <h3 :id="'repport-nb'">Hors-masse</h3> - <div class="alert alert-warning"> - <i class="icon-attention"></i> Les comptes des entrées suivantes ne sont pas qualifié. - </div> - <spent-line-p-f-i-grouped - :lines="byMasse.datas['N.B']" :total="spentlines.synthesis['N.B'].total" - @editcompte="handlerEditCompte" - @detailsline="handlerDetailsLine" - /> - </div> - - <div v-if="manageRecettes && Object.keys(byMasse.datas['recettes']).length > 0"> - <h3 :id="'repport-1'">Recettes</h3> - <spent-line-p-f-i-grouped - :lines="byMasse.datas['recettes']" :total="spentlines.synthesis['1'].total" - @editcompte="handlerEditCompte" - @detailsline="handlerDetailsLine" - /> - </div> - <div v-if="manageIgnored && Object.keys(byMasse.datas['ignorés']).length > 0"> - <h3 :id="'repport-0'">Ignorés</h3> - <spent-line-p-f-i-grouped - :lines="byMasse.datas['ignorés']" :total="spentlines.synthesis['0'].total" - @editcompte="handlerEditCompte" - @detailsline="handlerDetailsLine" - /> - </div> - </div> - </div> + <div v-if="manageRecettes && Object.keys(byMasse.datas['recettes']).length > 0"> + <h3 :id="'repport-1'">Recettes</h3> + <spent-line-p-f-i-grouped + :lines="byMasse.datas['recettes']" :total="spentlines.synthesis['1'].total" + @editcompte="handlerEditCompte" + @detailsline="handlerDetailsLine" + /> + </div> + <div v-if="manageIgnored && Object.keys(byMasse.datas['ignorés']).length > 0"> + <h3 :id="'repport-0'">Ignorés</h3> + <spent-line-p-f-i-grouped + :lines="byMasse.datas['ignorés']" :total="spentlines.synthesis['0'].total" + @editcompte="handlerEditCompte" + @detailsline="handlerDetailsLine" + /> </div> + </div> </div> - </section> + </div> + </div> + </section> </template> <script> - // nodejs node_modules/.bin/poi watch --format umd --moduleName SpentLinePFI --filename.js SpentLinePFI.js --dist public/js/oscar/dist public/js/oscar/src/SpentLinePFI.vue - - import SpentLinePFIGrouped from "./SpentLinePFIGrouped.vue"; - import axios from "axios"; - - export default { - props: [ - 'url', - 'urlSpentAffectation', - 'urlActivity', - 'urlSync', - 'urlDownload', - 'manageRecettes', - 'manageIgnored' - ], - - components: { - SpentLinePFIGrouped +// nodejs node_modules/.bin/poi watch --format umd --moduleName SpentLinePFI --filename.js SpentLinePFI.js --dist public/js/oscar/dist public/js/oscar/src/SpentLinePFI.vue + +import SpentLinePFIGrouped from "./SpentLinePFIGrouped.vue"; +import axios from "axios"; + +export default { + props: [ + 'url' + ], + + components: { + SpentLinePFIGrouped + }, + + data() { + return { + state: "masse", + error: null, + pendingMsg: "", + spentlines: null, + masses: {}, + details: null, + displayIgnored: true, + editCompte: null, + informations: null, + + // + manageRecettes: true, + + // URL + url_activity: null, + url_sync: null, + url_download: null, + url_spentaffectation: null, + } + }, + + computed: { + totalDepenses() { + let total = 0.0; + for (let i in this.spentlines.synthesis) { + if (i != '0' && i != '1') { + total += this.spentlines.synthesis[i].total; + } + } + return total; + }, + + byMasse() { + let out = { + datas: { + 'N.B': {}, + 'recettes': {}, + 'ignorés': {} }, + totaux: { + 'N.B': 0.0, + 'recettes': 0.0, + 'ignorés': 0.0 + } + }; + + for (let k in this.masses) { + out.datas[k] = {}; + out.totaux[k] = 0.0; + } + + if (this.spentlines) { + for (let s in this.spentlines.spents) { + + let line = this.spentlines.spents[s]; + let masse = line.masse; + let btart = line.btart; + + if (masse == '1') masse = 'recettes'; + if (masse == '0') masse = 'ignorés'; + let numPiece = line.numPiece; + + if (!out.datas.hasOwnProperty(masse)) { + masse = 'N.B'; + } + + if (!out.datas[masse].hasOwnProperty(numPiece)) { + out.datas[masse][numPiece] = { + 'ids': [], + 'numpiece': numPiece, + 'numSifac': [], + 'text': [], + 'types': [], + 'montant': 0.0, + 'montant_engage': 0.0, + 'montant_effectue': 0.0, + 'btart': btart, + 'compteBudgetaires': [], + 'comptes': [], + 'masse': [], + 'dateComptable': line.dateComptable, + 'datePaiement': line.datePaiement, + 'annee': line.dateAnneeExercice, + 'refPiece': line.refPiece, + details: [] + }; + } + out.datas[masse][numPiece].details.push(line); + + let text = line.texteFacture; + let designation = line.designation; + let type = line.type; + let compte = line.compteGeneral; + let compteBudgetaire = line.compteBudgetaire; + + if (out.datas[masse][numPiece].numSifac.indexOf(line.numSifac) == -1) { + out.datas[masse][numPiece].numSifac.push(line.numSifac); + } + + out.datas[masse][numPiece].montant += line.montant; + out.datas[masse][numPiece].montant_effectue += line.montant_effectue; + out.datas[masse][numPiece].montant_engage += line.montant_engage; + + + if (text && out.datas[masse][numPiece].text.indexOf(text) < 0) { + out.datas[masse][numPiece].text.push(text); + } + + if (designation && out.datas[masse][numPiece].text.indexOf(designation) < 0) { + out.datas[masse][numPiece].text.push(designation); + } + + if (type && out.datas[masse][numPiece].types.indexOf(type) < 0) { + out.datas[masse][numPiece].types.push(type); + } + + if (compte && out.datas[masse][numPiece].comptes.indexOf(compte) < 0) { + out.datas[masse][numPiece].comptes.push(compte); + } + + if (compteBudgetaire && out.datas[masse][numPiece].compteBudgetaires.indexOf(compteBudgetaire) < 0) { + out.datas[masse][numPiece].compteBudgetaires.push(compteBudgetaire); + } + } + } - data() { - return { - state: "masse", - error: null, - pendingMsg: "", - spentlines: null, - masses: {}, - details: null, - displayIgnored: false, - editCompte: null, - informations: null, + return out; + } + }, + + methods: { + //////////////////////////////////////////////////////////////// + // + // HANDLERS + // + //////////////////////////////////////////////////////////////// + handlerEditCompte(compte) { + this.editCompte = JSON.parse(JSON.stringify(this.spentlines.comptes[compte])); + }, + + handlerDetailsLine(line) { + this.details = line; + }, + + handlerAffectationCompte(compte) { + //$codeCompteFull => $compteAffectation + let affectations = {}; + affectations[compte.codeFull] = compte.annexe; + this.editCompte = null; + this.pendingMsg = "Modification de la masse pour " + compte.codeFull; + + let posted = new FormData(); + posted.append('affectation', JSON.stringify(affectations)); + + axios.post(this.url_spentaffectation, posted).then( + success => { + this.editCompte = null; + this.fetch(); + }, + error => { + if (error.status == 403) { + this.error = "Vous n'avez pas l'autorisation d'accès à ces informations."; + } else { + this.error = error.data } - }, - - computed: { - totalDepenses() { - let total = 0.0; - for (let i in this.spentlines.synthesis) { - if (i != '0' && i != '1') { - total += this.spentlines.synthesis[i].total; - } - } - return total; - }, - - byMasse() { - let out = { - datas: { - 'N.B': {}, - 'recettes': {}, - 'ignorés': {} - }, - totaux: { - 'N.B': 0.0, - 'recettes': 0.0, - 'ignorés': 0.0 - } - }; - - for (let k in this.masses) { - out.datas[k] = {}; - out.totaux[k] = 0.0; - } - - if( this.spentlines ) { - for (let s in this.spentlines.spents) { - - let line = this.spentlines.spents[s]; - let masse = line.masse; - let btart = line.btart; - - if( masse == '1' ) masse = 'recettes'; - if( masse == '0' ) masse = 'ignorés'; - let numPiece = line.numPiece; - - if( !out.datas.hasOwnProperty(masse) ){ - masse = 'N.B'; - } - - if( !out.datas[masse].hasOwnProperty(numPiece) ){ - out.datas[masse][numPiece] = { - 'ids': [], - 'numpiece': numPiece, - 'numSifac': [], - 'text': [], - 'types': [], - 'montant': 0.0, - 'montant_engage': 0.0, - 'montant_effectue': 0.0, - 'btart': btart, - 'compteBudgetaires': [], - 'comptes': [], - 'masse': [], - 'dateComptable': line.dateComptable, - 'datePaiement': line.datePaiement, - 'annee': line.dateAnneeExercice, - 'refPiece': line.refPiece, - details: [] - }; - } - out.datas[masse][numPiece].details.push(line); - - let text = line.texteFacture; - let designation = line.designation; - let type = line.type; - let compte = line.compteGeneral; - let compteBudgetaire = line.compteBudgetaire; - - if( out.datas[masse][numPiece].numSifac.indexOf(line.numSifac) == -1 ){ - out.datas[masse][numPiece].numSifac.push(line.numSifac); - } - - out.datas[masse][numPiece].montant += line.montant; - out.datas[masse][numPiece].montant_effectue += line.montant_effectue; - out.datas[masse][numPiece].montant_engage += line.montant_engage; - - - if( text && out.datas[masse][numPiece].text.indexOf(text) < 0 ){ - out.datas[masse][numPiece].text.push(text); - } - - if( designation && out.datas[masse][numPiece].text.indexOf(designation) < 0 ){ - out.datas[masse][numPiece].text.push(designation); - } - - if( type && out.datas[masse][numPiece].types.indexOf(type) < 0 ){ - out.datas[masse][numPiece].types.push(type); - } - - if( compte && out.datas[masse][numPiece].comptes.indexOf(compte) < 0 ){ - out.datas[masse][numPiece].comptes.push(compte); - } - - if( compteBudgetaire && out.datas[masse][numPiece].compteBudgetaires.indexOf(compteBudgetaire) < 0 ){ - out.datas[masse][numPiece].compteBudgetaires.push(compteBudgetaire); - } - } - } - - return out; + this.pendingMsg = ""; + } + ); + }, + + //////////////////////////////////////////////////////////////// + // + // OPERATIONS REST + // + //////////////////////////////////////////////////////////////// + + /** + * Chargement des jalons depuis l'API + */ + fetch() { + this.pendingMsg = "Chargement des dépense"; + + axios.get(this.url).then( + success => { + this.masses = success.data.spents.masses; + this.spentlines = success.data.spents; + this.informations = success.data.spents.informations; + + this.url_sync = success.data.spents.url_sync; + this.url_activity = success.data.spents.url_activity; + this.url_spentaffectation = success.data.spents.url_spentaffectation; + this.url_download = success.data.spents.url_download; + }, + error => { + if (error.status == 403) { + this.error = "Vous n'avez pas l'autorisation d'accès à ces informations."; + } else { + this.error = "Impossible de charger les dépenses pour ce PFI : " + error.data } - }, - - methods: { - //////////////////////////////////////////////////////////////// - // - // HANDLERS - // - //////////////////////////////////////////////////////////////// - handlerEditCompte(compte){ - this.editCompte = JSON.parse(JSON.stringify(this.spentlines.comptes[compte])); - }, - - handlerDetailsLine(line){ - this.details = line; - }, - - handlerAffectationCompte(compte){ - //$codeCompteFull => $compteAffectation - let affectations = {}; - affectations[compte.codeFull] = compte.annexe; - this.editCompte = null; - this.pendingMsg = "Modification de la masse pour " + compte.codeFull; - console.log(this.pendingMsg); - - axios.post(this.urlSpentAffectation, {'affectation': affectations }).then( - success => { - this.editCompte = null; - this.fetch(); - }, - error => { - if( error.status == 403 ){ - this.error = "Vous n'avez pas l'autorisation d'accès à ces informations."; - } else { - this.error = error.data - } - this.pendingMsg = ""; - } - ); - }, - - //////////////////////////////////////////////////////////////// - // - // OPERATIONS REST - // - //////////////////////////////////////////////////////////////// - - /** - * Chargement des jalons depuis l'API - */ - fetch() { - this.pendingMsg = "Chargement des dépense"; - - axios.get(this.url).then( - success => { - this.masses = success.data.spents.masses; - this.spentlines = success.data.spents; - this.informations = success.data.spents.informations; - }, - error => { - if (error.status == 403) { - this.error = "Vous n'avez pas l'autorisation d'accès à ces informations."; - } else { - this.error = "Impossible de charger les dépenses pour ce PFI : " + error.data - } - } - ).then(n => { - this.pendingMsg = ""; - }); - }, - }, - - mounted() { - this.fetch() - } - } + } + ).then(n => { + this.pendingMsg = ""; + }); + }, + }, + + mounted() { + this.fetch() + } +} </script> \ No newline at end of file -- GitLab