Files

360 lines
16 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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