Création rapport feuille de pointage par projet

Bonjour, lors d’une facturation, je voudrais pouvoir imprimer une feuille récapitulative des pointages liés à ce projet (plusieurs personnes ont pointé avec plusieurs feuilles de pointage sans passer un temps plein).
Je voudrais avoir le jour de travail, le nom de la personne, son poste et le nombre d’heure passé dans la journée avec le total en bas de page.
J’ai regardé les rapports SQL et rapports script mais je ne comprend pas très bien comment ils marchent.
Dans le cas d’usage, il y a “tabEvent” et je ne comprend pas bien d’où il sort et l’engrenage avec les column au dessus (est ce qu’elles doivent avoir le même nom que les données des DocType).

Merci pour vos Ă©clairages.
Pierre

Bonjour Pierre,

Voici une proposition, Ă©tape par Ă©tape.
J’espère que ça complètera la documentation!

Rapport de requĂŞte (SQL):

  1. On crée un nouveau rapport, de type Rapport de Requête avec un joli nom.
    Je choisi un document de référence, qui permettra de placer le rapport dans le module correspondant à ce document et de conditionner les autorisations associées à ce rapport (modifiables dans les Autorisations du rôle pour la page et le rapport)

  2. On souhaite obtenir les pointages liés au projet associé à une facture. Il faut donc qu’on puisse filtrer nos données soit par projet, soit par facture.
    Je décide d’ajouter un filtre permettant de sélectionner une facture de vente:

Mon filtre est un champ de type Lien, qui permet de récupérer tous les documents liés. J’indique Sales Invoice dans les options pour faire cette liaison.
Le nom du filtre peut ĂŞtre celui que je veux. Il me servira de variable dans ma requĂŞte.

  1. J’ajoute des colonnes pour les données que je souhaite obtenir:

Les noms des champs peuvent être ce que vous voulez. Il faudra juste formatter la requête pour renvoyer une donnée avec le même nom.
Ici j’ai mis Lien pour le champ employee et Employee dans la case Options pour que ça fasse un lien vers la fiche Employé lors de la génération du rapport.

  1. J’écris ma requête:
SELECT DATE_FORMAT(td.from_time, '%%Y-%%m-%%d') as workday,
e.employee_name, e.designation,
td.hours as working_time
FROM `tabSales Invoice` si
LEFT JOIN `tabTimesheet Detail` td
ON td.project = si.project
RIGHT JOIN `tabTimesheet` t
ON td.parent = t.name
LEFT JOIN `tabEmployee` e
ON e.name = t.employee
WHERE si.name = %(sales_invoice)s
  • Je cherche Ă  obtenir les champs from_time et hours depuis la table Timesheet Details (la table enfant du type de document Feuille de temps), designation et employee_name (car employee dans la table Timesheet ne donne que son identifiant) depuis la table employee
  • Je vais donc requĂŞter d’abord la table Sales Invoice pour rĂ©cupĂ©rer le champ project me permettant d’obtenir toutes les lignes liĂ©es Ă  ce projet dans la table Timesheet Details.
  • Puis je rĂ©cupère l’identifiant de l’employĂ© correspondant depuis la table Timesheet grâce au champ parent disponible dans toutes les tables enfant.
  • Je rĂ©cupère les champs employee_name et designation dans la fiche EmployĂ© de cet employĂ©
  • Enfin, je filtre cette requĂŞte sur la facture sĂ©lectionnĂ©e dans mon filtre

Rapport de script (Python)

L’équivalent en Python.
Il faudrait optimiser le script en ne faisant qu’une seule requête pour les employés, sinon le résultat est le même qu’au dessus.

project = frappe.db.get_value("Sales Invoice", filters.sales_invoice, "project")

timesheet_details = frappe.get_all("Timesheet Detail", filters={"project": project}, fields=["name", "parent", "hours", "from_time"])

result = []
for timesheet_detail in timesheet_details:
    employee_id = frappe.db.get_value("Timesheet", timesheet_detail.parent, "employee")
    employee = frappe.db.get_value("Employee", employee_id, ["employee_name", "designation"], as_dict=True)
    result.append(
        {
            "workday": frappe.utils.getdate(timesheet_detail.from_time),
            "employee": employee.employee_name,
            "designation": employee.designation,
            "working_time": timesheet_detail.hours
        }
    )

Voici aussi un exemple d’ajout de tableau directement dans la facture, via un champ HTML personnalisé et un script en Jinja:

<div>
    <table class="table table-bordered table-condensed">
        <thead>
            <tr>
                <th>Jour de travail</th>
                <th>Nom de la personne</th>
                <th>Poste</th>
                <th>Nombre d'heures passées dans la journée</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                {% for td in frappe.get_all("Timesheet Detail", filters={"project": doc.project}, fields=["name", "parent", "hours", "from_time"]) %}
                    {% set employee_id = frappe.db.get_value("Timesheet", td.parent, "employee") %}
                    {% set employee = frappe.db.get_value("Employee", employee_id, ["employee_name", "designation"], as_dict=True) %}
                    <td>
                        <div class="value">{{ frappe.utils.format_date(td.from_time) }}</div>
                    </td>
                    <td>
                        <div class="value">{{ employee.employee_name or "" }}</div>
                    </td>
                    <td>
                        <div class="value">{{ employee.designation or "" }}</div>
                    </td>
                    <td>
                        <div class="value">{{ td.hours }}</div>
                    </td>
                {% endfor %}
            </tr>
        </tbody>
    </table>
</div>

Voici ce que ça donne:

Bon week-end !

1 « J'aime »

Super, merci c’est tout à fait ce que je cherchais.

