Initial commit – AufmaßCreater v2.35
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user