Files
crm/README.md
T

12 KiB
Raw Blame History

Van Emous CRM

Eenvoudige CRM in Python (Flask) met SQLite, in de Van Emous-huisstijl. Versie 0.0.1.

Functies

  • Leads en klanten (zoals in EspoCRM): status (nieuw → in behandeling → offerte → gewonnen/verloren), toewijzen aan een gebruiker, verwachte waarde, bron, omzetten van lead naar klant, tijdlijn met notities, e-mail, statuswissels en formulierinzendingen, bestanden en afbeeldingen, CSV-export.
  • Relaties (zoals accounts in EspoCRM), in twee soorten: klanten (bundels zoals een huishouden, familie of bedrijf, met meerdere contactpersonen en hun rol) en partners met een type (leverancier, aannemer, installateur, ...; de typen pas je aan bij Instellingen). Een lead omzetten naar klant zet de persoon in een nieuwe of bestaande klantrelatie. Leveranciers van producten worden automatisch partners.
  • Gekozen model per lead/contact: een woningontwerp uit de configurator (house-design-JSON, via het formulier, de API of geüpload bij Bestanden) wordt het gekozen model; het nieuwste telt, een ander ontwerp kies je op de contactpagina. Daar staat het in een 3D-viewer op de engine van de configurator (/files/<id>/viewer): 3D, doorsnede en plattegrond, met maten, m², inhoud, gewicht en een indicatieve prijs (uit de kosten-CSV's van de configurator). In de leadlijst staat het model als kolom. Met Lagen in de viewer kies je een bouwfase (t/m regelwerk, elektra, beplating, …) of losse lagen (regelwerk, elektra, kozijnen, dak, …), zoals Lagen in de configurator, zodat de ploeg de opbouw kan bekijken.
  • PDF's per ontwerp (HousePdf van de configurator, gemaakt in de viewer en bewaard bij het contact): een werktekening (alle tekenbladen, elektra, zaaglijst en kosten; intern, niet in het klantportaal) en een klant-PDF zoals de light-configurator die maakt (LightPdf: voorblad met beeld, kerncijfers, keuzes, indicatieve prijs en QR-code, dan aanzichten en plattegrond en de werkwijze; wel in het portaal). Ontbreken ze, dan maakt de viewer ze vanzelf bij het eerste openen; opnieuw maken via PDF in de weergave. Op de contactpagina bij het gekozen model en op de relatiepagina (Ontwerpen en tekeningen, voor alle contactpersonen).
  • Op mobiel staan bij contact en relatie de gegevens bovenaan, de 3D-weergave verderop.
  • Taken/opvolging per contact met datum en eigenaar; "Mijn taken" op het dashboard.
  • API voor formulieren (POST /api/leads): JSON, gewone formulieren en multipart met bestanden. Nederlandse en Engelse veldnamen worden herkend, onbekende velden bewaard. Dubbele leads (zelfde e-mail) komen bij het bestaande contact. Er is een honeypot (_gotcha) en een redirect naar een bedankpagina. Een house-design-object uit de configurator wordt als ontwerp bij de lead opgeslagen. Voorbeelden staan onder Instellingen.
  • Import van CSV en JSON voor leads/contactpersonen, relaties en producten, met een voorbeeld en kolomkoppeling. Het formaat van de configurator (;, komma-decimalen, BOM, #-regels) werkt direct.
  • Inbox: IMAP ophalen en SMTP versturen, mails automatisch gekoppeld aan contacten (op e-mailadres), contact aanmaken vanuit een mail, beantwoorden. Voorinstellingen voor OVHcloud (Zimbra / MX Plan) en Gmail, beveiliging per server (SSL/TLS of STARTTLS), een kopie van verstuurde mail in een IMAP-map (Sent bij Zimbra) en een knop om de verbinding te testen (Instellingen › E-mail). Nieuwe e-mail opstellen (ook doorsturen), met CC en bijlagen; mappen Verzonden en Archief (archiveren raakt de mailbox op de server niet), zoeken, als ongelezen markeren en alles als gelezen markeren.
  • E-mailtemplates: onderwerp en tekst met velden als {voornaam}, {model} en {afzender}, te kiezen bij een nieuwe e-mail, een antwoord en op de pagina van een contact. Velden worden direct ingevuld als het contact bekend is, anders bij het versturen (contact op het eerste adres in Aan).
  • Agenda: een agenda per gebruiker, samen in een maandoverzicht met de taken die een datum hebben; afspraken aan een contact koppelen (ook op de contactpagina en het dashboard), eenvoudige herhaling. Via CalDAV (/dav/, ontdekking via /.well-known/caldav) zet elke gebruiker zijn agenda op iPhone, Mac, Android (DAVx⁵) of in Thunderbird; inloggen met e-mailadres en een app-wachtwoord uit Mijn account.
  • Zoeken en Nieuw in de bovenbalk: één zoekveld voor leads, contacten, relaties, offertes, afspraken, producten en e-mail (live resultaten, / springt erheen), en een knop om direct iets nieuws aan te maken.
  • Producten met SKU als vaste sleutel, prijs, btw, eenheid, leverancier (partner) en afbeelding. Een import of POST /api/products werkt producten bij op SKU (alleen de meegegeven velden) en legt elke mutatie vast met de oude en de nieuwe waarde. Optioneel worden producten die niet meer in de lijst staan inactief gezet. Offerteregels bewaren hun eigen prijs, dus bestaande offertes veranderen niet. Een product dat in offertes gebruikt is, wordt bij verwijderen gearchiveerd. Export naar CSV in hetzelfde formaat als de import.
  • Offertes met regels (product of vrije regel), btw per tarief en een afdrukversie die je als PDF opslaat via de browser.
  • Bestek uit ontwerp bij een offerte (of "Offerte met bestek" bij het gekozen model): de engine van de configurator rekent het ontwerp door zoals Kosten & tijden (static/bestek.js, CostModel.compute met de kosten-CSV's) en zet alle posten met hoeveelheden in hoofdstukken (casco, isolatie, gevel, dak, vloer, kozijnen, afwerking, inrichting, verlichting, elektra met wcd's, schakelaars en meters leiding per type, transport en plaatsing). Elke post krijgt zijn kostprijs plus algemene kosten en marge; samen precies het totaal van de configurator. Schroeven, zaag- en boorwerk staan per hoofdstuk samen als "Bevestiging en bewerking". In de offerte per post (standaard: aantal × prijs per eenheid) of per hoofdstuk; het afrondingsverschil van de prijzen per eenheid gaat naar de laatste regel van 1 post zonder product, zodat het totaal precies klopt. Het bestek zelf komt als bijlage (zonder prijzen) in de afdrukversie en het portaal.
  • Producten uit het bestek: posten met een vaste prijs per eenheid (wcd, schakelaar, aansluitpunten, leidingen per type, spots en lampen, toilet, douche, keuken per m, deuren, ramen per maat, gevel- en dakbedekking per m², isolatie, plaatmateriaal, ...) worden gekoppeld aan een product met de kostencode van de configurator als SKU (elektra.wcdDubbel, inrichting.toilet, kozijnen.raam.1200x500; dezelfde codes als in kosten/leveranciers.csv). Ontbreekt het product, dan wordt het aangemaakt met de prijs per eenheid van de berekening; bestaande productprijzen worden alleen bijgewerkt als je dat aanvinkt (met producthistorie). De regels staan in PRODUCT_RULES in static/bestek.js.
  • Gebruikers met de rollen beheerder (ook gebruikers en instellingen) en medewerker.
  • Klantportaal (/portal): de klant krijgt een uitnodigingslink (kopiëren of mailen vanaf het contact), stelt een wachtwoord in en kan dan offertes bekijken, accepteren of afwijzen, bestanden uploaden en berichten sturen. Een geaccepteerde offerte zet het contact op gewonnen/klant.
  • Effecten met GSAP (cdnjs, 3.12.5) volgens de motion-regels van de huisstijl, in static/fx.js. Ze staan uit bij prefers-reduced-motion, en na 3 seconden is de inhoud altijd zichtbaar.

Starten

python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python app.py            # http://127.0.0.1:5000

De viewer heeft de engine van de configurator nodig (niet in git, ca. 160 MB met texturen):

tools/configurator-sync.sh [../configurator]   # kopieert js/, assets/, kosten/ naar static/configurator/

Opnieuw draaien als er in de configurator iets aan de weergave verandert; deploy.sh doet dit zelf bij elke deploy.

Demoleads (lorem ipsum, elk met een standaardmodel als gekozen model):

node tools/export-models.js        # standaardmodellen (HouseModel.MODELS) → demo/modellen/*.json
.venv/bin/python app.py demo       # leads aanmaken; bestaande demoleads worden overgeslagen

Bij de eerste keer maak je het beheerdersaccount aan. Daarna vul je onder Instellingen de bedrijfsgegevens, de e-mail en de toegestane websites voor de API in.

Online zetten (server)

Op een nieuwe Ubuntu-server (26.04 LTS) eenmalig, nadat de DNS van het domein naar de server wijst:

scp -r server ubuntu@SERVER:
ssh ubuntu@SERVER sudo bash server/setup.sh crm.voorbeeld.nl jij@voorbeeld.nl

server/setup.sh installeert Python, nginx, certbot, rsync en sqlite3, zet de tijdzone op Europe/Amsterdam, maakt de gebruiker crm met /srv/vanemous-crm (releases/, current, data/), de systemd-dienst vanemous-crm (gunicorn op 127.0.0.1:5050, CRM_PROXY=1), de nginx-site met HTTPS, de firewall (SSH, 80, 443), automatische beveiligingsupdates en cron (e-mail ophalen elke 10 minuten, elke nacht een databaseback-up, 7 dagen rond).

Een versie uitrollen (of terugrollen) vanaf je eigen machine:

tools/release.sh crm@crm.voorbeeld.nl           # nieuwste tag
tools/release.sh crm@crm.voorbeeld.nl v0.0.1    # bepaalde versie

Dat stuurt git archive <tag> en de 3D-engine uit ../configurator naar de server; server/activate.sh maakt daar een venv, back-upt de database, zet current om en herstart de dienst. Start de nieuwe versie niet, dan gaat hij terug naar de vorige. Logboek: sudo journalctl -u vanemous-crm.

CRM_PROXY=1 laat de app de X-Forwarded-Proto/-Host-headers van de proxy volgen (links naar buiten, zoals portaaluitnodigingen, worden dan https://domein/...) en zet sessiecookies op alleen-HTTPS. De proxy moet uploads tot 25 MB doorlaten (nginx: client_max_body_size 25m).

Lokaal zoals op de server, maar zonder nginx: ./deploy.sh (zie het script).

Back-ups buiten de server (bijv. de back-upoptie van OVH of een rsync van /srv/vanemous-crm/data) regel je apart: de nachtelijke kopie staat op dezelfde schijf en dekt uploads/ niet.

Tectum (configurator) op dezelfde server

De configurator (../configurator) staat als statische site op een eigen subdomein, helemaal achter één wachtwoord (nginx basic auth). Eenmalig, na server/setup.sh:

scp -r server ubuntu@SERVER:
ssh -t ubuntu@SERVER sudo bash server/tectum-setup.sh tectum.voorbeeld.nl jij@voorbeeld.nl [gebruikersnaam]

Dat vraagt het wachtwoord (gebruikersnaam standaard vanemous; wijzigen: sudo WACHTWOORD=1 bash ...), maakt /srv/www/tectum (van crm, zodat releases via dezelfde SSH-toegang gaan) en de nginx-site met HTTPS. Uitrollen of terugrollen vanaf je eigen machine, vanuit een tag van de configurator:

tools/tectum-release.sh crm@tectum.voorbeeld.nl           # nieuwste tag
tools/tectum-release.sh crm@tectum.voorbeeld.nl v0.7.6    # bepaalde versie

Opslag

Alles staat in data/: crm.db (SQLite), uploads/ (bestanden), secret.key (sessies). Een back-up is een kopie van die map. Nieuwe kolommen worden bij het starten automatisch aan een bestaande database toegevoegd (MIGRATIONS in app.py). Houd data/ buiten de webroot: er staan ook mailwachtwoorden in.

CRM_DATA=/ander/pad verplaatst de datamap, HOST en PORT stellen de ontwikkelserver in.

Structuur

app.py        routes en logica
importer.py   CSV/JSON inlezen en kolommen herkennen
mail.py       IMAP/SMTP (voorinstellingen, verbindingstest, kopie in Verzonden)
ical.py       iCalendar lezen/schrijven en herhalingen uitvouwen (agenda en CalDAV)
schema.sql    database
templates/    pagina's (Jinja)
static/       style.css (huisstijltokens), fx.js (GSAP-effecten), search.js (zoekveld), logo.svg, viewer.js (3D-viewer), bestek.js
static/configurator/   engine van de configurator (tools/configurator-sync.sh, niet in git)
tools/        configurator-sync.sh, export-models.js (standaardmodellen als JSON)
demo/modellen standaardmodellen als house-design-JSON voor de demoleads

De plattegrond in static/viewer.js is overgenomen uit js/light/light.js (drawPlan) van de configurator; verandert die, dan hier meenemen.