Accounting per Department of customer

We made a small script (PHP) for getting the time spent per ticket / per Department / per Month as an overview…

maybe someone needs it too or gets 1-2 ideas from it.

We needed to get costs by department of the customer and different rates per worker per ticket (we use our zammad for internal stuff ONLY and need to split the costs).

It’s pretty self explanatory.

columns atm are
TicketID, Ticket Title, Ticket Owner, Department, Time Supporter 1, Costs Supporter 1, Time Supporter 2, Costs Supporter 2 (extend as you like), Costs Total per Ticket.

We also started to sum things up by department (second table) but it’s not finished yet. Anyway, you’ll get the idea.
We will also add csv and pdf generation by cron.

Above there is a date/month dropdown (works in edge atm).

I’ll post newer versions when available.

And of course I would love to see such functionality in the next release (but I think it doesnt make sense for the majority of users to get costs per department per customer)…

Anyway… it took us 2-3 hours and maybe someone can use it too.

<html>
<head>

<style type="text/css">

.bod {font-family:Arial, sans-serif;font-size:14px;}

table  {border-style:solid; border:1;border-collapse:collapse;border-spacing:0;}
table td{font-family:Arial, sans-serif;font-size:14px;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:black;}
table th{font-family:Arial, sans-serif;font-size:14px;font-weight:normal;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:black;font-weight:bold;}
.tg .tg-0lax{text-align:left;vertical-align:top}        
</style>
</head>
<body class="bod">
<h1>IT-Abrechnung</h1>
<form action="/it-abrechnung.php" method=post>
Bitte Monat auswählen:<input type=month name="month"><br>
<input type=submit>
</form>


<?php
$reg='/^\d{4}-\d{2}$/';
if(ISSET($_POST['month'])){
        $month=$_POST['month'];
        if (preg_match($reg,$month)){
                echo ("Erstelle Tabelle f&uuml;r $month<br>");
                $year=substr($month,0,4);
                $month=substr($month,5);
                $dbconn = pg_connect("host=localhost dbname=zammad user=XXXXXXXXXXXXX password=XXXXXXXXXXXXX");
                echo ("<h2>Summe pro Ticket</h2><br>\n");
                createTicketTable($year,$month,$dbconn);
                echo ("<br><br><br><h2>Summe pro Abteilung</h2><br>\n");
                createAbteilungTable($year,$month,$dbconn);
                pg_close($dbconn);
        }else{
                echo ("Falscher Eingabewert<br>Muss im Format YYYY-MM sein");
        }
}

