From 383077589523fcdfc18caec42a1796d9e11bf916 Mon Sep 17 00:00:00 2001 From: MaPaLo76 <72209721+MaPaLo76@users.noreply.github.com> Date: Fri, 6 Jun 2025 17:25:17 +0200 Subject: [PATCH] intial --- .gitignore | 138 +++++++++++++++++++++++++++++++++++++++++++++++ README.md | 56 +++++++++++++++++++ requirements.txt | 10 ++++ secrets.tmp.yml | 12 +++++ verteiler.py | 102 +++++++++++++++++++++++++++++++++++ 5 files changed, 318 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 requirements.txt create mode 100644 secrets.tmp.yml create mode 100644 verteiler.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ed1468 --- /dev/null +++ b/.gitignore @@ -0,0 +1,138 @@ +# Project based +login.yml +secrets.yml +*.xlsx +2024* +output +~* +.vscode + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +docsrc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# VS Code settings +.vscode/ouput diff --git a/README.md b/README.md new file mode 100644 index 0000000..0298818 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# Email an alle FKS Nutzer der letzten 2 Jahre + +## Voraussetzungen + +- Python 3.x +- Virtuelle Umgebung (optional, aber empfohlen) + + +## Installation + +1. Erstelle eine virtuelle Umgebung: + + ```sh + python -m venv .venv + ``` + +2. Aktiviere die virtuelle Umgebung: + + - Auf Windows: + + ```sh + .venv\Scripts\activate + ``` + + - Auf macOS/Linux: + + ```sh + source odoo_env/bin/activate + ``` + +3. Installiere die erforderlichen Pakete: + + ```sh + pip install -r requirements.txt + ``` + +## Konfiguration + +Erstelle eine Datei secrets.yml im Projektverzeichnis mit den Odoo API-Zugangsdaten: + + ```yaml + odoo-api: + url: "https://your-odoo-instance.com" + db: "your-database-name" + username: "your-username" + password: "your-password" + ``` + +## Verwendung + + + + +## Lizenz + +Dieses Projekt ist unter der MIT-Lizenz lizenziert. Weitere Informationen findest du in der `LICENSE`-Datei. \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b4e2557 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +PyYAML +pandas +PyPDF2[full] +tabulate +reportlab +docxtpl +docx2pdf +secure-smtplib +click +#email diff --git a/secrets.tmp.yml b/secrets.tmp.yml new file mode 100644 index 0000000..0f18fb1 --- /dev/null +++ b/secrets.tmp.yml @@ -0,0 +1,12 @@ +odoo-api: + url: 'http://youweblink.net/' + db: 'enter your db name' + username: 'enter your username' + password: 'enter your super secret password' +mail: + smtp_server: 'smtp.youremailprovider.com' + smtp_port: 465 # Für SSL, alternativ 587 für STARTTLS + email_sender: 'your-email@example.com' + email_password: 'your-email-password' + email_receiver: 'receiver-email@example.com' + diff --git a/verteiler.py b/verteiler.py new file mode 100644 index 0000000..cd261ac --- /dev/null +++ b/verteiler.py @@ -0,0 +1,102 @@ +import click +import logging +from datetime import datetime +from dateutil.relativedelta import relativedelta +from tools_odoo import load_odoo_api_credentials, test_odoo_api_connection, send_email + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +PRODUCT_NAMES = ["Formatkreissäge", "Hobel"] +EMAIL_SUBJECT = "Deine Maschinenutzung in der Offenen Werkstatt" +EMAIL_BODY_TEMPLATE = """Hallo {name}, + +wir haben gesehen, dass du in den letzten zwei Jahren unsere Formatkreissäge oder Hobel genutzt hast. +Vielen Dank für dein Vertrauen! Wenn du Fragen oder Feedback hast, melde dich gerne bei uns. + +Dein Team der Offenen Werkstatt +""" + + +def get_relevant_product_ids(models, db, uid, password): + domain = [('name', 'in', PRODUCT_NAMES)] + product_templates = models.execute_kw(db, uid, password, 'product.template', 'search_read', [domain], {'fields': ['id']}) + template_ids = [pt['id'] for pt in product_templates] + + if not template_ids: + logger.warning("⚠️ Keine Produkte mit den gewünschten Namen gefunden.") + return [] + + product_ids = models.execute_kw(db, uid, password, 'product.product', 'search', [[('product_tmpl_id', 'in', template_ids)]]) + logger.info(f"🔍 {len(product_ids)} Produktvarianten zu {PRODUCT_NAMES} gefunden.") + return product_ids + + +def get_partners_from_pos_orders(models, db, uid, password, product_ids): + since = datetime.now() - relativedelta(years=2) + since_str = since.strftime('%Y-%m-%d %H:%M:%S') + + order_line_domain = [ + ('product_id', 'in', product_ids), + ('create_date', '>=', since_str), + ] + + order_lines = models.execute_kw(db, uid, password, 'pos.order.line', 'search_read', [order_line_domain], {'fields': ['order_id'], 'limit': 100000}) + order_ids = list({line['order_id'][0] for line in order_lines if line['order_id']}) + + logger.info(f"🧾 {len(order_ids)} POS-Bestellungen mit relevanten Produkten seit {since_str} gefunden.") + + if not order_ids: + return [] + + orders = models.execute_kw(db, uid, password, 'pos.order', 'read', [order_ids], {'fields': ['partner_id']}) + partner_ids = list({order['partner_id'][0] for order in orders if order['partner_id']}) + + logger.info(f"👥 {len(partner_ids)} eindeutige Partner identifiziert.") + return partner_ids + + +def get_partner_emails(models, db, uid, password, partner_ids): + partners = models.execute_kw(db, uid, password, 'res.partner', 'read', [partner_ids], {'fields': ['name', 'email']}) + return [p for p in partners if p.get('email')] + + +@click.command() +@click.option('--send', is_flag=True, help='Wenn gesetzt, wird sofort eine E-Mail versendet.') +def main(send): + secrets = load_odoo_api_credentials() + if not secrets: + return + + url, db, uid, models = test_odoo_api_connection(secrets) + if not uid: + return + + product_ids = get_relevant_product_ids(models, db, uid, secrets['odoo-api']['password']) + if not product_ids: + return + + partner_ids = get_partners_from_pos_orders(models, db, uid, secrets['odoo-api']['password'], product_ids) + if not partner_ids: + return + + partners = get_partner_emails(models, db, uid, secrets['odoo-api']['password'], partner_ids) + + logger.info(f"📋 Es wurden {len(partners)} Partner mit E-Mail gefunden.") + + for partner in partners: + name = partner['name'] + email = partner['email'] + message = EMAIL_BODY_TEMPLATE.format(name=name) + + if send: + #send_email(secrets, partner, subject=EMAIL_SUBJECT, body=message) + logger.info(f"📧 E-Mail an {email} gesendet.") + else: + logger.info(f"🔍 Testmodus: {email} würde E-Mail erhalten. Name: {name}") + + logger.info("🏁 Fertig.") + + +if __name__ == '__main__': + main()