Afin que le tableau soit plus léger je souhaite regrouper dans les heures passé dans la journée même si j’ai 2 enregistrements distincts. Un petit traitement de la liste en python devrait faire l’affaire (je ne sais pas si c’est faisable en sql).
Par contre y a t il une solution « console Â» qui permette facilement de dĂ©bugger le script et d’utiliser un print pour afficher directement les variables. Par exemple : « print(timeshee_details) Â» qui permet plus facilement de faire les requettes.

Deuxième question : comment intégrer ce rapport dans le doctype de vente, ou du moins le mettre plus en forme?

Merci d’avance
[/quote]

Super, merci c’est tout à fait ce que je cherchais.

Afin que le tableau soit plus léger je souhaite regrouper dans les heures passé dans la journée même si j’ai 2 enregistrements distincts. Un petit traitement de la liste en python devrait faire l’affaire (je ne sais pas si c’est faisable en sql).
Par contre y a t il une solution « console Â» qui permette facilement de dĂ©bugger le script et d’utiliser un print pour afficher directement les variables. Par exemple : « print(timeshee_details) Â» qui permet plus facilement de faire les requettes. Peux-t-on sortir aussi le nombre d’heure cumulĂ© par personne dans chaque rapport ?

Deuxième question : comment intégrer ce rapport dans le doctype de vente, ou du moins le mettre plus en forme?

PS: le liens avec la fiche employé ne se fait pas. Car le chemin renvoyé est avec le nom et non l’id:
image faut-il créer un autre column avec employee_id et le liens ou y a t il une autre solution.

Merci d’avance

Bonjour @oryxr,

Si tu es auto-hébergé, tu peux ajouter la clé/valeur "logging": 1 dans site_config.json, ça permet d’utiliser l’utilitaire log() dans les rapports et ça imprimera le résultat dans la console du navigateur.
Sinon tu peux utiliser frappe.log_error() qui crée un journal d’erreur.

Pour grouper en SQL, il est possible d’utiliser GROUP BY (exemple: SQL GROUP BY - SQL)

Pour la seconde question, tu souhaites l’ajouter dans le module “Ventes” ? Il est possible de personnaliser l’espace de travail “Ventes” pour l’ajouter en raccourci par exemple.

Merci @chdecultot pour toutes ces infos.
Pour la deuxième question, j’ai oublié de joindre l’impression écran, je souhaiterai avoir un rendu similaire au rapport directement dans la liste de Feuille de Temps avec en plus le montant.



Cela permettra de mieux visualiser les temps passé.

Merci

Bonjour @oryxr,

Il est possible de personnaliser la table enfant “Liste de Feuille de Temps” en effectuant les opérations suivantes:

  • Depuis le menu Personnalisation de formulaire, tu peux ajouter un champ “Poste” faisant rĂ©fĂ©rence au champ designation de la fiche EmployĂ©:

Ca permettra d’avoir accès directement à la donnée dans la feuille de temps.

  • Ensuite, il faut personnaliser le formulaire “Feuille de temps de la facture d’achat” (accessible en cliquant sur Personnaliser depuis une facture puis Personnaliser une table enfant).
    Tu peux ajouter les champs qu’il te faut dans ton formulaire, par exemple:

On a accès à un lien vers la feuille de temps via le champ time_sheet, on peut donc récupérer le nom de l’employé et le poste directement.

Condition à ajouter dans le champ Récupérer depuis le champ:
Employé: time_sheet.employee_name
Poste: time_sheet.designation

Pour la date, c’est différent. On a accès à l’identifiant de la ligne de la feuille de temps via le champ timesheet_detail, mais ce n’est pas un lien, donc la fonctionnalité Récupérer depuis le champ ne pourra pas fonctionner.

On peut, par contre, ajouter un script python qui récupère la date et l’heure de début et enregistre la date dans le formulaire:

Le déclenchement peut se faire Avant validation par exemple:

for td in doc.timesheets:
    td.day = frappe.utils.getdate(
        frappe.db.get_value("Timesheet Detail", td.timesheet_detail, "from_time")
    )

Exemple:

Il ne reste plus qu’à jouer sur l’emplacement des champs, l’affichage dans la vue en liste/grille et le nombre de colonnes utilisables par chaque champ pour formater le tableau.

Bonne journée !

Merci pour toutes ces infos précieuses, ça fonctionne très bien.
J’abuse peut-être un peu, mais j’ai vu que l’on pouvait rajouter des boutons, à quel endroit doit-on mettre l’appel du script pour réaliser la mise à jour du tableau sans avoir besoin d’enregistrer?


Merci d’avance

Re-bonjour @oryxr,

Quand on ajoute un bouton, ça ajoute un événement dans l’API de formulaire Javascript qui permet d’ajouter une logique via un script client.
Par exemple ici:

frappe.ui.form.on('Sales Invoice', {
	update_timesheet(frm) {
		// déclenchement de l'action
	}
})

Dans le cas présent, je pense qu’un bouton n’est pas nécessaire, il faut ajouter un script du type (non testé):

frappe.ui.form.on('Sales Invoice Timesheet', {
	timesheet_detail(frm, child_doctype, child_docname) {
	    const row = locals[child_doctype][child_docname];
            if (row.timesheet_detail) {
	        frappe.db.get_value("Timesheet Detail", row.timesheet_detail, "from_time", r => {
	            frappe.model.set_value(child_doctype, child_docname, "day", r.from_time);
	        })
            }
	}
})

Grosso modo, à chaque fois que la valeur de la ligne du document de référence est modifiée, on récupère la date correspondante.

Très bien, merci, je vais tester et je vous tiens au courant