function createAbteilungTable($year,$month,$dbconn){
        $result = pg_query("SELECT 
         u.department as Abteilung,
         SUM(CASE WHEN tta.created_by_id = 4 THEN tta.time_unit ELSE 0 END) AS Zeit_MAO,
         SUM(CASE WHEN tta.created_by_id = 4 THEN tta.time_unit ELSE 0 END) * 0.55 AS Summe_MAO,
         SUM(CASE WHEN tta.created_by_id = 3 THEN tta.time_unit ELSE 0 END) AS Zeit_AN,
         SUM(CASE WHEN tta.created_by_id = 3 THEN tta.time_unit ELSE 0 END) * 1.15 AS Summe_AN
        FROM ticket_time_accountings tta
        LEFT JOIN tickets t ON tta.ticket_id = t.id
        LEFT JOIN users u ON u.id = t.customer_id
        WHERE EXTRACT(Year FROM tta.created_at) = $year AND EXTRACT(Month FROM tta.created_at) = $month
        GROUP BY 
          u.department
        ;");
        
        echo ("
        <table>
                <tr>
                        <th>Abteilung</th>
                        <th>Zeit Malte</th>
                        <th>Summe Malte</th>
                        <th>Zeit Alex</th>
                        <th>Summe Alex</th>
                        <th>Summe Gesamt</th>
                </tr>\n");
        while ($line = pg_fetch_array($result,null,PGSQL_ASSOC)){
        
                echo "\t<tr>\n";
                echo("\t\t<td align=center>".$line['abteilung']."</td>\n");
                echo("\t\t<td align=right>".number_format($line['zeit_mao']/60,$decimals=2)." h</td>\n");
                echo("\t\t<td align=right>".number_format($line['summe_mao'],$decimals=2)." €</td>\n");
                echo("\t\t<td align=right>".number_format($line['zeit_an']/60,$decimals=2)." h</td>\n");
                echo("\t\t<td align=right>".number_format($line['summe_an'],$decimals=2)." €</td>\n");
                $sum=$line['summe_mao']+$line['summe_an'];
                echo("\t\t<td align=right>".number_format($sum,$decimals=2)." €</td>\n");
                #echo "\t\t<td>$sum</td>\n";
                echo "\t</tr>\n";
        }
        echo "</table>\n";
        pg_free_result($result);
}

function createTicketTable($year,$month,$dbconn){
        $result = pg_query("SELECT 
         t.id,
         t.title,
         CONCAT(u.firstname, ' ', u.lastname) AS Auftraggeber,
         u.department as Abteilung,
         SUM(CASE WHEN tta.created_by_id = 4 THEN tta.time_unit ELSE 0 END) AS Zeit_MAO,
         SUM(CASE WHEN tta.created_by_id = 4 THEN tta.time_unit ELSE 0 END) * 0.95 AS Summe_MAO,
         SUM(CASE WHEN tta.created_by_id = 3 THEN tta.time_unit ELSE 0 END) AS Zeit_AN,
         SUM(CASE WHEN tta.created_by_id = 3 THEN tta.time_unit ELSE 0 END) * 1.05 AS Summe_AN
        FROM tickets t 
        LEFT JOIN users u ON u.id = t.customer_id
        LEFT JOIN ticket_time_accountings tta ON tta.ticket_id = t.id
        WHERE EXTRACT(Year FROM tta.created_at) = $year AND EXTRACT(Month FROM tta.created_at) = $month
        GROUP BY 
          t.id,
          t.title,
          u.lastname,
          u.firstname,
          u.department
        ;");
        
        echo ("
        <table>
                <tr>
                        <th>Ticket-ID</th>
                        <th>Ticket</th>
                        <th>Auftraggeber</th>
                        <th>Abteilung</th>
                        <th>Zeit Malte</th>
                        <th>Summe Malte</th>
                        <th>Zeit Alex</th>
                        <th>Summe Alex</th>
                        <th>Summe Gesamt</th>
                </tr>\n");
        while ($line = pg_fetch_array($result,null,PGSQL_ASSOC)){
        
                echo "\t<tr>\n";
                echo("\t\t<td><a href=\"http://XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/#ticket/zoom/".$line['id']."\">".$line['id']."</a></td>\n");
                echo("\t\t<td>".$line['title']."</td>\n");
                echo("\t\t<td>".$line['auftraggeber']."</td>\n");
                echo("\t\t<td>".$line['abteilung']."</td>\n");
                echo("\t\t<td>".number_format($line['zeit_mao'])."</td>\n");
                echo("\t\t<td>".number_format($line['summe_mao'],$decimals=2)." €</td>\n");
                echo("\t\t<td>".number_format($line['zeit_an'])."</td>\n");
                echo("\t\t<td>".number_format($line['summe_an'],$decimals=2)." €</td>\n");
                $sum=$line['summe_mao']+$line['summe_an'];
                echo("\t\t<td>".number_format($sum,$decimals=2)." €</td>\n");
                #echo "\t\t<td>$sum</td>\n";
                echo "\t</tr>\n";
        }
        echo "</table>\n";
        
        pg_free_result($result);
        #phpinfo();
}
?>

</body>
</html>
2 Likes

Ok, script is finished now…

it looks like this:

We added another column for the ticket time sum.

This is the bottom sum up per department
ausw02

A cron generates the excel reports every 1st day of the month

PHP Script:

<html>
<head>

<style type="text/css">

.bod {font-family:Arial, sans-serif;font-size:14px;}

table  {border-style:solid; border:1;border-collapse:collapse;border-spacing:0;}
table td{font-family:Arial, sans-serif;font-size:14px;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:black;}
table th{font-family:Arial, sans-serif;font-size:14px;font-weight:normal;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:black;font-weight:bold;}
.tg .tg-0lax{text-align:left;vertical-align:top}        
</style>
<title>IT-Abrechnung</title>
</head>
<body class="bod">
<h1>IT-Abrechnung</h1>
Auswertungen von vorangegangenen Monaten finden sich hier: <a href="http://XXXXXXXXX.de/it-abrechnung/xlsx">Klick</a>
<br>
<br>
<form action="it-abrechnung.php" method=post>
Bitte Monat auswählen:<input type=month name="month"><br>
<input type=submit>
</form>


<?php
$reg='/^\d{4}-\d{2}$/';
if(ISSET($_POST['month'])){
        $month=$_POST['month'];
        if (preg_match($reg,$month)){
                echo ("Erstelle Tabelle f&uuml;r $month<br>");
                $year=substr($month,0,4);
                $month=substr($month,5);
                $dbconn = pg_connect("host=localhost dbname=zammad user=XXXXXXXXXX password=XXXXXXXXXX");
                echo ("<h2>Summe pro Ticket</h2><br>\n");
                createTicketTable($year,$month,$dbconn);
                echo ("<br><br><br><h2>Summe pro Abteilung</h2><br>\n");
                createAbteilungTable($year,$month,$dbconn);
                pg_close($dbconn);
        }else{
                echo ("Falscher Eingabewert<br>Muss im Format YYYY-MM sein");
        }
}

function createAbteilungTable($year,$month,$dbconn){
        $result = pg_query("SELECT 
         u.department as Abteilung,
         SUM(CASE WHEN tta.created_by_id = 4 THEN tta.time_unit ELSE 0 END) AS Zeit_MAO,
         SUM(CASE WHEN tta.created_by_id = 4 THEN tta.time_unit ELSE 0 END) * 0.85 AS Summe_MAO,
         SUM(CASE WHEN tta.created_by_id = 3 THEN tta.time_unit ELSE 0 END) AS Zeit_AN,
         SUM(CASE WHEN tta.created_by_id = 3 THEN tta.time_unit ELSE 0 END) * 1.10 AS Summe_AN
        FROM ticket_time_accountings tta
        LEFT JOIN tickets t ON tta.ticket_id = t.id
        LEFT JOIN users u ON u.id = t.customer_id
        WHERE EXTRACT(Year FROM tta.created_at) = $year AND EXTRACT(Month FROM tta.created_at) = $month
        GROUP BY 
          u.department
        ;");
        
        echo ("
        <table>
                <tr>
                        <th>Abteilung</th>
                        <th>Zeit Malte</th>
                        <th>Summe Malte</th>
                        <th>Zeit Alex</th>
                        <th>Summe Alex</th>
                        <th>Zeit Gesamt</th>
                        <th>Summe Gesamt</th>
                </tr>\n");
        while ($line = pg_fetch_array($result,null,PGSQL_ASSOC)){
        
                echo "\t<tr>\n";
                echo("\t\t<td align=center>".$line['abteilung']."</td>\n");
                echo("\t\t<td align=right>".number_format($line['zeit_mao']/60,$decimals=2)." h</td>\n");
                echo("\t\t<td align=right>".number_format($line['summe_mao'],$decimals=2)." €</td>\n");
                echo("\t\t<td align=right>".number_format($line['zeit_an']/60,$decimals=2)." h</td>\n");
                echo("\t\t<td align=right>".number_format($line['summe_an'],$decimals=2)." €</td>\n");
                $sum=$line['zeit_mao']+$line['zeit_an'];
    $sum=$sum/60;
                echo("\t\t<td align=right>".number_format($sum,$decimals=2)." h</td>\n");
                $sum=$line['summe_mao']+$line['summe_an'];
                echo("\t\t<td align=right>".number_format($sum,$decimals=2)." €</td>\n");
                #echo "\t\t<td>$sum</td>\n";
                echo "\t</tr>\n";
        }
        echo "</table>\n";
        pg_free_result($result);
}

function createTicketTable($year,$month,$dbconn){
        $result = pg_query("SELECT 
         t.id,
         t.title,
         CONCAT(u.firstname, ' ', u.lastname) AS Auftraggeber,
         u.department as Abteilung,
         SUM(CASE WHEN tta.created_by_id = 4 THEN tta.time_unit ELSE 0 END) AS Zeit_MAO,
         SUM(CASE WHEN tta.created_by_id = 4 THEN tta.time_unit ELSE 0 END) * 0.55 AS Summe_MAO,
         SUM(CASE WHEN tta.created_by_id = 3 THEN tta.time_unit ELSE 0 END) AS Zeit_AN,
         SUM(CASE WHEN tta.created_by_id = 3 THEN tta.time_unit ELSE 0 END) * 1.15 AS Summe_AN
        FROM tickets t 
        LEFT JOIN users u ON u.id = t.customer_id
        LEFT JOIN ticket_time_accountings tta ON tta.ticket_id = t.id
        WHERE EXTRACT(Year FROM tta.created_at) = $year AND EXTRACT(Month FROM tta.created_at) = $month
        GROUP BY 
          t.id,
          t.title,
          u.lastname,
          u.firstname,
          u.department
        ;");
        
        echo ("
        <table>
                <tr>
                        <th>Ticket-ID</th>
                        <th>Ticket</th>
                        <th>Auftraggeber</th>
                        <th>Abteilung</th>
                        <th>Zeit Malte</th>
                        <th>Summe Malte</th>
                        <th>Zeit Alex</th>
                        <th>Summe Alex</th>
      <th>Zeit Gesamt</th>
                        <th>Summe Gesamt</th>
                </tr>\n");
        while ($line = pg_fetch_array($result,null,PGSQL_ASSOC)){
        
                echo "\t<tr>\n";
                echo("\t\t<td align=center><a href=\"http://XXXXXXXXXXXXXX.de/#ticket/zoom/".$line['id']."\">".$line['id']."</a></td>\n");
                echo("\t\t<td align=center>".$line['title']."</td>\n");
                echo("\t\t<td align=center>".$line['auftraggeber']."</td>\n");
                echo("\t\t<td align=center>".$line['abteilung']."</td>\n");
                echo("\t\t<td align=right>".number_format($line['zeit_mao'])."</td>\n");
                echo("\t\t<td align=right>".number_format($line['summe_mao'],$decimals=2)." €</td>\n");
                echo("\t\t<td align=right>".number_format($line['zeit_an'])."</td>\n");
                echo("\t\t<td align=right>".number_format($line['summe_an'],$decimals=2)." €</td>\n");
                $sum=$line['zeit_mao']+$line['zeit_an'];
    $sum=$sum/60;
                echo("\t\t<td align=right>".number_format($sum,$decimals=2)." h</td>\n");
                $sum=$line['summe_mao']+$line['summe_an'];
                echo("\t\t<td align=right>".number_format($sum,$decimals=2)." €</td>\n");
                #echo "\t\t<td>$sum</td>\n";
                echo "\t</tr>\n";
        }
        echo "</table>\n";
        
        pg_free_result($result);
        #phpinfo();
}
?>

</body>
</html>

A bit of python for the xlsx generation

#!/bin/python
import xlsxwriter
import psycopg2
import datetime
from os import rename

today=datetime.date.today()
first=today.replace(day=1)
lastMonth= first - datetime.timedelta(days=1)
month = lastMonth.month
year = lastMonth.year

print (month)
print (year)

conn = psycopg2.connect(host="localhost",database="zammad",user="XXXXXX",password="XXXXXXXXX")


workbook = xlsxwriter.Workbook('/var/www/it-abrechnung/xlsx/Auswertung_' + str(year) + '-' + str(month) + '.xlsx')

wf_head = workbook.add_format({'bold':True})
wf_cur = workbook.add_format({'num_format':'#,##0.00 \€'})
wf_hour = workbook.add_format({'num_format':'#,##0.00 \h'})
wf_num = workbook.add_format({'num_format':'##0.00'})


def set_col_width(sheet,cols):
  length_list = [len(x) for x in sheet.columns]
  for i, width in enumerate(length_list):
    sheet.set_column(i,i,width)


ws_t = workbook.add_worksheet('Pro Ticket')
ws_t.write(0,0,'Ticket-ID',wf_head)
ws_t.write(0,1,'Ticket',wf_head)
ws_t.write(0,2,'Auftraggeber',wf_head)
ws_t.write(0,3,'Abteilung',wf_head)
ws_t.write(0,4,'Zeit MAO',wf_head)
ws_t.write(0,5,'Summe MAO',wf_head)
ws_t.write(0,6,'Zeit AN',wf_head)
ws_t.write(0,7,'Summe AN',wf_head)
ws_t.write(0,8,'Ges. Zeit',wf_head)
ws_t.write(0,9,'Ges. Summe',wf_head)

cur=conn.cursor()

request="""SELECT
 t.id,
 t.title,
 CONCAT(u.firstname, ' ', u.lastname) AS Auftraggeber,
 u.department as Abteilung,
 SUM(CASE WHEN tta.created_by_id = 4 THEN tta.time_unit ELSE 0 END) AS Zeit_MAO,
 SUM(CASE WHEN tta.created_by_id = 4 THEN tta.time_unit ELSE 0 END) * 0.55 AS Summe_MAO,
 SUM(CASE WHEN tta.created_by_id = 3 THEN tta.time_unit ELSE 0 END) AS Zeit_AN,
 SUM(CASE WHEN tta.created_by_id = 3 THEN tta.time_unit ELSE 0 END) * 1.15 AS Summe_AN
FROM tickets t
LEFT JOIN users u ON u.id = t.customer_id
LEFT JOIN ticket_time_accountings tta ON tta.ticket_id = t.id
WHERE EXTRACT(Year FROM tta.created_at) = %s AND EXTRACT(Month FROM tta.created_at) = %s
GROUP BY
  t.id,
  t.title,
  u.lastname,
  u.firstname,
  u.department
;"""
lens=[11,7,13,11,9,12,8,9,10,11]
cur.execute(request,(year,month))
drows=cur.fetchall()
ws_row=1
for drow in drows: 
  ws_t.write_string(ws_row,0,str(drow[0]))    # T-ID
  lens[0]=max(lens[0],len(str(drow[0]))+1)
  ws_t.write_string(ws_row,1,drow[1])         # T-Text
  lens[1]=max(lens[1],len(str(drow[1]))+1)
  ws_t.write_string(ws_row,2,str(drow[2]))    # Auftraggeber
  lens[2]=max(lens[2],len(str(drow[2]))+1)
  ws_t.write_string(ws_row,3,str(drow[3]))    # Abteilung
  lens[3]=max(lens[3],len(str(drow[3]))+1)
  ws_t.write(ws_row,4,drow[4],wf_num)
  lens[4]=max(lens[4],len(str(drow[4]))+1)
  ws_t.write(ws_row,5,drow[5],wf_cur)
  lens[5]=max(lens[5],len(str(drow[5]))+3)
  ws_t.write(ws_row,6,drow[6],wf_num)
  lens[6]=max(lens[6],len(str(drow[6]))+1)
  ws_t.write(ws_row,7,drow[7],wf_cur)
  lens[7]=max(lens[7],len(str(drow[7]))+3)
  ws_t.write(ws_row,8,(drow[5]+drow[7])/60,wf_hour)
  lens[8]=max(lens[8],len('{:0.2f}'.format((drow[5]+drow[7])/60))+3)
  ws_t.write(ws_row,9,drow[4]+drow[6],wf_cur)
  lens[9]=max(lens[9],len(str(drow[4]+drow[6]))+3)
  ws_row += 1
for i in range(0,10):
  ws_t.set_column(i,i,lens[i])

cur.close()

##### Pro Abteilung
ws_a = workbook.add_worksheet('Pro Abteilung')
ws_a.write(0,0,'Abteilung',wf_head)
ws_a.write(0,1,'Zeit MAO',wf_head)
ws_a.write(0,2,'Summe MAO',wf_head)
ws_a.write(0,3,'Zeit AN',wf_head)
ws_a.write(0,4,'Summe AN',wf_head)
ws_a.write(0,5,'Ges. Zeit',wf_head)
ws_a.write(0,6,'Ges. Summe',wf_head)

cur=conn.cursor()

request="""SELECT
 u.department as Abteilung,
 SUM(CASE WHEN tta.created_by_id = 4 THEN tta.time_unit ELSE 0 END) AS Zeit_MAO,
 SUM(CASE WHEN tta.created_by_id = 4 THEN tta.time_unit ELSE 0 END) * 0.55 AS Summe_MAO,
 SUM(CASE WHEN tta.created_by_id = 3 THEN tta.time_unit ELSE 0 END) AS Zeit_AN,
 SUM(CASE WHEN tta.created_by_id = 3 THEN tta.time_unit ELSE 0 END) * 1.15 AS Summe_AN
FROM ticket_time_accountings tta
LEFT JOIN tickets t ON tta.ticket_id = t.id
LEFT JOIN users u ON u.id = t.customer_id
WHERE EXTRACT(Year FROM tta.created_at) = %s AND EXTRACT(Month FROM tta.created_at) = %s
GROUP BY
  u.department
;"""


cur.execute(request,(year,month))
drows=cur.fetchall()
ws_row=1
lens=[11,9,12,8,9,10,11]
for drow in drows: 
  ws_a.write_string(ws_row,0,str(drow[0]))    # Abteilung 
  lens[0]=max(lens[0],len(str(drow[0]))+1)
  ws_a.write(ws_row,1,drow[1]/60,wf_hour)
  lens[1]=max(lens[1],len(str(drow[1]))+3)
  ws_a.write(ws_row,2,drow[2],wf_cur)
  lens[2]=max(lens[2],len(str(drow[2]))+3)
  ws_a.write(ws_row,3,drow[3]/60,wf_hour)
  lens[3]=max(lens[3],len(str(drow[3]))+3)
  ws_a.write(ws_row,4,drow[4],wf_cur)
  lens[4]=max(lens[4],len(str(drow[4]))+3)
  ws_a.write(ws_row,5,(drow[3]+drow[1])/60,wf_hour)
  lens[5]=max(lens[5],len('{:0.2f}'.format((drow[1]+drow[3])/60))+3)
  ws_a.write(ws_row,6,drow[2]+drow[4],wf_cur)
  lens[6]=max(lens[6],len(str(drow[2]+drow[4]))+3)
  ws_row += 1
cur.close()
for i in range(0,7):
  ws_a.set_column(i,i,lens[i])

conn.close()

workbook.close()

And of course the cronjob

1 1 1 * * /usr/bin/python3 /root/Script/GenerateOverview.py > /dev/null 2>&1

Thats it. Have fun with it :wink: