139 lines
6.1 KiB
Python
139 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
||
"""Folie 8 – Funktionszuordnung je System (aus Anforderungskatalog_LIS_Fit-Gap v2.xlsx).
|
||
Pro System eine Kachel; Funktionen NUR dort = kraeftig/dunkel, auch woanders = grau mit
|
||
'· auch: ...'. Einspaltig, Fira Sans Condensed. (adesso-Stil, deutsch, dunkelblau.)"""
|
||
|
||
import os
|
||
from pptx import Presentation
|
||
from pptx.util import Inches, Pt
|
||
from pptx.dml.color import RGBColor
|
||
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
|
||
from pptx.enum.shapes import MSO_SHAPE
|
||
from openpyxl import load_workbook
|
||
from openpyxl.utils import column_index_from_string as ci
|
||
from adesso_style import (WHITE, INK, BLUE, TEAL, GREEN, CORAL, LIGHT, MUTE, RECF,
|
||
F, FC, chrome)
|
||
|
||
BG = RGBColor(0x0A, 0x3D, 0x6B)
|
||
SHARE = RGBColor(0x6B, 0x77, 0x85) # grau fuer "auch woanders"
|
||
|
||
# ---- Daten aus dem Excel lesen --------------------------------------------
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
wb = load_workbook(os.path.join(HERE, "Anforderungskatalog_LIS_Fit-Gap v2.xlsx"), data_only=True)
|
||
ws = wb["Anforderungen & Fit-Gap"]
|
||
SYS = [('H', 'Order Entry'), ('N', 'AB+M'), ('O', 'Integrationsserver'), ('P', 'PVT'),
|
||
('Q', 'R. LabOps'), ('R', 'Sysmex'), ('S', 'SAP'), ('T', 'Consens')]
|
||
ABBR = {'Order Entry': 'OE', 'AB+M': 'AB+M', 'Integrationsserver': 'Int.-Server',
|
||
'PVT': 'PVT', 'R. LabOps': 'R.LabOps', 'Sysmex': 'Sysmex', 'SAP': 'SAP',
|
||
'Consens': 'Consens'}
|
||
|
||
reqs = []; cur = None
|
||
for r in range(5, ws.max_row + 1):
|
||
b = ws.cell(r, 2).value; c = ws.cell(r, 3).value
|
||
marks = {d: (ws.cell(r, ci(col)).value not in (None, '')) for col, d in SYS}
|
||
if b:
|
||
cur = {'name': (str(c).strip() if c else str(b)), 'm': dict(marks)}
|
||
reqs.append(cur)
|
||
elif cur:
|
||
for d, v in marks.items():
|
||
if v: cur['m'][d] = True
|
||
if c: cur['name'] = (cur['name'] + ' / ' + str(c).strip())
|
||
|
||
persys = {d: [] for _, d in SYS}
|
||
for req in reqs:
|
||
active = [d for _, d in SYS if req['m'][d]]
|
||
for d in active:
|
||
persys[d].append((req['name'], [o for o in active if o != d]))
|
||
for d in persys:
|
||
persys[d].sort(key=lambda it: (1 if it[1] else 0))
|
||
|
||
# ---- PPTX ------------------------------------------------------------------
|
||
prs = Presentation(); prs.slide_width = Inches(13.333); prs.slide_height = Inches(7.5)
|
||
SW, SH = prs.slide_width, prs.slide_height
|
||
s = prs.slides.add_slide(prs.slide_layouts[6])
|
||
|
||
|
||
def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), adj=None):
|
||
sh = s.shapes.add_shape(kind, Inches(x), Inches(y), Inches(w), Inches(h))
|
||
if fill is None: sh.fill.background()
|
||
else: sh.fill.solid(); sh.fill.fore_color.rgb = fill
|
||
if line is None: sh.line.fill.background()
|
||
else: sh.line.color.rgb = line; sh.line.width = lw
|
||
if adj is not None:
|
||
try: sh.adjustments[0] = adj
|
||
except Exception: pass
|
||
sh.shadow.inherit = False
|
||
return sh
|
||
|
||
|
||
def txt(x, y, w, h, t, size=12, color=WHITE, bold=False, align=PP_ALIGN.LEFT,
|
||
font=F, anchor=MSO_ANCHOR.MIDDLE):
|
||
tb = s.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h)); tf = tb.text_frame
|
||
tf.word_wrap = True; tf.vertical_anchor = anchor
|
||
tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0)
|
||
p = tf.paragraphs[0]; p.alignment = align
|
||
r = p.add_run(); r.text = t
|
||
r.font.size = Pt(size); r.font.bold = bold; r.font.name = font; r.font.color.rgb = color
|
||
return tb
|
||
|
||
|
||
def cut(t, n):
|
||
return t if len(t) <= n else t[:n].rstrip() + "…"
|
||
|
||
|
||
def tile(x, y, w, h, title, items, accent, isize, sa, name_max):
|
||
shp(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, w, h, fill=WHITE, adj=0.05)
|
||
txt(x + 0.22, y + 0.14, w - 0.44, 0.32, title, size=14, color=INK, bold=True,
|
||
anchor=MSO_ANCHOR.MIDDLE)
|
||
n = len([i for i in items if not i[1]])
|
||
txt(x + w - 1.7, y + 0.18, 1.5, 0.24, "%d · %d nur hier" % (len(items), n),
|
||
size=8.5, color=SHARE, align=PP_ALIGN.RIGHT, font=FC, anchor=MSO_ANCHOR.MIDDLE)
|
||
shp(MSO_SHAPE.RECTANGLE, x + 0.22, y + 0.50, w - 0.44, 0.030, fill=accent)
|
||
|
||
tb = s.shapes.add_textbox(Inches(x + 0.22), Inches(y + 0.62), Inches(w - 0.40),
|
||
Inches(h - 0.74))
|
||
tf = tb.text_frame; tf.word_wrap = True; tf.vertical_anchor = MSO_ANCHOR.TOP
|
||
tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0)
|
||
for i, (name, others) in enumerate(items):
|
||
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
|
||
p.alignment = PP_ALIGN.LEFT; p.line_spacing = 0.96; p.space_after = Pt(sa)
|
||
r = p.add_run(); r.font.name = FC; r.font.size = Pt(isize)
|
||
if others:
|
||
r.text = cut(name, name_max); r.font.bold = False; r.font.color.rgb = SHARE
|
||
r2 = p.add_run(); r2.text = " · auch: " + ", ".join(ABBR[o] for o in others)
|
||
r2.font.name = FC; r2.font.size = Pt(isize - 1); r2.font.color.rgb = SHARE
|
||
else:
|
||
r.text = cut(name, name_max + 4); r.font.bold = True; r.font.color.rgb = INK
|
||
|
||
|
||
# ---- Hintergrund + Header --------------------------------------------------
|
||
shp(MSO_SHAPE.RECTANGLE, 0, 0, SW.inches, SH.inches, fill=BG)
|
||
chrome(s, "Funktionszuordnung je System", 9)
|
||
txt(0.42, 0.96, 12.5, 0.3,
|
||
"Dunkel/fett = nur dieses System · grau „· auch: …" + chr(34) + " = Funktion liegt auch in anderen Systemen",
|
||
size=11, color=LIGHT, anchor=MSO_ANCHOR.MIDDLE, font=FC)
|
||
|
||
CY, CH = 1.46, 5.42
|
||
COLS = [0.40, 3.58, 6.76, 9.94]; CW = 2.98
|
||
|
||
# Spalte 1: Integrationsserver (hoch)
|
||
tile(COLS[0], CY, CW, CH, "Integrationsserver", persys['Integrationsserver'],
|
||
CORAL, isize=9, sa=1.5, name_max=30)
|
||
# Spalte 2: Order Entry (hoch)
|
||
tile(COLS[1], CY, CW, CH, "Order Entry", persys['Order Entry'],
|
||
BLUE, isize=10.5, sa=5, name_max=30)
|
||
|
||
# Spalten 3 & 4: je 3 kleine Kacheln
|
||
SH3 = 1.68; GAP = 0.19
|
||
ys = [CY, CY + SH3 + GAP, CY + 2 * (SH3 + GAP)]
|
||
small3 = ['AB+M', 'Consens', 'R. LabOps']
|
||
small4 = ['PVT', 'Sysmex', 'SAP']
|
||
for name, y in zip(small3, ys):
|
||
tile(COLS[2], y, CW, SH3, name, persys[name], TEAL, isize=9.5, sa=3, name_max=30)
|
||
for name, y in zip(small4, ys):
|
||
tile(COLS[3], y, CW, SH3, name, persys[name], TEAL, isize=9.5, sa=3, name_max=30)
|
||
|
||
out = os.path.join(HERE, "Folie8.pptx")
|
||
prs.save(out)
|
||
print("saved", out)
|