From ef8aaa7fd72f7825deb4703d0a6c9d7a9d49adf3 Mon Sep 17 00:00:00 2001 From: Christophe Battarel Date: Tue, 29 Jun 2021 15:26:04 +0200 Subject: [PATCH] generate intervention from time spent --- htdocs/core/class/html.form.class.php | 166 ++++++++++++++++++++++++++ htdocs/langs/en_US/interventions.lang | 1 + htdocs/langs/en_US/projects.lang | 7 +- htdocs/langs/fr_FR/interventions.lang | 1 + htdocs/langs/fr_FR/projects.lang | 7 +- htdocs/projet/tasks/time.php | 134 +++++++++++++++++++-- 6 files changed, 307 insertions(+), 9 deletions(-) diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index b3ed477596c..9e1bcb34251 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -8741,6 +8741,172 @@ class Form return $out; } + /** + * Output a combo list with interventions qualified for a third party + * + * @param int $socid Id third party (-1=all, 0=only projects not linked to a third party, id=projects not linked or linked to third party id) + * @param int $selected Id intervention preselected + * @param string $htmlname Name of HTML select + * @param int $maxlength Maximum length of label + * @param int $option_only Return only html options lines without the select tag + * @param string $show_empty Add an empty line ('1' or string to show for empty line) + * @param int $discard_closed Discard closed projects (0=Keep,1=hide completely,2=Disable) + * @param int $forcefocus Force focus on field (works with javascript only) + * @param int $disabled Disabled + * @param string $morecss More css added to the select component + * @param string $projectsListId ''=Automatic filter on project allowed. List of id=Filter on project ids. + * @param string $showproject 'all' = Show project info, ''=Hide project info + * @param User $usertofilter User object to use for filtering + * @return int Nbr of project if OK, <0 if KO + */ + public function selectIntervention($socid = -1, $selected = '', $htmlname = 'interid', $maxlength = 24, $option_only = 0, $show_empty = '1', $discard_closed = 0, $forcefocus = 0, $disabled = 0, $morecss = 'maxwidth500', $projectsListId = '', $showproject = 'all', $usertofilter = null) + { + global $user, $conf, $langs; + + require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php'; + + if (is_null($usertofilter)) + { + $usertofilter = $user; + } + + $out = ''; + + $hideunselectables = false; + if (!empty($conf->global->PROJECT_HIDE_UNSELECTABLES)) $hideunselectables = true; + + if (empty($projectsListId)) + { + if (empty($usertofilter->rights->projet->all->lire)) + { + $projectstatic = new Project($this->db); + $projectsListId = $projectstatic->getProjectsAuthorizedForUser($usertofilter, 0, 1); + } + } + + // Search all projects + $sql = 'SELECT i.rowid, i.ref as ref, p.fk_soc, p.fk_statut, p.public,'; + $sql .= ' s.nom as name'; + $sql .= ' FROM '.MAIN_DB_PREFIX.'projet as p'; + $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'societe as s ON s.rowid = p.fk_soc,'; + $sql .= ' '.MAIN_DB_PREFIX.'fichinter as i'; + $sql .= " WHERE p.entity IN (".getEntity('project').")"; + $sql .= " AND i.fk_projet = p.rowid AND i.fk_statut=0"; //Brouillons seulement + if ($projectsListId) $sql.= " AND p.rowid IN (".$projectsListId.")"; + if ($socid == 0) $sql.= " AND (p.fk_soc=0 OR p.fk_soc IS NULL)"; + if ($socid > 0) $sql.= " AND (p.fk_soc=".$socid." OR p.fk_soc IS NULL)"; + $sql .= " GROUP BY i.ref ORDER BY p.ref, i.ref ASC"; + + $resql = $this->db->query($sql); + if ($resql) + { + // Use select2 selector + if (!empty($conf->use_javascript_ajax)) + { + include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php'; + $comboenhancement = ajax_combobox($htmlname, '', 0, $forcefocus); + $out .= $comboenhancement; + $morecss = 'minwidth200imp maxwidth500'; + } + + if (empty($option_only)) { + $out .= ''; + } + + print $out; + + $this->db->free($resql); + return $num; + } + else + { + dol_print_error($this->db); + return -1; + } + } + /** * Output a combo list with invoices qualified for a third party * diff --git a/htdocs/langs/en_US/interventions.lang b/htdocs/langs/en_US/interventions.lang index 51079fca278..ff5de8b1b3e 100644 --- a/htdocs/langs/en_US/interventions.lang +++ b/htdocs/langs/en_US/interventions.lang @@ -66,3 +66,4 @@ RepeatableIntervention=Template of intervention ToCreateAPredefinedIntervention=To create a predefined or recurring intervention, create a common intervention and convert it into intervention template Reopen=Reopen ConfirmReopenIntervention=Are you sure you want to open back the intervention %s? +GenerateInter=Generate intervention diff --git a/htdocs/langs/en_US/projects.lang b/htdocs/langs/en_US/projects.lang index 2fda7e1df0e..0bc78b8c62d 100644 --- a/htdocs/langs/en_US/projects.lang +++ b/htdocs/langs/en_US/projects.lang @@ -241,6 +241,7 @@ LatestModifiedProjects=Latest %s modified projects OtherFilteredTasks=Other filtered tasks NoAssignedTasks=No assigned tasks found (assign project/tasks to the current user from the top select box to enter time on it) ThirdPartyRequiredToGenerateInvoice=A third party must be defined on project to be able to invoice it. +ThirdPartyRequiredToGenerateInvoice=A third party must be defined on project to be able to create intervention. ChooseANotYetAssignedTask=Choose a task not yet assigned to you # Comments trans AllowCommentOnTask=Allow user comments on tasks @@ -252,10 +253,12 @@ SendProjectRef=Information project %s ModuleSalaryToDefineHourlyRateMustBeEnabled=Module 'Salaries' must be enabled to define employee hourly rate to have time spent valorized NewTaskRefSuggested=Task ref already used, a new task ref is required TimeSpentInvoiced=Time spent billed +TimeSpentForIntervention=Time spent TimeSpentForInvoice=Time spent OneLinePerUser=One line per user ServiceToUseOnLines=Service to use on lines InvoiceGeneratedFromTimeSpent=Invoice %s has been generated from time spent on project +InterventionGeneratedFromTimeSpent=Intervention %s has been generated from time spent on project ProjectBillTimeDescription=Check if you enter timesheet on tasks of project AND you plan to generate invoice(s) from the timesheet to bill the customer of the project (do not check if you plan to create invoice that is not based on entered timesheets). Note: To generate invoice, go on tab 'Time spent' of the project and select lines to include. ProjectFollowOpportunity=Follow opportunity ProjectFollowTasks=Follow tasks or time spent @@ -264,7 +267,9 @@ UsageOpportunity=Usage: Opportunity UsageTasks=Usage: Tasks UsageBillTimeShort=Usage: Bill time InvoiceToUse=Draft invoice to use +InterToUse=Draft intervention to use NewInvoice=New invoice +NewInter=New intervention OneLinePerTask=One line per task OneLinePerPeriod=One line per period OneLinePerTimeSpentLine=One line for each time spent declaration @@ -274,4 +279,4 @@ AddPersonToTask=Add also to tasks UsageOrganizeEvent=Usage: Event Organization PROJECT_CLASSIFY_CLOSED_WHEN_ALL_TASKS_DONE=Classify project as closed when all its tasks are completed (100%% progress) PROJECT_CLASSIFY_CLOSED_WHEN_ALL_TASKS_DONE_help=Note: existing projects with all tasks at 100 %% progress won't be affected: you will have to close them manually. This option only affects open projects. -SelectLinesOfTimeSpentToInvoice=Select lines of time spent that are unbilled, then bulk action "Generate Invoice" to bill them \ No newline at end of file +SelectLinesOfTimeSpentToInvoice=Select lines of time spent that are unbilled, then bulk action "Generate Invoice" to bill them diff --git a/htdocs/langs/fr_FR/interventions.lang b/htdocs/langs/fr_FR/interventions.lang index 43722fe2bfd..50c24662069 100644 --- a/htdocs/langs/fr_FR/interventions.lang +++ b/htdocs/langs/fr_FR/interventions.lang @@ -64,3 +64,4 @@ InterLineDuration=Durée ligne intervention InterLineDesc=Description ligne intervention RepeatableIntervention=Modèle d'intervention ToCreateAPredefinedIntervention=Pour créer une intervention prédéfinie ou récurrente, créez une intervention standard et convertissez-la en modèle d'intervention +GenerateInter=Générer une fiche d'intervention diff --git a/htdocs/langs/fr_FR/projects.lang b/htdocs/langs/fr_FR/projects.lang index 6b342e4eb88..d574362118a 100644 --- a/htdocs/langs/fr_FR/projects.lang +++ b/htdocs/langs/fr_FR/projects.lang @@ -241,6 +241,7 @@ LatestModifiedProjects=Les %s derniers projets modifiés OtherFilteredTasks=Autres tâches filtrées NoAssignedTasks=Aucune tâche assignée (assignez un projet/tâche à l'utilisateur depuis la liste déroulante utilisateur en haut pour pouvoir saisir du temps dessus) ThirdPartyRequiredToGenerateInvoice=Un tiers doit être défini sur le projet pour pouvoir le facturer. +ThirdPartyRequiredToGenerateIntervention=Un tiers doit être défini sur le projet pour pouvoir créer une intervention. ChooseANotYetAssignedTask=Choisissez une tâche qui ne vous est pas encore assignée # Comments trans AllowCommentOnTask=Autoriser les utilisateurs à ajouter des commentaires sur les tâches @@ -253,18 +254,22 @@ ModuleSalaryToDefineHourlyRateMustBeEnabled=Le module 'Paiement des salaires des NewTaskRefSuggested=Réf de tâche déjà utilisée, une nouvelle référence de tâche est requise TimeSpentInvoiced=Temps passé facturé TimeSpentForInvoice=Temps consommés +TimeSpentForIntervention=Temps consommés OneLinePerUser=Une ligne par utilisateur ServiceToUseOnLines=Service à utiliser sur les lignes InvoiceGeneratedFromTimeSpent=La facture %s a été générée à partir du temps passé sur le projet +InterventionGeneratedFromTimeSpent=L'intervention %s a été générée à partir du temps passé sur le projet ProjectBillTimeDescription=Cochez si vous saisissez du temps sur les tâches du projet ET prévoyez de générer des factures à partir des temps pour facturer le client du projet (ne cochez pas si vous comptez créer une facture qui n'est pas basée sur la saisie des temps). Note: Pour générer une facture, aller sur l'onglet 'Temps consommé' du project et sélectionnez les lignes à inclure. -ProjectFollowOpportunity=Suivre une opportunité +ProjectFollowOpportunity=Suivre une opportunité ProjectFollowTasks=Suivre des tâches ou du temps passé Usage=Usage UsageOpportunity=Utilisation: Opportunité UsageTasks=Utilisation: Tâches UsageBillTimeShort=Utilisation: Facturation du temps InvoiceToUse=Facture brouillon à utiliser +InterToUse=Intervention brouillon à utiliser NewInvoice=Nouvelle facture +NewInter=Nouvelle intervention OneLinePerTask=Une ligne par tâche OneLinePerPeriod=Une ligne par période OneLinePerTimeSpentLine=One line for each time spent declaration diff --git a/htdocs/projet/tasks/time.php b/htdocs/projet/tasks/time.php index a9316bcedcd..62890b5b749 100644 --- a/htdocs/projet/tasks/time.php +++ b/htdocs/projet/tasks/time.php @@ -5,7 +5,7 @@ * Copyright (C) 2011 Juanjo Menent * Copyright (C) 2018 Ferran Marcet * Copyright (C) 2018 Frédéric France - * Copyright (C) 2019 Christophe Battarel + * Copyright (C) 2019-2021 Christophe Battarel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -115,7 +115,7 @@ $extrafields->fetch_name_optionals_label($object->table_element); if (GETPOST('cancel', 'alpha')) { $action = ''; } -if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend' && $massaction != 'confirm_generateinvoice') { +if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend' && $massaction != 'confirm_generateinvoice' && $massaction != 'confirm_generateinter') { $massaction = ''; } @@ -536,6 +536,90 @@ if ($action == 'confirm_generateinvoice') { } } +if ($action == 'confirm_generateinter') +{ + $langs->load('interventions'); + + if (!empty($projectstatic->socid)) $projectstatic->fetch_thirdparty(); + + if (!($projectstatic->thirdparty->id > 0)) { + setEventMessages($langs->trans("ThirdPartyRequiredToGenerateIntervention"), null, 'errors'); + } else { + include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; + include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php'; + include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; + + + require_once DOL_DOCUMENT_ROOT.'/fichinter/class/fichinter.class.php'; + $tmpinter = new Fichinter($db); + $tmptimespent = new Task($db); + $fuser = new User($db); + + $db->begin(); + $interToUse = GETPOST('interid', 'int'); + + + $tmpinter->socid = $projectstatic->thirdparty->id; + $tmpinter->date = dol_mktime(GETPOST('rehour', 'int'), GETPOST('remin', 'int'), GETPOST('resec', 'int'), GETPOST('remonth', 'int'), GETPOST('reday', 'int'), GETPOST('reyear', 'int')); + $tmpinter->fk_project = $projectstatic->id; + $tmpinter->description = $projectstatic->title . ( ! empty($projectstatic->description) ? '-' . $projectstatic->label : '' ); + + if ($interToUse) { + $tmpinter->fetch($interToUse); + } + else { + $result = $tmpinter->create($user); + if ($result <= 0) + { + $error++; + setEventMessages($tmpinter->error, $tmpinter->errors, 'errors'); + } + } + + if (!$error) + { + $arrayoftasks = array(); + foreach ($toselect as $key => $value) + { + // Get userid, timepent + $object->fetchTimeSpent($value); + // $object->id is the task id + $arrayoftasks[$object->timespent_id]['timespent'] = $object->timespent_duration; + $arrayoftasks[$object->timespent_id]['totalvaluetodivideby3600'] = $object->timespent_duration * $object->timespent_thm; + $arrayoftasks[$object->timespent_id]['note'] = $object->timespent_note; + $arrayoftasks[$object->timespent_id]['date'] = date('Y-m-d H:i:s', $object->timespent_datehour); + } + + foreach ($arrayoftasks as $timespent_id => $value) + { + $ftask = new Task($db); + $ftask->fetch($object->id); + // Define qty per hour + $qtyhour = $value['timespent'] / 3600; + $qtyhourtext = convertSecondToTime($value['timespent'], 'all', $conf->global->MAIN_DURATION_OF_WORKDAY); + + // Add lines + $lineid = $tmpinter->addline($user, $tmpinter->id, $ftask->label . ( ! empty($value['note']) ? ' - ' . $value['note'] : '' ), $value['date'], $value['timespent']); + } + } + + if (!$error) + { + $urltointer = $tmpinter->getNomUrl(0); + $mesg = $langs->trans("InterventionGeneratedFromTimeSpent", '{s1}'); + $mesg = str_replace('{s1}', $urltointer, $mesg); + setEventMessages($mesg, null, 'mesgs'); + + //var_dump($tmpinvoice); + + $db->commit(); + } + else + { + $db->rollback(); + } + } +} /* * View @@ -749,12 +833,16 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0) { 'generateinvoice'=>$langs->trans("GenerateBill"), //'builddoc'=>$langs->trans("PDFMerge"), ); - //if ($user->rights->projet->creer) $arrayofmassactions['predelete']=''.$langs->trans("Delete"); - if (in_array($massaction, array('presend', 'predelete', 'generateinvoice'))) { - $arrayofmassactions = array(); - } - $massactionbutton = $form->selectMassAction('', $arrayofmassactions); } + if ( ! empty($conf->ficheinter->enabled) && $user->rights->ficheinter->creer) { + $langs->load("interventions"); + $arrayofmassactions['generateinter'] = $langs->trans("GenerateInter"); + } + //if ($user->rights->projet->creer) $arrayofmassactions['predelete']=''.$langs->trans("Delete"); + if (in_array($massaction, array('presend', 'predelete', 'generateinvoice', 'generateinter'))) { + $arrayofmassactions = array(); + } + $massactionbutton = $form->selectMassAction('', $arrayofmassactions); // Show section with information of task. If id of task is not defined and project id defined, then $projectidforalltimes is not empty. if (empty($projectidforalltimes)) { @@ -944,6 +1032,8 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0) { print ''; } elseif ($massaction == 'generateinvoice' && $user->rights->facture->lire) { print ''; + } elseif ($massaction == 'generateinter' && $user->rights->ficheinter->lire) { + print ''; } else { print ''; } @@ -1026,6 +1116,36 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0) { print ''; $massaction = ''; } + } elseif ($massaction == 'generateinter') { + // Form to convert time spent into invoice + print ''; + + if ($projectstatic->thirdparty->id > 0) { + print ''; + print ''; + print ''; + print ''; + print ''; + print '
'; + print $langs->trans('InterToUse'); + print ''; + $form->selectIntervention($projectstatic->thirdparty->id, '', 'interid', 24, 0, $langs->trans('NewInter'), + 1, 0, 0, 'maxwidth500', '', 'all'); + print '
'; + + print '
'; + print '
'; + print ' '; + print ''; + print '
'; + print '
'; + } else { + print '
'.$langs->trans("ThirdPartyRequiredToGenerateInter").'
'; + print '
'; + print ''; + print '
'; + $massaction = ''; + } } /*