v0.0.7: documenten met versies bij leads/klanten en relaties (tabel documents, versies in files; oude bestanden worden bij het starten versie 1 van een eigen document); woning = document met versies, de viewer toont de nieuwste (oudere via keuzelijst), nieuwe versie uploaden met opmerking, PDF's per woningversie; kaart (kaart-app, /kaart/) onder de woning en bij een klantrelatie zodra het adres bekend is, met de kavel en getekende woningen uit een kaveltoets-lead; blokken zonder gegevens verborgen; knop Bewerken in Tectum (TECTUM_URL) en API /api/tectum/woningen (lijst, ontwerp, nieuwe versie met conflictcontrole) met sleutel TECTUM_CRM_KEY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
db3e26d258
commit
c69c3773dc
+48
-48
@@ -1,5 +1,6 @@
|
||||
{% extends 'base.html' %}
|
||||
{% from '_compose.html' import compose %}
|
||||
{% from '_documents.html' import documents_list with context %}
|
||||
{% block title %}{{ c.name }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="head">
|
||||
@@ -28,15 +29,24 @@
|
||||
|
||||
<div class="row gx-4 stack">
|
||||
<div class="col-lg-7 col-xl-8">
|
||||
{% if woningen %}
|
||||
<div class="card"><div class="card-body">
|
||||
<div class="card-top">
|
||||
<h2>Gekozen model</h2>
|
||||
{% if design %}
|
||||
<h2>Woning</h2>
|
||||
<div class="actions">
|
||||
{% if designs|length > 1 %}
|
||||
{% if woningen|length > 1 or not woning %}
|
||||
<form method="post" action="{{ url_for('contact_design', cid=c.id) }}" class="inline">
|
||||
<select class="form-select form-select-sm w-auto" name="file_id" onchange="this.form.submit()" aria-label="Ander ontwerp kiezen">
|
||||
{% for d in designs %}<option value="{{ d.id }}" {{ 'selected' if d.id == design.id }}>{{ d.title }} · {{ d.created_at|dt('%d-%m-%Y') }}</option>{% endfor %}
|
||||
<select class="form-select form-select-sm w-auto" name="document_id" onchange="this.form.submit()" aria-label="Andere woning kiezen">
|
||||
{% if not woning %}<option value="">Kies een woning…</option>{% endif %}
|
||||
{% for d in woningen %}<option value="{{ d.id }}" {{ 'selected' if woning and d.id == woning.id }}>{{ d.title }} · {{ d.updated_at|dt('%d-%m-%Y') }}</option>{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if woning %}
|
||||
{% if woning.versions|length > 1 %}
|
||||
<form method="get" action="{{ url_for('contact_view', cid=c.id) }}" class="inline">
|
||||
<select class="form-select form-select-sm w-auto" name="versie" onchange="this.form.submit()" aria-label="Versie">
|
||||
{% for v in woning.versions %}<option value="{{ v.id }}" {{ 'selected' if v.id == shown.id }}>Versie {{ v.version }}{{ ' (nieuwste)' if loop.first }} · {{ v.created_at|dt('%d-%m-%Y') }}</option>{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
{% endif %}
|
||||
@@ -44,19 +54,22 @@
|
||||
<input type="hidden" name="contact_id" value="{{ c.id }}"><input type="hidden" name="bestek" value="1">
|
||||
<button class="btn btn-primary btn-sm">Offerte met bestek</button>
|
||||
</form>
|
||||
<a class="btn btn-outline-primary btn-sm" href="{{ url_for('design_viewer', fid=design.id) }}" target="_blank">Openen</a>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{{ url_for('upload_file', name=design.stored) }}" download="{{ design.original }}">JSON</a>
|
||||
{% if tectum_url %}<a class="btn btn-outline-primary btn-sm" href="{{ tectum_url }}" target="_blank" title="Openen in Tectum; Opslaan in CRM maakt een nieuwe versie">Bewerken in Tectum</a>{% endif %}
|
||||
<a class="btn btn-outline-primary btn-sm" href="{{ url_for('design_viewer', fid=shown.id) }}" target="_blank">Openen</a>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{{ url_for('upload_file', name=shown.stored) }}" download="{{ shown.original }}">JSON</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if design %}
|
||||
<div class="viewer-frame"><iframe src="{{ url_for('design_viewer', fid=design.id) }}" title="3D-weergave {{ design.title }}" loading="lazy" allowfullscreen></iframe></div>
|
||||
<p class="muted" style="margin:.75rem 0 0"><strong style="color:var(--ink)">{{ design.title }}</strong>
|
||||
{% if woning %}
|
||||
<div class="viewer-frame"><iframe src="{{ url_for('design_viewer', fid=shown.id) }}" title="3D-weergave {{ woning.title }}" loading="lazy" allowfullscreen></iframe></div>
|
||||
<p class="muted" style="margin:.75rem 0 0"><strong style="color:var(--ink)">{{ woning.title }}</strong>
|
||||
· versie {{ shown.version }}{% if shown.id != woning.latest.id %} (nieuwste: {{ woning.latest.version }}){% endif %}{% if shown.note %} · {{ shown.note }}{% endif %}
|
||||
· {{ design_meta.units }} unit{{ 's' if design_meta.units != 1 }}{% if design_meta.address %} · {{ design_meta.address }}{% endif %}
|
||||
· ontvangen {{ design.created_at|dt }}{% if design_meta.version %} · configurator {{ design_meta.version }}{% endif %}</p>
|
||||
· {{ shown.created_at|dt }}{% if design_meta.version %} · configurator {{ design_meta.version }}{% endif %}</p>
|
||||
{% if design_meta.note %}<p style="white-space:pre-line;margin:.5rem 0 0">{{ design_meta.note }}</p>{% endif %}
|
||||
<p class="pdf-links" style="margin:.5rem 0 0">Tekeningen:
|
||||
{% for k, label in PDF_KINDS.items() %}{% set p = pdfs.get(k) %}
|
||||
{% if shown.id == woning.latest.id %}
|
||||
<p class="pdf-links" style="margin:.5rem 0 0">Tekeningen van deze versie:
|
||||
{% for k, label in PDF_KINDS.items() %}{% set p = pdfs.get(k) if pdfs.get(k) and pdfs.get(k).current else None %}
|
||||
<a data-pdf="{{ k }}" href="{{ p.url if p else '#' }}" target="_blank" {{ 'hidden' if not p }}>{{ (label[:1]|upper) ~ label[1:] }}</a><span class="muted" data-pdf-wait="{{ k }}" {{ 'hidden' if p }}>{{ label }} wordt gemaakt…</span>{% if k == 'werk' %} <span class="muted">(intern)</span> ·{% endif %}
|
||||
{% endfor %}
|
||||
<span class="muted">· opnieuw maken via PDF in de weergave</span></p>
|
||||
@@ -68,18 +81,28 @@
|
||||
if (w) w.hidden = true;
|
||||
});
|
||||
</script>
|
||||
{% elif designs %}
|
||||
<p class="muted">Er is geen model gekozen.</p>
|
||||
<form method="post" action="{{ url_for('contact_design', cid=c.id) }}" class="d-flex flex-wrap gap-2">
|
||||
<select class="form-select w-auto" name="file_id" aria-label="Ontwerp">
|
||||
{% for d in designs %}<option value="{{ d.id }}">{{ d.title }} · {{ d.created_at|dt('%d-%m-%Y') }}</option>{% endfor %}
|
||||
</select>
|
||||
<button class="btn btn-outline-primary">Kiezen</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<details style="margin-top:1rem">
|
||||
<summary style="font-size:1rem">Nieuwe versie uploaden</summary>
|
||||
<form method="post" action="{{ url_for('document_version', did=woning.id) }}" enctype="multipart/form-data" class="row g-2">
|
||||
<div class="col-md-6"><input class="form-control" type="file" name="file" accept=".json,application/json" required aria-label="Woningontwerp (JSON)"></div>
|
||||
<div class="col-md"><input class="form-control" name="note" placeholder="Wat is er veranderd?" aria-label="Opmerking"></div>
|
||||
<div class="col-md-auto"><button class="btn btn-outline-primary">Opslaan als versie {{ woning.latest.version + 1 }}</button></div>
|
||||
</form>
|
||||
</details>
|
||||
{% else %}
|
||||
<p class="empty">Nog geen model. Een ontwerp uit de configurator (Bestand › Opslaan, JSON) komt hier vanzelf als je het bij Bestanden uploadt of als het met het formulier meekomt.</p>
|
||||
<p class="muted" style="margin:0">Er is geen woning gekozen.</p>
|
||||
{% endif %}
|
||||
</div></div>
|
||||
{% endif %}
|
||||
|
||||
{% if kaart %}
|
||||
<div class="card"><div class="card-body">
|
||||
<div class="card-top"><h2>Kavel</h2>
|
||||
<div class="actions"><a class="btn btn-outline-primary btn-sm" href="{{ kaart }}" target="_blank">Openen</a></div></div>
|
||||
<div class="kaart-frame"><iframe src="{{ kaart }}" title="Kaart van de kavel" loading="lazy"></iframe></div>
|
||||
</div></div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card o-2"><div class="card-body">
|
||||
<h2>Taken</h2>
|
||||
@@ -196,31 +219,8 @@
|
||||
</div></div>
|
||||
|
||||
<div class="card"><div class="card-body">
|
||||
<h2>Bestanden</h2>
|
||||
{% if files %}
|
||||
<div class="files">
|
||||
{% for f in files %}
|
||||
<div class="file">
|
||||
<a href="{{ url_for('design_viewer', fid=f.id) if f.title else url_for('upload_file', name=f.stored) }}" target="_blank" class="thumb">
|
||||
{% if f.mime.startswith('image/') %}<img src="{{ url_for('upload_file', name=f.stored) }}" alt="{{ f.original }}" loading="lazy">
|
||||
{% elif f.title %}3D{% else %}{{ f.original.rsplit('.', 1)[-1][:4] if '.' in f.original else 'bestand' }}{% endif %}
|
||||
</a>
|
||||
<div class="name" title="{{ f.original }}">{{ f.title or f.original }}</div>
|
||||
{% if f.kind == 'werk' %}<div class="muted">Intern, niet in portaal</div>{% endif %}
|
||||
{% if f.title %}{% if c.design_file_id == f.id %}<div class="muted">Gekozen model</div>{% else %}
|
||||
<form method="post" action="{{ url_for('contact_design', cid=c.id) }}"><input type="hidden" name="file_id" value="{{ f.id }}"><button class="linkbtn">Kies als model</button></form>
|
||||
{% endif %}{% endif %}
|
||||
<form method="post" action="{{ url_for('file_delete', fid=f.id) }}" onsubmit="return confirm('Bestand verwijderen?')">
|
||||
<button class="linkbtn">Verwijderen</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post" action="{{ url_for('contact_upload', cid=c.id) }}" enctype="multipart/form-data">
|
||||
<input class="form-control" type="file" name="files" multiple required aria-label="Bestanden kiezen">
|
||||
<div class="actions"><button class="btn btn-outline-primary btn-sm">Uploaden</button></div>
|
||||
</form>
|
||||
<h2>Documenten</h2>
|
||||
{{ documents_list(documents, url_for('contact_upload', cid=c.id), c.design_id or -1, url_for('contact_design', cid=c.id)) }}
|
||||
</div></div>
|
||||
|
||||
<div class="card"><div class="card-body">
|
||||
|
||||
Reference in New Issue
Block a user