From c992fed3e66036e112bca9d4566b79f3db38feee Mon Sep 17 00:00:00 2001 From: Paul J Stevens Date: Sat, 13 Jan 2024 21:36:20 +0100 Subject: [PATCH] refactoring --- .pre-commit-config.yaml | 14 +++++ Makefile | 6 +- etc/supervisord.conf | 2 +- pdm.lock | 128 +++++++++++++++++++++++++++++++++++++++- pyproject.toml | 4 +- src/bij1/erp/config.py | 65 ++++++++++++++++++++ src/bij1/erp/models.py | 52 ++++++++-------- 7 files changed, 239 insertions(+), 32 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 src/bij1/erp/config.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..14316d8 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,14 @@ +# File format: https://pre-commit.com/#plugins +# Supported hooks: https://pre-commit.com/hooks.html +# Running "make format" fixes most issues for you +repos: + - repo: https://github.com/ambv/black + rev: 23.12.1 + hooks: + - id: black + exclude: ^.*\b(migrations)\b.*$ + - repo: https://github.com/charliermarsh/ruff-pre-commit + # Ruff version. + rev: "v0.1.13" + hooks: + - id: ruff diff --git a/Makefile b/Makefile index f440220..dce697d 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ DBNAME=bij1 install: pdm install -d + pre-commit install setup: TRYTOND_DATABASE_URI=postgresql://localhost/$(DBNAME) trytond-admin -d $(DBNAME) --all --verbose @@ -13,8 +14,11 @@ setup: run: supervisord +configure: + bin/bij1_setup + sync: - bin/airtable -c etc/dev.ini -d $(DBNAME) + bin/bij1_airtable frontend: frontend/package.json make -C frontend diff --git a/etc/supervisord.conf b/etc/supervisord.conf index 48a978e..1ce5666 100644 --- a/etc/supervisord.conf +++ b/etc/supervisord.conf @@ -24,7 +24,7 @@ stopasgroup=true redirect_stderr=true [program:api] -command=uvicorn bij1.api.main:app --reload --port 5000 +command=uvicorn bij1.api.main:app --reload-dir=%(ENV_PWD)s/src/ --port 5000 stopasgroup=true redirect_stderr=true diff --git a/pdm.lock b/pdm.lock index 178960a..0b888f3 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "dev"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:7505f6c4957e308846e2f5aa7d93c6569ae440f1e934f4e370578ead99fce95a" +content_hash = "sha256:0a53b25a34df010604c4713ea2e930d7689fdf1959140aeefd5550c0a9511c4e" [[package]] name = "annotated-types" @@ -44,6 +44,17 @@ files = [ {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, ] +[[package]] +name = "cfgv" +version = "3.4.0" +requires_python = ">=3.8" +summary = "Validate configuration and produce human readable error messages." +groups = ["default"] +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + [[package]] name = "charset-normalizer" version = "3.3.2" @@ -122,6 +133,16 @@ files = [ {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, ] +[[package]] +name = "distlib" +version = "0.3.8" +summary = "Distribution utilities" +groups = ["default"] +files = [ + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, +] + [[package]] name = "factory-boy" version = "3.3.0" @@ -181,6 +202,17 @@ files = [ {file = "fastapi-0.108.0.tar.gz", hash = "sha256:5056e504ac6395bf68493d71fcfc5352fdbd5fda6f88c21f6420d80d81163296"}, ] +[[package]] +name = "filelock" +version = "3.13.1" +requires_python = ">=3.8" +summary = "A platform independent file lock." +groups = ["default"] +files = [ + {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, + {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, +] + [[package]] name = "genshi" version = "0.7.7" @@ -219,6 +251,17 @@ files = [ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] +[[package]] +name = "identify" +version = "2.5.33" +requires_python = ">=3.8" +summary = "File identification library for Python" +groups = ["default"] +files = [ + {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"}, + {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"}, +] + [[package]] name = "idna" version = "3.6" @@ -347,6 +390,20 @@ files = [ {file = "marshmallow-3.20.1.tar.gz", hash = "sha256:5d2371bbe42000f2b3fb5eaa065224df7d8f8597bc19a1bbfa5bfe7fba8da889"}, ] +[[package]] +name = "nodeenv" +version = "1.8.0" +requires_python = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +summary = "Node.js virtual environment builder" +groups = ["default"] +dependencies = [ + "setuptools", +] +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + [[package]] name = "packaging" version = "23.2" @@ -368,6 +425,17 @@ files = [ {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, ] +[[package]] +name = "platformdirs" +version = "4.1.0" +requires_python = ">=3.8" +summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +groups = ["default"] +files = [ + {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, + {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, +] + [[package]] name = "pluggy" version = "1.3.0" @@ -389,6 +457,24 @@ files = [ {file = "polib-1.2.0.tar.gz", hash = "sha256:f3ef94aefed6e183e342a8a269ae1fc4742ba193186ad76f175938621dbfc26b"}, ] +[[package]] +name = "pre-commit" +version = "3.6.0" +requires_python = ">=3.9" +summary = "A framework for managing and maintaining multi-language pre-commit hooks." +groups = ["default"] +dependencies = [ + "cfgv>=2.0.0", + "identify>=1.0.0", + "nodeenv>=0.11.1", + "pyyaml>=5.1", + "virtualenv>=20.10.0", +] +files = [ + {file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"}, + {file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"}, +] + [[package]] name = "proteus" version = "6.8.1" @@ -609,6 +695,30 @@ files = [ {file = "python_stdnum-1.19-py2.py3-none-any.whl", hash = "sha256:1b5b401ad3f45b798b0317313b781a433f5d7a5ff2c9feb8054664f76f78644e"}, ] +[[package]] +name = "pyyaml" +version = "6.0.1" +requires_python = ">=3.6" +summary = "YAML parser and emitter for Python" +groups = ["default"] +files = [ + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + [[package]] name = "relatorio" version = "0.10.1" @@ -1180,6 +1290,22 @@ files = [ {file = "uvicorn-0.25.0.tar.gz", hash = "sha256:6dddbad1d7ee0f5140aba5ec138ddc9612c5109399903828b4874c9937f009c2"}, ] +[[package]] +name = "virtualenv" +version = "20.25.0" +requires_python = ">=3.7" +summary = "Virtual Python Environment builder" +groups = ["default"] +dependencies = [ + "distlib<1,>=0.3.7", + "filelock<4,>=3.12.2", + "platformdirs<5,>=3.9.1", +] +files = [ + {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, + {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, +] + [[package]] name = "werkzeug" version = "3.0.1" diff --git a/pyproject.toml b/pyproject.toml index 6258a99..b1448f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,13 +24,15 @@ dependencies = [ "uvicorn>=0.25.0", "gunicorn>=21.2.0", "supervisor>=4.2.5", + "pre-commit>=3.6.0", ] requires-python = ">=3.11" readme = "README.md" license = {text = "Proprietary"} [project.scripts] -airtable = "bij1.erp.airtable:airtable" +bij1_airtable = "bij1.erp.airtable:airtable" +bij1_setup = "bij1.erp.config:configure" [tool.pdm] package-dir = "src" diff --git a/src/bij1/erp/config.py b/src/bij1/erp/config.py new file mode 100644 index 0000000..743c0a8 --- /dev/null +++ b/src/bij1/erp/config.py @@ -0,0 +1,65 @@ +from decimal import Decimal +from typing import Any +from trytond.transaction import Transaction +from bij1.erp import pool + + +__all__ = ["config"] + +COMPANY_NAME = "BIJ1" +CURRENCY_CODE = "EUR" +CURRENCY_SYMBOL = "€" +SUBSCRIPTION_DESCRIPTION = f"Lidmaatschap {COMPANY_NAME}" + + +class CONFIG: + company: Any + currency: Any + subscription_description: str = SUBSCRIPTION_DESCRIPTION + + def __init__(self): + with Transaction().start(pool.database_name, 0): + self._company() + self._currency() + + def _party(self): + Party = pool.get("party.party") + if not (party := Party.search([("name", "=", COMPANY_NAME)])): + party = Party() + party.name = COMPANY_NAME + party.save() + else: + party = party[0] + return party + + def _company(self): + Company = pool.get("company.company") + if not (company := Company.search([("party.name", "=", COMPANY_NAME)])): + company = Company() + company.party = self._party() + company.save() + else: + company = company[0] + + self.company = company + + def _currency(self): + Currency = pool.get("currency.currency") + if not (currency := Currency.search([("code", "=", "EUR")])): + currency = Currency() + currency.rounding = Decimal("0.01") + currency.digits = 2 + else: + currency = currency[0] + + currency.code = CURRENCY_CODE + currency.symbol = CURRENCY_SYMBOL + currency.save() + self.currency = currency + + +config = CONFIG() + + +def configure(): + pass diff --git a/src/bij1/erp/models.py b/src/bij1/erp/models.py index 0f50195..81dd65c 100644 --- a/src/bij1/erp/models.py +++ b/src/bij1/erp/models.py @@ -5,6 +5,7 @@ from decimal import Decimal import typing import logging from bij1.erp import pool +from bij1.erp.config import config logger = logging.getLogger(__name__) @@ -42,8 +43,6 @@ class Member: def save_party(self): Party = pool.get("party.party") - Company = pool.get("company.company") - self.company = Company.search([])[0] if not (obj := Party.search([("name", "=", self.name)])): obj = Party() obj.name = self.name @@ -61,6 +60,7 @@ class Member: address.street = self.address address.postal_code = self.postcode address.city = self.city + address.invoice = True address.save() def save_email(self): @@ -150,7 +150,7 @@ class Member: if not (self.iban and self.mandate): return Mandate = pool.get("account.payment.sepa.mandate") - if Mandate.search([('identification', '=', self.mandate)]): + if Mandate.search([("identification", "=", self.mandate)]): return BankAccount = pool.get("bank.account") @@ -167,7 +167,7 @@ class Member: account_number = bank_account.numbers[0] mandate = Mandate() mandate.identification = self.mandate - mandate.company = self.company + mandate.company = config.company mandate.account_number = account_number mandate.party = self.party mandate.signature_date = self.since @@ -176,45 +176,41 @@ class Member: def save_subscription(self): if not self.period: return - Currency = pool.get('currency.currency') - currency, = Currency.search([('code', '=', 'EUR')]) - Subscription = pool.get('sale.subscription') - SubscriptionLine = pool.get('sale.subscription.line') - SubscriptionService = pool.get('sale.subscription.service') - services = SubscriptionService.search([('active', '=', True)]) + Subscription = pool.get("sale.subscription") + SubscriptionLine = pool.get("sale.subscription.line") + SubscriptionService = pool.get("sale.subscription.service") + services = SubscriptionService.search([("active", "=", True)]) services = {x.consumption_recurrence.name: x for x in services} - if not (subscription := Subscription.search(['party', '=', self.party])): + if not (service := services.get(self.period.lower())): + raise Exception("unkown period: %s" % self.period) + + if not (subscription := Subscription.search(["party", "=", self.party])): subscription = Subscription() - subscription.company = self.company + subscription.company = config.company subscription.party = self.party - subscription.currency = currency + subscription.currency = config.currency subscription.start_date = self.since - subscription.invoice_start_date = date(2024,1,1) + subscription.invoice_start_date = date(2024, 1, 1) + subscription.invoice_address = self.party.addresses[0] + subscription.invoice_recurrence = service.consumption_recurrence else: subscription = subscription[0] - subscription.description = "Lidmaatschap BIJ1" - if not subscription.invoice_address: - subscription.invoice_address = self.party.addresses[0] - - service = services.get(self.period.lower()) - if not service: - raise Exception("unkown period: %s" % self.period) - - subscription.invoice_recurrence = service.consumption_recurrence + subscription.description = config.subscription_description subscription.save() - if not (line := SubscriptionLine.search([ - ('subscription', '=', subscription), - ('service', '=', service) - ])): + if not ( + line := SubscriptionLine.search( + [("subscription", "=", subscription), ("service", "=", service)] + ) + ): line = SubscriptionLine() line.service = service line.subscription = subscription line.quantity = 1 line.unit_price = self.amount - line.start_date = max([self.since, date(2024,1,1)]) + line.start_date = max([self.since, date(2024, 1, 1)]) line.unit = service.product.default_uom line.save()