Initial commit – AufmaßCreater v2.35

This commit is contained in:
2026-06-10 11:03:43 +02:00
commit 84c933ea9c
2823 changed files with 490495 additions and 0 deletions
+359
View File
@@ -0,0 +1,359 @@
import os
from datetime import datetime
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, Border, Side, PatternFill
from openpyxl.utils import get_column_letter
from openpyxl.drawing.image import Image as XlImage
from collections import defaultdict
def _val(v):
if v is None or v == 0 or v == '':
return None
return v
def _ist_trenner(pos):
return (not pos.pos_nr or pos.pos_nr == '') and (pos.faktor == 0 or pos.faktor is None) and (pos.laenge == 0 or pos.laenge is None) and (pos.breite == 0 or pos.breite is None) and (pos.tiefe == 0 or pos.tiefe is None) and (pos.menge == 0 or pos.menge is None) and (pos.einzelpreis == 0 or pos.einzelpreis is None) and (pos.gesamtpreis == 0 or pos.gesamtpreis is None)
def _tb():
return Border(left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'), bottom=Side(style='thin'))
def _fmt_date(d):
if d is None:
return None
if isinstance(d, str):
d = d[:10]
for fmt in ('%Y-%m-%d', '%d.%m.%Y'):
try:
return datetime.strptime(d, fmt).strftime('%d.%m.%Y')
except ValueError:
continue
return d
return d.strftime('%d.%m.%Y')
def export_project_to_excel(project, aufmass, positionen, output_path, company=None):
wb = Workbook()
ws = wb.active
ws.title = "Aufmaß"
ws.page_setup.paperSize = ws.PAPERSIZE_A4
ws.page_setup.orientation = 'landscape'
ws.page_setup.fitToWidth = 1
ws.page_setup.fitToHeight = 0
ws.sheet_properties.pageSetUpPr.fitToPage = True
ws.page_margins.left = 0.3
ws.page_margins.right = 0.3
ws.page_margins.top = 0.4
ws.page_margins.bottom = 0.7
ws.oddFooter.center.text = ""
ws.oddFooter.right.text = "&P/&N"
ws.oddFooter.right.font = "Calibri,11"
header_fill = PatternFill(start_color='2F5496', end_color='2F5496', fill_type='solid')
header_font = Font(name='Calibri', size=11, bold=True, color='FFFFFF')
label_font = Font(name='Calibri', size=10, bold=True)
value_font = Font(name='Calibri', size=10)
value_font_bold = Font(name='Calibri', size=10, bold=True)
title_font = Font(name='Calibri', size=16, bold=True)
header_box_fill = PatternFill(start_color='F2F2F2', end_color='F2F2F2', fill_type='solid')
trenner_fill = PatternFill(start_color='F5F5F5', end_color='F5F5F5', fill_type='solid')
row = 1
firmen_name = company.name if company else 'Aufmaß'
logo_placed = False
if company and company.logo:
logo_path = company.logo
if not os.path.isabs(logo_path):
logo_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), logo_path)
if os.path.exists(logo_path):
try:
img = XlImage(logo_path)
img.width = 120
img.height = 60
ws.add_image(img, 'A1')
ws.row_dimensions[1].height = 60
ws.merge_cells(start_row=1, start_column=4, end_row=1, end_column=13)
ws.cell(row=1, column=4, value='Aufmaß').font = title_font
ws.cell(row=1, column=4).alignment = Alignment(horizontal='center', vertical='center')
logo_placed = True
except Exception:
pass
if not logo_placed:
ws.cell(row=1, column=1, value=firmen_name).font = title_font
row = 2
row += 1
label_align = Alignment(horizontal='left', vertical='center')
shrink_val = Alignment(horizontal='left', vertical='center', shrink_to_fit=True)
line = row
for ci in range(1, 14):
ws.cell(row=line, column=ci).border = _tb()
pairs = [
('Vertrag:', _val(project.vertrag), (1, 2, 4)),
('LV-Name:', _val(project.lv_name), (5, 6, 9)),
('Aufmaß-Datum:', _fmt_date(project.datum), (10, 11, 13)),
]
for label, val, (ls, vs, ve) in pairs:
c = ws.cell(row=line, column=ls, value=label)
c.font = label_font; c.fill = header_box_fill; c.alignment = label_align
c = ws.cell(row=line, column=vs, value=val)
c.font = value_font; c.alignment = shrink_val
if ve > vs:
ws.merge_cells(start_row=line, start_column=vs, end_row=line, end_column=ve)
row += 1
line = row
for ci in range(1, 14):
ws.cell(row=line, column=ci).border = _tb()
c = ws.cell(row=line, column=1, value='Projekt:')
c.font = label_font; c.fill = header_box_fill; c.alignment = label_align
c = ws.cell(row=line, column=2, value=_val(project.bezeichnung))
c.font = value_font_bold; c.alignment = shrink_val
ws.merge_cells(start_row=line, start_column=2, end_row=line, end_column=4)
c = ws.cell(row=line, column=5, value='Baustelle:')
c.font = label_font; c.fill = header_box_fill; c.alignment = label_align
c = ws.cell(row=line, column=6, value=_val(project.baustelle))
c.font = value_font; c.alignment = shrink_val
ws.merge_cells(start_row=line, start_column=6, end_row=line, end_column=13)
row += 1
line = row
for ci in range(1, 14):
ws.cell(row=line, column=ci).border = _tb()
c = ws.cell(row=line, column=1, value='Typ:')
c.font = label_font; c.fill = header_box_fill; c.alignment = label_align
c = ws.cell(row=line, column=2, value=_val(aufmass.typ if aufmass else None))
c.font = value_font; c.alignment = shrink_val
ws.merge_cells(start_row=line, start_column=2, end_row=line, end_column=4)
c = ws.cell(row=line, column=5, value='Bauabschnitt:')
c.font = label_font; c.fill = header_box_fill; c.alignment = label_align
c = ws.cell(row=line, column=6, value=_val(project.bauabschnitt))
c.font = value_font; c.alignment = shrink_val
ws.merge_cells(start_row=line, start_column=6, end_row=line, end_column=13)
row += 1
ap_name = f'{_val(project.ansprechpartner_vorname)} {_val(project.ansprechpartner_nachname)}'.strip()
# Row A: SM-Nr + Startdatum + Ansprechpartner Name + Tel
line = row
for ci in range(1, 14):
ws.cell(row=line, column=ci).border = _tb()
c = ws.cell(row=line, column=1, value='SM-Nr.:')
c.font = label_font; c.fill = header_box_fill; c.alignment = label_align
c = ws.cell(row=line, column=2, value=_val(project.sm_nr))
c.font = value_font; c.alignment = shrink_val
ws.merge_cells(start_row=line, start_column=2, end_row=line, end_column=3)
c = ws.cell(row=line, column=4, value='Startdatum:')
c.font = label_font; c.fill = header_box_fill; c.alignment = label_align
c = ws.cell(row=line, column=5, value=_fmt_date(project.datum_start))
c.font = value_font; c.alignment = shrink_val
ws.merge_cells(start_row=line, start_column=5, end_row=line, end_column=6)
c = ws.cell(row=line, column=7, value='Name:')
c.font = label_font; c.fill = header_box_fill; c.alignment = label_align
c = ws.cell(row=line, column=8, value=_val(ap_name))
c.font = value_font; c.alignment = shrink_val
ws.merge_cells(start_row=line, start_column=8, end_row=line, end_column=10)
c = ws.cell(row=line, column=11, value='Tel:')
c.font = label_font; c.fill = header_box_fill; c.alignment = label_align
c = ws.cell(row=line, column=12, value=_val(project.ansprechpartner_tel))
c.font = value_font; c.alignment = shrink_val
ws.merge_cells(start_row=line, start_column=12, end_row=line, end_column=13)
row += 1
# Row B: Abruf-Nr + Enddatum + Email
line = row
for ci in range(1, 14):
ws.cell(row=line, column=ci).border = _tb()
c = ws.cell(row=line, column=1, value='Abruf-Nr.:')
c.font = label_font; c.fill = header_box_fill; c.alignment = label_align
c = ws.cell(row=line, column=2, value=_val(project.abruf_nr))
c.font = value_font; c.alignment = shrink_val
ws.merge_cells(start_row=line, start_column=2, end_row=line, end_column=3)
c = ws.cell(row=line, column=4, value='Enddatum:')
c.font = label_font; c.fill = header_box_fill; c.alignment = label_align
c = ws.cell(row=line, column=5, value=_fmt_date(project.datum_ende))
c.font = value_font; c.alignment = shrink_val
ws.merge_cells(start_row=line, start_column=5, end_row=line, end_column=6)
c = ws.cell(row=line, column=7, value='Email:')
c.font = label_font; c.fill = header_box_fill; c.alignment = label_align
c = ws.cell(row=line, column=8, value=_val(project.ansprechpartner_email))
c.font = value_font; c.alignment = shrink_val
ws.merge_cells(start_row=line, start_column=8, end_row=line, end_column=13)
row += 1
row += 1
headers = [
'Abschnitt', 'Pos-Nr', 'Faktor', 'Länge', 'Breite', 'Tiefe',
'Menge', 'EH', 'Kurztext', 'Bemerkung', 'Menge', 'EP (€)', 'GP (€)'
]
header_row = row
for col, h in enumerate(headers, 1):
cell = ws.cell(row=row, column=col, value=h)
cell.font = header_font
cell.fill = header_fill
cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
cell.border = _tb()
data_last_row = header_row + len(positionen)
if positionen:
ws.auto_filter.ref = f'A{header_row}:M{data_last_row}'
start_row = row
pos_counter = 0
col_widths = [0.0] * 14
for i, pos in enumerate(positionen):
row = start_row + 1 + i
menge = pos.menge if pos.menge else None
menge_hinten = pos.menge_hinten if pos.menge_hinten else None
if pos.einheit in ('ST', 'LE', 'STD', 'h', 'Psch'):
menge = pos.faktor * 1 if pos.faktor else None
l = pos.laenge if pos.laenge else None
b = pos.breite if pos.breite else None
t = pos.tiefe if pos.tiefe else None
ep = pos.einzelpreis if pos.einzelpreis else None
gp = pos.gesamtpreis if pos.gesamtpreis else None
faktor = pos.faktor if pos.faktor else None
ist_trenner = _ist_trenner(pos)
if ist_trenner:
ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=13)
for c in range(1, 14):
cell = ws.cell(row=row, column=c)
cell.fill = trenner_fill
cell.border = _tb()
continue
pos_counter += 1
values = [
_val(pos.abschnitt),
pos.pos_nr or None,
faktor,
l, b, t,
menge,
pos.einheit or None,
_val(pos.kurztext),
_val(pos.bemerkung),
menge_hinten,
ep, gp,
]
for col, val in enumerate(values, 1):
cell = ws.cell(row=row, column=col, value=val) if val is not None else ws.cell(row=row, column=col, value='')
cell.font = value_font
cell.border = _tb()
if val is not None:
if col in (7, 11):
cell.number_format = '#,##0.00'
display = '{:,.2f}'.format(val)
elif col in (12, 13):
cell.number_format = '#,##0.00'
display = '{:,.2f}'.format(val)
elif col == 3:
display = '{:.2f}'.format(val) if isinstance(val, float) else str(val)
elif col in (4, 5, 6):
display = '{:.2f}'.format(val) if isinstance(val, float) else str(val)
else:
display = str(val)
else:
display = ''
col_widths[col] = max(col_widths[col], len(display))
sum_row = None
if positionen:
sum_row = start_row + 1 + len(positionen) + 1
ws.merge_cells(start_row=sum_row, start_column=1, end_row=sum_row, end_column=11)
ws.cell(row=sum_row, column=12, value='Summe:').font = Font(name='Calibri', size=11, bold=True)
ws.cell(row=sum_row, column=12).alignment = Alignment(horizontal='right')
gesamt = sum(p.gesamtpreis or 0 for p in positionen if not _ist_trenner(p))
cell = ws.cell(row=sum_row, column=13, value=gesamt)
cell.font = Font(name='Calibri', size=11, bold=True)
cell.number_format = '#,##0.00'
for c in range(1, 14):
ws.cell(row=sum_row, column=c).border = _tb()
if pos_counter > 0:
sum_end = sum_row if sum_row else (start_row + 1 + len(positionen))
summary_start = sum_end + 2
# Title row merged across all 13 cols
for ci in range(1, 14):
ws.cell(row=summary_start, column=ci).border = _tb()
ws.cell(row=summary_start, column=ci).fill = PatternFill(start_color='D6E4F0', end_color='D6E4F0', fill_type='solid')
sc = ws.cell(row=summary_start, column=1, value='Mengen- und Positions-Zusammenfassung')
sc.font = Font(name='Calibri', size=12, bold=True, color='2F5496')
sc.alignment = Alignment(horizontal='center', vertical='center')
ws.merge_cells(start_row=summary_start, start_column=1, end_row=summary_start, end_column=13)
# Summary header borders on 13 cols, gap cols 6-10 merged
shr = summary_start + 1
for ci in range(1, 14):
ws.cell(row=shr, column=ci).border = _tb()
ws.cell(row=shr, column=ci).fill = header_fill
sum_headers = {'Pos-Nr': (1, 1), 'Kurztext': (2, 10), 'Menge': (11, 11), 'EP (€)': (12, 12), 'GP (€)': (13, 13)}
for h, (cs, ce) in sum_headers.items():
cell = ws.cell(row=shr, column=cs, value=h)
cell.font = header_font
cell.alignment = Alignment(horizontal='center', vertical='center')
if ce > cs:
ws.merge_cells(start_row=shr, start_column=cs, end_row=shr, end_column=ce)
ws.merge_cells(start_row=shr, start_column=2, end_row=shr, end_column=10)
groups = defaultdict(lambda: {'kurztext': '', 'menge': 0.0, 'ep': 0.0, 'gp': 0.0})
seen_pos = []
for pos in positionen:
if _ist_trenner(pos) or not pos.pos_nr:
continue
key = pos.pos_nr
if key not in groups:
seen_pos.append(key)
groups[key]['kurztext'] = pos.kurztext or ''
groups[key]['menge'] += pos.menge_hinten if pos.menge_hinten else (pos.menge or 0)
groups[key]['ep'] = pos.einzelpreis or 0
groups[key]['gp'] += pos.gesamtpreis or 0
# Summary data rows borders on 13 cols, gap cols 6-10 merged
r = shr + 1
for key in seen_pos:
g = groups[key]
for ci in range(1, 14):
ws.cell(row=r, column=ci).border = _tb()
vals = [(1, 1, key), (2, 10, g['kurztext']), (11, 11, g['menge']), (12, 12, g['ep']), (13, 13, g['gp'])]
for cs, ce, v in vals:
cell = ws.cell(row=r, column=cs, value=v)
cell.font = value_font
if cs >= 11:
cell.number_format = '#,##0.00'
if ce > cs:
ws.merge_cells(start_row=r, start_column=cs, end_row=r, end_column=ce)
# No gap merge needed since Kurztext goes to column 10
r += 1
# Summary sum row borders on 13 cols, gap cols 1-10 merged
for ci in range(1, 14):
ws.cell(row=r, column=ci).border = _tb()
ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=10)
ws.cell(row=r, column=12, value='Summe:').font = Font(name='Calibri', size=10, bold=True)
ws.cell(row=r, column=12).alignment = Alignment(horizontal='right')
total_gp = sum(g['gp'] for g in groups.values())
cell = ws.cell(row=r, column=13, value=total_gp)
cell.font = Font(name='Calibri', size=10, bold=True)
cell.number_format = '#,##0.00'
min_widths = [0, 17, 9, 10, 10, 9, 11, 10, 6, 9, 14, 10, 8, 10]
max_widths = [0, 17, 15, 10, 12, 12, 12, 14, 8, 55, 45, 14, 14, 16]
for i in range(1, 14):
raw = col_widths[i]
w = max(raw * 1.2 + 1, min_widths[i])
w = min(w, max_widths[i])
ws.column_dimensions[get_column_letter(i)].width = w
ws.print_area = f'A1:M{r}'
os.makedirs(os.path.dirname(output_path), exist_ok=True)
wb.save(output_path)
return output_path