diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000000000000000000000000000000000000..60317f4987d885fc380d731c6dd8792325342f44 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,215 @@ +module.exports = { + extends: [ + "eslint:recommended", + "plugin:vue/strongly-recommended", + "plugin:@intlify/vue-i18n/recommended", + ], + rules: { + "no-unused-vars": "warn", + "vue/no-unused-vars": "off", + "vue/multi-word-component-names": "off", + "@intlify/vue-i18n/key-format-style": [ + "error", + "snake_case", + { + splitByDots: false, + }, + ], + // "@intlify/vue-i18n/no-unused-keys": ["warn", {}], + "@intlify/vue-i18n/no-raw-text": [ + "error", + { + ignoreNodes: ["v-icon"], + ignorePattern: "^[-–—·#:()\\[\\]&\\.\\s]+$", + }, + ], + // Fixes for prettier (avoid eslint-config-prettier) + // The following rules can be used in some cases. See the README for more + // information. (These are marked with `0` instead of `"off"` so that a + // script can distinguish them.) + curly: 0, + "lines-around-comment": 0, + "max-len": 0, + "no-confusing-arrow": 0, + "no-mixed-operators": 0, + "no-tabs": 0, + "no-unexpected-multiline": 0, + quotes: 0, + "@typescript-eslint/quotes": 0, + "babel/quotes": 0, + "vue/html-self-closing": 0, + "vue/max-len": 0, + + // The rest are rules that you never need to enable when using Prettier. + "array-bracket-newline": "off", + "array-bracket-spacing": "off", + "array-element-newline": "off", + "arrow-parens": "off", + "arrow-spacing": "off", + "block-spacing": "off", + "brace-style": "off", + "comma-dangle": "off", + "comma-spacing": "off", + "comma-style": "off", + "computed-property-spacing": "off", + "dot-location": "off", + "eol-last": "off", + "func-call-spacing": "off", + "function-call-argument-newline": "off", + "function-paren-newline": "off", + "generator-star": "off", + "generator-star-spacing": "off", + "implicit-arrow-linebreak": "off", + indent: "off", + "jsx-quotes": "off", + "key-spacing": "off", + "keyword-spacing": "off", + "linebreak-style": "off", + "multiline-ternary": "off", + "newline-per-chained-call": "off", + "new-parens": "off", + "no-arrow-condition": "off", + "no-comma-dangle": "off", + "no-extra-parens": "off", + "no-extra-semi": "off", + "no-floating-decimal": "off", + "no-mixed-spaces-and-tabs": "off", + "no-multi-spaces": "off", + "no-multiple-empty-lines": "off", + "no-reserved-keys": "off", + "no-space-before-semi": "off", + "no-trailing-spaces": "off", + "no-whitespace-before-property": "off", + "no-wrap-func": "off", + "nonblock-statement-body-position": "off", + "object-curly-newline": "off", + "object-curly-spacing": "off", + "object-property-newline": "off", + "one-var-declaration-per-line": "off", + "operator-linebreak": "off", + "padded-blocks": "off", + "quote-props": "off", + "rest-spread-spacing": "off", + semi: "off", + "semi-spacing": "off", + "semi-style": "off", + "space-after-function-name": "off", + "space-after-keywords": "off", + "space-before-blocks": "off", + "space-before-function-paren": "off", + "space-before-function-parentheses": "off", + "space-before-keywords": "off", + "space-in-brackets": "off", + "space-in-parens": "off", + "space-infix-ops": "off", + "space-return-throw-case": "off", + "space-unary-ops": "off", + "space-unary-word-ops": "off", + "switch-colon-spacing": "off", + "template-curly-spacing": "off", + "template-tag-spacing": "off", + "unicode-bom": "off", + "wrap-iife": "off", + "wrap-regex": "off", + "yield-star-spacing": "off", + "@babel/object-curly-spacing": "off", + "@babel/semi": "off", + "@typescript-eslint/brace-style": "off", + "@typescript-eslint/comma-dangle": "off", + "@typescript-eslint/comma-spacing": "off", + "@typescript-eslint/func-call-spacing": "off", + "@typescript-eslint/indent": "off", + "@typescript-eslint/keyword-spacing": "off", + "@typescript-eslint/member-delimiter-style": "off", + "@typescript-eslint/no-extra-parens": "off", + "@typescript-eslint/no-extra-semi": "off", + "@typescript-eslint/object-curly-spacing": "off", + "@typescript-eslint/semi": "off", + "@typescript-eslint/space-before-blocks": "off", + "@typescript-eslint/space-before-function-paren": "off", + "@typescript-eslint/space-infix-ops": "off", + "@typescript-eslint/type-annotation-spacing": "off", + "babel/object-curly-spacing": "off", + "babel/semi": "off", + "flowtype/boolean-style": "off", + "flowtype/delimiter-dangle": "off", + "flowtype/generic-spacing": "off", + "flowtype/object-type-curly-spacing": "off", + "flowtype/object-type-delimiter": "off", + "flowtype/quotes": "off", + "flowtype/semi": "off", + "flowtype/space-after-type-colon": "off", + "flowtype/space-before-generic-bracket": "off", + "flowtype/space-before-type-colon": "off", + "flowtype/union-intersection-spacing": "off", + "react/jsx-child-element-spacing": "off", + "react/jsx-closing-bracket-location": "off", + "react/jsx-closing-tag-location": "off", + "react/jsx-curly-newline": "off", + "react/jsx-curly-spacing": "off", + "react/jsx-equals-spacing": "off", + "react/jsx-first-prop-new-line": "off", + "react/jsx-indent": "off", + "react/jsx-indent-props": "off", + "react/jsx-max-props-per-line": "off", + "react/jsx-newline": "off", + "react/jsx-one-expression-per-line": "off", + "react/jsx-props-no-multi-spaces": "off", + "react/jsx-tag-spacing": "off", + "react/jsx-wrap-multilines": "off", + "standard/array-bracket-even-spacing": "off", + "standard/computed-property-even-spacing": "off", + "standard/object-curly-even-spacing": "off", + "unicorn/empty-brace-spaces": "off", + "unicorn/no-nested-ternary": "off", + "unicorn/number-literal-case": "off", + "vue/array-bracket-newline": "off", + "vue/array-bracket-spacing": "off", + "vue/arrow-spacing": "off", + "vue/block-spacing": "off", + "vue/block-tag-newline": "off", + "vue/brace-style": "off", + "vue/comma-dangle": "off", + "vue/comma-spacing": "off", + "vue/comma-style": "off", + "vue/dot-location": "off", + "vue/func-call-spacing": "off", + "vue/html-closing-bracket-newline": "off", + "vue/html-closing-bracket-spacing": "off", + "vue/html-end-tags": "off", + "vue/html-indent": "off", + "vue/html-quotes": "off", + "vue/key-spacing": "off", + "vue/keyword-spacing": "off", + "vue/max-attributes-per-line": "off", + "vue/multiline-html-element-content-newline": "off", + "vue/multiline-ternary": "off", + "vue/mustache-interpolation-spacing": "off", + "vue/no-extra-parens": "off", + "vue/no-multi-spaces": "off", + "vue/no-spaces-around-equal-signs-in-attribute": "off", + "vue/object-curly-newline": "off", + "vue/object-curly-spacing": "off", + "vue/object-property-newline": "off", + "vue/operator-linebreak": "off", + "vue/quote-props": "off", + "vue/script-indent": "off", + "vue/singleline-html-element-content-newline": "off", + "vue/space-in-parens": "off", + "vue/space-infix-ops": "off", + "vue/space-unary-ops": "off", + "vue/template-curly-spacing": "off", + }, + settings: { + "vue-i18n": { + localeDir: "./aleksis/core/frontend/messages/*.{json}", + messageSyntaxVersion: "^8.0.0", + }, + }, + env: { + es2021: true, + }, + parserOptions: { + ecmaVersion: "latest", + }, +}; diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b97f64fa6b8de380209c4d933d18ee374a98e28c..c71fd45f792695baa4f1401c9ba41ce345f6b411 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,15 +1,15 @@ include: - - project: "AlekSIS/official/AlekSIS" - file: /ci/general.yml - - project: "AlekSIS/official/AlekSIS" - file: /ci/prepare/lock.yml - - project: "AlekSIS/official/AlekSIS" - file: /ci/test/lint.yml - - project: "AlekSIS/official/AlekSIS" - file: /ci/test/security.yml - - project: "AlekSIS/official/AlekSIS" - file: /ci/build/dist.yml - - project: "AlekSIS/official/AlekSIS" - file: /ci/publish/pypi.yml - - project: "AlekSIS/official/AlekSIS" - file: /ci/docker/image.yml + - project: "AlekSIS/official/AlekSIS" + file: /ci/general.yml + - project: "AlekSIS/official/AlekSIS" + file: /ci/prepare/lock.yml + - project: "AlekSIS/official/AlekSIS" + file: /ci/test/lint.yml + - project: "AlekSIS/official/AlekSIS" + file: /ci/test/security.yml + - project: "AlekSIS/official/AlekSIS" + file: /ci/build/dist.yml + - project: "AlekSIS/official/AlekSIS" + file: /ci/publish/pypi.yml + - project: "AlekSIS/official/AlekSIS" + file: /ci/docker/image.yml diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000000000000000000000000000000000..38d141b743fd55678f50077c0617924475817095 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,91 @@ +# Byte-compiled / optimized / DLL files +*$py.class +*.py[cod] +__pycache__/ + +# Distribution / packaging +*.egg +*.egg-info/ +.Python +.eggs/ +.installed.cfg +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ + +# Installer logs +pip-delete-this-directory.txt +pip-log.txt + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# pyenv +.python-version + +# Environments +.env +.venv +ENV/ +env/ +venv/ + +# Editors +*~ +DEADJOE +\#*# + +# IntelliJ +.idea +.idea/ + +# Database +db.sqlite3 + +# Sphinx +docs/_build/ + +# TeX +*.aux + +# Generated files +/node_modules/ +/static/ +/whoosh_index/ +poetry.lock + +.coverage +.mypy_cache/ +.tox/ +htmlcov/ +maintenance_mode_state.txt +media/ +package-lock.json +yarn.lock + +# VSCode +.vscode/ +.history/ +*.code-workspace + +/cache + +# Add HTML files to avoid problems with unsupported Django templates +*.html + +# Do not check/reformat generated files +aleksis/core/util/licenses.json +.vite/ diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 32dbe404414d0a0f687a2c026266bf263e8996a7..054e1dc63d669f99a3c67f4384abf77af7d9068c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,28 @@ All notable changes to this project will be documented in this file. The format is based on `Keep a Changelog`_, and this project adheres to `Semantic Versioning`_. +Unreleased +---------- + +This version requires AlekSIS-Core 3.0. It is incompatible with any previous +version. + +Removed +~~~~~~~ + +* Legacy menu integration for AlekSIS-Core pre-3.0 + + +Added +~~~~~ + +* Support for SPA in AlekSIS-Core 3.0 + +Fixed +----- + +* Replace usage of deprecated `ugettext_lazy` with `gettext_lazy`. + `1.2`_ - 2022-03-20 ------------------- diff --git a/aleksis/apps/tezor/forms.py b/aleksis/apps/tezor/forms.py index a28e20466fb6aeb1c469404855a34604d1187bc2..15871e4dec9df19d1fd0a2ee3be5515356bef04d 100644 --- a/aleksis/apps/tezor/forms.py +++ b/aleksis/apps/tezor/forms.py @@ -34,7 +34,24 @@ class EditClientForm(ExtensibleForm): class Meta: model = Client - fields = ["name", "email", "payment_variants"] + fields = [ + "name", + "email", + "pledge_enabled", + "sofort_enabled", + "sofort_api_id", + "sofort_api_key", + "sofort_project_id", + "paypal_enabled", + "paypal_client_id", + "paypal_secret", + "paypal_capture", + "sdd_enabled", + "sdd_creditor", + "sdd_creditor_identifier", + "sdd_iban", + "sdd_bic", + ] class EditInvoiceGroupForm(ExtensibleForm): @@ -43,4 +60,4 @@ class EditInvoiceGroupForm(ExtensibleForm): class Meta: model = InvoiceGroup - exclude = ["client"] + fields = ["name", "template_name"] diff --git a/aleksis/apps/tezor/frontend/index.js b/aleksis/apps/tezor/frontend/index.js new file mode 100644 index 0000000000000000000000000000000000000000..1faad005dfbbe16d7f2190fd2c921721a5e7ed6a --- /dev/null +++ b/aleksis/apps/tezor/frontend/index.js @@ -0,0 +1,133 @@ +export default { + meta: { + inMenu: true, + titleKey: "tezor.menu_title", + icon: "mdi-piggy-bank-outline", + permission: "tezor.view_menu_rule", + }, + children: [ + { + path: "invoice/:token/print/", + component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"), + props: { + byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true, + }, + name: "tezor.printInvoice", + }, + { + path: "invoice/:token/pay", + component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"), + props: { + byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true, + }, + name: "tezor.doPayment", + }, + { + path: "clients/", + component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"), + props: { + byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true, + }, + name: "tezor.clients", + meta: { + inMenu: true, + titleKey: "tezor.clients.menu_title", + icon: "mdi-domain", + permission: "tezor.can_view_clients", + }, + }, + { + path: "client/create/", + component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"), + props: { + byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true, + }, + name: "tezor.createClient", + }, + { + path: "client/:pk/edit/", + component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"), + props: { + byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true, + }, + name: "tezor.editClientByPk", + }, + { + path: "client/:pk/delete/", + component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"), + props: { + byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true, + }, + name: "tezor.deleteClientByPk", + }, + { + path: "client/:pk/", + component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"), + props: { + byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true, + }, + name: "tezor.clientByPk", + }, + { + path: "client/:pk/invoice_groups/create/", + component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"), + props: { + byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true, + }, + name: "tezor.createInvoiceGroup", + }, + { + path: "invoice_group/:pk/edit/", + component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"), + props: { + byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true, + }, + name: "tezor.editInvoiceGroupByPk", + }, + { + path: "invoice_group/:pk/", + component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"), + props: { + byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true, + }, + name: "tezor.invoiceGroupByPk", + }, + { + path: "invoice_group/:pk/delete/", + component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"), + props: { + byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true, + }, + name: "tezor.deleteInvoiceGroupByPk", + }, + { + path: "invoices/my/", + component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"), + props: { + byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true, + }, + name: "tezor.personalInvoices", + meta: { + inMenu: true, + titleKey: "tezor.personal_invoices.menu_title", + icon: "mdi-receipt-outline", + }, + }, + { + path: "invoice/:slug/", + component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"), + props: { + byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true, + }, + name: "tezor.invoiceByToken", + }, + { + path: "invoice/:token/send/", + component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"), + props: { + byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true, + }, + name: "tezor.sendInvoiceByToken", + }, + ], +}; diff --git a/aleksis/apps/tezor/frontend/messages/en.json b/aleksis/apps/tezor/frontend/messages/en.json new file mode 100644 index 0000000000000000000000000000000000000000..24811b78708ef68a2f6d092cf186b36d0641514a --- /dev/null +++ b/aleksis/apps/tezor/frontend/messages/en.json @@ -0,0 +1,11 @@ +{ + "tezor": { + "menu_title": "Payments and Money", + "clients": { + "menu_title": "Manage clients" + }, + "personal_invoices": { + "menu_title": "My invoices" + } + } +} diff --git a/aleksis/apps/tezor/locale/de_DE/LC_MESSAGES/django.po b/aleksis/apps/tezor/locale/de_DE/LC_MESSAGES/django.po index 03159e1d58131b47cf73e2789a706e350e3c4321..55d0d147a8b0d6cfd697a011ca7903b09dec3552 100644 --- a/aleksis/apps/tezor/locale/de_DE/LC_MESSAGES/django.po +++ b/aleksis/apps/tezor/locale/de_DE/LC_MESSAGES/django.po @@ -8,10 +8,9 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-03-22 21:24+0000\n" -"PO-Revision-Date: 2022-08-03 16:34+0000\n" -"Last-Translator: Jonathan Weth <teckids@jonathanweth.de>\n" -"Language-Team: German <https://translate.edugit.org/projects/aleksis/" -"aleksis-app-tezor/de/>\n" +"PO-Revision-Date: 2023-01-10 19:53+0000\n" +"Last-Translator: Robert Seimetz <robert.seimetz@teckids.org>\n" +"Language-Team: German <https://translate.edugit.org/projects/aleksis/aleksis-app-tezor/de/>\n" "Language: de_DE\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -219,7 +218,7 @@ msgstr "Art. Nr." #: aleksis/apps/tezor/tables.py:11 msgid "Item" -msgstr "Artikel" +msgstr "Objekt" #: aleksis/apps/tezor/tables.py:13 msgid "Tax Rate" diff --git a/aleksis/apps/tezor/locale/ru/LC_MESSAGES/django.po b/aleksis/apps/tezor/locale/ru/LC_MESSAGES/django.po index 72578f4ebc9ad160783230d7891396dd906aeabf..507d5d7805c77ed94e21c515323005f12d312855 100644 --- a/aleksis/apps/tezor/locale/ru/LC_MESSAGES/django.po +++ b/aleksis/apps/tezor/locale/ru/LC_MESSAGES/django.po @@ -8,17 +8,14 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-04-23 12:54+0000\n" -"PO-Revision-Date: 2022-06-12 05:32+0000\n" +"PO-Revision-Date: 2023-05-26 04:39+0000\n" "Last-Translator: Serhii Horichenko <m@sgg.im>\n" -"Language-Team: Russian <https://translate.edugit.org/projects/aleksis/" -"aleksis-app-tezor/ru/>\n" +"Language-Team: Russian <https://translate.edugit.org/projects/aleksis/aleksis-app-tezor/ru/>\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n" -"%100>=11 && n%100<=14)? 2 : 3);\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" "X-Generator: Weblate 4.12.1\n" #: aleksis/apps/tezor/forms.py:18 @@ -69,7 +66,7 @@ msgstr "ПрÑмой дебет SEPA" #: aleksis/apps/tezor/models/base.py:18 msgid "Name" -msgstr "ИмÑ" +msgstr "Полное имÑ" #: aleksis/apps/tezor/models/base.py:19 msgid "Email" @@ -223,7 +220,7 @@ msgstr "Ðрт.â„–" #: aleksis/apps/tezor/tables.py:11 msgid "Item" -msgstr "Ðртикул" +msgstr "Объект" #: aleksis/apps/tezor/tables.py:13 msgid "Tax Rate" diff --git a/aleksis/apps/tezor/locale/uk/LC_MESSAGES/django.po b/aleksis/apps/tezor/locale/uk/LC_MESSAGES/django.po index 209f8eff4a50278cdf2ee396cd39b7eca59022a3..3f14c34808e2d242a10c8c0d8e4a7d54341faae5 100644 --- a/aleksis/apps/tezor/locale/uk/LC_MESSAGES/django.po +++ b/aleksis/apps/tezor/locale/uk/LC_MESSAGES/django.po @@ -8,18 +8,14 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-04-23 12:54+0000\n" -"PO-Revision-Date: 2022-07-01 11:55+0000\n" +"PO-Revision-Date: 2023-01-25 05:58+0000\n" "Last-Translator: Serhii Horichenko <m@sgg.im>\n" -"Language-Team: Ukrainian <https://translate.edugit.org/projects/aleksis/" -"aleksis-app-tezor/uk/>\n" +"Language-Team: Ukrainian <https://translate.edugit.org/projects/aleksis/aleksis-app-tezor/uk/>\n" "Language: uk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 " -"? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > " -"14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % " -"100 >=11 && n % 100 <=14 )) ? 2: 3);\n" +"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n" "X-Generator: Weblate 4.12.1\n" #: aleksis/apps/tezor/forms.py:18 @@ -224,7 +220,7 @@ msgstr "Ðрт.â„–" #: aleksis/apps/tezor/tables.py:11 msgid "Item" -msgstr "Ðртикул" +msgstr "Об'єкт" #: aleksis/apps/tezor/tables.py:13 msgid "Tax Rate" diff --git a/aleksis/apps/tezor/menus.py b/aleksis/apps/tezor/menus.py deleted file mode 100644 index ed66d6af6513265871fcc9de8054e6d147d2d690..0000000000000000000000000000000000000000 --- a/aleksis/apps/tezor/menus.py +++ /dev/null @@ -1,49 +0,0 @@ -from django.utils.translation import gettext_lazy as _ - -MENUS = { - "NAV_MENU_CORE": [ - { - "name": _("Payments and Money"), - "url": "#", - "root": True, - "svg_icon": "mdi:piggy-bank", - "validators": [ - "menu_generator.validators.is_authenticated", - "aleksis.core.util.core_helpers.has_person", - ], - "submenu": [ - { - "name": _("Manage clients"), - "url": "clients", - "svg_icon": "mdi:domain", - "validators": [ - ( - "aleksis.core.util.predicates.permission_validator", - "tezor.view_clients_rule", - ) - ], - }, - { - "name": _("Payment variants"), - "url": "payment_variants", - "svg_icon": "mdi:account-credit-card-outline", - "validators": [ - ( - "aleksis.core.util.predicates.permission_validator", - "tezor.view_paymentvariants_rule", - ) - ], - }, - { - "name": _("My invoices"), - "url": "personal_invoices", - "svg_icon": "fa6-solid:file-invoice-dollar", - "validators": [ - "menu_generator.validators.is_authenticated", - "aleksis.core.util.core_helpers.has_person", - ], - }, - ], - } - ] -} diff --git a/aleksis/apps/tezor/migrations/0001_initial.py b/aleksis/apps/tezor/migrations/0001_initial.py index cbf05ad8ae5f1d30f15e09b5865f0484d5c9ab7d..a3de6485422db8c19122e4def2e93f803b737b34 100644 --- a/aleksis/apps/tezor/migrations/0001_initial.py +++ b/aleksis/apps/tezor/migrations/0001_initial.py @@ -1,7 +1,7 @@ # Generated by Django 3.2.12 on 2022-03-06 21:33 import aleksis.core.mixins -import django.contrib.sites.managers +import aleksis.core.managers from django.db import migrations, models import django.db.models.deletion @@ -25,7 +25,7 @@ class Migration(migrations.Migration): ('site', models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.site')), ], managers=[ - ('objects', django.contrib.sites.managers.CurrentSiteManager()), + ('objects', aleksis.core.managers.AlekSISBaseManager()), ], ), migrations.CreateModel( @@ -39,7 +39,7 @@ class Migration(migrations.Migration): ('site', models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.site')), ], managers=[ - ('objects', django.contrib.sites.managers.CurrentSiteManager()), + ('objects', aleksis.core.managers.AlekSISBaseManager()), ], ), migrations.CreateModel( diff --git a/aleksis/apps/tezor/migrations/0003_manual_invoicing.py b/aleksis/apps/tezor/migrations/0003_manual_invoicing.py index 90b7100ced2bcb58c6016004b88dcc38d15e5ee9..125bae2603ba33764d4dcb3c245d92dc06a2255e 100644 --- a/aleksis/apps/tezor/migrations/0003_manual_invoicing.py +++ b/aleksis/apps/tezor/migrations/0003_manual_invoicing.py @@ -1,6 +1,6 @@ # Generated by Django 3.2.12 on 2022-03-12 21:41 -import django.contrib.sites.managers +import aleksis.core.managers from django.db import migrations, models import django.db.models.deletion @@ -29,7 +29,7 @@ class Migration(migrations.Migration): 'abstract': False, }, managers=[ - ('objects', django.contrib.sites.managers.CurrentSiteManager()), + ('objects', aleksis.core.managers.AlekSISBaseManager()), ], ), migrations.AddField( diff --git a/aleksis/apps/tezor/migrations/0007_client_payment_variants.py b/aleksis/apps/tezor/migrations/0007_client_payment_variants.py index 26e258ef53333dc9644b69f289b2d54f6947e9a1..92b9380215f1a1fa285e615ebe39efde0c9a353d 100644 --- a/aleksis/apps/tezor/migrations/0007_client_payment_variants.py +++ b/aleksis/apps/tezor/migrations/0007_client_payment_variants.py @@ -39,7 +39,7 @@ def configure_clients(apps, schema_editor): values[f"{variant}_enabled"] = False warnings.warn(f"Payment variant {variant} enabled but {field.name} not configured!") - Client.objects.update(**values) + Client._base_manager.update(**values) class Migration(migrations.Migration): diff --git a/aleksis/apps/tezor/migrations/0010_alter_client_options_alter_invoice_options_and_more.py b/aleksis/apps/tezor/migrations/0010_alter_client_options_alter_invoice_options_and_more.py new file mode 100644 index 0000000000000000000000000000000000000000..45e0a284018bdca6cfcd10fea7e791ba6dc6aa7c --- /dev/null +++ b/aleksis/apps/tezor/migrations/0010_alter_client_options_alter_invoice_options_and_more.py @@ -0,0 +1,31 @@ +# Generated by Django 4.2.3 on 2023-07-22 15:41 + +import aleksis.core.managers +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('sites', '0002_alter_domain_unique'), + ('tezor', '0009_invoice_billing_phone'), + ] + + operations = [ + migrations.AddField( + model_name='client', + name='managed_by_app_label', + field=models.CharField(blank=True, editable=False, max_length=255, verbose_name='App label of app responsible for managing this instance'), + ), + migrations.AddField( + model_name='invoicegroup', + name='managed_by_app_label', + field=models.CharField(blank=True, editable=False, max_length=255, verbose_name='App label of app responsible for managing this instance'), + ), + migrations.AddField( + model_name='invoiceitem', + name='managed_by_app_label', + field=models.CharField(blank=True, editable=False, max_length=255, verbose_name='App label of app responsible for managing this instance'), + ), + ] diff --git a/aleksis/apps/tezor/models/base.py b/aleksis/apps/tezor/models/base.py index f7a052ac8ea3fe7b1c1bc41d62b8b722355f4c10..9adda38b122458151649d996cb979b2eba5e1eef 100644 --- a/aleksis/apps/tezor/models/base.py +++ b/aleksis/apps/tezor/models/base.py @@ -147,3 +147,6 @@ class Client(ExtensibleModel): class Meta: verbose_name = _("Client") verbose_name_plural = _("Clients") + constraints = [ + models.UniqueConstraint(fields=["name", "site"], name="uniq_client_per_site"), + ] diff --git a/aleksis/apps/tezor/models/invoice.py b/aleksis/apps/tezor/models/invoice.py index 5ea79e900e9c21879b2305d309e702e87e2a28e8..8e5c9d0ed0aada84fa1ed03a0c75e70ef19886c8 100644 --- a/aleksis/apps/tezor/models/invoice.py +++ b/aleksis/apps/tezor/models/invoice.py @@ -27,14 +27,16 @@ class InvoiceGroup(ExtensibleModel): verbose_name=_("Template to render invoices with as PDF"), blank=True, max_length=255 ) - def __str__(self) -> str: - return self.name - class Meta: + verbose_name = _("Invoice Group") + verbose_name_plural = _("Invoice Groups") constraints = [ models.UniqueConstraint(fields=["client", "name"], name="group_uniq_per_client") ] + def __str__(self) -> str: + return self.name + class Invoice(BasePayment, PureDjangoModel): STATUS_ICONS = { @@ -73,6 +75,17 @@ class Invoice(BasePayment, PureDjangoModel): ) items = models.ManyToManyField("InvoiceItem", verbose_name=_("Invoice items")) + class Meta: + verbose_name = _("Invoice") + verbose_name_plural = _("Invoices") + constraints = [ + models.UniqueConstraint(fields=["number", "group"], name="number_uniq_per_group"), + ] + permissions = (("send_invoice_email", _("Can send invoice by email")),) + + def __str__(self): + return self.number + def save(self, *args, **kwargs): if self.person: person = self.person @@ -100,6 +113,9 @@ class Invoice(BasePayment, PureDjangoModel): variants = [v for v in self.group.client.payment_variants.all() if v.label == self.variant] return variants[0] if variants else None + def get_absolute_url(self): + return reverse("invoice_by_token", kwargs={"slug": self.token}) + def get_variant_name(self): return PaymentVariant.get_payment_variants_as_dict()[self.variant].name @@ -127,12 +143,6 @@ class Invoice(BasePayment, PureDjangoModel): return None - class Meta: - constraints = [ - models.UniqueConstraint(fields=["number", "group"], name="number_uniq_per_group"), - ] - permissions = (("send_invoice_email", _("Can send invoice by email")),) - def get_billing_email_recipients(self): if hasattr(self.for_object, "get_billing_email_recipients"): return self.for_object.get_billing_email_recipients() @@ -171,9 +181,6 @@ class Invoice(BasePayment, PureDjangoModel): return TotalsTable(values) - def get_absolute_url(self): - return reverse("invoice_by_token", kwargs={"slug": self.token}) - def get_success_url(self): return self.get_absolute_url() @@ -192,6 +199,13 @@ class InvoiceItem(ExtensibleModel): verbose_name=_("Tax rate"), max_digits=4, decimal_places=1, default="0.0" ) + class Meta: + verbose_name = _("Invoice Item") + verbose_name_plural = _("Invoice Items") + + def __str__(self): + return f"{self.sku}: {self.description}" + def as_purchased_item(self): return PurchasedItem( name=self.description, diff --git a/aleksis/apps/tezor/rules.py b/aleksis/apps/tezor/rules.py index eebd93ae55f20cff8fade37929b2849007d5e950..c96f6de07293312f178b3922fb0f8525d154e8ad 100644 --- a/aleksis/apps/tezor/rules.py +++ b/aleksis/apps/tezor/rules.py @@ -182,3 +182,8 @@ rules.add_perm("tezor.send_invoice_email_rule", send_invoice_email_predicate) view_own_invoices_predicate = has_person rules.add_perm("tezor.view_own_invoices_list_rule", view_own_invoices_predicate) + +view_menu_predicate = ( + view_own_invoices_predicate | view_clients_predicate | view_invoice_groups_predicate +) +rules.add_perm("tezor.view_menu_rule", view_menu_predicate) diff --git a/aleksis/apps/tezor/tables.py b/aleksis/apps/tezor/tables.py index f4091e710ca9ddb6767515f8b103ab8949ac0c41..97490264c69c0508d6bfe862b8b3691b390c81ce 100644 --- a/aleksis/apps/tezor/tables.py +++ b/aleksis/apps/tezor/tables.py @@ -119,7 +119,7 @@ class InvoicesTable(tables.Table): verbose_name=_("View"), text=_("View"), ) - print = tables.LinkColumn( + print_action = tables.LinkColumn( "print_invoice", args=[A("token")], verbose_name=_("Print"), diff --git a/aleksis/apps/tezor/views.py b/aleksis/apps/tezor/views.py index a5724182e1ca7e5002c12c53dc5d2dad80829b3e..76fb0b9673314f84dbc3a3f1079bfae75e0f1365 100644 --- a/aleksis/apps/tezor/views.py +++ b/aleksis/apps/tezor/views.py @@ -194,11 +194,9 @@ class InvoiceGroupCreateView(PermissionRequiredMixin, AdvancedCreateView): def form_valid(self, form): client = Client.objects.get(id=self.kwargs["pk"]) - InvoiceGroup.objects.create( - client=client, - name=form.cleaned_data["name"], - template_name=form.cleaned_data["template_name"], - ) + self.object = form.save() + self.object.client = client + self.object.save() return redirect(self.get_success_url()) diff --git a/pyproject.toml b/pyproject.toml index e888291ee7889b3902944b3e8dd57b9cbba38633..dfd2d24e55b11cca61dbc7ca891df91f902d4fed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "AlekSIS-App-Tezor" -version = "1.2.1.dev0" +version = "2.0.dev0" packages = [ { include = "aleksis" } ] @@ -22,24 +22,67 @@ classifiers = [ "Intended Audience :: Education", "Topic :: Education" ] +maintainers = ["Jonathan Weth <dev@jonathanweth.de>", "Dominik George <dominik.george@teckids.org>"] + +[[tool.poetry.source]] +name = "PyPI" +priority = "primary" [[tool.poetry.source]] name = "gitlab" url = "https://edugit.org/api/v4/projects/461/packages/pypi/simple" -secondary = true - +priority = "supplemental" [tool.poetry.dependencies] python = "^3.9" -aleksis-core = "^2.8.1.dev0" +aleksis-core = "^4.0.0.dev0" django-payments = { version = "^1.0.0", extras = ["sofort"] } django-payments-sepa = "^1.0.1" -[tool.poetry.dev-dependencies] -aleksis-builddeps = "*" - [tool.poetry.plugins."aleksis.app"] tezor = "aleksis.apps.tezor.apps:DefaultConfig" + +[tool.poetry.group.dev.dependencies] +django-stubs = "^4.2" + +safety = "^2.3.5" + +flake8 = "^6.0.0" +flake8-django = "^1.0.0" +flake8-fixme = "^1.1.1" +flake8-mypy = "^17.8.0" +flake8-bandit = "^4.1.1" +flake8-builtins = "^2.0.0" +flake8-docstrings = "^1.5.0" +flake8-rst-docstrings = "^0.3.0" + +black = ">=21.0" +flake8-black = "^0.3.0" + +isort = "^5.0.0" +flake8-isort = "^6.0.0" + +curlylint = "^0.13.0" + +[tool.poetry.group.test.dependencies] +pytest = "^7.2" +pytest-django = "^4.1" +pytest-django-testing-postgresql = "^0.2" +pytest-cov = "^4.0.0" +pytest-sugar = "^0.9.2" +selenium = "<4.10.0" +freezegun = "^1.1.0" + +[tool.poetry.group.docs] +optional = true + +[tool.poetry.group.docs.dependencies] +sphinx = "^7.0" +sphinxcontrib-django = "^2.3.0" +sphinxcontrib-svg2pdfconverter = "^1.1.1" +sphinx-autodoc-typehints = "^1.7" +sphinx_material = "^0.0.35" + [tool.black] line-length = 100 exclude = "/migrations/" @@ -57,5 +100,5 @@ no_autofocus = true tabindex_no_positive = true [build-system] -requires = ["poetry>=1.0"] -build-backend = "poetry.masonry.api" +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/tox.ini b/tox.ini index 4a7c30f2c068c0bf871527ec6e248af8591e7c25..6e4b77ab1ded935257117696975c2150772cd85c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,16 +1,15 @@ [tox] skipsdist = True skip_missing_interpreters = true -envlist = py37,py38,py39 +envlist = py39,py310,py311 [testenv] whitelist_externals = poetry - sudo skip_install = true envdir = {toxworkdir}/globalenv commands_pre = poetry install - poetry run aleksis-admin yarn install + poetry run aleksis-admin vite build poetry run aleksis-admin collectstatic --no-input commands = poetry run pytest --cov=. {posargs} aleksis/ @@ -27,6 +26,8 @@ commands = poetry run black --check --diff aleksis/ poetry run isort -c --diff --stdout aleksis/ poetry run flake8 {posargs} aleksis/ + poetry run sh -c "aleksis-admin yarn run prettier --check --ignore-path={toxinidir}/.prettierignore {toxinidir}" + poetry run sh -c "aleksis-admin yarn run eslint {toxinidir}/aleksis/**/*/frontend/**/*.{js,vue} --config={toxinidir}/.eslintrc.js --resolve-plugins-relative-to=." [testenv:security] commands = @@ -46,6 +47,7 @@ commands = poetry run make -C docs/ html {posargs} commands = poetry run isort aleksis/ poetry run black aleksis/ + poetry run sh -c "aleksis-admin yarn run prettier --write --ignore-path={toxinidir}/.prettierignore {toxinidir}" [testenv:makemessages] commands = @@ -55,7 +57,7 @@ commands = [flake8] max_line_length = 100 exclude = migrations,tests -ignore = A002,A003,BLK100,E203,E231,W503,D100,D101,D102,D103,D104,D105,D106,D107,RST215,RST214,F821,F841,S106,T100,T101,DJ05 +ignore = BLK100,E203,E231,W503,D100,D101,D102,D103,D104,D105,D106,D107,RST215,RST214,F821,F841,S106,T100,T101,DJ05 [isort] profile = black