def toelichting_block(toelichtingen): if not toelichtingen: return toelichting_grid = Grid(cols=2) for t in toelichtingen: toelichting_grid.add_row([TextBlock(t[0], style=BOLD), TextBlock(t[1], style=ITALIC)]) return VBlock([TextBlock(f'Toelichting', style=BOLD), toelichting_grid])
def render_vrije_dagen_page(output_folder: Path): table = VBlock([ Table( vrije_dagen_overzicht(), TableConfig( headers=[ 'Naam', 'Vorig jaar', 'Dit jaar nieuw', 'Dit jaar beschikbaar', 'Totaal', 'Pool' ], aligns=['left', 'right', 'right', 'right', 'right', 'right'], formats=['', '.5', '.5', '.5', '.5', '.5'], totals=[0, 0, 0, 0, 0, 1], ), ), ]) page = Page([ TextBlock('Vrije dagen', HEADER_SIZE), table, TextBlock( f'Pool = Dagen van vorig jaar + Dagen dit jaar * deel van het jaar dat is geweest ({fraction_of_the_year_past()*100:.0f}%).' ), ]) page.render(output_folder / 'freedays.html')
def planning_chart(): # Vulling van de planning uit de planning database vulling = vulling_van_de_planning() if not vulling: return TextBlock("Kon de planning niet ophalen", color=RED) xy_values = [{"x": a["monday"], "y": a["filled"]} for a in vulling] return VBlock([ TextBlock("Planning", MID_SIZE), TextBlock( f"Percentage gevuld met niet interne projecten<br/>de komende {len(xy_values)} weken.", color=GRAY, ), ScatterChart( xy_values, ChartConfig( width=250, height=150, colors=["#6666cc", "#ddeeff"], x_type="int", min_x_axis=xy_values[0]["x"], max_x_axis=xy_values[-2]["x"], min_y_axis=0, max_y_axis=100, ), ), ])
def add_normal_row(title, value, shift=False, value_color=None): row = [TextBlock(title)] value_text = TextBlock(value, text_format='.', color=value_color) if shift: row += ['', value_text] else: row += [value_text, ''] grid.add_row(row)
def add_subtotal_row(title, value, style=TOPLINE): grid.add_row( [ TextBlock(title, style=BOLD), '', TextBlock(value, text_format='.', style=BOLD), ], styles=['', style, style], )
def add_subtotal_row(title, subtotal, style=TOPLINE): grid.add_row( [ TextBlock(title, style=BOLD), '', TextBlock(subtotal[0], text_format='.', style=BOLD), '', '', TextBlock(subtotal[1], text_format='.', style=BOLD, color="GRAY"), ], styles=['', style, style, '', style, style], )
def operations_block(): return VBlock([ TextBlock("Operations", HEADER_SIZE), TextBlock("KPI's", MID_SIZE), HBlock([kpi_block(verbose=False)], link="operations.html", padding=40), TextBlock(""), # Todo: verticale marge mogelijk maken operations_chart(), TextBlock(""), # Todo: verticale marge mogelijk maken # billable_chart(), corrections_block(), planning_chart(), ])
def error_block(): errs = log.get_errors() if not errs: return None error_lines = [ TextBlock( f'<b>{err["file"]}, {err["function"]}</b><br/>{err["message"]}', DEF_SIZE, width=260, color=RED, ) for err in errs ] return VBlock([TextBlock("Errors", MID_SIZE)] + error_lines)
def render_billable_page(output_folder: Path): users = sorted(tuple_of_productie_users()) fromday = Day().plus_months(-6) period = Period(fromday) cols = 3 rows = len(users) // cols + 1 grid = Grid(rows, cols) row = 0 col = 0 for user in users: labels, hours = billable_trend_person_week(user, period) # {weekno: hours} dict hours_data = HoursData(period, [user]) chart = BarChart( hours, ChartConfig( width=400, height=220, colors=['#ddeeff'], bottom_labels=labels, max_y_axis=40, y_axis_max_ticks=5, ), ) user_block = VBlock( [ TextBlock( f'{user} {hours_data.effectivity():.0f}% / {hours_data.billable_perc():.0f}%', font_size=MID_SIZE ), chart, ] ) grid.set_cell(row, col, user_block) col += 1 if col == cols: col = 0 row += 1 page = Page( [ TextBlock('Billable uren', HEADER_SIZE), TextBlock( 'Billable uren per week het afgelopen halfjaar.<br/><br/>' + 'Grafiek toont uren gewerkt op billable projecten zonder rekening te houden met correcties.<br/>' + 'Percentages zijn effectiviteit en billable.', color="gray", ), grid, ] ) page.render(output_folder / 'billable.html')
def travelbase_block(): bookings = get_bookings_per_week(booking_type="bookings", only_complete_weeks=True) if not isinstance(bookings, pd.DataFrame): return TextBlock("Kon boekingen niet ophalen", color=RED) legend = ", ".join( [f"{brand}: {int(bookings[brand].sum())}" for brand in BRANDS]) return VBlock( [ TextBlock("Travelbase", MID_SIZE), TextBlock("Aantal boekingen per week", color=GRAY), travelbase_scatterchart(bookings, 250, 180), TextBlock(legend), ], link="travelbase.html", )
def finance_block(): return VBlock([ TextBlock("Finance", HEADER_SIZE), omzet_chart(), debiteuren_block(), cash_block(), ])
def add_normal_row(title, result): grid.add_row( [ TextBlock(title), TextBlock(result[0], text_format='.'), '', '', TextBlock(result[1], text_format='.', color="GRAY"), '', ] ) if toelichting_sheet: toelichting = toelichting_sheet[title, 'Toelichting'] if toelichting: b = toelichtingen # zeer merkwaardige constructie maar krijg een foutmelding als ik direct iets toevoeg aan toelichtingen b += [(title, toelichting)]
def omzet_chart(): # Behaalde omzet per week return VBlock( [ TextBlock('Omzet'), TextBlock("per week, laatste 6 maanden...", DEF_SIZE, color=GRAY), TrendLines().chart( "omzet_per_week", 250, 150, x_start=months_ago(6), min_y_axis=0, max_y_axis=80000, ), ], link="billable.html", )
def render_onderhanden_werk_page(output_folder: Path): day = Day() page = Page([ TextBlock(f'Onderhanden werk per {day.strftime("%d/%m")}', HEADER_SIZE), onderhanden_werk_list(day) ]) page.render(output_folder / 'onderhanden.html')
def hr_block(): return VBlock([ TextBlock("HR", HEADER_SIZE), team_block(), tevredenheid_block(), verzuim_block(), # vakantiedagen_block(), error_block(), ])
def render_maandrapportage_page(monthly_folder, output_folder: Path): lines = [] files = sorted( [f for f in monthly_folder.iterdir() if f.suffix == '.html']) for file in files: month_num = int(file.stem.split('_')[1]) htmlpath = monthly_folder / file pdfpath = os.path.relpath(htmlpath.with_suffix('.pdf'), start=output_folder) htmlpath = os.path.relpath(htmlpath, start=output_folder) lines += [ HBlock([ TextBlock(MAANDEN[month_num - 1], url=htmlpath), TextBlock('pdf', url=pdfpath) ]) ] page = Page([TextBlock('Maandrapportages', HEADER_SIZE)] + lines) page.render(output_folder / 'maandrapportages.html')
def cash_block(): return VBlock([ TextBlock("Cash", MID_SIZE), TrendLines().chart("cash", 250, 150, min_y_axis=0, x_start=months_ago(6)), ])
def corrections_block(): weeks_back = 4 interesting_correction = 8 period = Period(Day().plus_weeks(-weeks_back)) def corrections_percentage_coloring(value): return dependent_color(value, red_treshold=5, green_treshold=3) def project_link(_, fullline): return f"https://oberon.simplicate.com/projects/{fullline[0]}/hours" result = VBlock( [ TextBlock("Correcties", MID_SIZE), HBlock( [ TextBlock( corrections_percentage(period), MID_SIZE, text_format="%", color=corrections_percentage_coloring, ), TextBlock( f"correcties op uren van<br/> week {period.fromday.week_number()} " + f"tot en met week {period.fromday.week_number() + weeks_back - 1}.", color=GRAY, ), ], padding=70, ), Table( largest_corrections(interesting_correction, period), TableConfig( headers=[], aligns=["left", "left", "right"], hide_columns=[0], row_linking=project_link, ), ), ], link="corrections.html", ) return result
def render_resultaat_vergelijking_page(): omzet = VBlock([ TextBlock('Omzet vergelijking', MID_SIZE), LineChart( [ omzet_per_maand(), omzet_begroot_per_maand(), omzet_vorig_jaar_per_maand() ], ChartConfig( width=900, height=600, labels=[ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ], colors=['#4285f4', '#f4b400', '#db4437'], bottom_labels=['Omzet', 'Omzet begroot', 'Omzet vorig jaar'], ), ), ]) winst = VBlock([ TextBlock('Winst vergelijking', MID_SIZE), LineChart( [ winst_per_maand(), winst_begroot_per_maand(), winst_vorig_jaar_per_maand() ], ChartConfig( width=900, height=600, labels=MAANDEN, colors=['#4285f4', '#f4b400', '#db4437'], bottom_labels=['Winst', 'Winst begroot', 'Winst vorig jaar'], ), ), ]) page = Page([TextBlock('Resultaat', HEADER_SIZE), VBlock([omzet, winst])]) page.render('output/resultaat_vergelijking.html')
def render_travelbase_page(output_folder): bookings = get_bookings_per_week(booking_type='bookings') if not isinstance(bookings, pd.DataFrame): return # An error occurred, no use to proceed totals = [(brand, bookings[brand].sum()) for brand in BRANDS] totals_table = Table( totals, TableConfig(aligns=['left', 'right'], formats=['', '0'], totals=[0, 1])) page = Page([ TextBlock('Travelbase', HEADER_SIZE), TextBlock( 'Aantal boekingen per week. Weken lopen van maandag t/m zondag.', color='gray'), bar_chart(bookings, 600, 400), totals_table, ]) page.render(output_folder / 'travelbase.html')
def add_subtotal_row(title, subtotal, budget=None, style=TOPLINE): if budget: budget_month = TextBlock(budget[0], text_format='.', color=GRAY, style=BOLD) budget_ytd = TextBlock(budget[1], text_format='.', color=GRAY, style=BOLD) else: budget_month, budget_ytd = 0, 0 grid.add_row( [ TextBlock(title, style=BOLD), '', TextBlock(subtotal[0], text_format='.', style=BOLD), budget_month, '', '', TextBlock(subtotal[1], text_format='.', style=BOLD), budget_ytd, ], styles=['', style, style, '', '', style, style, ''], )
def hours_block(year, month): month_names = [] data = [] for m in range(month): month_names += [TextBlock(MAANDEN[m])] fromday = Day(year, m + 1, 1) untilday = fromday.plus_months(1) period = Period(fromday, untilday) data += [HoursData(period)] headers = month_names + ['', 'YTD'] curyear = datetime.datetime.today().strftime('%Y') total_period = Period(curyear + '-01-01') data += [None, HoursData(total_period)] grid = kpi_grid(headers, data) chart = None if month >= 3: # Voor maart heeft een grafiekje niet veel zin chart = BarChart( [d.omzet for d in data[:-2]], # -2 is lelijk maar haalt de total col eraf ChartConfig( width=60 * month, height=150, colors=['#ddeeff'], bottom_labels=[MAANDEN[m] for m in range(month)], y_axis_max_ticks=5, ), ) return VBlock([ TextBlock('Billable uren', MID_SIZE), TextBlock( '''Beschikbare uren zijn alle uren die we hebben na afrek van vrije dagen en verzuim.<br/> Klant uren zijn alle uren besteed aan werk voor klanten. <br/> Billable uren is wat er over is na correcties. <br/> Omzet is de omzet gemaakt in die uren.''', color=GRAY, ), grid, VBlock([TextBlock('Omzet op uren per maand'), chart], css_class="no-print"), ])
def team_block(): fte = aantal_fte() # fte_begroot = aantal_fte_begroot() fte_color = BLACK # dependent_color(fte_begroot - fte, 1, -1) return VBlock([ TextBlock("Team", MID_SIZE), HBlock([ VBlock([ TextBlock("Aantal mensen", DEF_SIZE, padding=5, color=GRAY), TextBlock(aantal_mensen(), MID_SIZE, text_format=".5"), ]), VBlock([ TextBlock("Aantal FTE", DEF_SIZE, padding=5, color=GRAY), TextBlock(fte, MID_SIZE, color=fte_color, text_format=".5"), # TextBlock('Begroot', defsize, padding=5, color=GRAY), # TextBlock(fte_begroot, midsize, color=GRAY, format='.5'), ]), ]), ])
def render_operations_page(output_folder: Path, year: int = None): weeks = 20 if year: # Use given year. Create page with year name in it html_page = f'operations {year}.html' else: # Use the current year (default) year = int(datetime.datetime.today().strftime('%Y')) html_page = 'operations.html' total_period = Period(f'{year}-01-01', f'{year + 1}-01-01') page = Page([ TextBlock('Operations KPI' 's', HEADER_SIZE), TextBlock( f"Belangrijkste KPI's per week de afgelopen {weeks} weken", color="gray", ), kpi_block(weeks=weeks, total_period=total_period, total_title='YTD'), ]) page.render(output_folder / html_page)
def verzuim_block(): period = Period(Day().plus_months(-3)) verzuim = verzuimpercentage(period) verzuim_color = dependent_color(verzuim, 3, 1.5) return VBlock( [ TextBlock("Verzuim", MID_SIZE), TextBlock("Verzuimpercentage de laatste 3 maanden", DEF_SIZE, color=GRAY), TextBlock( verzuim, MID_SIZE, text_format="%1", color=verzuim_color, tooltip= "Gemiddeld bij DDA in 2019: 3.0%. Groen bij 1,5%, rood bij 3%", ), ], link="absence.html", )
def klanten_block(): klanten = VBlock( [ TextBlock("Klanten", MID_SIZE), TextBlock("Top 3 klanten laatste 6 maanden", DEF_SIZE, padding=10, color=GRAY), Table( top_x_klanten_laatste_zes_maanden(3), TableConfig( headers=[], aligns=["left", "right", "right"], formats=["", "€", "%"], totals=[0, 0, 1], ), ), ], link="clients.html", ) return klanten
def render_sales_page(output_folder: Path): sales_trajecten = VBlock([ TextBlock('Actieve salestrajecten', MID_SIZE), Table( sales_waarde_details(), TableConfig( headers=[ 'klant', 'project', 'grootte', 'kans', 'fase', 'waarde', 'bron' ], aligns=[ 'left', 'left', 'right', 'right', 'left', 'right', 'left' ], formats=['', '', '€', '%', '', '€', ''], totals=[0, 0, 1, 0, 0, 1, 0], ), ), ]) pijplijn = VBlock([ TextBlock('Werk in de pijplijn', MID_SIZE), TextBlock('Moet uit Simplicate komen'), # Table( # werk_in_pijplijn_details(), # TableConfig( # headers=['klant', 'project', '% af', 'onderhanden', 'eigenaar'], # aligns=['left', 'left', 'right', 'right', 'left'], # formats=['', '', '%', '€', ''], # totals=[0, 0, 0, 1, 0], # ), # ), ]) page = Page( [TextBlock('Sales', HEADER_SIZE), HBlock([sales_trajecten, pijplijn])]) page.render(output_folder / 'sales.html')
def billable_chart(): months_back = 3 return VBlock([ TextBlock( f"Billable, hele team, laatste {months_back} maanden", DEF_SIZE, color=GRAY, ), TrendLines().chart("billable_hele_team", 250, 150, x_start=months_ago(months_back)), ])
def render_verzuim_page(output_folder: Path): timesheet = Timesheet() months = 3 period = Period(Day().plus_months(-months)) table = VBlock([ Table( verzuim_list(period), TableConfig( headers=["Naam", "Dag", "soort", "Dagen"], aligns=["left", "left", "left", "right"], formats=[ "", "", "", ".5", ], totals=[0, 0, 0, 1], ), ), ]) verzuim = verzuimpercentage(period) verzuim_color = dependent_color(verzuim, 3, 1.5) page = Page([ TextBlock("Verzuim", HEADER_SIZE), TextBlock(f"De afgelopen {months} maanden", color=GRAY), HBlock([ VBlock([ TextBlock("Geboekte uren", DEF_SIZE, color="gray", padding=5), TextBlock("Verzuim uren", DEF_SIZE, color="gray"), TextBlock("Verzuimopercentage", DEF_SIZE, color="gray"), ]), VBlock([ TextBlock( timesheet.normal_hours(period), DEF_SIZE, text_format=".", padding=5, ), TextBlock(timesheet.absence_hours(period), DEF_SIZE, text_format="."), TextBlock(verzuim, verzuim_color, text_format="%1"), ]), ]), table, ]) page.render(output_folder / "absence.html")
def add_normal_row(title, result, budget=None): if budget: budget_month = TextBlock(budget[0], text_format='.', color=GRAY) budget_ytd = TextBlock(budget[1], text_format='.', color=GRAY) else: budget_month, budget_ytd = 0, 0 grid.add_row( [ TextBlock(title), TextBlock(result[0], text_format='.'), '', budget_month, '', TextBlock(result[1], text_format='.'), '', budget_ytd, ] ) if toelichting_sheet: toelichting = toelichting_sheet[title, 'Toelichting'] if toelichting: b = toelichtingen # zeer merkwaardige constructie maar krijg een foutmelding als ik direct iets toevoeg aan toelichtingen b += [(title, toelichting)]