diff --git a/.deepsource.toml b/.deepsource.toml index c2162e34..25bc3d76 100644 --- a/.deepsource.toml +++ b/.deepsource.toml @@ -6,4 +6,3 @@ enabled = true [analyzers.meta] runtime_version = "3.x.x" - diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml deleted file mode 100644 index 0a4c322e..00000000 --- a/.github/workflows/ci-tests.yml +++ /dev/null @@ -1,111 +0,0 @@ -# CI: sets up a Frappe bench and runs the PropMS test suite. -# -# Confirmed against the dev bench: frappe 15.100.1, erpnext 15.98.1 -# (both on version-15). ERPNext is required — PropMS's doc_events hook -# directly into ERPNext doctypes (Sales Invoice, Journal Entry Account, -# Material Request, Sales Order) even though it's not declared in -# hooks.py required_apps. -# -# TODO before this will run green: -# - Confirm the default/target branches under `on:` match your repo -# (develop vs main). -# - Confirm Python version (`python --version` in the bench). - -name: CI - -on: - pull_request: - branches: [develop, main] - push: - branches: [develop, main] - -env: - FRAPPE_BRANCH: version-15 - ERPNEXT_BRANCH: version-15 # only used if the get-app step below is uncommented - -jobs: - tests: - runs-on: ubuntu-latest - - services: - mysql: - image: mariadb:10.6 - env: - MYSQL_ROOT_PASSWORD: root - ports: - - 3306:3306 - options: >- - --health-cmd="mysqladmin ping" - --health-interval=10s - --health-timeout=5s - --health-retries=5 - redis-cache: - image: redis:alpine - ports: - - 13000:6379 - redis-queue: - image: redis:alpine - ports: - - 11000:6379 - - steps: - - name: Checkout PropMS - uses: actions/checkout@v4 - with: - path: apps/propms - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" # TODO: confirm - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: "18" - - - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y wkhtmltopdf mariadb-client - - - name: Install bench - run: pip install frappe-bench - - - name: Init bench - run: | - bench init frappe-bench \ - --frappe-branch ${{ env.FRAPPE_BRANCH }} \ - --skip-redis-config-generation \ - --skip-assets \ - --python "$(which python)" - - - name: Get ERPNext - working-directory: frappe-bench - run: bench get-app erpnext --branch ${{ env.ERPNEXT_BRANCH }} - - - name: Get PropMS - working-directory: frappe-bench - run: bench get-app propms ../apps/propms - - - name: Configure bench for CI services - working-directory: frappe-bench - run: | - bench set-mariadb-host 127.0.0.1 - bench set-redis-cache-host redis://localhost:13000 - bench set-redis-queue-host redis://localhost:11000 - bench set-redis-socketio-host redis://localhost:11000 - sed -i 's/redis_socketio/redis_queue/g' sites/common_site_config.json || true - - - name: Create test site - working-directory: frappe-bench - run: | - bench new-site test_site \ - --mariadb-root-password root \ - --admin-password admin \ - --no-mariadb-socket - bench --site test_site install-app propms - - - name: Run PropMS tests - working-directory: frappe-bench - run: bench --site test_site run-tests --app propms diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index f2ee62d5..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,89 +0,0 @@ -name: CI - -on: - push: - branches: - - develop - pull_request: - -concurrency: - group: develop-propms-${{ github.event.number }} - cancel-in-progress: true - -jobs: - tests: - runs-on: ubuntu-latest - name: Server - - services: - redis-cache: - image: redis:alpine - ports: - - 13000:6379 - redis-queue: - image: redis:alpine - ports: - - 11000:6379 - mariadb: - image: mariadb:10.6 - env: - MYSQL_ROOT_PASSWORD: root - ports: - - 3306:3306 - options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3 - - steps: - - name: Clone - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.10' - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 18 - check-latest: true - - - name: Cache pip - uses: actions/cache@v4 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py', '**/setup.cfg') }} - restore-keys: | - ${{ runner.os }}-pip- - ${{ runner.os }}- - - - name: Install MariaDB Client - run: sudo apt-get install -y mariadb-client - - - name: Setup - run: | - pip install frappe-bench - bench init --skip-redis-config-generation --skip-assets --frappe-branch version-15 --python "$(which python)" ~/frappe-bench - mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'" - mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'" - - - name: Install - working-directory: /home/runner/frappe-bench - run: | - bench get-app --skip-assets erpnext --branch version-15 --resolve-deps - bench get-app --skip-assets propms $GITHUB_WORKSPACE --resolve-deps - bench setup requirements --dev - bench new-site --db-root-password root --admin-password admin test_site - bench --site test_site install-app erpnext - bench --site test_site install-app propms - env: - CI: 'Yes' - - - name: Smoke Test - working-directory: /home/runner/frappe-bench - run: | - bench --site test_site set-config allow_tests true - bench --site test_site execute erpnext.setup.utils.before_tests - bench --site test_site migrate - bench --site test_site list-apps - env: - TYPE: server diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 7a897352..57d5a5ef 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -8,51 +8,55 @@ permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: linters-propms-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: - linter: - name: 'Frappe Linter' + semgrep: + name: Frappe Linter runs-on: ubuntu-latest - if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 with: fetch-depth: 0 + - uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: "3.11" cache: pip - - name: Install pre-commit - run: pip install pre-commit - - name: Run pre-commit on changed files - run: | - pre-commit run \ - --show-diff-on-failure \ - --color=always \ - --from-ref origin/${{ github.base_ref }} \ - --to-ref HEAD - name: Download Semgrep rules run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules + - name: Install Semgrep + run: pip install semgrep + + # Blocking: real bugs and security issues only - name: Run Semgrep rules run: | - pip install semgrep - semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness + semgrep scan --config ./frappe-semgrep-rules/rules \ + --config r/python.lang.security \ + --severity=ERROR --error propms + + # Informational: style and i18n warnings, never fails the build + - name: Semgrep warnings (non-blocking) + if: always() + run: | + semgrep scan --config ./frappe-semgrep-rules/rules \ + --config r/python.lang.security \ + --severity=WARNING propms || true deps-vulnerable-check: - name: 'Vulnerable Dependency Check' + name: Vulnerable Dependency Check runs-on: ubuntu-latest steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: - python-version: '3.10' - - - uses: actions/checkout@v4 + python-version: "3.11" - name: Cache pip uses: actions/cache@v4 @@ -66,5 +70,4 @@ jobs: - name: Install and run pip-audit run: | pip install pip-audit - cd ${GITHUB_WORKSPACE} pip-audit --desc on . diff --git a/.github/workflows/modernize-frappe.yml b/.github/workflows/modernize-frappe.yml index 02b15b99..e6e37596 100644 --- a/.github/workflows/modernize-frappe.yml +++ b/.github/workflows/modernize-frappe.yml @@ -9,4 +9,4 @@ permissions: jobs: modernize: - uses: Aakvatech-Limited/frappe-maintenance/.github/workflows/modernize-frappe-reusable.yml@main \ No newline at end of file + uses: Aakvatech-Limited/frappe-maintenance/.github/workflows/modernize-frappe-reusable.yml@main diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 00000000..a1834900 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,31 @@ +name: Pre-commit + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: precommit-propms-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + pre-commit: + name: pre-commit + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: pip + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index e2383439..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Generate Semantic Release - -on: - workflow_dispatch: - -permissions: - contents: write - issues: write - pull-requests: write - -concurrency: - group: release-${{ github.ref }} - cancel-in-progress: true - -jobs: - release: - name: Release - runs-on: ubuntu-latest - steps: - - name: Checkout Entire Repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - persist-credentials: false - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Setup dependencies - run: | - npm install @semantic-release/git @semantic-release/exec --no-save - - - name: Create Release - env: - GH_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }} - GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }} - GIT_AUTHOR_NAME: "Aakvatech Release Bot" - GIT_AUTHOR_EMAIL: "info@aakvatech.com" - GIT_COMMITTER_NAME: "Aakvatech Release Bot" - GIT_COMMITTER_EMAIL: "info@aakvatech.com" - run: npx semantic-release diff --git a/.github/workflows/semantic-commits.yml b/.github/workflows/semantic-commits.yml new file mode 100644 index 00000000..a3024790 --- /dev/null +++ b/.github/workflows/semantic-commits.yml @@ -0,0 +1,31 @@ +name: Semantic Commits + +on: + pull_request: {} + +permissions: + contents: read + +concurrency: + group: commitcheck-propms-${{ github.event.number }} + cancel-in-progress: true + +jobs: + commitlint: + name: Check Commit Messages + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 200 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + check-latest: true + + - name: Check commit messages + run: | + npm install @commitlint/cli @commitlint/config-conventional conventional-changelog-conventionalcommits + npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} diff --git a/.gitignore b/.gitignore index 70899f1a..823a8764 100755 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ *.egg-info *.swp tags -propms/docs/current \ No newline at end of file +propms/docs/current + +# Cloned by the frappe-semgrep-rules pre-commit hook +frappe-semgrep-rules/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6f522dfe..6f82128f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,6 @@ -exclude: 'node_modules|.git' +exclude: "^(node_modules/|frappe-semgrep-rules/|[.]vscode/|.*/node_modules/)" default_stages: [pre-commit] +default_install_hook_types: [pre-commit, commit-msg, pre-push] fail_fast: false repos: @@ -7,20 +8,74 @@ repos: rev: v5.0.0 hooks: - id: trailing-whitespace - exclude: ".*json$|.*txt$|.*csv|.*md|.*svg" + exclude: '\.(json|txt|csv|md|svg)$' - id: end-of-file-fixer - exclude: ".*json$|.*csv" + exclude: '\.(json|csv|svg)$' - id: check-merge-conflict - id: check-ast - id: check-json - id: check-toml - id: check-yaml + - id: debug-statements + - id: no-commit-to-branch + args: + - --branch + - main + - --branch + - master + - --branch + - production + - --branch + - version-14 + - --branch + - version-15 + - --branch + - version-16 - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.1 + rev: v0.13.2 hooks: - id: ruff - name: "ruff lint" + name: "Run ruff import sorter" + args: ["--select=I", "--fix"] + files: '^propms/.*\.py$' + - id: ruff + name: "Run ruff linter" args: ["--fix"] + files: '^propms/.*\.py$' - id: ruff-format - name: "ruff format" + name: "Run ruff formatter" + files: '^propms/.*\.py$' + + - repo: local + hooks: + - id: frappe-semgrep-rules + name: "Frappe Semgrep Security Rules" + entry: bash -c 'if [ ! -d frappe-semgrep-rules/.git ]; then rm -rf frappe-semgrep-rules && GIT_TEMPLATE_DIR="" git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules; fi && semgrep scan --config ./frappe-semgrep-rules/rules --config r/python.lang.security --severity=ERROR --error --quiet "$@"' -- + language: python + additional_dependencies: ["semgrep"] + types: [python] + files: '^propms/.*\.py$' + pass_filenames: true + require_serial: true + + - id: full-repository-check + name: "Full repository check before push" + entry: bash -c 'if command -v pre-commit >/dev/null 2>&1; then exec pre-commit run --all-files --hook-stage pre-commit --show-diff-on-failure --color=always; else exec python3 -m pre_commit run --all-files --hook-stage pre-commit --show-diff-on-failure --color=always; fi' + language: system + stages: [pre-push] + pass_filenames: false + always_run: true + verbose: true + + - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook + rev: v9.22.0 + hooks: + - id: commitlint + stages: [commit-msg] + additional_dependencies: ["@commitlint/config-conventional"] + +ci: + autoupdate_schedule: weekly + skip: [frappe-semgrep-rules, full-repository-check] + submodules: false diff --git a/.releaserc.json b/.releaserc.json deleted file mode 100644 index 5b37bb96..00000000 --- a/.releaserc.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "branches": ["version-15"], - "plugins": [ - [ - "@semantic-release/commit-analyzer", - { - "preset": "angular", - "releaseRules": [ - { "breaking": true, "release": false } - ] - } - ], - "@semantic-release/release-notes-generator", - [ - "@semantic-release/exec", - { - "prepareCmd": "sed -ir -E \"s/\\\"[0-9]+\\.[0-9]+\\.[0-9]+\\\"/\\\"${nextRelease.version}\\\"/\" propms/__init__.py" - } - ], - [ - "@semantic-release/git", - { - "assets": ["propms/__init__.py"], - "message": "chore(release): Bumped to Version ${nextRelease.version}\n\n${nextRelease.notes}" - } - ], - "@semantic-release/github" - ] -} diff --git a/LICENSE b/LICENSE index 1a5b374d..5852795b 100755 --- a/LICENSE +++ b/LICENSE @@ -1 +1 @@ -License: GPL \ No newline at end of file +License: GPL diff --git a/MANIFEST.in b/MANIFEST.in index 782f0182..b4b3f3fa 100755 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -15,4 +15,4 @@ recursive-include propms *.png recursive-include propms *.py recursive-include propms *.svg recursive-include propms *.txt -recursive-exclude propms *.pyc \ No newline at end of file +recursive-exclude propms *.pyc diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 00000000..300da21e --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,13 @@ +module.exports = { + extends: ["@commitlint/config-conventional"], + rules: { + "subject-empty": [2, "never"], + "type-case": [2, "always", "lower-case"], + "type-empty": [2, "never"], + "type-enum": [ + 2, + "always", + ["build", "chore", "ci", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test"], + ], + }, +}; diff --git a/propms/__init__.py b/propms/__init__.py index 0eaffdf5..1609d49b 100755 --- a/propms/__init__.py +++ b/propms/__init__.py @@ -1,4 +1 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - __version__ = "15.3.0" diff --git a/propms/auto_custom.py b/propms/auto_custom.py index 780886d9..045aa198 100755 --- a/propms/auto_custom.py +++ b/propms/auto_custom.py @@ -1,239 +1,226 @@ -from __future__ import unicode_literals -from datetime import datetime -from erpnext.controllers.accounts_controller import get_taxes_and_charges -from frappe.model.mapper import get_mapped_doc -from frappe.utils import add_days, today, date_diff, getdate, add_months -from propms.lease_invoice import getDueDate import calendar +import traceback +from datetime import datetime + import frappe import frappe.permissions import frappe.share -import traceback +from erpnext.controllers.accounts_controller import get_taxes_and_charges from frappe import _ +from frappe.model.mapper import get_mapped_doc +from frappe.utils import add_days, add_months, date_diff, getdate, today + +from propms.lease_invoice import getDueDate @frappe.whitelist() def app_error_log(title, error): - frappe.throw( - msg=error, - exc=traceback.format_exc(), - title=str("User:") + str(title), - is_minimizable=None, - ) + frappe.throw( + msg=error, + exc=traceback.format_exc(), + title="User:" + str(title), + is_minimizable=None, + ) @frappe.whitelist() def makeSalesInvoice(self, method): - try: - if self.doctype == "Stock Entry": - return - if ( - self.doctype == "Material Request" - and self.material_request_type == "Material Issue" - ): - - if self.status == "Issued": - result = checkIssue(self.name) - if result: - items = [] - issue_details = frappe.get_doc("Issue", result) - if issue_details.customer: - material_request_details = frappe.get_doc( - "Material Request", self.name - ) - if not material_request_details.sales_invoice: - if not len(material_request_details.items) == 0: - for item in material_request_details.items: - item_json = {} - item_json["item_code"] = item.item_code - item_json["qty"] = item.qty - items.append(item_json) - sales_invoice = frappe.get_doc( - dict( - doctype="Sales Invoice", - company=self.company, - fiscal_year=frappe.db.get_single_value( - "Global Defaults", "current_fiscal_year" - ), - posting_date=today(), - items=items, - taxes_and_charges=frappe.get_value( - "Company", - self.company, - "default_tax_template", - ), - customer=str(issue_details.customer), - due_date=add_days(today(), 2), - update_stock=1, - ) - ).insert() - if sales_invoice.name: - assignInvoiceNameInMR( - sales_invoice.name, - material_request_details.name, - ) - getTax(sales_invoice) - sales_invoice.calculate_taxes_and_totals() - changeStatusIssue(self.name, self.status) - else: - if self.customer: - if not len(self.materials_required) == 0: - items = [] - for row in self.materials_required: - material_request_details = frappe.get_doc( - "Material Request", row.material_request - ) - if ( - material_request_details.status == "Issued" - and not material_request_details.sales_invoice - ): - - if not len(material_request_details.items) == 0: - for item in material_request_details.items: - item_json = {} - item_json["item_code"] = item.item_code - item_json["qty"] = item.qty - items.append(item_json) - sales_invoice = frappe.get_doc( - dict( - doctype="Sales Invoice", - company=self.company, - fiscal_year=frappe.db.get_single_value( - "Global Defaults", "current_fiscal_year" - ), - posting_date=today(), - items=items, - taxes_and_charges=frappe.get_value( - "Company", - self.company, - "default_tax_template", - ), - customer=str(self.customer), - due_date=add_days(today(), 2), - update_stock=1, - ) - ).insert() - if sales_invoice.name: - assignInvoiceNameInMR( - sales_invoice.name, - material_request_details.name, - ) - if sales_invoice.taxes_and_charges: - getTax(sales_invoice) - sales_invoice.calculate_taxes_and_totals() - except Exception as e: - app_error_log(frappe.session.user, str(e)) + try: + if self.doctype == "Stock Entry": + return + if self.doctype == "Material Request" and self.material_request_type == "Material Issue": + if self.status == "Issued": + result = checkIssue(self.name) + if result: + items = [] + issue_details = frappe.get_doc("Issue", result) + if issue_details.customer: + material_request_details = frappe.get_doc("Material Request", self.name) + if not material_request_details.sales_invoice: + if not len(material_request_details.items) == 0: + for item in material_request_details.items: + item_json = {} + item_json["item_code"] = item.item_code + item_json["qty"] = item.qty + items.append(item_json) + sales_invoice = frappe.get_doc( + dict( + doctype="Sales Invoice", + company=self.company, + fiscal_year=frappe.db.get_single_value( + "Global Defaults", "current_fiscal_year" + ), + posting_date=today(), + items=items, + taxes_and_charges=frappe.get_value( + "Company", + self.company, + "default_tax_template", + ), + customer=str(issue_details.customer), + due_date=add_days(today(), 2), + update_stock=1, + ) + ).insert() + if sales_invoice.name: + assignInvoiceNameInMR( + sales_invoice.name, + material_request_details.name, + ) + getTax(sales_invoice) + sales_invoice.calculate_taxes_and_totals() + changeStatusIssue(self.name, self.status) + else: + if self.customer: + if not len(self.materials_required) == 0: + items = [] + for row in self.materials_required: + material_request_details = frappe.get_doc("Material Request", row.material_request) + if ( + material_request_details.status == "Issued" + and not material_request_details.sales_invoice + ): + if not len(material_request_details.items) == 0: + for item in material_request_details.items: + item_json = {} + item_json["item_code"] = item.item_code + item_json["qty"] = item.qty + items.append(item_json) + sales_invoice = frappe.get_doc( + dict( + doctype="Sales Invoice", + company=self.company, + fiscal_year=frappe.db.get_single_value( + "Global Defaults", "current_fiscal_year" + ), + posting_date=today(), + items=items, + taxes_and_charges=frappe.get_value( + "Company", + self.company, + "default_tax_template", + ), + customer=str(self.customer), + due_date=add_days(today(), 2), + update_stock=1, + ) + ).insert() + if sales_invoice.name: + assignInvoiceNameInMR( + sales_invoice.name, + material_request_details.name, + ) + if sales_invoice.taxes_and_charges: + getTax(sales_invoice) + sales_invoice.calculate_taxes_and_totals() + except Exception as e: + app_error_log(frappe.session.user, str(e)) def getTax(sales_invoice): - taxes = get_taxes_and_charges( - "Sales Taxes and Charges Template", sales_invoice.taxes_and_charges - ) - for tax in taxes: - sales_invoice.append("taxes", tax) + taxes = get_taxes_and_charges("Sales Taxes and Charges Template", sales_invoice.taxes_and_charges) + for tax in taxes: + sales_invoice.append("taxes", tax) def checkIssue(name): - data = frappe.db.sql( - """select parent from `tabIssue Materials Detail` where material_request=%s""", - name, - ) - if data: - if not data[0][0] is None: - return data[0][0] - else: - return False - else: - return False + data = frappe.db.sql( + """select parent from `tabIssue Materials Detail` where material_request=%s""", + name, + ) + if data: + if data[0][0] is not None: + return data[0][0] + else: + return False + else: + return False def assignInvoiceNameInMR(invoice, pr): - frappe.db.sql( - """update `tabMaterial Request` set sales_invoice=%s where name=%s""", - (invoice, pr), - ) + frappe.db.sql( + """update `tabMaterial Request` set sales_invoice=%s where name=%s""", + (invoice, pr), + ) @frappe.whitelist() def changeStatusKeyset(self, method): - try: - keyset_name = getKeysetName(self.key_set) - if keyset_name: - doc = frappe.get_doc("Key Set", keyset_name) - if self.returned: - doc.status = "In" - else: - doc.status = "Out" - doc.save() - else: - frappe.throw(_("Key set not found - {0}.").format(self.key_set)) - - except Exception as e: - app_error_log(frappe.session.user, str(e)) + try: + keyset_name = getKeysetName(self.key_set) + if keyset_name: + doc = frappe.get_doc("Key Set", keyset_name) + if self.returned: + doc.status = "In" + else: + doc.status = "Out" + doc.save() + else: + frappe.throw(_("Key set not found - {0}.").format(self.key_set)) + + except Exception as e: + app_error_log(frappe.session.user, str(e)) def getKeysetName(name): - data = frappe.db.sql("""select name from `tabKey Set` where name=%s""", name) - if data: - if not data[0][0] is None: - return data[0][0] - else: - return False - else: - return False + data = frappe.db.sql("""select name from `tabKey Set` where name=%s""", name) + if data: + if data[0][0] is not None: + return data[0][0] + else: + return False + else: + return False @frappe.whitelist() def changeStatusIssue(name, status): - try: - issue_name = getIssueName(name) - if issue_name: - doc = frappe.get_doc("Issue Materials Detail", issue_name) - doc.material_status = status - doc.save() + try: + issue_name = getIssueName(name) + if issue_name: + doc = frappe.get_doc("Issue Materials Detail", issue_name) + doc.material_status = status + doc.save() - except Exception as e: - app_error_log(frappe.session.user, str(e)) + except Exception as e: + app_error_log(frappe.session.user, str(e)) def getIssueName(name): - data = frappe.db.sql( - """select name from `tabIssue Materials Detail` where material_request=%s""", - name, - ) - if data: - if not data[0][0] is None: - return data[0][0] - else: - return False - else: - return False + data = frappe.db.sql( + """select name from `tabIssue Materials Detail` where material_request=%s""", + name, + ) + if data: + if data[0][0] is not None: + return data[0][0] + else: + return False + else: + return False @frappe.whitelist() def validateSalesInvoiceItemDuplication(self, method): - for item in self.items: - for item_child in self.items: - if not item.name == item_child.name: - if item.item_code == item_child.item_code: - frappe.throw( - _("Duplicate Item Exists - {0}. Duplications are not allowed.").format( - item.item_code - ) - ) + for item in self.items: + for item_child in self.items: + if not item.name == item_child.name: + if item.item_code == item_child.item_code: + frappe.throw( + _("Duplicate Item Exists - {0}. Duplications are not allowed.").format(item.item_code) + ) @frappe.whitelist() def statusChangeBeforeLeaseExpire(): - try: - - # --------------------------------------------- - # Mark properties as "Off Lease in 3 Months" - # Only when lease *has* an end_date - # and ends within next 3 months. - # --------------------------------------------- - upcoming_expiry = frappe.db.sql( - """ + try: + # --------------------------------------------- + # Mark properties as "Off Lease in 3 Months" + # Only when lease *has* an end_date + # and ends within next 3 months. + # --------------------------------------------- + upcoming_expiry = frappe.db.sql( + """ SELECT l.name, l.property, l.end_date FROM `tabLease` l INNER JOIN `tabProperty` p ON l.property = p.name @@ -248,65 +235,62 @@ def statusChangeBeforeLeaseExpire(): AND l.end_date BETWEEN NOW() AND DATE_ADD(NOW(), INTERVAL 3 MONTH) AND p.status = 'On Lease' """, - as_dict=1, - ) + as_dict=1, + ) - for lease in upcoming_expiry: - frappe.db.set_value( - "Property", lease.property, "status", "Off Lease in 3 Months" - ) + for lease in upcoming_expiry: + frappe.db.set_value("Property", lease.property, "status", "Off Lease in 3 Months") + + except Exception as e: + app_error_log(frappe.session.user, str(e)) - except Exception as e: - app_error_log(frappe.session.user, str(e)) @frappe.whitelist() def statusChangeAfterLeaseExpire(): - try: - from frappe.query_builder import DocType - - Property = DocType('Property') - Lease = DocType('Lease') - - # Get properties that might need status change - properties = ( - frappe.qb.from_(Property) - .select(Property.name) - .where(Property.status.isin(['On Lease', 'Off Lease in 3 Months'])) - ).run(as_dict=True) - - properties_to_update = [] - - for prop in properties: - # Check if property has any active leases - active_leases = ( - frappe.qb.from_(Lease) - .select(Lease.name) - .where(Lease.property == prop.name) - .where(Lease.start_date <= frappe.utils.now()) - .where( - (Lease.end_date >= frappe.utils.now()) | - (Lease.end_date.isnull()) - ) - ).run() + try: + from frappe.query_builder import DocType + + Property = DocType("Property") + Lease = DocType("Lease") + + # Get properties that might need status change + properties = ( + frappe.qb.from_(Property) + .select(Property.name) + .where(Property.status.isin(["On Lease", "Off Lease in 3 Months"])) + ).run(as_dict=True) + + properties_to_update = [] + + for prop in properties: + # Check if property has any active leases + active_leases = ( + frappe.qb.from_(Lease) + .select(Lease.name) + .where(Lease.property == prop.name) + .where(Lease.start_date <= frappe.utils.now()) + .where((Lease.end_date >= frappe.utils.now()) | (Lease.end_date.isnull())) + ).run() - # If no active leases, add to update list - if not active_leases: - properties_to_update.append(prop.name) + # If no active leases, add to update list + if not active_leases: + properties_to_update.append(prop.name) - # Bulk update properties to Available - if properties_to_update: - frappe.qb.update(Property).set(Property.status, 'Available').where( - Property.name.isin(properties_to_update) - ).run() + # Bulk update properties to Available + if properties_to_update: + frappe.qb.update(Property).set(Property.status, "Available").where( + Property.name.isin(properties_to_update) + ).run() + + except Exception as e: + app_error_log(frappe.session.user, str(e)) - except Exception as e: - app_error_log(frappe.session.user, str(e)) @frappe.whitelist() def update_property_status(): - try: - active_lease_properties = frappe.db.sql( - """ + try: + active_lease_properties = frappe.db.sql( + """ SELECT DISTINCT p.name FROM `tabProperty` p INNER JOIN `tabLease` l ON l.property = p.name @@ -314,374 +298,358 @@ def update_property_status(): AND l.start_date <= NOW() AND l.lease_status = 'Active' """, - as_dict=1, - ) - # frappe.throw(str(active_lease_properties)) + as_dict=1, + ) + # frappe.throw(str(active_lease_properties)) + + for row in active_lease_properties: + frappe.db.set_value("Property", row.name, "status", "On Lease") + + except Exception as e: + app_error_log(frappe.session.user, str(e)) - for row in active_lease_properties: - frappe.db.set_value("Property", row.name, "status", "On Lease") - - except Exception as e: - app_error_log(frappe.session.user, str(e)) @frappe.whitelist() def getCheckList(): - checklist_doc = frappe.get_doc("Checklist Checkup Area", "Takeover") - if checklist_doc: - check_list = [] - for task in checklist_doc.task: - check = {} - check["checklist_task"] = task.task_name - check_list.append(check) - return check_list + checklist_doc = frappe.get_doc("Checklist Checkup Area", "Takeover") + if checklist_doc: + check_list = [] + for task in checklist_doc.task: + check = {} + check["checklist_task"] = task.task_name + check_list.append(check) + return check_list @frappe.whitelist() -def makeDailyCheckListForTakeover( - source_name, target_doc=None, ignore_permissions=True -): - try: - - def set_missing_values(source, target): - target.checkup_date = today() - target.area = "Takeover" - - doclist = get_mapped_doc( - "Lease", - source_name, - { - "Lease": { - "doctype": "Daily Checklist", - "field_map": {"property": "property"}, - } - }, - target_doc, - set_missing_values, - ignore_permissions=ignore_permissions, - ) - return doclist - - except Exception as e: - app_error_log(frappe.session.user, str(e)) +def makeDailyCheckListForTakeover(source_name, target_doc=None, ignore_permissions=True): + try: + + def set_missing_values(source, target): + target.checkup_date = today() + target.area = "Takeover" + + doclist = get_mapped_doc( + "Lease", + source_name, + { + "Lease": { + "doctype": "Daily Checklist", + "field_map": {"property": "property"}, + } + }, + target_doc, + set_missing_values, + ignore_permissions=ignore_permissions, + ) + return doclist + + except Exception as e: + app_error_log(frappe.session.user, str(e)) @frappe.whitelist() def makeJournalEntry(customer, date, amount): - try: - propm_setting = frappe.get_doc( - "Property Management Settings", "Property Management Settings" - ) - company = frappe.db.get_single_value("Global Defaults", "default_company") - company_doc = frappe.get_doc("Company", company) - j_entry = [] - j_entry_debit = {} - j_entry_debit["account"] = company_doc.default_receivable_account - j_entry_debit["party_type"] = "Customer" - j_entry_debit["party"] = customer - j_entry_debit["debit_in_account_currency"] = amount - j_entry.append(j_entry_debit) - j_entry_credit = {} - j_entry_credit["account"] = company_doc.default_cash_account - j_entry_credit["credit_in_account_currency"] = amount - j_entry.append(j_entry_credit) - j_entry = frappe.get_doc( - dict( - doctype="Journal Entry", - posting_date=date, - company=propm_setting.company, - accounts=j_entry, - mode_of_payment=propm_setting.security_deposit_payment_type, - ) - ).insert() - return j_entry.name - - except Exception as e: - app_error_log(frappe.session.user, str(e)) + try: + propm_setting = frappe.get_doc("Property Management Settings", "Property Management Settings") + company = frappe.db.get_single_value("Global Defaults", "default_company") + company_doc = frappe.get_doc("Company", company) + j_entry = [] + j_entry_debit = {} + j_entry_debit["account"] = company_doc.default_receivable_account + j_entry_debit["party_type"] = "Customer" + j_entry_debit["party"] = customer + j_entry_debit["debit_in_account_currency"] = amount + j_entry.append(j_entry_debit) + j_entry_credit = {} + j_entry_credit["account"] = company_doc.default_cash_account + j_entry_credit["credit_in_account_currency"] = amount + j_entry.append(j_entry_credit) + j_entry = frappe.get_doc( + dict( + doctype="Journal Entry", + posting_date=date, + company=propm_setting.company, + accounts=j_entry, + mode_of_payment=propm_setting.security_deposit_payment_type, + ) + ).insert() + return j_entry.name + + except Exception as e: + app_error_log(frappe.session.user, str(e)) @frappe.whitelist() def getMonthADD(date, month): - return add_months(getdate(date), int(month)) + return add_months(getdate(date), int(month)) @frappe.whitelist() def getDateDiff(date1, date2): - return date_diff(getdate(date1), getdate(date2)) + return date_diff(getdate(date1), getdate(date2)) @frappe.whitelist() def getNumberOfDays(date): - return calendar.monthrange(getdate(date).year, getdate(date).month)[1] + return calendar.monthrange(getdate(date).year, getdate(date).month)[1] @frappe.whitelist() def getMonthNo(date1, date2): - d1 = getdate(date1) - d2 = getdate(date2) - return diff_month( - datetime(d1.year, d1.month, d1.day), datetime(d2.year, d2.month, d2.day) - ) + d1 = getdate(date1) + d2 = getdate(date2) + return diff_month(datetime(d1.year, d1.month, d1.day), datetime(d2.year, d2.month, d2.day)) @frappe.whitelist() def makeInvoiceSchedule( - date, - item, - paid_by, - item_name, - name, - qty, - rate, - idx, - currency=None, - tax=None, - days_to_invoice_in_advance=None, - invoice_item_group=None, - document_type="Sales Invoice", + date, + item, + paid_by, + item_name, + name, + qty, + rate, + idx, + currency=None, + tax=None, + days_to_invoice_in_advance=None, + invoice_item_group=None, + document_type="Sales Invoice", ): - if not document_type: - document_type = "Sales Invoice" - try: - date_to_invoice = add_days(date, -1 * (days_to_invoice_in_advance or 0)) - frappe.get_doc( - dict( - idx=idx, - doctype="Lease Invoice Schedule", - parent=name, - parentfield="lease_invoice_schedule", - parenttype="lease", - date_to_invoice=date_to_invoice, - schedule_start_date=date, - lease_item=item, - paid_by=paid_by, - lease_item_name=item_name, - qty=qty, - rate=rate, - currency=currency, - tax=tax, - invoice_item_group=invoice_item_group, - document_type=document_type, - ) - ).insert() - # frappe.msgprint(str(doc.name)) - except Exception as e: - app_error_log(frappe.session.user, str(e)) + if not document_type: + document_type = "Sales Invoice" + try: + date_to_invoice = add_days(date, -1 * (days_to_invoice_in_advance or 0)) + frappe.get_doc( + dict( + idx=idx, + doctype="Lease Invoice Schedule", + parent=name, + parentfield="lease_invoice_schedule", + parenttype="lease", + date_to_invoice=date_to_invoice, + schedule_start_date=date, + lease_item=item, + paid_by=paid_by, + lease_item_name=item_name, + qty=qty, + rate=rate, + currency=currency, + tax=tax, + invoice_item_group=invoice_item_group, + document_type=document_type, + ) + ).insert() + # frappe.msgprint(str(doc.name)) + except Exception as e: + app_error_log(frappe.session.user, str(e)) def diff_month(d1, d2): - if d1.day >= d2.day - 1: - return (d1.year - d2.year) * 12 + d1.month - d2.month - else: - return (d1.year - d2.year) * 12 + d1.month - d2.month - 1 + if d1.day >= d2.day - 1: + return (d1.year - d2.year) * 12 + d1.month - d2.month + else: + return (d1.year - d2.year) * 12 + d1.month - d2.month - 1 @frappe.whitelist() def getDateMonthDiff(start_date, end_date, month_factor): - month_count = 0 - no_month = 0 - month_float = 0 - # frappe.msgprint("start_date: " + str(start_date) + " --- end_date: " + str(end_date)) - while start_date <= end_date: - period_end_date = add_days(add_months(start_date, month_factor), -1) - # frappe.msgprint("start_date: " + str(start_date) + " --- period_end_date: " + str(period_end_date)) - if period_end_date <= end_date: - # add month and set new start date to calculate next month_count - month_count = month_count + month_factor - start_date = add_months(start_date, month_factor) - else: - # find last number of days - days = float( - date_diff(getdate(end_date), getdate(add_months(start_date, no_month))) - + 1 - ) - # msg = "no_month = 0 so Days calculated: " + str(days) + " between " + str(start_date) + " and " + str(end_date) - # frappe.msgprint(msg) - # start_date to cater for correct number of days in month in case the start date is feb - no_days_in_month = float( - calendar.monthrange( - getdate(start_date).year, getdate(start_date).month - )[1] - ) - # msg = "no_month = 0 so No of Days calculated: " + str(no_days_in_month) + " between " + str(start_date) + " and " + str(end_date) - # frappe.msgprint(msg) - month_float = days / no_days_in_month - # frappe.msgprint("month_float = " + str(month_float) + " for days = " + str(days) + " and total number of days = " + str(no_days_in_month)) - start_date = add_months(start_date, month_factor) - month_count = month_count + no_month + month_float - return month_count + month_count = 0 + no_month = 0 + month_float = 0 + # frappe.msgprint("start_date: " + str(start_date) + " --- end_date: " + str(end_date)) + while start_date <= end_date: + period_end_date = add_days(add_months(start_date, month_factor), -1) + # frappe.msgprint("start_date: " + str(start_date) + " --- period_end_date: " + str(period_end_date)) + if period_end_date <= end_date: + # add month and set new start date to calculate next month_count + month_count = month_count + month_factor + start_date = add_months(start_date, month_factor) + else: + # find last number of days + days = float(date_diff(getdate(end_date), getdate(add_months(start_date, no_month))) + 1) + # msg = "no_month = 0 so Days calculated: " + str(days) + " between " + str(start_date) + " and " + str(end_date) + # frappe.msgprint(msg) + # start_date to cater for correct number of days in month in case the start date is feb + no_days_in_month = float( + calendar.monthrange(getdate(start_date).year, getdate(start_date).month)[1] + ) + # msg = "no_month = 0 so No of Days calculated: " + str(no_days_in_month) + " between " + str(start_date) + " and " + str(end_date) + # frappe.msgprint(msg) + month_float = days / no_days_in_month + # frappe.msgprint("month_float = " + str(month_float) + " for days = " + str(days) + " and total number of days = " + str(no_days_in_month)) + start_date = add_months(start_date, month_factor) + month_count = month_count + no_month + month_float + return month_count @frappe.whitelist() def get_active_meter_from_property(property_id, meter_type): - """Get Active Meter Number""" - meter_data = frappe.db.sql( - """SELECT meter_number + """Get Active Meter Number""" + meter_data = frappe.db.sql( + """SELECT meter_number FROM `tabProperty Meter Reading` WHERE parent=%s AND meter_type=%s AND status='Active'""", - (property_id, meter_type), - as_dict=True, - ) - if meter_data: - return meter_data[0].meter_number - else: - return "" + (property_id, meter_type), + as_dict=True, + ) + if meter_data: + return meter_data[0].meter_number + else: + return "" @frappe.whitelist() def get_active_meter_customer_from_property(property_id, meter_type): - # Unused as per conversation with Vimal on 2019-08-11 - """Get Active Meter Customer Name""" - meter_data = frappe.db.sql( - """SELECT invoice_customer + # Unused as per conversation with Vimal on 2019-08-11 + """Get Active Meter Customer Name""" + meter_data = frappe.db.sql( + """SELECT invoice_customer FROM `tabProperty Meter Reading` WHERE parent=%s AND meter_type=%s AND status='Active'""", - (property_id, meter_type), - as_dict=True, - ) - if meter_data: - return meter_data[0].invoice_customer - else: - return "" + (property_id, meter_type), + as_dict=True, + ) + if meter_data: + return meter_data[0].invoice_customer + else: + return "" @frappe.whitelist() def get_previous_meter_reading(meter_number, property_id, meter_type): - """Get Previous Meter Reading""" - previous_reading_details = frappe.db.sql( - """SELECT md.current_meter_reading as 'previous_reading', + """Get Previous Meter Reading""" + previous_reading_details = frappe.db.sql( + """SELECT md.current_meter_reading as 'previous_reading', m.reading_date as 'reading_date' FROM `tabMeter Reading Detail` AS md INNER JOIN `tabMeter Reading` AS m ON md.parent=m.name WHERE md.meter_number=%s AND m.docstatus=1 ORDER BY m.reading_date DESC limit 1""", - meter_number, - as_dict=True, - ) - if len(previous_reading_details) >= 1: - # print previous_reading_details[0].previous_reading - return previous_reading_details[0] - else: - initial_reading_details = frappe.db.sql( - """SELECT initial_meter_reading as 'previous_reading', + meter_number, + as_dict=True, + ) + if len(previous_reading_details) >= 1: + # print previous_reading_details[0].previous_reading + return previous_reading_details[0] + else: + initial_reading_details = frappe.db.sql( + """SELECT initial_meter_reading as 'previous_reading', installation_date as 'reading_date' FROM `tabProperty Meter Reading` WHERE parent=%s AND meter_type=%s AND meter_number=%s AND status='Active'""", - (property_id, meter_type, meter_number), - as_dict=True, - ) - if len(initial_reading_details) >= 1: - return initial_reading_details[0] - else: - return 0 + (property_id, meter_type, meter_number), + as_dict=True, + ) + if len(initial_reading_details) >= 1: + return initial_reading_details[0] + else: + return 0 @frappe.whitelist() def make_invoice_meter_reading(self, method): - for meter_row in self.meter_reading_detail: - if int(meter_row.do_not_create_invoice) != 1: - item_detail = get_item_details( - self.meter_type, - meter_row.reading_difference, - meter_row.previous_reading_date, - add_days(self.reading_date, -1), - ) - # Changed from propert/meter customer lookup to pos cusotmer lookup as per conversation with Vimal on 2019-11-08 - leasename = get_latest_active_lease(meter_row.property) - lease = frappe.get_doc("Lease", leasename) - # customer = get_active_meter_customer_from_property(meter_row.property,self.meter_type) - customer = lease.customer - if customer: - meter_row.invoice_number = make_invoice( - self.reading_date, - customer, - meter_row.property, - item_detail, - self.meter_type, - meter_row.previous_reading_date, - add_days(self.reading_date, -1), - ) - # meter_row.invoice_number = si_no - # frappe.db.set_value("Meter Reading Detail",meter_row.name,"invoice_number",si_no) - self.db_update() + for meter_row in self.meter_reading_detail: + if int(meter_row.do_not_create_invoice) != 1: + item_detail = get_item_details( + self.meter_type, + meter_row.reading_difference, + meter_row.previous_reading_date, + add_days(self.reading_date, -1), + ) + # Changed from propert/meter customer lookup to pos cusotmer lookup as per conversation with Vimal on 2019-11-08 + leasename = get_latest_active_lease(meter_row.property) + lease = frappe.get_doc("Lease", leasename) + # customer = get_active_meter_customer_from_property(meter_row.property,self.meter_type) + customer = lease.customer + if customer: + meter_row.invoice_number = make_invoice( + self.reading_date, + customer, + meter_row.property, + item_detail, + self.meter_type, + meter_row.previous_reading_date, + add_days(self.reading_date, -1), + ) + # meter_row.invoice_number = si_no + # frappe.db.set_value("Meter Reading Detail",meter_row.name,"invoice_number",si_no) + self.db_update() @frappe.whitelist() -def make_invoice( - meter_date, customer, property_id, items, lease_item, from_date=None, to_date=None -): - company = frappe.db.get_value("Property", property_id, "company") - try: - sales_invoice = frappe.get_doc( - dict( - doctype="Sales Invoice", - company=company, - posting_date=meter_date, - items=items, - lease=get_latest_active_lease(property_id), - lease_item=lease_item, - customer=str(customer), - due_date=getDueDate(meter_date, str(customer)), - taxes_and_charges=frappe.get_value( - "Company", company, "default_tax_template" - ), - cost_center=get_cost_center(property_id), - from_date=from_date, - to_date=to_date, - ) - ).insert() - if sales_invoice.taxes_and_charges: - get_tax(sales_invoice) - sales_invoice.calculate_taxes_and_totals() - sales_invoice.save() - return sales_invoice.name - except Exception as e: - app_error_log(frappe.session.user, str(e)) +def make_invoice(meter_date, customer, property_id, items, lease_item, from_date=None, to_date=None): + company = frappe.db.get_value("Property", property_id, "company") + try: + sales_invoice = frappe.get_doc( + dict( + doctype="Sales Invoice", + company=company, + posting_date=meter_date, + items=items, + lease=get_latest_active_lease(property_id), + lease_item=lease_item, + customer=str(customer), + due_date=getDueDate(meter_date, str(customer)), + taxes_and_charges=frappe.get_value("Company", company, "default_tax_template"), + cost_center=get_cost_center(property_id), + from_date=from_date, + to_date=to_date, + ) + ).insert() + if sales_invoice.taxes_and_charges: + get_tax(sales_invoice) + sales_invoice.calculate_taxes_and_totals() + sales_invoice.save() + return sales_invoice.name + except Exception as e: + app_error_log(frappe.session.user, str(e)) @frappe.whitelist() def get_tax(sales_invoice): - taxes = get_taxes_and_charges( - "Sales Taxes and Charges Template", sales_invoice.taxes_and_charges - ) - for tax in taxes: - sales_invoice.append("taxes", tax) + taxes = get_taxes_and_charges("Sales Taxes and Charges Template", sales_invoice.taxes_and_charges) + for tax in taxes: + sales_invoice.append("taxes", tax) @frappe.whitelist() def get_cost_center(property_id): - return frappe.db.get_value("Property", property_id, "cost_center") + return frappe.db.get_value("Property", property_id, "cost_center") @frappe.whitelist() def get_item_details(item, qty, service_start_date, service_end_date): - item_dict = [] - item_json = {} - item_json["item_code"] = item - item_json["qty"] = qty - item_json["service_start_date"] = service_start_date - item_json["service_end_date"] = service_end_date - item_dict.append(item_json) - return item_dict + item_dict = [] + item_json = {} + item_json["item_code"] = item + item_json["qty"] = qty + item_json["service_start_date"] = service_start_date + item_json["service_end_date"] = service_end_date + item_dict.append(item_json) + return item_dict @frappe.whitelist() def get_latest_active_lease(property_id): - lease_details = frappe.get_all( - "Lease", - filters={"property": property_id}, - fields=["name"], - order_by="lease_date desc", - limit=1, - ) - if len(lease_details) >= 1: - return lease_details[0].name - else: - return "" + lease_details = frappe.get_all( + "Lease", + filters={"property": property_id}, + fields=["name"], + order_by="lease_date desc", + limit=1, + ) + if len(lease_details) >= 1: + return lease_details[0].name + else: + return "" diff --git a/propms/config/desktop.py b/propms/config/desktop.py index d217fbc6..c95333aa 100755 --- a/propms/config/desktop.py +++ b/propms/config/desktop.py @@ -1,17 +1,15 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals from frappe import _ def get_data(): - return [ - { - "module_name": "Property Management Solution", - "category": "Domains", - "color": "grey", - "icon": "octicon octicon-home", - "type": "module", - "label": _("Property Management"), - "description": "Property, lease, maintenance jobs, keys and analytics", - }, - ] + return [ + { + "module_name": "Property Management Solution", + "category": "Domains", + "color": "grey", + "icon": "octicon octicon-home", + "type": "module", + "label": _("Property Management"), + "description": "Property, lease, maintenance jobs, keys and analytics", + }, + ] diff --git a/propms/config/docs.py b/propms/config/docs.py index 846fb030..9c090a58 100755 --- a/propms/config/docs.py +++ b/propms/config/docs.py @@ -9,4 +9,4 @@ def get_context(context): - context.brand_html = "Property Management Solution" + context.brand_html = "Property Management Solution" diff --git a/propms/config/property_management_solution.py b/propms/config/property_management_solution.py index f7da7e82..b7a4687f 100644 --- a/propms/config/property_management_solution.py +++ b/propms/config/property_management_solution.py @@ -1,200 +1,197 @@ -from __future__ import unicode_literals from frappe import _ def get_data(): - config = [ - { - "label": _("Property Documents"), - "items": [ - { - "type": "doctype", - "name": "Property", - "description": _("Property that needs to be managed."), - }, - { - "type": "doctype", - "name": "Lease", - "description": _("Lease pertaining to the properties."), - }, - { - "type": "doctype", - "name": "Key Set Detail", - "description": _("Key Set Detail"), - }, - { - "type": "doctype", - "name": "Tool Item Record", - "description": _("Tool Item Record"), - }, - { - "type": "doctype", - "name": "Daily Checklist", - "description": _("Daily Checklist"), - }, - { - "type": "doctype", - "name": "Exit", - "description": _("Exit"), - }, - { - "type": "doctype", - "name": "Meter Reading", - "description": _("Meter Reading"), - }, - { - "type": "doctype", - "name": "Outsourcing Attendance", - "description": _("Outsourcing Attendance"), - }, - { - "type": "doctype", - "name": "Insurance", - "description": _("Insurance"), - }, - { - "type": "doctype", - "name": "Security Attendance", - "description": _("Security Attendance"), - }, - { - "type": "doctype", - "name": "Withholding Tax Summary", - "description": _("Withholding Tax Summary"), - }, - ], - }, - { - "label": _("Property Masters"), - "icon": "fa fa-cog", - "items": [ - { - "type": "doctype", - "name": "Unit Type", - "label": _("Unit Type"), - "description": _("Unit Type definition."), - }, - { - "type": "doctype", - "name": "Property", - "description": _("Property database."), - }, - { - "type": "doctype", - "name": "Checklist Checkup Area", - "icon": "fa fa-sitemap", - "label": _("Checklist Checkup Area"), - "description": _("Areas for Checklist Checkup."), - }, - { - "type": "doctype", - "name": "Guard Shift", - "label": _("Guard Shift"), - "description": _("Shif for security guards."), - }, - { - "type": "doctype", - "name": "Key Set", - "icon": "fa fa-sitemap", - "label": _("Key Set"), - "description": _("Key sets in custody."), - }, - { - "type": "doctype", - "name": "Tool Item Set", - "icon": "fa fa-sitemap", - "label": _("Tool Item Set"), - "description": _("Tool Item Sets in custody."), - }, - { - "type": "doctype", - "name": "Outsourcing Category", - "label": _("Outsourcing Category"), - "description": _("Outsourcing Category definition."), - }, - { - "type": "doctype", - "name": "Property Amenity", - "label": _("Property Amenity"), - "description": _("Property Amenity definition."), - }, - { - "type": "doctype", - "name": "Security Deposit Details", - "label": _("Security Deposit Details"), - "description": _("Security Deposit Details definition."), - }, - { - "type": "doctype", - "name": "Meter", - "label": _("Meter database"), - "description": _("Register all meters here."), - }, - ], - }, - { - "label": _("Property Settings"), - "items": [ - { - "type": "doctype", - "name": "Property Management Settings", - "label": _("Property Management Settings"), - "description": _("Property Management Settings"), - }, - ], - }, - { - "label": _("Property Analytics"), - "items": [ - { - "type": "report", - "name": "Outsourcing Attendance", - "is_query_report": True, - "doctype": "Outsourcing Attendance", - }, - { - "type": "report", - "name": "Security Attendance Report", - "is_query_report": True, - "doctype": "Security Attendance", - }, - { - "type": "report", - "name": "Security Deposit", - "is_query_report": True, - "doctype": "Journal Entry", - }, - { - "type": "report", - "name": "Debtors Report", - "is_query_report": True, - "doctype": "Sales Invoice", - }, - { - "type": "report", - "name": "Creditors Report", - "is_query_report": True, - "doctype": "Purchase Invoice", - }, - { - "type": "report", - "name": "Lease Information", - "is_query_report": True, - "doctype": "Lease", - "label": _("Lease Report"), - "description": _( - "This is to show status of every lease by type of property" - ), - }, - { - "type": "report", - "name": "Property Status", - "is_query_report": True, - "doctype": "Property", - "label": _("Property Status"), - "description": _("Information about all properties in the system"), - }, - ], - }, - ] - return config + config = [ + { + "label": _("Property Documents"), + "items": [ + { + "type": "doctype", + "name": "Property", + "description": _("Property that needs to be managed."), + }, + { + "type": "doctype", + "name": "Lease", + "description": _("Lease pertaining to the properties."), + }, + { + "type": "doctype", + "name": "Key Set Detail", + "description": _("Key Set Detail"), + }, + { + "type": "doctype", + "name": "Tool Item Record", + "description": _("Tool Item Record"), + }, + { + "type": "doctype", + "name": "Daily Checklist", + "description": _("Daily Checklist"), + }, + { + "type": "doctype", + "name": "Exit", + "description": _("Exit"), + }, + { + "type": "doctype", + "name": "Meter Reading", + "description": _("Meter Reading"), + }, + { + "type": "doctype", + "name": "Outsourcing Attendance", + "description": _("Outsourcing Attendance"), + }, + { + "type": "doctype", + "name": "Insurance", + "description": _("Insurance"), + }, + { + "type": "doctype", + "name": "Security Attendance", + "description": _("Security Attendance"), + }, + { + "type": "doctype", + "name": "Withholding Tax Summary", + "description": _("Withholding Tax Summary"), + }, + ], + }, + { + "label": _("Property Masters"), + "icon": "fa fa-cog", + "items": [ + { + "type": "doctype", + "name": "Unit Type", + "label": _("Unit Type"), + "description": _("Unit Type definition."), + }, + { + "type": "doctype", + "name": "Property", + "description": _("Property database."), + }, + { + "type": "doctype", + "name": "Checklist Checkup Area", + "icon": "fa fa-sitemap", + "label": _("Checklist Checkup Area"), + "description": _("Areas for Checklist Checkup."), + }, + { + "type": "doctype", + "name": "Guard Shift", + "label": _("Guard Shift"), + "description": _("Shif for security guards."), + }, + { + "type": "doctype", + "name": "Key Set", + "icon": "fa fa-sitemap", + "label": _("Key Set"), + "description": _("Key sets in custody."), + }, + { + "type": "doctype", + "name": "Tool Item Set", + "icon": "fa fa-sitemap", + "label": _("Tool Item Set"), + "description": _("Tool Item Sets in custody."), + }, + { + "type": "doctype", + "name": "Outsourcing Category", + "label": _("Outsourcing Category"), + "description": _("Outsourcing Category definition."), + }, + { + "type": "doctype", + "name": "Property Amenity", + "label": _("Property Amenity"), + "description": _("Property Amenity definition."), + }, + { + "type": "doctype", + "name": "Security Deposit Details", + "label": _("Security Deposit Details"), + "description": _("Security Deposit Details definition."), + }, + { + "type": "doctype", + "name": "Meter", + "label": _("Meter database"), + "description": _("Register all meters here."), + }, + ], + }, + { + "label": _("Property Settings"), + "items": [ + { + "type": "doctype", + "name": "Property Management Settings", + "label": _("Property Management Settings"), + "description": _("Property Management Settings"), + }, + ], + }, + { + "label": _("Property Analytics"), + "items": [ + { + "type": "report", + "name": "Outsourcing Attendance", + "is_query_report": True, + "doctype": "Outsourcing Attendance", + }, + { + "type": "report", + "name": "Security Attendance Report", + "is_query_report": True, + "doctype": "Security Attendance", + }, + { + "type": "report", + "name": "Security Deposit", + "is_query_report": True, + "doctype": "Journal Entry", + }, + { + "type": "report", + "name": "Debtors Report", + "is_query_report": True, + "doctype": "Sales Invoice", + }, + { + "type": "report", + "name": "Creditors Report", + "is_query_report": True, + "doctype": "Purchase Invoice", + }, + { + "type": "report", + "name": "Lease Information", + "is_query_report": True, + "doctype": "Lease", + "label": _("Lease Report"), + "description": _("This is to show status of every lease by type of property"), + }, + { + "type": "report", + "name": "Property Status", + "is_query_report": True, + "doctype": "Property", + "label": _("Property Status"), + "description": _("Information about all properties in the system"), + }, + ], + }, + ] + return config diff --git a/propms/hooks.py b/propms/hooks.py index 074fe966..0d923bcc 100755 --- a/propms/hooks.py +++ b/propms/hooks.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -from . import __version__ as app_version - app_name = "propms" app_title = "Property Management Solution" app_publisher = "Aakvatech" @@ -25,17 +21,17 @@ # include js in page # page_js = {"page" : "public/js/file.js"} page_js = { - "pos": "property_management_solution/point_of_sale.js", - "point-of-sale": "property_management_solution/point_of_sale.js", + "pos": "property_management_solution/point_of_sale.js", + "point-of-sale": "property_management_solution/point_of_sale.js", } # include js in doctype views # doctype_js = {"doctype" : "public/js/doctype.js"} doctype_js = { - "Sales Invoice": "property_management_solution/sales_invoice.js", - "Journal Entry Account": "property_management_solution/journal_entry_account.js", - "Issue": "property_management_solution/issue.js", - "Company": "property_management_solution/company.js", + "Sales Invoice": "property_management_solution/sales_invoice.js", + "Journal Entry Account": "property_management_solution/journal_entry_account.js", + "Issue": "property_management_solution/issue.js", + "Company": "property_management_solution/company.js", } # doctype_list_js = {"doctype" : "public/js/doctype_list.js"} # doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} @@ -66,13 +62,13 @@ # before_install = "propms.install.before_install" after_install = [ - "propms.utils.create_custom_fields.execute", - "propms.utils.create_property_setter.execute", + "propms.utils.create_custom_fields.execute", + "propms.utils.create_property_setter.execute", ] after_migrate = [ - "propms.utils.create_custom_fields.execute", - "propms.utils.create_property_setter.execute", + "propms.utils.create_custom_fields.execute", + "propms.utils.create_property_setter.execute", ] # Desk Notifications @@ -102,41 +98,37 @@ doc_events = { - "Issue": { - "validate": [ - "propms.issue_hook.validate", - ], - }, - "Property": { - "validate": "propms.property_increment.validate_property_increment_settings", - }, - "Material Request": { - "validate": "propms.auto_custom.makeSalesInvoice", - "on_update": "propms.auto_custom.makeSalesInvoice", - "on_change": "propms.auto_custom.makeSalesInvoice", - }, - "Sales Order": { - "validate": "propms.auto_custom.validateSalesInvoiceItemDuplication" - }, - "Key Set Detail": {"on_change": "propms.auto_custom.changeStatusKeyset"}, - "Meter Reading": {"on_submit": "propms.auto_custom.make_invoice_meter_reading"}, + "Issue": { + "validate": [ + "propms.issue_hook.validate", + ], + }, + "Property": { + "validate": "propms.property_increment.validate_property_increment_settings", + }, + "Material Request": { + "validate": "propms.auto_custom.makeSalesInvoice", + "on_update": "propms.auto_custom.makeSalesInvoice", + "on_change": "propms.auto_custom.makeSalesInvoice", + }, + "Sales Order": {"validate": "propms.auto_custom.validateSalesInvoiceItemDuplication"}, + "Key Set Detail": {"on_change": "propms.auto_custom.changeStatusKeyset"}, + "Meter Reading": {"on_submit": "propms.auto_custom.make_invoice_meter_reading"}, } scheduler_events = { - "daily_long": [ - "propms.property_management_solution.doctype.lease.lease.update_lease_statuses" + "daily_long": ["propms.property_management_solution.doctype.lease.lease.update_lease_statuses"], + "daily": [ + "propms.auto_custom.statusChangeBeforeLeaseExpire", + "propms.auto_custom.statusChangeAfterLeaseExpire", + "propms.property_increment.run_property_increment_engine", ], - "daily": [ - "propms.auto_custom.statusChangeBeforeLeaseExpire", - "propms.auto_custom.statusChangeAfterLeaseExpire", - "propms.property_increment.run_property_increment_engine", - ], - "cron": { - # "00 12 * * *": ["propms.lease_invoice.leaseInvoiceAutoCreate"], - "00 00 * * *": ["propms.lease_invoice_schedule.make_lease_invoice_schedule"], - "00 12 * * *": ["propms.lease_invoice.enqueue_lease_invoice_auto_create"], - } + "cron": { + # "00 12 * * *": ["propms.lease_invoice.leaseInvoiceAutoCreate"], + "00 00 * * *": ["propms.lease_invoice_schedule.make_lease_invoice_schedule"], + "00 12 * * *": ["propms.lease_invoice.enqueue_lease_invoice_auto_create"], + }, } diff --git a/propms/issue_hook.py b/propms/issue_hook.py index c8ddacd7..3773a21b 100644 --- a/propms/issue_hook.py +++ b/propms/issue_hook.py @@ -1,348 +1,325 @@ -from __future__ import unicode_literals import frappe +from erpnext.controllers.accounts_controller import get_taxes_and_charges +from erpnext.stock.get_item_details import get_pos_profile +from erpnext.utilities.product import get_price from frappe import _ from frappe.utils import today -from erpnext.utilities.product import get_price -from erpnext.stock.get_item_details import get_pos_profile + from propms.auto_custom import get_latest_active_lease -from erpnext.controllers.accounts_controller import get_taxes_and_charges def make_transaction(doc, for_self_consumption=False): - is_grouped = frappe.db.get_value( - "Property Management Settings", None, "group_maintenance_job_items" - ) - if not is_grouped: - is_grouped = 0 - is_grouped = int(is_grouped) - company = doc.company - if not company: - company = frappe.db.get_single_value("Global Defaults", "default_company") - cost_center = frappe.db.get_value("Property", doc.property_name, "cost_center") - submit_maintenance_stock_entry = frappe.db.get_value( - "Property Management Settings", None, "submit_maintenance_stock_entry" - ) - submit_maintenance_invoice = frappe.db.get_value( - "Property Management Settings", None, "submit_maintenance_invoice" - ) - # TODO: Remove this after stability of Stock Entry - self_consumption_customer = frappe.db.get_value( - "Property Management Settings", None, "self_consumption_customer" - ) - if not submit_maintenance_stock_entry: - submit_maintenance_stock_entry = 0 - submit_maintenance_stock_entry = int(submit_maintenance_stock_entry) - user_remarks = "Transaction for Maintenance Job Card {0}".format(doc.name) - lease = get_latest_active_lease(doc.property_name) + is_grouped = frappe.db.get_single_value("Property Management Settings", "group_maintenance_job_items") + if not is_grouped: + is_grouped = 0 + is_grouped = int(is_grouped) + company = doc.company + if not company: + company = frappe.db.get_single_value("Global Defaults", "default_company") + cost_center = frappe.db.get_value("Property", doc.property_name, "cost_center") + submit_maintenance_stock_entry = frappe.db.get_single_value( + "Property Management Settings", "submit_maintenance_stock_entry" + ) + submit_maintenance_invoice = frappe.db.get_single_value( + "Property Management Settings", "submit_maintenance_invoice" + ) + # TODO: Remove this after stability of Stock Entry + self_consumption_customer = frappe.db.get_single_value( + "Property Management Settings", "self_consumption_customer" + ) + if not submit_maintenance_stock_entry: + submit_maintenance_stock_entry = 0 + submit_maintenance_stock_entry = int(submit_maintenance_stock_entry) + user_remarks = f"Transaction for Maintenance Job Card {doc.name}" + lease = get_latest_active_lease(doc.property_name) + + def make_stock_entry(items_list=None, pos=None): + if not len(items_list) > 0: + return - def make_stock_entry(items_list=None, pos=None): - if not len(items_list) > 0: - return + # Create a stock entry for purpose material issue + stock_entry_doc = frappe.get_doc( + { + "doctype": "Stock Entry", + "stock_entry_type": "Material Issue", + "purpose": "Material Issue", + "posting_date": today(), + "remarks": user_remarks, + "company": doc.company, + "items": items_list, + "from_warehouse": frappe.db.get_single_value("Stock Settings", "default_warehouse"), + } + ) + if stock_entry_doc: + stock_entry_doc.insert(ignore_permissions=True) + stock_entry_url = frappe.utils.get_url_to_form(stock_entry_doc.doctype, stock_entry_doc.name) + se_msgprint = f"Stock Entry Created {stock_entry_doc.name}" + frappe.flags.ignore_account_permission = True + if submit_maintenance_stock_entry == 1 and not pos: + stock_entry_doc.submit() + if pos: + frappe.throw(_("POS Stock Entry cannot be created for Self Consumption items")) + frappe.msgprint(_(se_msgprint)) + for item_row in doc.materials_billed: + if ( + item_row.item + and item_row.quantity + and item_row.material_status == "Self Consumption" + and not item_row.stock_entry + ): + item_row.stock_entry = stock_entry_doc.name + frappe.db.set_value( + "Issue Materials Billed", + item_row.name, + "stock_entry", + stock_entry_doc.name, + ) + frappe.db.commit() - # Create a stock entry for purpose material issue - stock_entry_doc = frappe.get_doc( - { - "doctype": "Stock Entry", - "stock_entry_type": "Material Issue", - "purpose": "Material Issue", - "posting_date": today(), - "remarks": user_remarks, - "company": doc.company, - "items": items_list, - "from_warehouse": frappe.db.get_single_value("Stock Settings", "default_warehouse"), - } - ) - if stock_entry_doc: - stock_entry_doc.insert(ignore_permissions=True) - stock_entry_url = frappe.utils.get_url_to_form( - stock_entry_doc.doctype, stock_entry_doc.name - ) - se_msgprint = "Stock Entry Created {1}".format( - stock_entry_url, stock_entry_doc.name - ) - frappe.flags.ignore_account_permission = True - if submit_maintenance_stock_entry == 1 and not pos: - stock_entry_doc.submit() - if pos: - frappe.throw(_("POS Stock Entry cannot be created for Self Consumption items")) - frappe.msgprint(_(se_msgprint)) - for item_row in doc.materials_billed: - if ( - item_row.item - and item_row.quantity - and item_row.material_status == "Self Consumption" - and not item_row.stock_entry - ): - item_row.stock_entry = stock_entry_doc.name - frappe.db.set_value( - "Issue Materials Billed", - item_row.name, - "stock_entry", - stock_entry_doc.name, - ) - frappe.db.commit() + def make_sales_invoice(items_list=None, pos=None, self_customer=None): + if not len(items_list) > 0 or not doc.customer: + return + default_tax_template = frappe.db.get_value("Company", company, "default_maintenance_tax_template") + if not default_tax_template: + url = frappe.utils.get_url_to_form("Company", company) + frappe.throw(_(f"Please Setup Default Maintenance Tax Template in {company}")) + if self_customer: + invoice_customer = self_consumption_customer + else: + invoice_customer = doc.customer + is_pos = 0 + pos_profile = "" + naming_series = "" + if pos: + user_pos_profile = get_pos_profile(company) + is_pos = 1 + pos_profile = user_pos_profile.name + naming_series = user_pos_profile.naming_series + default_tax_template = user_pos_profile.taxes_and_charges or default_tax_template + invoice_doc = frappe.get_doc( + dict( + is_pos=is_pos, + pos_profile=pos_profile, + naming_series=naming_series, + doctype="Sales Invoice", + customer=invoice_customer, + company=company, + posting_date=today(), + due_date=today(), + ignore_pricing_rule=1, + items=items_list, + update_stock=1, + remarks=user_remarks, + cost_center=cost_center, + lease=lease, + taxes_and_charges=default_tax_template, + job_card=doc.name, + ) + ).insert(ignore_permissions=True) + invoice_doc.reload() + if invoice_doc.taxes_and_charges and not pos: + getTax(invoice_doc) + invoice_doc.calculate_taxes_and_totals() + invoice_doc.run_method("set_missing_values") + invoice_doc.run_method("calculate_taxes_and_totals") + invoice_doc.save() + if invoice_doc: + invoice_url = frappe.utils.get_url_to_form(invoice_doc.doctype, invoice_doc.name) + si_msgprint = f"Sales invoice Created {invoice_doc.name}" + frappe.flags.ignore_account_permission = True + if submit_maintenance_invoice == 1 and not pos: + invoice_doc.submit() + if pos: + make_sales_pos_payment(invoice_doc, user_pos_profile.name) + si_msgprint = "POS " + si_msgprint + frappe.msgprint(_(si_msgprint)) + for item_row in doc.materials_billed: + if ( + item_row.item + and item_row.quantity + and item_row.invoiced == 1 + and not item_row.sales_invoice + ): + item_row.sales_invoice = invoice_doc.name + frappe.db.set_value( + "Issue Materials Billed", + item_row.name, + "sales_invoice", + invoice_doc.name, + ) + frappe.db.commit() - def make_sales_invoice(items_list=None, pos=None, self_customer=None): - if not len(items_list) > 0 or not doc.customer: - return - default_tax_template = frappe.db.get_value( - "Company", company, "default_maintenance_tax_template" - ) - if not default_tax_template: - url = frappe.utils.get_url_to_form("Company", company) - frappe.throw( - _( - "Please Setup Default Maintenance Tax Template in {1}".format( - url, company - ) - ) - ) - if self_customer: - invoice_customer = self_consumption_customer - else: - invoice_customer = doc.customer - is_pos = 0 - pos_profile = "" - naming_series = "" - if pos: - user_pos_profile = get_pos_profile(company) - is_pos = 1 - pos_profile = user_pos_profile.name - naming_series = user_pos_profile.naming_series - default_tax_template = ( - user_pos_profile.taxes_and_charges or default_tax_template - ) - invoice_doc = frappe.get_doc( - dict( - is_pos=is_pos, - pos_profile=pos_profile, - naming_series=naming_series, - doctype="Sales Invoice", - customer=invoice_customer, - company=company, - posting_date=today(), - due_date=today(), - ignore_pricing_rule=1, - items=items_list, - update_stock=1, - remarks=user_remarks, - cost_center=cost_center, - lease=lease, - taxes_and_charges=default_tax_template, - job_card=doc.name, - ) - ).insert(ignore_permissions=True) - invoice_doc.reload() - if invoice_doc.taxes_and_charges and not pos: - getTax(invoice_doc) - invoice_doc.calculate_taxes_and_totals() - invoice_doc.run_method("set_missing_values") - invoice_doc.run_method("calculate_taxes_and_totals") - invoice_doc.save() - if invoice_doc: - invoice_url = frappe.utils.get_url_to_form( - invoice_doc.doctype, invoice_doc.name - ) - si_msgprint = "Sales invoice Created {1}".format( - invoice_url, invoice_doc.name - ) - frappe.flags.ignore_account_permission = True - if submit_maintenance_invoice == 1 and not pos: - invoice_doc.submit() - if pos: - make_sales_pos_payment(invoice_doc, user_pos_profile.name) - si_msgprint = "POS " + si_msgprint - frappe.msgprint(_(si_msgprint)) - for item_row in doc.materials_billed: - if ( - item_row.item - and item_row.quantity - and item_row.invoiced == 1 - and not item_row.sales_invoice - ): - item_row.sales_invoice = invoice_doc.name - frappe.db.set_value( - "Issue Materials Billed", - item_row.name, - "sales_invoice", - invoice_doc.name, - ) - frappe.db.commit() + def getTax(sales_invoice): + taxes = get_taxes_and_charges("Sales Taxes and Charges Template", sales_invoice.taxes_and_charges) + for tax in taxes: + sales_invoice.append("taxes", tax) - def getTax(sales_invoice): - taxes = get_taxes_and_charges( - "Sales Taxes and Charges Template", sales_invoice.taxes_and_charges - ) - for tax in taxes: - sales_invoice.append("taxes", tax) + def make_sales_pos_payment(invoice_doc, pos_profile_name): + default_mode_of_payment = frappe.db.get_value( + "Sales Invoice Payment", + {"parent": invoice_doc.name, "default": 1}, + ["mode_of_payment", "type", "account"], + as_dict=1, + ) + payment_row = invoice_doc.append("payments", {}) + payment_row.mode_of_payment = default_mode_of_payment.mode_of_payment + payment_row.amount = invoice_doc.grand_total + payment_row.base_amount = invoice_doc.grand_total + payment_row.account = default_mode_of_payment.account + invoice_doc.submit() - def make_sales_pos_payment(invoice_doc, pos_profile_name): - default_mode_of_payment = frappe.db.get_value( - "Sales Invoice Payment", - {"parent": invoice_doc.name, "default": 1}, - ["mode_of_payment", "type", "account"], - as_dict=1, - ) - payment_row = invoice_doc.append("payments", {}) - payment_row.mode_of_payment = default_mode_of_payment.mode_of_payment - payment_row.amount = invoice_doc.grand_total - payment_row.base_amount = invoice_doc.grand_total - payment_row.account = default_mode_of_payment.account - invoice_doc.submit() + if is_grouped == 1: + # Make grouped Sales Invoice for POS items + items = [] + for item_row in doc.materials_billed: + if ( + item_row.item + and item_row.quantity + and item_row.material_status == "Bill" + and not item_row.sales_invoice + and item_row.is_pos + ): + item_dict = dict( + item_code=item_row.item, + qty=item_row.quantity, + rate=item_row.rate, + cost_center=cost_center, + item_tax_template=get_taxes_template(item_row.item), + ) + items.append(item_dict) + item_row.invoiced = 1 + make_sales_invoice(items, pos=True) - if is_grouped == 1: - # Make grouped Sales Invoice for POS items - items = [] - for item_row in doc.materials_billed: - if ( - item_row.item - and item_row.quantity - and item_row.material_status == "Bill" - and not item_row.sales_invoice - and item_row.is_pos - ): - item_dict = dict( - item_code=item_row.item, - qty=item_row.quantity, - rate=item_row.rate, - cost_center=cost_center, - item_tax_template=get_taxes_template(item_row.item), - ) - items.append(item_dict) - item_row.invoiced = 1 - make_sales_invoice(items, pos=True) + # Make grouped items Sales Invoice for non-POS items + items = [] + for item_row in doc.materials_billed: + if ( + item_row.item + and item_row.quantity + and item_row.material_status == "Bill" + and not item_row.sales_invoice + and not item_row.is_pos + ): + item_dict = dict( + item_code=item_row.item, + qty=item_row.quantity, + rate=item_row.rate, + cost_center=cost_center, + item_tax_template=get_taxes_template(item_row.item), + ) + items.append(item_dict) + item_row.invoiced = 1 + make_sales_invoice(items, pos=False) - # Make grouped items Sales Invoice for non-POS items - items = [] - for item_row in doc.materials_billed: - if ( - item_row.item - and item_row.quantity - and item_row.material_status == "Bill" - and not item_row.sales_invoice - and not item_row.is_pos - ): - item_dict = dict( - item_code=item_row.item, - qty=item_row.quantity, - rate=item_row.rate, - cost_center=cost_center, - item_tax_template=get_taxes_template(item_row.item), - ) - items.append(item_dict) - item_row.invoiced = 1 - make_sales_invoice(items, pos=False) + else: # Not grouped + # Make Sales Invoice for non-grouped items + for item_row in doc.materials_billed: + items = [] + if ( + item_row.item + and item_row.quantity + and item_row.material_status == "Bill" + and not item_row.sales_invoice + ): + item_dict = dict( + item_code=item_row.item, + qty=item_row.quantity, + rate=item_row.rate, + cost_center=cost_center, + item_tax_template=get_taxes_template(item_row.item), + ) + items.append(item_dict) + item_row.invoiced = 1 + if item_row.is_pos: + pos = True + else: + pos = False + make_sales_invoice(items, pos) - else: # Not grouped - # Make Sales Invoice for non-grouped items - for item_row in doc.materials_billed: - items = [] - if ( - item_row.item - and item_row.quantity - and item_row.material_status == "Bill" - and not item_row.sales_invoice - ): - item_dict = dict( - item_code=item_row.item, - qty=item_row.quantity, - rate=item_row.rate, - cost_center=cost_center, - item_tax_template=get_taxes_template(item_row.item), - ) - items.append(item_dict) - item_row.invoiced = 1 - if item_row.is_pos: - pos = True - else: - pos = False - make_sales_invoice(items, pos) + # Make Stock Entry for Self Consumption items + if for_self_consumption and doc.status == "Closed": + items = [] + for item_row in doc.materials_billed: + if ( + item_row.item + and item_row.quantity + and item_row.material_status == "Self Consumption" + and not item_row.stock_entry + ): + item_dict = dict( + item_code=item_row.item, + qty=item_row.quantity, + rate=item_row.rate, + cost_center=cost_center, + ) + items.append(item_dict) + make_stock_entry(items, False) - # Make Stock Entry for Self Consumption items - if for_self_consumption and doc.status == "Closed": - items = [] - for item_row in doc.materials_billed: - if ( - item_row.item - and item_row.quantity - and item_row.material_status == "Self Consumption" - and not item_row.stock_entry - ): - item_dict = dict( - item_code=item_row.item, - qty=item_row.quantity, - rate=item_row.rate, - cost_center=cost_center, - ) - items.append(item_dict) - make_stock_entry(items, False) @frappe.whitelist() def get_item_rate(item, customer): - price_list = frappe.db.get_single_value("Selling Settings", "selling_price_list") - price_list = price_list or frappe.db.get_value( - "Customer", customer, "default_price_list" - ) - customer_group = frappe.db.get_value("Customer", customer, "customer_group") - company = frappe.db.get_single_value("Global Defaults", "default_company") - rate = get_price(item, price_list, customer_group, company) - if rate: - return rate["price_list_rate"] + price_list = frappe.db.get_single_value("Selling Settings", "selling_price_list") + price_list = price_list or frappe.db.get_value("Customer", customer, "default_price_list") + customer_group = frappe.db.get_value("Customer", customer, "customer_group") + company = frappe.db.get_single_value("Global Defaults", "default_company") + rate = get_price(item, price_list, customer_group, company) + if rate: + return rate["price_list_rate"] @frappe.whitelist() def get_items_group(): - property_doc = frappe.get_doc("Property Management Settings") - items_group_list = [] - for items_group in property_doc.maintenance_item_group: - items_group_list.append(items_group.item_group) - return items_group_list + property_doc = frappe.get_doc("Property Management Settings") + items_group_list = [] + for items_group in property_doc.maintenance_item_group: + items_group_list.append(items_group.item_group) + return items_group_list def validate_materials_required(doc): - have_items = 0 - for item in doc.materials_required: - if item.material_status != "Self Consumption": - have_items += 1 - if have_items > 0 and doc.status == "Closed": - frappe.throw( - _( - "The materials required has items and so the job card cannot be closed. Please confirm billing status fo the materials required." - ) - ) + have_items = 0 + for item in doc.materials_required: + if item.material_status != "Self Consumption": + have_items += 1 + if have_items > 0 and doc.status == "Closed": + frappe.throw( + _( + "The materials required has items and so the job card cannot be closed. Please confirm billing status fo the materials required." + ) + ) def validate(doc, method): - validate_materials_required(doc) - make_transaction(doc, for_self_consumption=False) - if doc.status == "Closed": - make_transaction(doc, for_self_consumption=True) + validate_materials_required(doc) + make_transaction(doc, for_self_consumption=False) + if doc.status == "Closed": + make_transaction(doc, for_self_consumption=True) def get_taxes_template(item_code): - item_tax_template = get_taxes_and_charges("Item", item_code) - if len(item_tax_template) > 0: - return item_tax_template[0]["item_tax_template"] - else: - return "" + item_tax_template = get_taxes_and_charges("Item", item_code) + if len(item_tax_template) > 0: + return item_tax_template[0]["item_tax_template"] + else: + return "" @frappe.whitelist() def get_stock_availability(item_code, company, is_pos): - warehouse = "" - if int(is_pos) == 1: - user_pos_profile = get_pos_profile(company) - warehouse = user_pos_profile.warehouse - if not warehouse: - warehouse = frappe.db.get_single_value("Stock Settings", "default_warehouse") - latest_sle = frappe.db.sql( - """select sum(actual_qty) as actual_qty - from `tabStock Ledger Entry` + warehouse = "" + if int(is_pos) == 1: + user_pos_profile = get_pos_profile(company) + warehouse = user_pos_profile.warehouse + if not warehouse: + warehouse = frappe.db.get_single_value("Stock Settings", "default_warehouse") + latest_sle = frappe.db.sql( + """select sum(actual_qty) as actual_qty + from `tabStock Ledger Entry` where item_code = %s and warehouse = %s limit 1""", - (item_code, warehouse), - as_dict=1, - ) + (item_code, warehouse), + as_dict=1, + ) - sle_qty = latest_sle[0].actual_qty or 0 if latest_sle else 0 - return sle_qty \ No newline at end of file + sle_qty = latest_sle[0].actual_qty or 0 if latest_sle else 0 + return sle_qty diff --git a/propms/lease_invoice.py b/propms/lease_invoice.py index 5120c5b6..25cddea0 100755 --- a/propms/lease_invoice.py +++ b/propms/lease_invoice.py @@ -1,310 +1,295 @@ -from __future__ import unicode_literals -from erpnext.controllers.accounts_controller import get_taxes_and_charges -from erpnext.accounts.party import get_due_date -from frappe.utils import add_days, today, add_months, getdate +import json +import traceback + import frappe import frappe.permissions import frappe.share -import json -import traceback +from erpnext.accounts.party import get_due_date +from erpnext.controllers.accounts_controller import get_taxes_and_charges from frappe import _ +from frappe.utils import add_days, add_months, getdate, today @frappe.whitelist() def app_error_log(title, error): - d = frappe.get_doc( - { - "doctype": "Custom Error Log", - "title": str("User:") + str(title), - "error": traceback.format_exc(), - } - ) - d = d.insert(ignore_permissions=True) - return d + d = frappe.get_doc( + { + "doctype": "Custom Error Log", + "title": "User:" + str(title), + "error": traceback.format_exc(), + } + ) + d = d.insert(ignore_permissions=True) + return d @frappe.whitelist() def makeInvoice( - date, - customer, - items, - currency=None, - lease=None, - lease_item=None, - qty=None, - schedule_start_date=None, - doctype="Sales Invoice", # Allow to create Sales Invoice or Sales Order + date, + customer, + items, + currency=None, + lease=None, + lease_item=None, + qty=None, + schedule_start_date=None, + doctype="Sales Invoice", # Allow to create Sales Invoice or Sales Order ): - """Create sales invoice from lease invoice schedule.""" - if not doctype: - doctype = "Sales Invoice" - try: - if not customer: - frappe.throw(_("Please select a Customer in Lease {0}").format(lease)) - company = frappe.get_value("Lease", lease, "company") - default_tax_template = frappe.get_value( - "Company", company, "default_tax_template" - ) - if qty != int(qty): - # it means the last invoice for the lease that may have fraction of months - subs_end_date = frappe.get_value("Lease", lease, "end_date") - else: - # month qty is not fractional - subs_end_date = add_days(add_months(schedule_start_date, qty), -1) - items_data = frappe.parse_json(items) - is_accrued = any(item.get("enable_deferred_revenue") for item in items_data) - invoice_posting_date = date if (is_accrued and date) else today() - doc = frappe.get_doc( - dict( - doctype=doctype, - company=company, - posting_date=invoice_posting_date, - set_posting_time=1 if is_accrued else 0, - items=items_data, - customer=str(customer), - due_date=getDueDate(invoice_posting_date, str(customer)), - currency=currency, - lease=lease, - lease_item=lease_item, - taxes_and_charges=default_tax_template, - from_date=schedule_start_date, - to_date=subs_end_date, - cost_center=getCostCenter(lease), - ) - ) - if doc.doctype == "Sales Order": - sales_order_date = doc.from_date - doc.transaction_date = sales_order_date - doc.posting_date = sales_order_date - doc.delivery_date = doc.to_date - if not doc.due_date or getdate(doc.due_date) < getdate(sales_order_date): - doc.due_date = sales_order_date - doc.set_missing_values() - if doc.doctype != "Sales Order": - doc.insert() - if doc.taxes_and_charges: - getTax(doc) - doc.calculate_taxes_and_totals() - if doc.doctype == "Sales Order": - doc.insert() - return doc - doc.save() - - if doctype == "Sales Order" and frappe.db.get_single_value("Property Management Settings", "auto_submit_sales_order"): - doc.submit() + """Create sales invoice from lease invoice schedule.""" + if not doctype: + doctype = "Sales Invoice" + try: + if not customer: + frappe.throw(_("Please select a Customer in Lease {0}").format(lease)) + company = frappe.get_value("Lease", lease, "company") + default_tax_template = frappe.get_value("Company", company, "default_tax_template") + if qty != int(qty): + # it means the last invoice for the lease that may have fraction of months + subs_end_date = frappe.get_value("Lease", lease, "end_date") + else: + # month qty is not fractional + subs_end_date = add_days(add_months(schedule_start_date, qty), -1) + items_data = frappe.parse_json(items) + is_accrued = any(item.get("enable_deferred_revenue") for item in items_data) + invoice_posting_date = date if (is_accrued and date) else today() + doc = frappe.get_doc( + dict( + doctype=doctype, + company=company, + posting_date=invoice_posting_date, + set_posting_time=1 if is_accrued else 0, + items=items_data, + customer=str(customer), + due_date=getDueDate(invoice_posting_date, str(customer)), + currency=currency, + lease=lease, + lease_item=lease_item, + taxes_and_charges=default_tax_template, + from_date=schedule_start_date, + to_date=subs_end_date, + cost_center=getCostCenter(lease), + ) + ) + if doc.doctype == "Sales Order": + sales_order_date = doc.from_date + doc.transaction_date = sales_order_date + doc.posting_date = sales_order_date + doc.delivery_date = doc.to_date + if not doc.due_date or getdate(doc.due_date) < getdate(sales_order_date): + doc.due_date = sales_order_date + doc.set_missing_values() + if doc.doctype != "Sales Order": + doc.insert() + if doc.taxes_and_charges: + getTax(doc) + doc.calculate_taxes_and_totals() + if doc.doctype == "Sales Order": + doc.insert() + return doc + doc.save() - # Check if auto submit is enabled in Property Management Settings - if doctype == "Sales Invoice" and frappe.db.get_single_value("Property Management Settings", "auto_submit_sales_invoice"): - doc.submit() - - return doc - except Exception as e: - app_error_log(frappe.session.user, str(e)) + if doctype == "Sales Order" and frappe.db.get_single_value( + "Property Management Settings", "auto_submit_sales_order" + ): + doc.submit() + + # Check if auto submit is enabled in Property Management Settings + if doctype == "Sales Invoice" and frappe.db.get_single_value( + "Property Management Settings", "auto_submit_sales_invoice" + ): + doc.submit() + + return doc + except Exception as e: + app_error_log(frappe.session.user, str(e)) @frappe.whitelist() def getTax(sales_invoice): - taxes = get_taxes_and_charges( - "Sales Taxes and Charges Template", sales_invoice.taxes_and_charges - ) - for tax in taxes: - sales_invoice.append("taxes", tax) + taxes = get_taxes_and_charges("Sales Taxes and Charges Template", sales_invoice.taxes_and_charges) + for tax in taxes: + sales_invoice.append("taxes", tax) @frappe.whitelist() def getDueDate(date, customer): - return get_due_date( - date, - "Customer", - str(customer), - frappe.db.get_single_value("Global Defaults", "default_company"), - date, - ) + return get_due_date( + date, + "Customer", + str(customer), + frappe.db.get_single_value("Global Defaults", "default_company"), + date, + ) @frappe.whitelist() def getCostCenter(name): - property_name = frappe.db.get_value("Lease", name, "property") - return frappe.db.get_value("Property", property_name, "cost_center") + property_name = frappe.db.get_value("Lease", name, "property") + return frappe.db.get_value("Property", property_name, "cost_center") @frappe.whitelist() def leaseInvoiceAutoCreate(): - """Prepare data to create sales invoice from lease invoice schedule. This is called from form button as well as daily schedule""" - try: - # frappe.msgprint("Started") - invoice_start_date = frappe.db.get_single_value( - "Property Management Settings", "invoice_start_date" - ) - lease_invoice = frappe.get_all( - "Lease Invoice Schedule", - filters={ - "date_to_invoice": ["between", (invoice_start_date, today())], - "invoice_number": "", - "sales_order_number": "" - }, - fields=[ - "name", - "date_to_invoice", - "invoice_number", - "sales_order_number", - "parent", - "parent", - "invoice_item_group", - "lease_item", - "paid_by", - "currency", - ], - order_by="parent, paid_by, invoice_item_group, date_to_invoice, currency, lease_item", - ) - if not lease_invoice: - return - # frappe.msgprint("Lease being generated for " + str(lease_invoice)) - row_num = 1 # to identify the 1st line of the list - prev_parent = "" - prev_customer = "" - prev_invoice_item_group = "" - prev_date_to_invoice = "" - lease_invoice_schedule_name = "" - prev_currency = "" - lease_invoice_schedule_list = [] - item_dict = [] - item_json = {} - # frappe.msgprint(str(lease_invoice)) - for row in lease_invoice: - # frappe.msgprint(str(invoice_item.name) + " " + str(invoice_item.lease_item)) - # Check if same lease, customer, invoice_item_group and date_to_invoice. - # Also should not be 1st row of the list - # frappe.msgprint(row.parent + " -- " + prev_parent + " -- " + row.paid_by + " -- " + prev_customer + " -- " + row.invoice_item_group + " -- " + prev_invoice_item_group + " -- " + str(row.date_to_invoice) + " -- " + str(prev_date_to_invoice) + " -- " + row.currency + " -- " + prev_currency) - if ( - not ( - row.parent == prev_parent - and row.paid_by == prev_customer - and row.invoice_item_group == prev_invoice_item_group - and row.date_to_invoice == prev_date_to_invoice - and row.currency == prev_currency - ) - and row_num != 1 - ): - # frappe.msgprint("Creating invoice for: " + str(item_dict)) - res = makeInvoice( - invoice_item.date_to_invoice, - invoice_item.paid_by, - json.dumps(item_dict), - invoice_item.currency, - invoice_item.parent, - invoice_item.lease_item, - invoice_item.qty, - invoice_item.schedule_start_date, - doctype=invoice_item.document_type, - ) - # frappe.msgprint("Result: " + str(res)) - if res: - # Loop through all list invoice names that were created and update them with same invoice number - for lease_invoice_schedule_name in lease_invoice_schedule_list: - # frappe.msgprint("---") - # frappe.msgprint("The lease invoice schedule " + str(lease_invoice_schedule_name) + " would be updated with invoice number " + str(res.name) ) - frappe.db.set_value( - "Lease Invoice Schedule", - lease_invoice_schedule_name, - "invoice_number" - if res.doctype == "Sales Invoice" - else "sales_order_number", - res.name, - ) - frappe.db.commit() # commit the changes to the database - frappe.msgprint(_("Lease Invoice generated with number: {0}").format(res.name)) - item_dict = [] - lease_invoice_schedule_list = ( - [] - ) # reset the list of names of lease_invoice_schedule - item_json = {} - # Now that the invoice would be created if required, load the record for preparing item_dict - invoice_item = frappe.get_doc("Lease Invoice Schedule", row.name) - if not (invoice_item.schedule_start_date): - invoice_item.schedule_start_date = invoice_item.date_to_invoice - lease_end_date = frappe.get_value("Lease", invoice_item.parent, "end_date") - item_json["item_code"] = invoice_item.lease_item - item_json["qty"] = invoice_item.qty - item_json["rate"] = invoice_item.rate - item_json["cost_center"] = getCostCenter(invoice_item.parent) - item_json["withholding_tax_rate"] = invoice_item.tax + """Prepare data to create sales invoice from lease invoice schedule. This is called from form button as well as daily schedule""" + try: + # frappe.msgprint("Started") + invoice_start_date = frappe.db.get_single_value("Property Management Settings", "invoice_start_date") + lease_invoice = frappe.get_all( + "Lease Invoice Schedule", + filters={ + "date_to_invoice": ["between", (invoice_start_date, today())], + "invoice_number": "", + "sales_order_number": "", + }, + fields=[ + "name", + "date_to_invoice", + "invoice_number", + "sales_order_number", + "parent", + "parent", + "invoice_item_group", + "lease_item", + "paid_by", + "currency", + ], + order_by="parent, paid_by, invoice_item_group, date_to_invoice, currency, lease_item", + ) + if not lease_invoice: + return + # frappe.msgprint("Lease being generated for " + str(lease_invoice)) + row_num = 1 # to identify the 1st line of the list + prev_parent = "" + prev_customer = "" + prev_invoice_item_group = "" + prev_date_to_invoice = "" + lease_invoice_schedule_name = "" + prev_currency = "" + invoice_item = None # previous row, used to flush a completed group + lease_invoice_schedule_list = [] + item_dict = [] + item_json = {} + # frappe.msgprint(str(lease_invoice)) + for row in lease_invoice: + # frappe.msgprint(str(invoice_item.name) + " " + str(invoice_item.lease_item)) + # Check if same lease, customer, invoice_item_group and date_to_invoice. + # Also should not be 1st row of the list + # frappe.msgprint(row.parent + " -- " + prev_parent + " -- " + row.paid_by + " -- " + prev_customer + " -- " + row.invoice_item_group + " -- " + prev_invoice_item_group + " -- " + str(row.date_to_invoice) + " -- " + str(prev_date_to_invoice) + " -- " + row.currency + " -- " + prev_currency) + if ( + not ( + row.parent == prev_parent + and row.paid_by == prev_customer + and row.invoice_item_group == prev_invoice_item_group + and row.date_to_invoice == prev_date_to_invoice + and row.currency == prev_currency + ) + and row_num != 1 + ): + # frappe.msgprint("Creating invoice for: " + str(item_dict)) + res = makeInvoice( + invoice_item.date_to_invoice, + invoice_item.paid_by, + json.dumps(item_dict), + invoice_item.currency, + invoice_item.parent, + invoice_item.lease_item, + invoice_item.qty, + invoice_item.schedule_start_date, + doctype=invoice_item.document_type, + ) + # frappe.msgprint("Result: " + str(res)) + if res: + # Loop through all list invoice names that were created and update them with same invoice number + for lease_invoice_schedule_name in lease_invoice_schedule_list: + # frappe.msgprint("---") + # frappe.msgprint("The lease invoice schedule " + str(lease_invoice_schedule_name) + " would be updated with invoice number " + str(res.name) ) + frappe.db.set_value( + "Lease Invoice Schedule", + lease_invoice_schedule_name, + "invoice_number" if res.doctype == "Sales Invoice" else "sales_order_number", + res.name, + ) + frappe.db.commit() # commit the changes to the database + frappe.msgprint(_("Lease Invoice generated with number: {0}").format(res.name)) + item_dict = [] + lease_invoice_schedule_list = [] # reset the list of names of lease_invoice_schedule + item_json = {} + # Now that the invoice would be created if required, load the record for preparing item_dict + invoice_item = frappe.get_doc("Lease Invoice Schedule", row.name) + if not (invoice_item.schedule_start_date): + invoice_item.schedule_start_date = invoice_item.date_to_invoice + lease_end_date = frappe.get_value("Lease", invoice_item.parent, "end_date") + item_json["item_code"] = invoice_item.lease_item + item_json["qty"] = invoice_item.qty + item_json["rate"] = invoice_item.rate + item_json["cost_center"] = getCostCenter(invoice_item.parent) + item_json["withholding_tax_rate"] = invoice_item.tax + + # Fetch deferred revenue configuration from Item Master and Item Defaults / Company + item_doc = frappe.get_cached_doc("Item", invoice_item.lease_item) + if item_doc.enable_deferred_revenue: + item_json["enable_deferred_revenue"] = 1 + company = frappe.get_value("Lease", invoice_item.parent, "company") + deferred_account = frappe.get_cached_value( + "Item Default", + {"parent": invoice_item.lease_item, "company": company}, + "deferred_revenue_account", + ) or frappe.get_cached_value("Company", company, "default_deferred_revenue_account") + if deferred_account: + item_json["deferred_revenue_account"] = deferred_account + else: + item_json["enable_deferred_revenue"] = 0 - # Fetch deferred revenue configuration from Item Master and Item Defaults / Company - item_doc = frappe.get_cached_doc("Item", invoice_item.lease_item) - if item_doc.enable_deferred_revenue: - item_json["enable_deferred_revenue"] = 1 - company = frappe.get_value("Lease", invoice_item.parent, "company") - deferred_account = ( - frappe.get_cached_value( - "Item Default", - {"parent": invoice_item.lease_item, "company": company}, - "deferred_revenue_account", - ) - or frappe.get_cached_value("Company", company, "default_deferred_revenue_account") - ) - if deferred_account: - item_json["deferred_revenue_account"] = deferred_account - else: - item_json["enable_deferred_revenue"] = 0 + item_json["service_start_date"] = str(invoice_item.schedule_start_date) + if invoice_item.qty != int(invoice_item.qty): + # it means the last invoice for the lease that may have fraction of months + subs_end_date = lease_end_date + else: + # month qty is not fractional + subs_end_date = add_days(add_months(invoice_item.schedule_start_date, invoice_item.qty), -1) + item_json["service_end_date"] = str(subs_end_date) + # Append to the dictionary as a dict() so that the values for the new row can be set + item_dict.append(dict(item_json)) + lease_invoice_schedule_list.append(invoice_item.name) - item_json["service_start_date"] = str(invoice_item.schedule_start_date) - if invoice_item.qty != int(invoice_item.qty): - # it means the last invoice for the lease that may have fraction of months - subs_end_date = lease_end_date - else: - # month qty is not fractional - subs_end_date = add_days( - add_months(invoice_item.schedule_start_date, invoice_item.qty), -1 - ) - item_json["service_end_date"] = str(subs_end_date) - # Append to the dictionary as a dict() so that the values for the new row can be set - item_dict.append(dict(item_json)) - lease_invoice_schedule_list.append(invoice_item.name) + # Remember the values for the next round + prev_parent = invoice_item.parent + prev_customer = invoice_item.paid_by + prev_invoice_item_group = invoice_item.invoice_item_group + prev_date_to_invoice = invoice_item.date_to_invoice + prev_currency = invoice_item.currency + row_num += 1 # increment by 1 + # Create the last invoice + res = makeInvoice( + invoice_item.date_to_invoice, + invoice_item.paid_by, + json.dumps(item_dict), + invoice_item.currency, + invoice_item.parent, + invoice_item.lease_item, + invoice_item.qty, + invoice_item.schedule_start_date, + doctype=invoice_item.document_type, + ) + if res: + # Loop through all list invoice names that were created and update them with same invoice number + for lease_invoice_schedule_name in lease_invoice_schedule_list: + # frappe.msgprint("The lease invoice schedule " + str(lease_invoice_schedule_name) + " would be updated with invoice number " + str(res.name)) + frappe.db.set_value( + "Lease Invoice Schedule", + lease_invoice_schedule_name, + "invoice_number" if res.doctype == "Sales Invoice" else "sales_order_number", + res.name, + ) + frappe.db.commit() # commit the changes to the database + frappe.msgprint(_("Lease Invoice generated with number: {0}").format(res.name)) - # Remember the values for the next round - prev_parent = invoice_item.parent - prev_customer = invoice_item.paid_by - prev_invoice_item_group = invoice_item.invoice_item_group - prev_date_to_invoice = invoice_item.date_to_invoice - prev_currency = invoice_item.currency - row_num += 1 # increment by 1 - # Create the last invoice - res = makeInvoice( - invoice_item.date_to_invoice, - invoice_item.paid_by, - json.dumps(item_dict), - invoice_item.currency, - invoice_item.parent, - invoice_item.lease_item, - invoice_item.qty, - invoice_item.schedule_start_date, - doctype=invoice_item.document_type, - ) - if res: - # Loop through all list invoice names that were created and update them with same invoice number - for lease_invoice_schedule_name in lease_invoice_schedule_list: - # frappe.msgprint("The lease invoice schedule " + str(lease_invoice_schedule_name) + " would be updated with invoice number " + str(res.name)) - frappe.db.set_value( - "Lease Invoice Schedule", - lease_invoice_schedule_name, - "invoice_number" - if res.doctype == "Sales Invoice" - else "sales_order_number", - res.name, - ) - frappe.db.commit() # commit the changes to the database - frappe.msgprint(_("Lease Invoice generated with number: {0}").format(res.name)) + except Exception as e: + app_error_log(frappe.session.user, str(e)) - except Exception as e: - app_error_log(frappe.session.user, str(e)) @frappe.whitelist() def enqueue_lease_invoice_auto_create(): - """Enqueue leaseInvoiceAutoCreate as a background job on the 'long' queue.""" - frappe.enqueue( - "propms.lease_invoice.leaseInvoiceAutoCreate", - queue="long", - now=False - ) - return "Lease invoice auto creation has been queued. You will be notified once done." + """Enqueue leaseInvoiceAutoCreate as a background job on the 'long' queue.""" + frappe.enqueue("propms.lease_invoice.leaseInvoiceAutoCreate", queue="long", now=False) + return "Lease invoice auto creation has been queued. You will be notified once done." diff --git a/propms/lease_invoice_schedule.py b/propms/lease_invoice_schedule.py index 982c4599..ff353334 100644 --- a/propms/lease_invoice_schedule.py +++ b/propms/lease_invoice_schedule.py @@ -1,145 +1,152 @@ -from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import add_days, today, getdate, add_months, get_first_day, get_last_day -from propms.auto_custom import app_error_log, makeInvoiceSchedule, getDateMonthDiff from frappe.query_builder import DocType +from frappe.utils import add_days, add_months, get_first_day, get_last_day, getdate, today + +from propms.auto_custom import app_error_log, makeInvoiceSchedule + def get_aligned_invoice_date(date): - """Returns the first day of the month for the given date""" - return get_first_day(date) + """Returns the first day of the month for the given date""" + return get_first_day(date) + @frappe.whitelist() def make_lease_invoice_schedule(): - # First check if make_invoice_schedule_up_to_tomorrow_only is enabled - settings = frappe.get_single("Property Management Settings") - make_schedule_up_to_tomorrow = settings.get("make_invoice_schedule_up_to_tomorrow_only", 0) - if not make_schedule_up_to_tomorrow: - return - - # Proceed with other computations only if the setting is enabled - use_valid_from_date = settings.use_valid_from_date - today_date = getdate(today()) - next_month_end = get_last_day(add_months(today_date, 1)) # End of next month - invoice_start_date = getdate(settings.get("invoice_start_date", None)) - - if not invoice_start_date: - frappe.throw(_("Please set Invoice Start Date in Property Management Settings")) - - Lease = DocType("Lease") - query = ( - frappe.qb.from_(Lease) - .select(Lease.name) - .where((Lease.start_date <= today_date)# Only check start_date, ignore end_date for inclusion - & (Lease.lease_status == "Active") # Filter for active leases - # Only check start_date, ignore end_date for inclusion - ) - ) - lease_names = [row[0] for row in frappe.db.sql(query.get_sql())] - - for lease_name in lease_names: - try: - lease = frappe.get_doc("Lease", lease_name) - lease_start = getdate(lease.start_date) - schedule_end = getdate(lease.end_date) if lease.end_date else next_month_end - - # Use the later date between lease_start and invoice_start_date - schedule_start = max(lease_start, invoice_start_date) if lease_start else invoice_start_date - if not schedule_start: - continue # skip if no start_date - - # Clean up schedule entries for removed lease items - lease_item_names = [li.lease_item for li in lease.lease_item] - schedule_items = frappe.get_all("Lease Invoice Schedule", filters={"parent": lease.name}, fields=["name", "lease_item"]) - for s in schedule_items: - if s.lease_item not in lease_item_names: - frappe.delete_doc("Lease Invoice Schedule", s.name) - - # Frequency map - freq_map = { - "Monthly": 1.0, - "Bi-Monthly": 2.0, - "Quarterly": 3.0, - "6 months": 6.0, - "Annually": 12.0, - } - - idx = 1 - for item in lease.lease_item: - if not item.frequency: - continue - - freq = freq_map.get(item.frequency) - if not freq: - frappe.log_error(f"Invalid frequency '{item.frequency}' for item {item.lease_item} in lease {lease.name}", "Invalid Frequency") - continue - - invoice_qty = float(freq) - - # Default to base amount - item_amount = item.amount - - # Get the latest active lease item that has valid_from date - lease_items = frappe.get_all( - "Lease Item", - filters={ - "parent": lease.name, - "lease_item": item.lease_item, - "valid_from": ("<=", today_date) - }, - fields=["amount", "valid_from"], - order_by="valid_from desc", - limit=1 - ) - - # Use amount from the most recent active lease item if found - if use_valid_from_date and lease_items: - item_amount = lease_items[0].amount - - # Get the first day of the month in which schedule_start falls - invoice_date = get_first_day(schedule_start) - - # Generate invoice schedules up to next month - while schedule_end >= invoice_date and invoice_date <= next_month_end: - # Calculate period end as last day of the month after freq months - invoice_period_end = get_last_day(add_months(invoice_date, freq - 1)) - - - # Only create schedule if date_to_invoice is between invoice start date and today + next month - if schedule_start <= invoice_date <= next_month_end: - exists = frappe.db.exists( - "Lease Invoice Schedule", - { - "parent": lease.name, - "lease_item": item.lease_item, - "date_to_invoice": invoice_date, - } - ) - - if not exists: - makeInvoiceSchedule( - invoice_date, - item.lease_item, - item.paid_by, - item.lease_item, - lease.name, - invoice_qty, - item_amount, - idx, - item.currency_code, - item.witholding_tax, - lease.days_to_invoice_in_advance, - item.invoice_item_group, - item.document_type, - ) - frappe.db.commit() - idx += 1 - - # Move to first day of next period - invoice_date = add_days(invoice_period_end, 1) - - frappe.msgprint(_(f"Completed invoice schedule for Lease: {lease.name}")) - - except Exception as e: - frappe.msgprint(_(f"Error in {lease_name}. Check app error log.")) - app_error_log(frappe.session.user, f"{lease_name}: {str(e)}") + # First check if make_invoice_schedule_up_to_tomorrow_only is enabled + settings = frappe.get_single("Property Management Settings") + make_schedule_up_to_tomorrow = settings.get("make_invoice_schedule_up_to_tomorrow_only", 0) + if not make_schedule_up_to_tomorrow: + return + + # Proceed with other computations only if the setting is enabled + use_valid_from_date = settings.use_valid_from_date + today_date = getdate(today()) + next_month_end = get_last_day(add_months(today_date, 1)) # End of next month + invoice_start_date = getdate(settings.get("invoice_start_date", None)) + + if not invoice_start_date: + frappe.throw(_("Please set Invoice Start Date in Property Management Settings")) + + Lease = DocType("Lease") + query = ( + frappe.qb.from_(Lease) + .select(Lease.name) + .where( + (Lease.start_date <= today_date) # Only check start_date, ignore end_date for inclusion + & (Lease.lease_status == "Active") # Filter for active leases + # Only check start_date, ignore end_date for inclusion + ) + ) + lease_names = [row[0] for row in frappe.db.sql(query.get_sql())] + + for lease_name in lease_names: + try: + lease = frappe.get_doc("Lease", lease_name) + lease_start = getdate(lease.start_date) + schedule_end = getdate(lease.end_date) if lease.end_date else next_month_end + + # Use the later date between lease_start and invoice_start_date + schedule_start = max(lease_start, invoice_start_date) if lease_start else invoice_start_date + if not schedule_start: + continue # skip if no start_date + + # Clean up schedule entries for removed lease items + lease_item_names = [li.lease_item for li in lease.lease_item] + schedule_items = frappe.get_all( + "Lease Invoice Schedule", filters={"parent": lease.name}, fields=["name", "lease_item"] + ) + for s in schedule_items: + if s.lease_item not in lease_item_names: + frappe.delete_doc("Lease Invoice Schedule", s.name) + + # Frequency map + freq_map = { + "Monthly": 1.0, + "Bi-Monthly": 2.0, + "Quarterly": 3.0, + "6 months": 6.0, + "Annually": 12.0, + } + + idx = 1 + for item in lease.lease_item: + if not item.frequency: + continue + + freq = freq_map.get(item.frequency) + if not freq: + frappe.log_error( + f"Invalid frequency '{item.frequency}' for item {item.lease_item} in lease {lease.name}", + "Invalid Frequency", + ) + continue + + invoice_qty = float(freq) + + # Default to base amount + item_amount = item.amount + + # Get the latest active lease item that has valid_from date + lease_items = frappe.get_all( + "Lease Item", + filters={ + "parent": lease.name, + "lease_item": item.lease_item, + "valid_from": ("<=", today_date), + }, + fields=["amount", "valid_from"], + order_by="valid_from desc", + limit=1, + ) + + # Use amount from the most recent active lease item if found + if use_valid_from_date and lease_items: + item_amount = lease_items[0].amount + + # Get the first day of the month in which schedule_start falls + invoice_date = get_first_day(schedule_start) + + # Generate invoice schedules up to next month + while schedule_end >= invoice_date and invoice_date <= next_month_end: + # Calculate period end as last day of the month after freq months + invoice_period_end = get_last_day(add_months(invoice_date, freq - 1)) + + # Only create schedule if date_to_invoice is between invoice start date and today + next month + if schedule_start <= invoice_date <= next_month_end: + exists = frappe.db.exists( + "Lease Invoice Schedule", + { + "parent": lease.name, + "lease_item": item.lease_item, + "date_to_invoice": invoice_date, + }, + ) + + if not exists: + makeInvoiceSchedule( + invoice_date, + item.lease_item, + item.paid_by, + item.lease_item, + lease.name, + invoice_qty, + item_amount, + idx, + item.currency_code, + item.witholding_tax, + lease.days_to_invoice_in_advance, + item.invoice_item_group, + item.document_type, + ) + frappe.db.commit() + idx += 1 + + # Move to first day of next period + invoice_date = add_days(invoice_period_end, 1) + + frappe.msgprint(_(f"Completed invoice schedule for Lease: {lease.name}")) + + except Exception as e: + frappe.msgprint(_(f"Error in {lease_name}. Check app error log.")) + app_error_log(frappe.session.user, f"{lease_name}: {str(e)}") diff --git a/propms/modules.txt b/propms/modules.txt index 86f0f772..f1785862 100755 --- a/propms/modules.txt +++ b/propms/modules.txt @@ -1 +1 @@ -Property Management Solution \ No newline at end of file +Property Management Solution diff --git a/propms/pos.js b/propms/pos.js index 602828b9..1b6857e0 100755 --- a/propms/pos.js +++ b/propms/pos.js @@ -426,8 +426,8 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ }); this.search_item.make_input(); - - + + this.cost_center = frappe.ui.form.make_control({ df: { "fieldtype": "Data", @@ -450,13 +450,13 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ } }, 400); }); - + this.search_cost_center = this.wrapper.find('.search-cost-center'); - + var dropdown_html = this.cost_center.map(function(cost_center) { return "
  • "+cost_center+"
  • "; }).join(""); - + this.search_item_group = this.wrapper.find('.search-item-group'); sorted_item_groups = this.get_sorted_item_groups() var dropdown_html = sorted_item_groups.map(function(item_group) { @@ -2113,4 +2113,4 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ frappe.throw(__("LocalStorage is full , did not save")) } } -}) \ No newline at end of file +}) diff --git a/propms/pos.py b/propms/pos.py index 37ebcd40..bb36a3cc 100644 --- a/propms/pos.py +++ b/propms/pos.py @@ -1,19 +1,18 @@ -from __future__ import unicode_literals import frappe @frappe.whitelist() def get_pos_data(cost_center): - property = frappe.get_list("Property", filters={"cost_center": cost_center}) - lease = None - one_lease = None - if property: - lease = frappe.get_all( - "Lease", - filters={"property": property[0].name}, - fields=["*"], - order_by="end_date desc", - ) - if lease: - one_lease = frappe.get_doc("Lease", lease[0].name) - return one_lease + property = frappe.get_list("Property", filters={"cost_center": cost_center}) + lease = None + one_lease = None + if property: + lease = frappe.get_all( + "Lease", + filters={"property": property[0].name}, + fields=["*"], + order_by="end_date desc", + ) + if lease: + one_lease = frappe.get_doc("Lease", lease[0].name) + return one_lease diff --git a/propms/property_increment.py b/propms/property_increment.py index 2f0886bf..86ee3712 100644 --- a/propms/property_increment.py +++ b/propms/property_increment.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import math import frappe @@ -7,298 +5,303 @@ from frappe.utils import add_months, getdate, nowdate from frappe.utils.data import cint, flt - VALID_UOM = {"Month": 1, "Year": 12} VALID_INCREMENT_TYPES = {"Percent", "Amount"} VALID_ROUNDING_MODES = {"Round", "Ceil", "Floor", "None"} def validate_property_increment_settings(doc, method=None): - if not cint(doc.get("enable_auto_increment")): - return + if not cint(doc.get("enable_auto_increment")): + return - horizon_months = cint(doc.get("auto_create_lease_items_for_months")) - if horizon_months <= 0: - frappe.throw(_("Auto Create Lease Items For Months must be greater than 0.")) + horizon_months = cint(doc.get("auto_create_lease_items_for_months")) + if horizon_months <= 0: + frappe.throw(_("Auto Create Lease Items For Months must be greater than 0.")) - rules = doc.get("lease_increment_rules") or [] - if not rules: - frappe.throw(_("Please add at least one Lease Increment Rule.")) + rules = doc.get("lease_increment_rules") or [] + if not rules: + frappe.throw(_("Please add at least one Lease Increment Rule.")) - rounding_mode = (doc.get("increment_rounding_mode") or "Round").strip() - if rounding_mode not in VALID_ROUNDING_MODES: - frappe.throw(_("Increment Rounding Mode must be one of: Round, Ceil, Floor, None.")) + rounding_mode = (doc.get("increment_rounding_mode") or "Round").strip() + if rounding_mode not in VALID_ROUNDING_MODES: + frappe.throw(_("Increment Rounding Mode must be one of: Round, Ceil, Floor, None.")) - rounding_precision = cint(doc.get("increment_rounding_precision")) - if rounding_precision < 0: - frappe.throw(_("Increment Rounding Precision cannot be negative.")) + rounding_precision = cint(doc.get("increment_rounding_precision")) + if rounding_precision < 0: + frappe.throw(_("Increment Rounding Precision cannot be negative.")) - default_effective_from = doc.get("increment_effective_from") - seen_rules = set() + default_effective_from = doc.get("increment_effective_from") + seen_rules = set() - for row in rules: - if not cint(row.get("is_active")): - continue + for row in rules: + if not cint(row.get("is_active")): + continue - lease_item = row.get("lease_item") - if not lease_item: - frappe.throw(_("Lease Item is mandatory in Lease Increment Rules.")) + lease_item = row.get("lease_item") + if not lease_item: + frappe.throw(_("Lease Item is mandatory in Lease Increment Rules.")) - increment_every = cint(row.get("increment_every")) - if increment_every <= 0: - frappe.throw(_("Increment Every must be greater than 0 for Lease Item {0}.").format(lease_item)) + increment_every = cint(row.get("increment_every")) + if increment_every <= 0: + frappe.throw(_("Increment Every must be greater than 0 for Lease Item {0}.").format(lease_item)) - increment_uom = row.get("increment_uom") - if increment_uom not in VALID_UOM: - frappe.throw(_("Increment UOM must be Month or Year for Lease Item {0}.").format(lease_item)) + increment_uom = row.get("increment_uom") + if increment_uom not in VALID_UOM: + frappe.throw(_("Increment UOM must be Month or Year for Lease Item {0}.").format(lease_item)) - increment_type = row.get("increment_type") - if increment_type not in VALID_INCREMENT_TYPES: - frappe.throw(_("Increment Type must be Percent or Amount for Lease Item {0}.").format(lease_item)) + increment_type = row.get("increment_type") + if increment_type not in VALID_INCREMENT_TYPES: + frappe.throw(_("Increment Type must be Percent or Amount for Lease Item {0}.").format(lease_item)) - increment_value = flt(row.get("increment_value")) - if increment_value <= 0: - frappe.throw(_("Increment Value must be greater than 0 for Lease Item {0}.").format(lease_item)) + increment_value = flt(row.get("increment_value")) + if increment_value <= 0: + frappe.throw(_("Increment Value must be greater than 0 for Lease Item {0}.").format(lease_item)) - rule_effective_from = row.get("rule_effective_from") or default_effective_from - if not rule_effective_from: - frappe.throw(_("Rule Effective From is required for Lease Item {0}.").format(lease_item)) + rule_effective_from = row.get("rule_effective_from") or default_effective_from + if not rule_effective_from: + frappe.throw(_("Rule Effective From is required for Lease Item {0}.").format(lease_item)) - dedupe_key = (lease_item, str(getdate(rule_effective_from))) - if dedupe_key in seen_rules: - frappe.throw(_("Duplicate active rule found for Lease Item {0} on {1}.").format(lease_item, dedupe_key[1])) - seen_rules.add(dedupe_key) + dedupe_key = (lease_item, str(getdate(rule_effective_from))) + if dedupe_key in seen_rules: + frappe.throw( + _("Duplicate active rule found for Lease Item {0} on {1}.").format(lease_item, dedupe_key[1]) + ) + seen_rules.add(dedupe_key) def run_property_increment_engine(): - _run_increment_engine() + _run_increment_engine() def _run_increment_engine(): - property_names = frappe.get_all("Property", filters={"enable_auto_increment": 1}, pluck="name") - if not property_names: - return - - for property_name in property_names: - try: - _process_property(property_name) - except Exception: - _log_increment_error( - title="Property Increment Engine Error", - details={ - "property": property_name, - "stage": "property_loop", - }, - ) + property_names = frappe.get_all("Property", filters={"enable_auto_increment": 1}, pluck="name") + if not property_names: + return + + for property_name in property_names: + try: + _process_property(property_name) + except Exception: + _log_increment_error( + title="Property Increment Engine Error", + details={ + "property": property_name, + "stage": "property_loop", + }, + ) def _process_property(property_name): - property_doc = frappe.get_doc("Property", property_name) - - if not cint(property_doc.get("enable_auto_increment")): - return - - horizon_months = cint(property_doc.get("auto_create_lease_items_for_months")) - if horizon_months <= 0: - return - - rules = [row for row in (property_doc.get("lease_increment_rules") or []) if cint(row.get("is_active"))] - if not rules: - return - - today = getdate(nowdate()) - horizon_end = getdate(add_months(today, horizon_months)) - default_effective_from = property_doc.get("increment_effective_from") - - leases = frappe.get_all( - "Lease", - filters={"property": property_doc.name, "lease_status": "Active", "docstatus": ["<", 2]}, - fields=["name", "start_date", "end_date"], - ) - - for lease_meta in leases: - try: - _process_lease( - property_doc=property_doc, - lease_name=lease_meta.name, - rules=rules, - default_effective_from=default_effective_from, - today=today, - horizon_end=horizon_end, - ) - except Exception: - _log_increment_error( - title="Property Increment Engine Lease Error", - details={ - "property": property_doc.name, - "lease": lease_meta.name, - "stage": "lease_loop", - }, - ) - continue + property_doc = frappe.get_doc("Property", property_name) + + if not cint(property_doc.get("enable_auto_increment")): + return + + horizon_months = cint(property_doc.get("auto_create_lease_items_for_months")) + if horizon_months <= 0: + return + + rules = [row for row in (property_doc.get("lease_increment_rules") or []) if cint(row.get("is_active"))] + if not rules: + return + + today = getdate(nowdate()) + horizon_end = getdate(add_months(today, horizon_months)) + default_effective_from = property_doc.get("increment_effective_from") + + leases = frappe.get_all( + "Lease", + filters={"property": property_doc.name, "lease_status": "Active", "docstatus": ["<", 2]}, + fields=["name", "start_date", "end_date"], + ) + + for lease_meta in leases: + try: + _process_lease( + property_doc=property_doc, + lease_name=lease_meta.name, + rules=rules, + default_effective_from=default_effective_from, + today=today, + horizon_end=horizon_end, + ) + except Exception: + _log_increment_error( + title="Property Increment Engine Lease Error", + details={ + "property": property_doc.name, + "lease": lease_meta.name, + "stage": "lease_loop", + }, + ) + continue def _process_lease(property_doc, lease_name, rules, default_effective_from, today, horizon_end): - lease = frappe.get_doc("Lease", lease_name) - lease_changed = False - - for rule in rules: - rule_context = _build_rule_context(rule, default_effective_from) - if not rule_context: - continue - - item_rows = [row for row in (lease.get("lease_item") or []) if row.lease_item == rule_context["lease_item_code"]] - if not item_rows: - continue - - changed = _apply_rule_to_lease( - lease=lease, - property_doc=property_doc, - rule=rule, - rule_context=rule_context, - item_rows=item_rows, - today=today, - horizon_end=horizon_end, - ) - lease_changed = lease_changed or changed - - if lease_changed: - lease.flags.ignore_validate_update_after_submit = True - lease.save(ignore_permissions=True) + lease = frappe.get_doc("Lease", lease_name) + lease_changed = False + + for rule in rules: + rule_context = _build_rule_context(rule, default_effective_from) + if not rule_context: + continue + + item_rows = [ + row + for row in (lease.get("lease_item") or []) + if row.lease_item == rule_context["lease_item_code"] + ] + if not item_rows: + continue + + changed = _apply_rule_to_lease( + lease=lease, + property_doc=property_doc, + rule=rule, + rule_context=rule_context, + item_rows=item_rows, + today=today, + horizon_end=horizon_end, + ) + lease_changed = lease_changed or changed + + if lease_changed: + lease.flags.ignore_validate_update_after_submit = True + lease.save(ignore_permissions=True) def _build_rule_context(rule, default_effective_from): - effective_from = rule.get("rule_effective_from") or default_effective_from - if not effective_from: - return None + effective_from = rule.get("rule_effective_from") or default_effective_from + if not effective_from: + return None - interval_months = cint(rule.get("increment_every")) * VALID_UOM.get(rule.get("increment_uom"), 0) - if interval_months <= 0: - return None + interval_months = cint(rule.get("increment_every")) * VALID_UOM.get(rule.get("increment_uom"), 0) + if interval_months <= 0: + return None - lease_item_code = rule.get("lease_item") - if not lease_item_code: - return None + lease_item_code = rule.get("lease_item") + if not lease_item_code: + return None - return { - "effective_from": getdate(effective_from), - "interval_months": interval_months, - "lease_item_code": lease_item_code, - } + return { + "effective_from": getdate(effective_from), + "interval_months": interval_months, + "lease_item_code": lease_item_code, + } def _apply_rule_to_lease(lease, property_doc, rule, rule_context, item_rows, today, horizon_end): - lease_changed = False - current_rows = _get_effective_rows(lease, item_rows) - existing_dates = {row["effective_date"] for row in current_rows} - candidate_date = rule_context["effective_from"] - interval_months = rule_context["interval_months"] - - while candidate_date <= horizon_end: - if lease.end_date and candidate_date > getdate(lease.end_date): - break - - if candidate_date >= today and candidate_date not in existing_dates: - prev_row = _get_previous_row(current_rows, candidate_date) - if prev_row: - new_amount = _apply_increment( - prev_row["amount"], - rule.get("increment_type"), - rule.get("increment_value"), - property_doc.get("increment_rounding_mode"), - property_doc.get("increment_rounding_precision"), - ) - _append_lease_item_version(lease, prev_row["row"], candidate_date, new_amount) - current_rows.append( - { - "row": lease.get("lease_item")[-1], - "effective_date": candidate_date, - "amount": new_amount, - } - ) - current_rows.sort(key=lambda x: x["effective_date"]) - existing_dates.add(candidate_date) - lease_changed = True - - candidate_date = getdate(add_months(candidate_date, interval_months)) - - return lease_changed + lease_changed = False + current_rows = _get_effective_rows(lease, item_rows) + existing_dates = {row["effective_date"] for row in current_rows} + candidate_date = rule_context["effective_from"] + interval_months = rule_context["interval_months"] + + while candidate_date <= horizon_end: + if lease.end_date and candidate_date > getdate(lease.end_date): + break + + if candidate_date >= today and candidate_date not in existing_dates: + prev_row = _get_previous_row(current_rows, candidate_date) + if prev_row: + new_amount = _apply_increment( + prev_row["amount"], + rule.get("increment_type"), + rule.get("increment_value"), + property_doc.get("increment_rounding_mode"), + property_doc.get("increment_rounding_precision"), + ) + _append_lease_item_version(lease, prev_row["row"], candidate_date, new_amount) + current_rows.append( + { + "row": lease.get("lease_item")[-1], + "effective_date": candidate_date, + "amount": new_amount, + } + ) + current_rows.sort(key=lambda x: x["effective_date"]) + existing_dates.add(candidate_date) + lease_changed = True + + candidate_date = getdate(add_months(candidate_date, interval_months)) + + return lease_changed def _append_lease_item_version(lease, source_row, candidate_date, new_amount): - lease.append( - "lease_item", - { - "lease_item": source_row.lease_item, - "frequency": source_row.frequency, - "amount": new_amount, - "currency_code": source_row.currency_code, - "charge_basis": source_row.charge_basis, - "charge_rate": source_row.charge_rate, - "witholding_tax": source_row.witholding_tax, - "paid_by": source_row.paid_by, - "invoice_item_group": source_row.invoice_item_group, - "document_type": source_row.document_type, - "valid_from": candidate_date, - "is_active": 0, - }, - ) + lease.append( + "lease_item", + { + "lease_item": source_row.lease_item, + "frequency": source_row.frequency, + "amount": new_amount, + "currency_code": source_row.currency_code, + "charge_basis": source_row.charge_basis, + "charge_rate": source_row.charge_rate, + "witholding_tax": source_row.witholding_tax, + "paid_by": source_row.paid_by, + "invoice_item_group": source_row.invoice_item_group, + "document_type": source_row.document_type, + "valid_from": candidate_date, + "is_active": 0, + }, + ) def _get_effective_rows(lease, item_rows): - lease_start = getdate(lease.start_date) if lease.start_date else getdate(nowdate()) - rows = [] - for row in item_rows: - effective_date = getdate(row.valid_from) if row.valid_from else lease_start - rows.append({"row": row, "effective_date": effective_date, "amount": flt(row.amount)}) - rows.sort(key=lambda x: x["effective_date"]) - return rows + lease_start = getdate(lease.start_date) if lease.start_date else getdate(nowdate()) + rows = [] + for row in item_rows: + effective_date = getdate(row.valid_from) if row.valid_from else lease_start + rows.append({"row": row, "effective_date": effective_date, "amount": flt(row.amount)}) + rows.sort(key=lambda x: x["effective_date"]) + return rows def _get_previous_row(rows, candidate_date): - previous = None - for row in rows: - if row["effective_date"] < candidate_date: - previous = row - else: - break - return previous + previous = None + for row in rows: + if row["effective_date"] < candidate_date: + previous = row + else: + break + return previous def _apply_increment(amount, increment_type, increment_value, rounding_mode, rounding_precision): - base_amount = flt(amount) - increment_value = flt(increment_value) - precision = cint(rounding_precision) if rounding_precision is not None else 2 - mode = (rounding_mode or "Round").strip() + base_amount = flt(amount) + increment_value = flt(increment_value) + precision = cint(rounding_precision) if rounding_precision is not None else 2 + mode = (rounding_mode or "Round").strip() - if increment_type == "Percent": - new_amount = base_amount + (base_amount * increment_value / 100.0) - else: - new_amount = base_amount + increment_value + if increment_type == "Percent": + new_amount = base_amount + (base_amount * increment_value / 100.0) + else: + new_amount = base_amount + increment_value - return _round_amount(new_amount, mode, precision) + return _round_amount(new_amount, mode, precision) def _round_amount(amount, mode, precision): - if mode == "None": - return amount - if mode == "Round": - return round(amount, precision) + if mode == "None": + return amount + if mode == "Round": + return round(amount, precision) - factor = 10 ** precision - if mode == "Ceil": - return math.ceil(amount * factor) / float(factor) - if mode == "Floor": - return math.floor(amount * factor) / float(factor) + factor = 10**precision + if mode == "Ceil": + return math.ceil(amount * factor) / float(factor) + if mode == "Floor": + return math.floor(amount * factor) / float(factor) - return round(amount, precision) + return round(amount, precision) def _log_increment_error(title, details=None): - context = details or {} - message_lines = ["Property Increment Engine exception."] - for key, value in context.items(): - message_lines.append(f"{key}: {value}") - message_lines.append("") - message_lines.append(frappe.get_traceback()) - frappe.log_error("\n".join(message_lines), title) + context = details or {} + message_lines = ["Property Increment Engine exception."] + for key, value in context.items(): + message_lines.append(f"{key}: {value}") + message_lines.append("") + message_lines.append(frappe.get_traceback()) + frappe.log_error("\n".join(message_lines), title) diff --git a/propms/property_management_solution/doctype/apartment_status/apartment_status.py b/propms/property_management_solution/doctype/apartment_status/apartment_status.py index 60286c6d..56dfa1a5 100755 --- a/propms/property_management_solution/doctype/apartment_status/apartment_status.py +++ b/propms/property_management_solution/doctype/apartment_status/apartment_status.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class ApartmentStatus(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/apartment_status/test_apartment_status.py b/propms/property_management_solution/doctype/apartment_status/test_apartment_status.py index 8d61d6eb..4d30dd00 100755 --- a/propms/property_management_solution/doctype/apartment_status/test_apartment_status.py +++ b/propms/property_management_solution/doctype/apartment_status/test_apartment_status.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestApartmentStatus(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/checklist_checkup_area/checklist_checkup_area.py b/propms/property_management_solution/doctype/checklist_checkup_area/checklist_checkup_area.py index b04c40ba..807605e2 100755 --- a/propms/property_management_solution/doctype/checklist_checkup_area/checklist_checkup_area.py +++ b/propms/property_management_solution/doctype/checklist_checkup_area/checklist_checkup_area.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class ChecklistCheckupArea(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/checklist_checkup_area/test_checklist_checkup_area.py b/propms/property_management_solution/doctype/checklist_checkup_area/test_checklist_checkup_area.py index 7c422b99..53fdf172 100755 --- a/propms/property_management_solution/doctype/checklist_checkup_area/test_checklist_checkup_area.py +++ b/propms/property_management_solution/doctype/checklist_checkup_area/test_checklist_checkup_area.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestChecklistCheckupArea(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/checklist_checkup_area_task/checklist_checkup_area_task.py b/propms/property_management_solution/doctype/checklist_checkup_area_task/checklist_checkup_area_task.py index 63c2b827..04414257 100755 --- a/propms/property_management_solution/doctype/checklist_checkup_area_task/checklist_checkup_area_task.py +++ b/propms/property_management_solution/doctype/checklist_checkup_area_task/checklist_checkup_area_task.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class ChecklistCheckupAreaTask(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/custom_error_log/custom_error_log.py b/propms/property_management_solution/doctype/custom_error_log/custom_error_log.py index f5951e96..daf6e840 100755 --- a/propms/property_management_solution/doctype/custom_error_log/custom_error_log.py +++ b/propms/property_management_solution/doctype/custom_error_log/custom_error_log.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class CustomErrorLog(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/custom_error_log/test_custom_error_log.py b/propms/property_management_solution/doctype/custom_error_log/test_custom_error_log.py index 7f44d2d5..8fdcb072 100755 --- a/propms/property_management_solution/doctype/custom_error_log/test_custom_error_log.py +++ b/propms/property_management_solution/doctype/custom_error_log/test_custom_error_log.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestCustomErrorLog(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/daily_checklist/daily_checklist.js b/propms/property_management_solution/doctype/daily_checklist/daily_checklist.js index c0c79c9e..48b2acf7 100755 --- a/propms/property_management_solution/doctype/daily_checklist/daily_checklist.js +++ b/propms/property_management_solution/doctype/daily_checklist/daily_checklist.js @@ -31,4 +31,4 @@ cur_frm.fields_dict['daily_checklist_detail'].grid.get_field('job_card').get_que ['Issue', 'docstatus', '=', '1'] ] } -}; \ No newline at end of file +}; diff --git a/propms/property_management_solution/doctype/daily_checklist/daily_checklist.py b/propms/property_management_solution/doctype/daily_checklist/daily_checklist.py index 408f09f6..ec8c2def 100755 --- a/propms/property_management_solution/doctype/daily_checklist/daily_checklist.py +++ b/propms/property_management_solution/doctype/daily_checklist/daily_checklist.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class DailyChecklist(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/daily_checklist/test_daily_checklist.py b/propms/property_management_solution/doctype/daily_checklist/test_daily_checklist.py index 8c2363d8..1de7f688 100755 --- a/propms/property_management_solution/doctype/daily_checklist/test_daily_checklist.py +++ b/propms/property_management_solution/doctype/daily_checklist/test_daily_checklist.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestDailyChecklist(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/daily_checklist_detail/daily_checklist_detail.py b/propms/property_management_solution/doctype/daily_checklist_detail/daily_checklist_detail.py index 673808d9..70cf0f2e 100755 --- a/propms/property_management_solution/doctype/daily_checklist_detail/daily_checklist_detail.py +++ b/propms/property_management_solution/doctype/daily_checklist_detail/daily_checklist_detail.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class DailyChecklistDetail(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/door/door.py b/propms/property_management_solution/doctype/door/door.py index 22712254..d52759ef 100755 --- a/propms/property_management_solution/doctype/door/door.py +++ b/propms/property_management_solution/doctype/door/door.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class Door(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/exit/exit.py b/propms/property_management_solution/doctype/exit/exit.py index fb2a797a..32d47269 100755 --- a/propms/property_management_solution/doctype/exit/exit.py +++ b/propms/property_management_solution/doctype/exit/exit.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class Exit(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/exit/test_exit.py b/propms/property_management_solution/doctype/exit/test_exit.py index 8fecabdb..31d2c407 100755 --- a/propms/property_management_solution/doctype/exit/test_exit.py +++ b/propms/property_management_solution/doctype/exit/test_exit.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestExit(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/flooring/flooring.py b/propms/property_management_solution/doctype/flooring/flooring.py index 70c4a7cf..33a553da 100755 --- a/propms/property_management_solution/doctype/flooring/flooring.py +++ b/propms/property_management_solution/doctype/flooring/flooring.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class Flooring(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/guard_shift/guard_shift.py b/propms/property_management_solution/doctype/guard_shift/guard_shift.py index 3eb758a7..8d9b0b30 100755 --- a/propms/property_management_solution/doctype/guard_shift/guard_shift.py +++ b/propms/property_management_solution/doctype/guard_shift/guard_shift.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class GuardShift(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/guard_shift/test_guard_shift.py b/propms/property_management_solution/doctype/guard_shift/test_guard_shift.py index e4cbb3c5..855995ab 100755 --- a/propms/property_management_solution/doctype/guard_shift/test_guard_shift.py +++ b/propms/property_management_solution/doctype/guard_shift/test_guard_shift.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestGuardShift(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/guard_shift_location/guard_shift_location.py b/propms/property_management_solution/doctype/guard_shift_location/guard_shift_location.py index abdf9dcc..0f4db64d 100755 --- a/propms/property_management_solution/doctype/guard_shift_location/guard_shift_location.py +++ b/propms/property_management_solution/doctype/guard_shift_location/guard_shift_location.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class GuardShiftLocation(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/insurance/insurance.py b/propms/property_management_solution/doctype/insurance/insurance.py index c0575bfb..02cd34c1 100755 --- a/propms/property_management_solution/doctype/insurance/insurance.py +++ b/propms/property_management_solution/doctype/insurance/insurance.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class Insurance(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/insurance/test_insurance.py b/propms/property_management_solution/doctype/insurance/test_insurance.py index 315b3bbd..1ba9515c 100755 --- a/propms/property_management_solution/doctype/insurance/test_insurance.py +++ b/propms/property_management_solution/doctype/insurance/test_insurance.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestInsurance(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/issue_materials_billed/issue_materials_billed.py b/propms/property_management_solution/doctype/issue_materials_billed/issue_materials_billed.py index 4b1014c3..a5e5a27b 100644 --- a/propms/property_management_solution/doctype/issue_materials_billed/issue_materials_billed.py +++ b/propms/property_management_solution/doctype/issue_materials_billed/issue_materials_billed.py @@ -1,12 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document class IssueMaterialsBilled(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/issue_materials_detail/issue_materials_detail.py b/propms/property_management_solution/doctype/issue_materials_detail/issue_materials_detail.py index bbd603e2..be98bff3 100755 --- a/propms/property_management_solution/doctype/issue_materials_detail/issue_materials_detail.py +++ b/propms/property_management_solution/doctype/issue_materials_detail/issue_materials_detail.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class IssueMaterialsDetail(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/key/key.py b/propms/property_management_solution/doctype/key/key.py index 0d386795..e2965972 100755 --- a/propms/property_management_solution/doctype/key/key.py +++ b/propms/property_management_solution/doctype/key/key.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class Key(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/key_set/key_set.py b/propms/property_management_solution/doctype/key_set/key_set.py index 5d500bff..f5ea7408 100755 --- a/propms/property_management_solution/doctype/key_set/key_set.py +++ b/propms/property_management_solution/doctype/key_set/key_set.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class KeySet(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/key_set/test_key_set.py b/propms/property_management_solution/doctype/key_set/test_key_set.py index 04276cf4..521cf70f 100755 --- a/propms/property_management_solution/doctype/key_set/test_key_set.py +++ b/propms/property_management_solution/doctype/key_set/test_key_set.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestKeySet(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/key_set_detail/key_set_detail.js b/propms/property_management_solution/doctype/key_set_detail/key_set_detail.js index 7002d899..c7fc9af3 100755 --- a/propms/property_management_solution/doctype/key_set_detail/key_set_detail.js +++ b/propms/property_management_solution/doctype/key_set_detail/key_set_detail.js @@ -26,4 +26,3 @@ cur_frm.set_query("key_set", function() { } } }); - diff --git a/propms/property_management_solution/doctype/key_set_detail/key_set_detail.py b/propms/property_management_solution/doctype/key_set_detail/key_set_detail.py index b1474d4b..e3a8c4a9 100755 --- a/propms/property_management_solution/doctype/key_set_detail/key_set_detail.py +++ b/propms/property_management_solution/doctype/key_set_detail/key_set_detail.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class KeySetDetail(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/key_set_detail/test_key_set_detail.py b/propms/property_management_solution/doctype/key_set_detail/test_key_set_detail.py index 71848799..cc5cd01d 100755 --- a/propms/property_management_solution/doctype/key_set_detail/test_key_set_detail.py +++ b/propms/property_management_solution/doctype/key_set_detail/test_key_set_detail.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestKeySetDetail(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/lease/lease.py b/propms/property_management_solution/doctype/lease/lease.py index a3014347..3d5ae9a4 100755 --- a/propms/property_management_solution/doctype/lease/lease.py +++ b/propms/property_management_solution/doctype/lease/lease.py @@ -1,589 +1,587 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.model.document import Document -from frappe.utils import add_days, today, getdate, add_months, get_datetime, now, nowdate, cint -from propms.auto_custom import app_error_log, makeInvoiceSchedule, getDateMonthDiff from frappe import _ +from frappe.model.document import Document +from frappe.utils import add_days, add_months, cint, get_datetime, getdate, now, nowdate, today + +from propms.auto_custom import app_error_log, getDateMonthDiff, makeInvoiceSchedule class Lease(Document): - def get_all_properties(self): - properties = set() - if self.property: - properties.add(self.property) - for item in self.lease_item: - if item.property_unit: - properties.add(item.property_unit) - return list(properties) - - def on_submit(self): - try: - properties = self.get_all_properties() - for prop in properties: - checklist_doc = frappe.get_doc("Checklist Checkup Area", "Handover") - if checklist_doc: - check_list = [] - for task in checklist_doc.task: - check = {} - check["checklist_task"] = task.task_name - check_list.append(check) - - frappe.get_doc( - dict( - doctype="Daily Checklist", - area="Handover", - checkup_date=self.start_date, - daily_checklist_detail=check_list, - property=prop, - ) - ).insert() - except Exception as e: - app_error_log(frappe.session.user, str(e)) - - def validate(self): - self.validate_days_to_invoice_in_advance() - try: - properties = self.get_all_properties() - # Lease Status Validation: Prevent multiple active leases per property - if self.lease_status == "Active": - for prop in properties: - max_allowed = cint(frappe.db.get_value("Property", prop, "max_active_leases")) or cint(frappe.db.get_single_value("Property Management Settings", "max_active_leases")) or 1 - # Query for other non-draft leases for the same property - conflicting_leases = frappe.db.get_all( - "Lease", - filters={ - "property": prop, - "lease_status": ["!=", "Draft"], - "name": ["!=", self.name], - "docstatus": ["<", 2], # Exclude cancelled - }, - fields=["name", "end_date", "lease_status"], - ) - active_conflicts = [ - l for l in conflicting_leases - if not l["end_date"] or getdate(l["end_date"]) >= getdate(self.start_date) - ] - if len(active_conflicts) >= max_allowed: - msg = _( - "Cannot activate lease {0} for property {1}.

    " - "• Max Allowed Active Leases: {2}
    " - "• Currently Active Leases: {3}

    " - "Please contact Administrator if you need to increase the limit for this property." - ).format(self.name, prop, max_allowed, len(active_conflicts)) - frappe.throw(msg, title=_("Active Lease Limit Exceeded")) - - for prop in properties: - if ( - self.skip_end_date == None - ): - if ( - get_datetime(add_months(self.end_date, -3)) - <= get_datetime(now()) - <= get_datetime(add_months(self.end_date, 3)) - ): - frappe.db.set_value( - "Property", prop, "status", "Off Lease in 3 Months" - ) - frappe.msgprint(_(f'Property "{prop}" has now been set Off Lease in 3 Months for Lease "{self.name}"')) - elif ( - self.lease_status != "Draft" - and ( - get_datetime(self.start_date) - <= get_datetime(now()) - <= get_datetime(add_months(self.end_date, -3)) - ) - ): - frappe.db.set_value("Property", prop, "status", "On Lease") - frappe.msgprint(_(f'Property "{prop}" has now been set On Lease from Active for Lease "{self.name}"')) - else: - if self.lease_status != "Draft": - frappe.db.set_value( - "Property", prop, "status", "On Lease" - ) - frappe.msgprint(_(f'Property "{prop}" has now been set On Lease from Active for Lease "{self.name}"')) - except frappe.ValidationError: - raise - except Exception as e: - app_error_log(frappe.session.user, str(e)) - self.set_lease_status() - - def validate_days_to_invoice_in_advance(self): - """Prevent changing 'Days to Invoice in Advance' once invoices have been generated.""" - if not self.is_new() and self.has_value_changed("days_to_invoice_in_advance"): - has_generated_invoices = any( - row.invoice_number or row.sales_order_number - for row in (self.lease_invoice_schedule or []) - ) - if not has_generated_invoices: - has_generated_invoices = frappe.db.exists( - "Lease Invoice Schedule", - { - "parent": self.name, - "invoice_number": ["is", "set"], - }, - ) - if has_generated_invoices: - frappe.throw( - _("Cannot change 'Days to Invoice in Advance' after invoices have been generated for this Lease."), - title=_("Field Read Only"), - ) - - - def set_lease_status(self): - """ - Set lease status on save. - - Only system-controlled statuses are automatically changed: - Upcoming, Active, Expired. - - All other statuses are considered manual and are not overwritten. - """ - - if self.lease_status not in get_system_controlled_statuses(): - return - - status = get_status_for_lease(self) - - if status: - self.lease_status = status + def get_all_properties(self): + properties = set() + if self.property: + properties.add(self.property) + for item in self.lease_item: + if item.property_unit: + properties.add(item.property_unit) + return list(properties) + + def on_submit(self): + try: + properties = self.get_all_properties() + for prop in properties: + checklist_doc = frappe.get_doc("Checklist Checkup Area", "Handover") + if checklist_doc: + check_list = [] + for task in checklist_doc.task: + check = {} + check["checklist_task"] = task.task_name + check_list.append(check) + + frappe.get_doc( + dict( + doctype="Daily Checklist", + area="Handover", + checkup_date=self.start_date, + daily_checklist_detail=check_list, + property=prop, + ) + ).insert() + except Exception as e: + app_error_log(frappe.session.user, str(e)) + + def validate(self): + self.validate_days_to_invoice_in_advance() + try: + properties = self.get_all_properties() + # Lease Status Validation: Prevent multiple active leases per property + if self.lease_status == "Active": + for prop in properties: + max_allowed = ( + cint(frappe.db.get_value("Property", prop, "max_active_leases")) + or cint( + frappe.db.get_single_value("Property Management Settings", "max_active_leases") + ) + or 1 + ) + # Query for other non-draft leases for the same property + conflicting_leases = frappe.db.get_all( + "Lease", + filters={ + "property": prop, + "lease_status": ["!=", "Draft"], + "name": ["!=", self.name], + "docstatus": ["<", 2], # Exclude cancelled + }, + fields=["name", "end_date", "lease_status"], + ) + active_conflicts = [ + l + for l in conflicting_leases + if not l["end_date"] or getdate(l["end_date"]) >= getdate(self.start_date) + ] + if len(active_conflicts) >= max_allowed: + msg = _( + "Cannot activate lease {0} for property {1}.

    " + "• Max Allowed Active Leases: {2}
    " + "• Currently Active Leases: {3}

    " + "Please contact Administrator if you need to increase the limit for this property." + ).format(self.name, prop, max_allowed, len(active_conflicts)) + frappe.throw(msg, title=_("Active Lease Limit Exceeded")) + + for prop in properties: + if self.skip_end_date is None: + if ( + get_datetime(add_months(self.end_date, -3)) + <= get_datetime(now()) + <= get_datetime(add_months(self.end_date, 3)) + ): + frappe.db.set_value("Property", prop, "status", "Off Lease in 3 Months") + frappe.msgprint( + _( + f'Property "{prop}" has now been set Off Lease in 3 Months for Lease "{self.name}"' + ) + ) + elif self.lease_status != "Draft" and ( + get_datetime(self.start_date) + <= get_datetime(now()) + <= get_datetime(add_months(self.end_date, -3)) + ): + frappe.db.set_value("Property", prop, "status", "On Lease") + frappe.msgprint( + _( + f'Property "{prop}" has now been set On Lease from Active for Lease "{self.name}"' + ) + ) + else: + if self.lease_status != "Draft": + frappe.db.set_value("Property", prop, "status", "On Lease") + frappe.msgprint( + _( + f'Property "{prop}" has now been set On Lease from Active for Lease "{self.name}"' + ) + ) + except frappe.ValidationError: + raise + except Exception as e: + app_error_log(frappe.session.user, str(e)) + self.set_lease_status() + + def validate_days_to_invoice_in_advance(self): + """Prevent changing 'Days to Invoice in Advance' once invoices have been generated.""" + if not self.is_new() and self.has_value_changed("days_to_invoice_in_advance"): + has_generated_invoices = any( + row.invoice_number or row.sales_order_number for row in (self.lease_invoice_schedule or []) + ) + if not has_generated_invoices: + has_generated_invoices = frappe.db.exists( + "Lease Invoice Schedule", + { + "parent": self.name, + "invoice_number": ["is", "set"], + }, + ) + if has_generated_invoices: + frappe.throw( + _( + "Cannot change 'Days to Invoice in Advance' after invoices have been generated for this Lease." + ), + title=_("Field Read Only"), + ) + + def set_lease_status(self): + """ + Set lease status on save. + + Only system-controlled statuses are automatically changed: + Upcoming, Active, Expired. + + All other statuses are considered manual and are not overwritten. + """ + + if self.lease_status not in get_system_controlled_statuses(): + return + + status = get_status_for_lease(self) + + if status: + self.lease_status = status def get_system_controlled_statuses(): - """ - Statuses controlled by system automation. + """ + Statuses controlled by system automation. - Any lease_status outside this set is treated as manually controlled - and will not be overwritten by validate() or the daily scheduler. - """ + Any lease_status outside this set is treated as manually controlled + and will not be overwritten by validate() or the daily scheduler. + """ - return {"Upcoming", "Active", "Expired"} + return {"Upcoming", "Active", "Expired"} def update_lease_statuses(): - """ - Daily scheduler method. - - Updates only system-controlled Lease statuses: - Upcoming, Active, Expired. - - Uses frappe.db.set_value() to avoid full document save hooks, - and adds a timeline comment for audit visibility. - """ - - today_date = getdate(nowdate()) - system_controlled_statuses = list(get_system_controlled_statuses()) - - leases = frappe.get_all( - "Lease", - fields=[ - "name", - "lease_status", - "start_date", - "end_date", - "skip_end_date", - ], - filters=[ - ["lease_status", "in", system_controlled_statuses], - ["docstatus", "<", 2], - ], - ) - - for lease in leases: - old_status = lease.lease_status - new_status = get_status_for_lease(lease, today_date) - - if not new_status or new_status == old_status: - continue - - frappe.db.set_value( - "Lease", - lease.name, - "lease_status", - new_status, - update_modified=True, - ) - - doc = frappe.get_doc("Lease", lease.name) - doc.add_comment( - "Info", - _( - "Lease Status automatically changed from {0} to {1} by daily scheduler." - ).format(old_status or "blank", new_status), - ) - - frappe.db.commit() + """ + Daily scheduler method. + + Updates only system-controlled Lease statuses: + Upcoming, Active, Expired. + + Uses frappe.db.set_value() to avoid full document save hooks, + and adds a timeline comment for audit visibility. + """ + + today_date = getdate(nowdate()) + system_controlled_statuses = list(get_system_controlled_statuses()) + + leases = frappe.get_all( + "Lease", + fields=[ + "name", + "lease_status", + "start_date", + "end_date", + "skip_end_date", + ], + filters=[ + ["lease_status", "in", system_controlled_statuses], + ["docstatus", "<", 2], + ], + ) + + for lease in leases: + old_status = lease.lease_status + new_status = get_status_for_lease(lease, today_date) + + if not new_status or new_status == old_status: + continue + + frappe.db.set_value( + "Lease", + lease.name, + "lease_status", + new_status, + update_modified=True, + ) + + doc = frappe.get_doc("Lease", lease.name) + doc.add_comment( + "Info", + _("Lease Status automatically changed from {0} to {1} by daily scheduler.").format( + old_status or "blank", new_status + ), + ) + + frappe.db.commit() def get_status_for_lease(lease, today_date=None): - """ - Return calculated Lease Status. + """ + Return calculated Lease Status. - Rules: - - Future start_date => Upcoming - - start_date <= today and end_date >= today => Active - - end_date < today => Expired - - If skip_end_date is checked, do not mark as Expired - - end_date equal to today remains Active until the next day - """ + Rules: + - Future start_date => Upcoming + - start_date <= today and end_date >= today => Active + - end_date < today => Expired + - If skip_end_date is checked, do not mark as Expired + - end_date equal to today remains Active until the next day + """ - today_date = today_date or getdate(nowdate()) + today_date = today_date or getdate(nowdate()) - start_date = getdate(lease.start_date) if lease.start_date else None - end_date = getdate(lease.end_date) if lease.end_date else None - skip_end_date = bool(lease.skip_end_date) + start_date = getdate(lease.start_date) if lease.start_date else None + end_date = getdate(lease.end_date) if lease.end_date else None + skip_end_date = bool(lease.skip_end_date) - if start_date and start_date > today_date: - return "Upcoming" + if start_date and start_date > today_date: + return "Upcoming" - if not skip_end_date and end_date and end_date < today_date: - return "Expired" + if not skip_end_date and end_date and end_date < today_date: + return "Expired" - if start_date and start_date <= today_date: - if skip_end_date or not end_date or end_date >= today_date: - return "Active" + if start_date and start_date <= today_date: + if skip_end_date or not end_date or end_date >= today_date: + return "Active" - return None + return None @frappe.whitelist() def getAllLease(): - # Below is temporarily created to manually run through all lease and refresh lease invoice schedule. Hardcoded to start from 1st Jan 2020. - frappe.msgprint(_( - "The task of making lease invoice schedule for all users has been sent for background processing." - )) - invoice_start_date = frappe.db.get_single_value( - "Property Management Settings", "invoice_start_date" - ) - lease_list = frappe.get_all( - "Lease", filters={"end_date": (">=", invoice_start_date)}, fields=["name"] - ) - # frappe.msgprint("Working on lease_list" + str(lease_list)) - lease_list_len = len(lease_list) - frappe.msgprint(_("Total number of lease to be processed is {0}").format(lease_list_len)) - for lease in lease_list: - make_lease_invoice_schedule(lease.name) + # Below is temporarily created to manually run through all lease and refresh lease invoice schedule. Hardcoded to start from 1st Jan 2020. + frappe.msgprint( + _("The task of making lease invoice schedule for all users has been sent for background processing.") + ) + invoice_start_date = frappe.db.get_single_value("Property Management Settings", "invoice_start_date") + lease_list = frappe.get_all("Lease", filters={"end_date": (">=", invoice_start_date)}, fields=["name"]) + # frappe.msgprint("Working on lease_list" + str(lease_list)) + lease_list_len = len(lease_list) + frappe.msgprint(_("Total number of lease to be processed is {0}").format(lease_list_len)) + for lease in lease_list: + make_lease_invoice_schedule(lease.name) # def on_update(self): @frappe.whitelist() def make_lease_invoice_schedule(leasedoc): - # frappe.msgprint("This is the parameter passed: " + str(leasedoc)) - lease = frappe.get_doc("Lease", str(leasedoc)) - try: - # Delete unnecessary records after lease end date - lease_invoice_schedule_list = frappe.get_list( - "Lease Invoice Schedule", - fields=[ - "name", - "parent", - "lease_item", - "invoice_number", - "date_to_invoice", - ], - filters={"parent": lease.name, "date_to_invoice": (">", lease.end_date)}, parent_doctype='Lease', - ) - for lease_invoice_schedule in lease_invoice_schedule_list: - frappe.delete_doc("Lease Invoice Schedule", lease_invoice_schedule.name) - # Only process lease that items and is current - if len(lease.lease_item) >= 1 and lease.end_date >= getdate(today()): - # Clean up records that are no longer required, i.e. of unnecessary lease items and unnecessary dates - # Records before Invoice Start Date - invoice_start_date = frappe.db.get_single_value( - "Property Management Settings", "invoice_start_date" - ) - lease_invoice_schedule_list = frappe.get_list( - "Lease Invoice Schedule", - fields=["name", "parent", "invoice_number", "date_to_invoice"], - filters={ - "parent": lease.name, - "date_to_invoice": ("<", invoice_start_date), - }, parent_doctype='Lease', - ) - # frappe.msgprint("Records before Invoice Start Date " + str(lease_invoice_schedule_list)) - for lease_invoice_schedule in lease_invoice_schedule_list: - # frappe.msgprint("Deleting Record before Invoice Start Date " + str(invoice_start_date) + str(lease_invoice_schedule.name)) - frappe.delete_doc("Lease Invoice Schedule", lease_invoice_schedule.name) - # Records of lease_items that no longer existing in lease.lease_item - lease_invoice_schedule_list = frappe.get_list( - "Lease Invoice Schedule", - fields=[ - "name", - "parent", - "lease_item", - "invoice_number", - "date_to_invoice", - ], - filters={"parent": lease.name}, parent_doctype='Lease', - ) - lease_items_list = frappe.get_list( - "Lease Item", - fields=["name", "parent", "lease_item"], - filters={"parent": lease.name}, parent_doctype='Lease', - ) - # Create list of lease items that are part of lease.lease_item - lease_item_name_list = [ - lease_item["lease_item"] for lease_item in lease_items_list - ] - # frappe.msgprint(str(lease_item_list)) - for lease_invoice_schedule in lease_invoice_schedule_list: - if lease_invoice_schedule.lease_item not in lease_item_name_list: - # frappe.msgprint("This lease item will be removed from invoice schedule " + str(lease_invoice_schedule.lease_item)) - frappe.delete_doc( - "Lease Invoice Schedule", lease_invoice_schedule.name - ) - item_invoice_frequency = { - "Monthly": 1.00, # .00 to make it float type - "Bi-Monthly": 2.00, - "Quarterly": 3.00, - "6 months": 6.00, - "Annually": 12.00, - } - idx = 1 - for item in lease.lease_item: - # frappe.msgprint("Lease item being processed: " + str(item.lease_item)) - lease_invoice_schedule_list = frappe.get_all( - "Lease Invoice Schedule", - fields=[ - "name", - "parent", - "lease_item", - "schedule_start_date", - "qty", - "invoice_number", - "date_to_invoice", - ], - filters={"parent": lease.name, "lease_item": item.lease_item}, - order_by="date_to_invoice", - ) - # frappe.msgprint(str(lease_invoice_schedule_list)) - # Get the latest item frequency incase lease was changed. - frequency_factor = item_invoice_frequency.get( - item.frequency, "Invalid frequency" - ) - # frappe.msgprint("Next Invoice date calculated: " + str(invoice_date)) - if frequency_factor == "Invalid frequency": - message = ( - "Invalid frequency: " - + str(item.frequency) - + " for " - + str(leasedoc) - + " not found. Contact the developers!" - ) - frappe.log_error("Frequency incorrect", message) - break - invoice_qty = float(frequency_factor) - end_date = lease.end_date - invoice_date = lease.start_date - # Find out the first invoice date on or after Invoice Start Date process. - while end_date >= invoice_date and invoice_date < invoice_start_date: - invoice_period_end = add_days( - add_months(invoice_date, frequency_factor), -1 - ) - # Set invoice_Qty as appropriate fraction of frequency_factor - if invoice_period_end > end_date: - invoice_qty = getDateMonthDiff(invoice_date, end_date, 1) - # frappe.msgprint("Invoice quantity corrected as " + str(invoice_qty)) - invoice_date = add_days(invoice_period_end, 1) - # If there is no lease_invoice_schedule_list found, i.e. it is fresh new list to be created - if not lease_invoice_schedule_list: - while end_date >= invoice_date: - invoice_period_end = add_days( - add_months(invoice_date, frequency_factor), -1 - ) - # frappe.msgprint("Invoice period end: " + str(invoice_period_end) + "--- Invoice Date: " + str(invoice_date)) - # frappe.msgprint("End Date: " + str(end_date)) - # set invoice_Qty as appropriate fraction of frequency_factor - if invoice_period_end > end_date: - invoice_qty = getDateMonthDiff(invoice_date, end_date, 1) - # frappe.msgprint("Invoice quantity corrected as " + str(invoice_qty)) - # frappe.msgprint("Making Fresh Invoice Schedule for " + str(invoice_date) - # + ", Quantity calculated: " + str(invoice_qty)) - makeInvoiceSchedule( - invoice_date, - item.lease_item, - item.paid_by, - item.lease_item, - lease.name, - invoice_qty, - item.amount, - idx, - item.currency_code, - item.witholding_tax, - lease.days_to_invoice_in_advance, - item.invoice_item_group, - item.document_type, - ) - idx += 1 - invoice_date = add_days(invoice_period_end, 1) - for lease_invoice_schedule in lease_invoice_schedule_list: - # frappe.msgprint("Upon entering lease_invoice_schedule_list - Date to invoice: " + str(lease_invoice_schedule.date_to_invoice) - # + " and invoice date to process is " + str(invoice_date)) - if not (lease_invoice_schedule.schedule_start_date): - lease_invoice_schedule.schedule_start_date = ( - lease_invoice_schedule.date_to_invoice - ) - while ( - end_date >= invoice_date - and lease_invoice_schedule.schedule_start_date > invoice_date - ): - invoice_period_end = add_days( - add_months(invoice_date, frequency_factor), -1 - ) - # frappe.msgprint("Upon entering Invoice period end: " + str(invoice_period_end) + "--- Invoice Date: " + str(invoice_date)) - # frappe.msgprint("End Date: " + str(end_date)) - # set invoice_Qty as appropriate fraction of frequency_factor - if invoice_period_end > end_date: - invoice_qty = getDateMonthDiff(invoice_date, end_date, 1) - # frappe.msgprint("Invoice quantity corrected as " + str(invoice_qty)) - # frappe.msgprint("Making Pre Invoice Schedule for " + str(invoice_date) + ", Quantity calculated: " + str(invoice_qty)) - makeInvoiceSchedule( - invoice_date, - item.lease_item, - item.paid_by, - item.lease_item, - lease.name, - invoice_qty, - item.amount, - idx, - item.currency_code, - item.witholding_tax, - lease.days_to_invoice_in_advance, - item.invoice_item_group, - item.document_type, - ) - idx += 1 - invoice_date = add_days(invoice_period_end, 1) - # frappe.msgprint(str(lease_invoice_schedule)) - # If the record already exists and invoice is generated - if ( - lease_invoice_schedule.invoice_number is not None - and lease_invoice_schedule.invoice_number != "" - ): - # frappe.msgprint("Lease Invoice Schedule retained: " + lease_invoice_schedule.name - # + " for invoice number: " + str(lease_invoice_schedule.invoice_number) - # + " dated " + str(lease_invoice_schedule.date_to_invoice) - # ) - # Set months as rounded up by 1 if the month is a fraction (last invoice for the lease item already created). - # Above needed to escape from infinite loop of rounded down date and therefore never reaching end of the lease. - if lease_invoice_schedule.qty != round( - lease_invoice_schedule.qty, 0 - ): - add_months_value = round(lease_invoice_schedule.qty, 0) + 1 - else: - add_months_value = lease_invoice_schedule.qty - # frappe.msgprint("Add Months Value" + str(add_months_value) + " due to qty = " + str(lease_invoice_schedule.qty)) - invoice_date = add_months( - lease_invoice_schedule.schedule_start_date, add_months_value - ) - # Set sequence to show it on the top - frappe.db.set_value( - "Lease Invoice Schedule", - lease_invoice_schedule.name, - "idx", - idx, - ) - idx += 1 - # If the invoice is not created - else: - # frappe.msgprint("Deleting schedule :" + lease_invoice_schedule.name + " dated: " + str(lease_invoice_schedule.date_to_invoice) + " for " + str(lease_invoice_schedule.lease_item)) - frappe.delete_doc( - "Lease Invoice Schedule", lease_invoice_schedule.name - ) - # frappe.msgprint("first invoice_date: " + str(invoice_date), "Lease Invoice Schedule") - while end_date >= invoice_date: - invoice_period_end = add_days( - add_months(invoice_date, frequency_factor), -1 - ) - # frappe.msgprint("Invoice period end: " + str(invoice_period_end) + "--- Invoice Date: " + str(invoice_date)) - # frappe.msgprint("End Date: " + str(end_date)) - # set invoice_Qty as appropriate fraction of frequency_factor - if invoice_period_end > end_date: - invoice_qty = getDateMonthDiff(invoice_date, end_date, 1) - # frappe.msgprint("Invoice quantity corrected as " + str(invoice_qty)) - # frappe.msgprint("Making Post Invoice Schedule for " + str(invoice_date) + ", Quantity calculated: " + str(invoice_qty)) - makeInvoiceSchedule( - invoice_date, - item.lease_item, - item.paid_by, - item.lease_item, - lease.name, - invoice_qty, - item.amount, - idx, - item.currency_code, - item.witholding_tax, - lease.days_to_invoice_in_advance, - item.invoice_item_group, - item.document_type, - ) - idx += 1 - invoice_date = add_days(invoice_period_end, 1) - - frappe.msgprint("Completed making of invoice schedule.") - - except Exception as e: - frappe.msgprint("Exception error! Check app error log.") - app_error_log(frappe.session.user, str(e)) + # frappe.msgprint("This is the parameter passed: " + str(leasedoc)) + lease = frappe.get_doc("Lease", str(leasedoc)) + try: + # Delete unnecessary records after lease end date + lease_invoice_schedule_list = frappe.get_list( + "Lease Invoice Schedule", + fields=[ + "name", + "parent", + "lease_item", + "invoice_number", + "date_to_invoice", + ], + filters={"parent": lease.name, "date_to_invoice": (">", lease.end_date)}, + parent_doctype="Lease", + ) + for lease_invoice_schedule in lease_invoice_schedule_list: + frappe.delete_doc("Lease Invoice Schedule", lease_invoice_schedule.name) + # Only process lease that items and is current + if len(lease.lease_item) >= 1 and lease.end_date >= getdate(today()): + # Clean up records that are no longer required, i.e. of unnecessary lease items and unnecessary dates + # Records before Invoice Start Date + invoice_start_date = frappe.db.get_single_value( + "Property Management Settings", "invoice_start_date" + ) + lease_invoice_schedule_list = frappe.get_list( + "Lease Invoice Schedule", + fields=["name", "parent", "invoice_number", "date_to_invoice"], + filters={ + "parent": lease.name, + "date_to_invoice": ("<", invoice_start_date), + }, + parent_doctype="Lease", + ) + # frappe.msgprint("Records before Invoice Start Date " + str(lease_invoice_schedule_list)) + for lease_invoice_schedule in lease_invoice_schedule_list: + # frappe.msgprint("Deleting Record before Invoice Start Date " + str(invoice_start_date) + str(lease_invoice_schedule.name)) + frappe.delete_doc("Lease Invoice Schedule", lease_invoice_schedule.name) + # Records of lease_items that no longer existing in lease.lease_item + lease_invoice_schedule_list = frappe.get_list( + "Lease Invoice Schedule", + fields=[ + "name", + "parent", + "lease_item", + "invoice_number", + "date_to_invoice", + ], + filters={"parent": lease.name}, + parent_doctype="Lease", + ) + lease_items_list = frappe.get_list( + "Lease Item", + fields=["name", "parent", "lease_item"], + filters={"parent": lease.name}, + parent_doctype="Lease", + ) + # Create list of lease items that are part of lease.lease_item + lease_item_name_list = [lease_item["lease_item"] for lease_item in lease_items_list] + # frappe.msgprint(str(lease_item_list)) + for lease_invoice_schedule in lease_invoice_schedule_list: + if lease_invoice_schedule.lease_item not in lease_item_name_list: + # frappe.msgprint("This lease item will be removed from invoice schedule " + str(lease_invoice_schedule.lease_item)) + frappe.delete_doc("Lease Invoice Schedule", lease_invoice_schedule.name) + item_invoice_frequency = { + "Monthly": 1.00, # .00 to make it float type + "Bi-Monthly": 2.00, + "Quarterly": 3.00, + "6 months": 6.00, + "Annually": 12.00, + } + idx = 1 + for item in lease.lease_item: + # frappe.msgprint("Lease item being processed: " + str(item.lease_item)) + lease_invoice_schedule_list = frappe.get_all( + "Lease Invoice Schedule", + fields=[ + "name", + "parent", + "lease_item", + "schedule_start_date", + "qty", + "invoice_number", + "date_to_invoice", + ], + filters={"parent": lease.name, "lease_item": item.lease_item}, + order_by="date_to_invoice", + ) + # frappe.msgprint(str(lease_invoice_schedule_list)) + # Get the latest item frequency incase lease was changed. + frequency_factor = item_invoice_frequency.get(item.frequency, "Invalid frequency") + # frappe.msgprint("Next Invoice date calculated: " + str(invoice_date)) + if frequency_factor == "Invalid frequency": + message = ( + "Invalid frequency: " + + str(item.frequency) + + " for " + + str(leasedoc) + + " not found. Contact the developers!" + ) + frappe.log_error("Frequency incorrect", message) + break + invoice_qty = float(frequency_factor) + end_date = lease.end_date + invoice_date = lease.start_date + # Find out the first invoice date on or after Invoice Start Date process. + while end_date >= invoice_date and invoice_date < invoice_start_date: + invoice_period_end = add_days(add_months(invoice_date, frequency_factor), -1) + # Set invoice_Qty as appropriate fraction of frequency_factor + if invoice_period_end > end_date: + invoice_qty = getDateMonthDiff(invoice_date, end_date, 1) + # frappe.msgprint("Invoice quantity corrected as " + str(invoice_qty)) + invoice_date = add_days(invoice_period_end, 1) + # If there is no lease_invoice_schedule_list found, i.e. it is fresh new list to be created + if not lease_invoice_schedule_list: + while end_date >= invoice_date: + invoice_period_end = add_days(add_months(invoice_date, frequency_factor), -1) + # frappe.msgprint("Invoice period end: " + str(invoice_period_end) + "--- Invoice Date: " + str(invoice_date)) + # frappe.msgprint("End Date: " + str(end_date)) + # set invoice_Qty as appropriate fraction of frequency_factor + if invoice_period_end > end_date: + invoice_qty = getDateMonthDiff(invoice_date, end_date, 1) + # frappe.msgprint("Invoice quantity corrected as " + str(invoice_qty)) + # frappe.msgprint("Making Fresh Invoice Schedule for " + str(invoice_date) + # + ", Quantity calculated: " + str(invoice_qty)) + makeInvoiceSchedule( + invoice_date, + item.lease_item, + item.paid_by, + item.lease_item, + lease.name, + invoice_qty, + item.amount, + idx, + item.currency_code, + item.witholding_tax, + lease.days_to_invoice_in_advance, + item.invoice_item_group, + item.document_type, + ) + idx += 1 + invoice_date = add_days(invoice_period_end, 1) + for lease_invoice_schedule in lease_invoice_schedule_list: + # frappe.msgprint("Upon entering lease_invoice_schedule_list - Date to invoice: " + str(lease_invoice_schedule.date_to_invoice) + # + " and invoice date to process is " + str(invoice_date)) + if not (lease_invoice_schedule.schedule_start_date): + lease_invoice_schedule.schedule_start_date = lease_invoice_schedule.date_to_invoice + while ( + end_date >= invoice_date and lease_invoice_schedule.schedule_start_date > invoice_date + ): + invoice_period_end = add_days(add_months(invoice_date, frequency_factor), -1) + # frappe.msgprint("Upon entering Invoice period end: " + str(invoice_period_end) + "--- Invoice Date: " + str(invoice_date)) + # frappe.msgprint("End Date: " + str(end_date)) + # set invoice_Qty as appropriate fraction of frequency_factor + if invoice_period_end > end_date: + invoice_qty = getDateMonthDiff(invoice_date, end_date, 1) + # frappe.msgprint("Invoice quantity corrected as " + str(invoice_qty)) + # frappe.msgprint("Making Pre Invoice Schedule for " + str(invoice_date) + ", Quantity calculated: " + str(invoice_qty)) + makeInvoiceSchedule( + invoice_date, + item.lease_item, + item.paid_by, + item.lease_item, + lease.name, + invoice_qty, + item.amount, + idx, + item.currency_code, + item.witholding_tax, + lease.days_to_invoice_in_advance, + item.invoice_item_group, + item.document_type, + ) + idx += 1 + invoice_date = add_days(invoice_period_end, 1) + # frappe.msgprint(str(lease_invoice_schedule)) + # If the record already exists and invoice is generated + if ( + lease_invoice_schedule.invoice_number is not None + and lease_invoice_schedule.invoice_number != "" + ): + # frappe.msgprint("Lease Invoice Schedule retained: " + lease_invoice_schedule.name + # + " for invoice number: " + str(lease_invoice_schedule.invoice_number) + # + " dated " + str(lease_invoice_schedule.date_to_invoice) + # ) + # Set months as rounded up by 1 if the month is a fraction (last invoice for the lease item already created). + # Above needed to escape from infinite loop of rounded down date and therefore never reaching end of the lease. + if lease_invoice_schedule.qty != round(lease_invoice_schedule.qty, 0): + add_months_value = round(lease_invoice_schedule.qty, 0) + 1 + else: + add_months_value = lease_invoice_schedule.qty + # frappe.msgprint("Add Months Value" + str(add_months_value) + " due to qty = " + str(lease_invoice_schedule.qty)) + invoice_date = add_months( + lease_invoice_schedule.schedule_start_date, add_months_value + ) + # Set sequence to show it on the top + frappe.db.set_value( + "Lease Invoice Schedule", + lease_invoice_schedule.name, + "idx", + idx, + ) + idx += 1 + # If the invoice is not created + else: + # frappe.msgprint("Deleting schedule :" + lease_invoice_schedule.name + " dated: " + str(lease_invoice_schedule.date_to_invoice) + " for " + str(lease_invoice_schedule.lease_item)) + frappe.delete_doc("Lease Invoice Schedule", lease_invoice_schedule.name) + # frappe.msgprint("first invoice_date: " + str(invoice_date), "Lease Invoice Schedule") + while end_date >= invoice_date: + invoice_period_end = add_days(add_months(invoice_date, frequency_factor), -1) + # frappe.msgprint("Invoice period end: " + str(invoice_period_end) + "--- Invoice Date: " + str(invoice_date)) + # frappe.msgprint("End Date: " + str(end_date)) + # set invoice_Qty as appropriate fraction of frequency_factor + if invoice_period_end > end_date: + invoice_qty = getDateMonthDiff(invoice_date, end_date, 1) + # frappe.msgprint("Invoice quantity corrected as " + str(invoice_qty)) + # frappe.msgprint("Making Post Invoice Schedule for " + str(invoice_date) + ", Quantity calculated: " + str(invoice_qty)) + makeInvoiceSchedule( + invoice_date, + item.lease_item, + item.paid_by, + item.lease_item, + lease.name, + invoice_qty, + item.amount, + idx, + item.currency_code, + item.witholding_tax, + lease.days_to_invoice_in_advance, + item.invoice_item_group, + item.document_type, + ) + idx += 1 + invoice_date = add_days(invoice_period_end, 1) + + frappe.msgprint("Completed making of invoice schedule.") + + except Exception as e: + frappe.msgprint("Exception error! Check app error log.") + app_error_log(frappe.session.user, str(e)) @frappe.whitelist() def initiate_lease_renewal(source_lease_name): - # 1. Permission checks - if not any(r in frappe.get_roles() for r in ["Property Manager", "System Manager"]): - frappe.throw(_("You are not authorized to renew leases. Only Property Managers and System Managers can perform this action."), frappe.PermissionError) - - # 2. Fetch source document - source_doc = frappe.get_doc("Lease", source_lease_name) - - # 3. Status checks - if source_doc.lease_status not in ["Active", "Expired"]: - frappe.throw(_("Lease {0} is not eligible for renewal. Status must be Active or Expired.").format(source_lease_name), frappe.ValidationError) - - # 4. Duplicate renewal check (ignoring terminated or aborted renewals) - duplicate_exists = frappe.db.exists("Lease", { - "renewed_from": source_lease_name, - "lease_status": ["not in", ["Not Materialized", "Terminated"]] - }) - if duplicate_exists: - frappe.throw(_("A renewal lease already exists for this lease: {0}").format(duplicate_exists), frappe.ValidationError) - - # 5. Clone Lease using standard frappe.copy_doc - new_lease = frappe.copy_doc(source_doc) - new_lease.set("lease_invoice_schedule", []) # Clear old invoice schedules - - # Set renewal reference fields - new_lease.renewed_from = source_lease_name - new_lease.lease_status = "Renewal to Previous Lease" - new_lease.renewal_initiated_by = frappe.session.user - - # Calculate dates - if source_doc.end_date: - new_lease.start_date = add_days(source_doc.end_date, 1) - if source_doc.start_date: - duration_days = (getdate(source_doc.end_date) - getdate(source_doc.start_date)).days - new_lease.end_date = add_days(new_lease.start_date, duration_days) - else: - new_lease.start_date = today() - - # Update valid_from date for items - for item in new_lease.lease_item: - item.valid_from = new_lease.start_date - - # Insert and save the new Lease as draft - new_lease.insert(ignore_permissions=True) - - # Post a message/comment to the old lease with initiator and link details - comment_text = _("Lease renewal draft {0} has been initiated by {1} on {2}.").format( - new_lease.name, - frappe.session.user, - frappe.utils.formatdate(today()) - ) - source_doc.add_comment(text=comment_text) - - return new_lease.name - - + # 1. Permission checks + if not any(r in frappe.get_roles() for r in ["Property Manager", "System Manager"]): + frappe.throw( + _( + "You are not authorized to renew leases. Only Property Managers and System Managers can perform this action." + ), + frappe.PermissionError, + ) + + # 2. Fetch source document + source_doc = frappe.get_doc("Lease", source_lease_name) + + # 3. Status checks + if source_doc.lease_status not in ["Active", "Expired"]: + frappe.throw( + _("Lease {0} is not eligible for renewal. Status must be Active or Expired.").format( + source_lease_name + ), + frappe.ValidationError, + ) + + # 4. Duplicate renewal check (ignoring terminated or aborted renewals) + duplicate_exists = frappe.db.exists( + "Lease", + {"renewed_from": source_lease_name, "lease_status": ["not in", ["Not Materialized", "Terminated"]]}, + ) + if duplicate_exists: + frappe.throw( + _("A renewal lease already exists for this lease: {0}").format(duplicate_exists), + frappe.ValidationError, + ) + + # 5. Clone Lease using standard frappe.copy_doc + new_lease = frappe.copy_doc(source_doc) + new_lease.set("lease_invoice_schedule", []) # Clear old invoice schedules + + # Set renewal reference fields + new_lease.renewed_from = source_lease_name + new_lease.lease_status = "Renewal to Previous Lease" + new_lease.renewal_initiated_by = frappe.session.user + + # Calculate dates + if source_doc.end_date: + new_lease.start_date = add_days(source_doc.end_date, 1) + if source_doc.start_date: + duration_days = (getdate(source_doc.end_date) - getdate(source_doc.start_date)).days + new_lease.end_date = add_days(new_lease.start_date, duration_days) + else: + new_lease.start_date = today() + + # Update valid_from date for items + for item in new_lease.lease_item: + item.valid_from = new_lease.start_date + + # Insert and save the new Lease as draft + new_lease.insert(ignore_permissions=True) + + # Post a message/comment to the old lease with initiator and link details + comment_text = _( + "Lease renewal draft {0} has been initiated by {1} on {2}." + ).format(new_lease.name, frappe.session.user, frappe.utils.formatdate(today())) + source_doc.add_comment(text=comment_text) + + return new_lease.name diff --git a/propms/property_management_solution/doctype/lease/test_lease.py b/propms/property_management_solution/doctype/lease/test_lease.py index d1a28862..229e5e29 100755 --- a/propms/property_management_solution/doctype/lease/test_lease.py +++ b/propms/property_management_solution/doctype/lease/test_lease.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestLease(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/lease_invoice_schedule/lease_invoice_schedule.py b/propms/property_management_solution/doctype/lease_invoice_schedule/lease_invoice_schedule.py index f44a6b2c..cf21dd93 100755 --- a/propms/property_management_solution/doctype/lease_invoice_schedule/lease_invoice_schedule.py +++ b/propms/property_management_solution/doctype/lease_invoice_schedule/lease_invoice_schedule.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class LeaseInvoiceSchedule(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/lease_invoice_schedule/test_lease_invoice_schedule.py b/propms/property_management_solution/doctype/lease_invoice_schedule/test_lease_invoice_schedule.py index d8770772..7f5c0ea2 100755 --- a/propms/property_management_solution/doctype/lease_invoice_schedule/test_lease_invoice_schedule.py +++ b/propms/property_management_solution/doctype/lease_invoice_schedule/test_lease_invoice_schedule.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestLeaseInvoiceSchedule(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/lease_item/lease_item.py b/propms/property_management_solution/doctype/lease_item/lease_item.py index dc69b9a3..7b1e543e 100755 --- a/propms/property_management_solution/doctype/lease_item/lease_item.py +++ b/propms/property_management_solution/doctype/lease_item/lease_item.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class LeaseItem(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/lease_item/test_lease_item.py b/propms/property_management_solution/doctype/lease_item/test_lease_item.py index a5efc62d..70deac9f 100755 --- a/propms/property_management_solution/doctype/lease_item/test_lease_item.py +++ b/propms/property_management_solution/doctype/lease_item/test_lease_item.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestLeaseItem(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/meter/meter.js b/propms/property_management_solution/doctype/meter/meter.js index ff6cc36a..37ff9a6a 100644 --- a/propms/property_management_solution/doctype/meter/meter.js +++ b/propms/property_management_solution/doctype/meter/meter.js @@ -13,6 +13,3 @@ frappe.ui.form.on('Meter', { } }); - - - diff --git a/propms/property_management_solution/doctype/meter/meter.py b/propms/property_management_solution/doctype/meter/meter.py index 68270d46..83a34132 100644 --- a/propms/property_management_solution/doctype/meter/meter.py +++ b/propms/property_management_solution/doctype/meter/meter.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class Meter(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/meter/test_meter.py b/propms/property_management_solution/doctype/meter/test_meter.py index 60710e74..758e30cf 100644 --- a/propms/property_management_solution/doctype/meter/test_meter.py +++ b/propms/property_management_solution/doctype/meter/test_meter.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestMeter(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/meter_reading/meter_reading.py b/propms/property_management_solution/doctype/meter_reading/meter_reading.py index da298956..e89272a0 100644 --- a/propms/property_management_solution/doctype/meter_reading/meter_reading.py +++ b/propms/property_management_solution/doctype/meter_reading/meter_reading.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class MeterReading(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/meter_reading/test_meter_reading.py b/propms/property_management_solution/doctype/meter_reading/test_meter_reading.py index 2b348ae5..c287788e 100644 --- a/propms/property_management_solution/doctype/meter_reading/test_meter_reading.py +++ b/propms/property_management_solution/doctype/meter_reading/test_meter_reading.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestMeterReading(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/meter_reading_detail/meter_reading_detail.py b/propms/property_management_solution/doctype/meter_reading_detail/meter_reading_detail.py index 0f910ff9..40a853aa 100644 --- a/propms/property_management_solution/doctype/meter_reading_detail/meter_reading_detail.py +++ b/propms/property_management_solution/doctype/meter_reading_detail/meter_reading_detail.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class MeterReadingDetail(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/meter_reading_detail/test_meter_reading_detail.py b/propms/property_management_solution/doctype/meter_reading_detail/test_meter_reading_detail.py index d414da4f..0b0577bc 100644 --- a/propms/property_management_solution/doctype/meter_reading_detail/test_meter_reading_detail.py +++ b/propms/property_management_solution/doctype/meter_reading_detail/test_meter_reading_detail.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestMeterReadingDetail(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/multiselect_item_group/multiselect_item_group.py b/propms/property_management_solution/doctype/multiselect_item_group/multiselect_item_group.py index 5f4e55ee..35bfca61 100644 --- a/propms/property_management_solution/doctype/multiselect_item_group/multiselect_item_group.py +++ b/propms/property_management_solution/doctype/multiselect_item_group/multiselect_item_group.py @@ -1,12 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document class MultiSelectItemGroup(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/outsource_contact/outsource_contact.py b/propms/property_management_solution/doctype/outsource_contact/outsource_contact.py index a49be7a3..3f764305 100755 --- a/propms/property_management_solution/doctype/outsource_contact/outsource_contact.py +++ b/propms/property_management_solution/doctype/outsource_contact/outsource_contact.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class OutsourceContact(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/outsource_contact/test_outsource_contact.py b/propms/property_management_solution/doctype/outsource_contact/test_outsource_contact.py index af8252eb..08183dfe 100755 --- a/propms/property_management_solution/doctype/outsource_contact/test_outsource_contact.py +++ b/propms/property_management_solution/doctype/outsource_contact/test_outsource_contact.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestOutsourceContact(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/outsourcing_attendance/outsourcing_attendance.py b/propms/property_management_solution/doctype/outsourcing_attendance/outsourcing_attendance.py index 5de918ad..92f145c1 100755 --- a/propms/property_management_solution/doctype/outsourcing_attendance/outsourcing_attendance.py +++ b/propms/property_management_solution/doctype/outsourcing_attendance/outsourcing_attendance.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class OutsourcingAttendance(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/outsourcing_attendance/test_outsourcing_attendance.py b/propms/property_management_solution/doctype/outsourcing_attendance/test_outsourcing_attendance.py index 909a56a8..b0eb102a 100755 --- a/propms/property_management_solution/doctype/outsourcing_attendance/test_outsourcing_attendance.py +++ b/propms/property_management_solution/doctype/outsourcing_attendance/test_outsourcing_attendance.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestOutsourcingAttendance(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/outsourcing_attendance_details/outsourcing_attendance_details.py b/propms/property_management_solution/doctype/outsourcing_attendance_details/outsourcing_attendance_details.py index 06efbb08..bbf168d1 100755 --- a/propms/property_management_solution/doctype/outsourcing_attendance_details/outsourcing_attendance_details.py +++ b/propms/property_management_solution/doctype/outsourcing_attendance_details/outsourcing_attendance_details.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class OutsourcingAttendanceDetails(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/outsourcing_category/outsourcing_category.py b/propms/property_management_solution/doctype/outsourcing_category/outsourcing_category.py index 2316b31b..511ed538 100755 --- a/propms/property_management_solution/doctype/outsourcing_category/outsourcing_category.py +++ b/propms/property_management_solution/doctype/outsourcing_category/outsourcing_category.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class OutsourcingCategory(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/outsourcing_category/test_outsourcing_category.py b/propms/property_management_solution/doctype/outsourcing_category/test_outsourcing_category.py index 69dbdae1..dddba74a 100755 --- a/propms/property_management_solution/doctype/outsourcing_category/test_outsourcing_category.py +++ b/propms/property_management_solution/doctype/outsourcing_category/test_outsourcing_category.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestOutsourcingCategory(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/outsourcing_shift/outsourcing_shift.py b/propms/property_management_solution/doctype/outsourcing_shift/outsourcing_shift.py index 671acc00..3f750c0f 100755 --- a/propms/property_management_solution/doctype/outsourcing_shift/outsourcing_shift.py +++ b/propms/property_management_solution/doctype/outsourcing_shift/outsourcing_shift.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class OutsourcingShift(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/outsourcing_shift/test_outsourcing_shift.py b/propms/property_management_solution/doctype/outsourcing_shift/test_outsourcing_shift.py index 1ab8d31a..fb198142 100755 --- a/propms/property_management_solution/doctype/outsourcing_shift/test_outsourcing_shift.py +++ b/propms/property_management_solution/doctype/outsourcing_shift/test_outsourcing_shift.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestOutsourcingShift(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/outsourcing_shift_location/outsourcing_shift_location.py b/propms/property_management_solution/doctype/outsourcing_shift_location/outsourcing_shift_location.py index 8a27d188..01ebc0b6 100755 --- a/propms/property_management_solution/doctype/outsourcing_shift_location/outsourcing_shift_location.py +++ b/propms/property_management_solution/doctype/outsourcing_shift_location/outsourcing_shift_location.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class OutsourcingShiftLocation(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/paint/paint.py b/propms/property_management_solution/doctype/paint/paint.py index a7252f57..6992e62c 100755 --- a/propms/property_management_solution/doctype/paint/paint.py +++ b/propms/property_management_solution/doctype/paint/paint.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class Paint(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/property/property.py b/propms/property_management_solution/doctype/property/property.py index 779b199b..2abc6932 100755 --- a/propms/property_management_solution/doctype/property/property.py +++ b/propms/property_management_solution/doctype/property/property.py @@ -1,75 +1,74 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals -from frappe.utils.nestedset import NestedSet + import frappe from frappe import _ +from frappe.utils.nestedset import NestedSet class Property(NestedSet): - nsm_parent_field = "parent_property" - - def validate(self): - self.validate_status_with_active_leases() - - def validate_status_with_active_leases(self): - """Prevent property status updates when active leases exist""" - if not self.name or self.is_new(): - return - - # Check if status is changing - old_doc = self.get_doc_before_save() - if old_doc and old_doc.status == self.status: - return # Status not changing - - # If property has active leases, prevent status change - active_leases = self.get_active_leases() - if active_leases: - lease_names = [lease.name for lease in active_leases] - frappe.throw( - _("Cannot change property status. Active leases exist: {0}").format(", ".join(lease_names)) - ) - - def get_active_leases(self): - """Get active leases for this property""" - if not self.name: - return [] - - from frappe.query_builder import DocType - Lease = DocType('Lease') - - active_leases = ( - frappe.qb.from_(Lease) - .select(Lease.name, Lease.start_date, Lease.end_date, Lease.skip_end_date) - .where(Lease.property == self.name) - .where(Lease.start_date <= frappe.utils.now()) - .where( - (Lease.end_date >= frappe.utils.now()) | - (Lease.skip_end_date == 1) - ) - ).run(as_dict=True) - - return active_leases - - def on_trash(self, allow_root_deletion=True): - super().on_trash(allow_root_deletion) + nsm_parent_field = "parent_property" + + def validate(self): + self.validate_status_with_active_leases() + + def validate_status_with_active_leases(self): + """Prevent property status updates when active leases exist""" + if not self.name or self.is_new(): + return + + # Check if status is changing + old_doc = self.get_doc_before_save() + if old_doc and old_doc.status == self.status: + return # Status not changing + + # If property has active leases, prevent status change + active_leases = self.get_active_leases() + if active_leases: + lease_names = [lease.name for lease in active_leases] + frappe.throw( + _("Cannot change property status. Active leases exist: {0}").format(", ".join(lease_names)) + ) + + def get_active_leases(self): + """Get active leases for this property""" + if not self.name: + return [] + + from frappe.query_builder import DocType + + Lease = DocType("Lease") + + active_leases = ( + frappe.qb.from_(Lease) + .select(Lease.name, Lease.start_date, Lease.end_date, Lease.skip_end_date) + .where(Lease.property == self.name) + .where(Lease.start_date <= frappe.utils.now()) + .where((Lease.end_date >= frappe.utils.now()) | (Lease.skip_end_date == 1)) + ).run(as_dict=True) + + return active_leases + + def on_trash(self, allow_root_deletion=True): + super().on_trash(allow_root_deletion) @frappe.whitelist() def add_node(): - from frappe.desk.treeview import make_tree_args + from frappe.desk.treeview import make_tree_args + + args = frappe.form_dict + args = make_tree_args(**frappe.form_dict) - args = frappe.form_dict - args = make_tree_args(**frappe.form_dict) + if args["is_root"]: + args["parent_property"] = None - if args["is_root"]: - args["parent_property"] = None + doc = frappe.get_doc(args) + + doc.save() - doc = frappe.get_doc(args) - doc.save() @frappe.whitelist() def get_children(doctype, parent=None, company=None, is_root=False): if is_root: @@ -82,4 +81,3 @@ def get_children(doctype, parent=None, company=None, is_root=False): ] return frappe.get_list(doctype, fields=fields, filters=filters, order_by="name") - diff --git a/propms/property_management_solution/doctype/property/property_tree.js b/propms/property_management_solution/doctype/property/property_tree.js index 9039c7d6..4c9d00a7 100644 --- a/propms/property_management_solution/doctype/property/property_tree.js +++ b/propms/property_management_solution/doctype/property/property_tree.js @@ -5,7 +5,7 @@ frappe.treeview_settings["Property"] = { root_label: "Property", filters: [ { - fieldname: "company", + fieldname: "company", fieldtype:"Link", options: "Company", label: __("Company"), diff --git a/propms/property_management_solution/doctype/property/test_property.py b/propms/property_management_solution/doctype/property/test_property.py index 7c400f44..edaa54ce 100755 --- a/propms/property_management_solution/doctype/property/test_property.py +++ b/propms/property_management_solution/doctype/property/test_property.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestProperty(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/property_amenity/property_amenity.py b/propms/property_management_solution/doctype/property_amenity/property_amenity.py index 29a4da9d..ddc6ac66 100755 --- a/propms/property_management_solution/doctype/property_amenity/property_amenity.py +++ b/propms/property_management_solution/doctype/property_amenity/property_amenity.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class PropertyAmenity(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/property_amenity/test_property_amenity.py b/propms/property_management_solution/doctype/property_amenity/test_property_amenity.py index 388d426e..8dd6ab5e 100755 --- a/propms/property_management_solution/doctype/property_amenity/test_property_amenity.py +++ b/propms/property_management_solution/doctype/property_amenity/test_property_amenity.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestPropertyAmenity(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/property_management_settings/property_management_settings.js b/propms/property_management_solution/doctype/property_management_settings/property_management_settings.js index adc55369..aa0ba363 100755 --- a/propms/property_management_solution/doctype/property_management_settings/property_management_settings.js +++ b/propms/property_management_solution/doctype/property_management_settings/property_management_settings.js @@ -2,5 +2,5 @@ // For license information, please see license.txt frappe.ui.form.on('Property Management Settings', { - + }); diff --git a/propms/property_management_solution/doctype/property_management_settings/property_management_settings.py b/propms/property_management_solution/doctype/property_management_settings/property_management_settings.py index 3573d6aa..c43417b6 100755 --- a/propms/property_management_solution/doctype/property_management_settings/property_management_settings.py +++ b/propms/property_management_solution/doctype/property_management_settings/property_management_settings.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class PropertyManagementSettings(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/property_management_settings/test_property_management_settings.py b/propms/property_management_solution/doctype/property_management_settings/test_property_management_settings.py index 52771dd8..73a87052 100755 --- a/propms/property_management_solution/doctype/property_management_settings/test_property_management_settings.py +++ b/propms/property_management_solution/doctype/property_management_settings/test_property_management_settings.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestPropertyManagementSettings(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/property_meter_reading/property_meter_reading.py b/propms/property_management_solution/doctype/property_meter_reading/property_meter_reading.py index 279401ed..5aa5c9f0 100644 --- a/propms/property_management_solution/doctype/property_meter_reading/property_meter_reading.py +++ b/propms/property_management_solution/doctype/property_meter_reading/property_meter_reading.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class PropertyMeterReading(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/property_meter_reading/test_property_meter_reading.py b/propms/property_management_solution/doctype/property_meter_reading/test_property_meter_reading.py index 200d12a5..40449cad 100644 --- a/propms/property_management_solution/doctype/property_meter_reading/test_property_meter_reading.py +++ b/propms/property_management_solution/doctype/property_meter_reading/test_property_meter_reading.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestPropertyMeterReading(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/property_unit/property_unit.py b/propms/property_management_solution/doctype/property_unit/property_unit.py index 03ed1abc..a84fef11 100755 --- a/propms/property_management_solution/doctype/property_unit/property_unit.py +++ b/propms/property_management_solution/doctype/property_unit/property_unit.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class PropertyUnit(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/security_attendance/security_attendance.js b/propms/property_management_solution/doctype/security_attendance/security_attendance.js index 8b3d6fcc..b22842b8 100755 --- a/propms/property_management_solution/doctype/security_attendance/security_attendance.js +++ b/propms/property_management_solution/doctype/security_attendance/security_attendance.js @@ -31,4 +31,4 @@ cur_frm.fields_dict['attendance_details'].grid.get_field('guard_empid').get_quer ['Employee', 'department', 'like', 'Security -%'] ] } -}; \ No newline at end of file +}; diff --git a/propms/property_management_solution/doctype/security_attendance/security_attendance.py b/propms/property_management_solution/doctype/security_attendance/security_attendance.py index 92fd1026..6c003229 100755 --- a/propms/property_management_solution/doctype/security_attendance/security_attendance.py +++ b/propms/property_management_solution/doctype/security_attendance/security_attendance.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class SecurityAttendance(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/security_attendance/test_security_attendance.py b/propms/property_management_solution/doctype/security_attendance/test_security_attendance.py index 51f5833b..b97a6116 100755 --- a/propms/property_management_solution/doctype/security_attendance/test_security_attendance.py +++ b/propms/property_management_solution/doctype/security_attendance/test_security_attendance.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestSecurityAttendance(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/security_attendance_details/security_attendance_details.js b/propms/property_management_solution/doctype/security_attendance_details/security_attendance_details.js index 1402e1b7..80cd5e4f 100755 --- a/propms/property_management_solution/doctype/security_attendance_details/security_attendance_details.js +++ b/propms/property_management_solution/doctype/security_attendance_details/security_attendance_details.js @@ -7,4 +7,4 @@ cur_frm.set_query("guard_empid", function() { "department": ["like", "Security - "] } } -}); \ No newline at end of file +}); diff --git a/propms/property_management_solution/doctype/security_attendance_details/security_attendance_details.py b/propms/property_management_solution/doctype/security_attendance_details/security_attendance_details.py index 4a41de52..5a478f6d 100755 --- a/propms/property_management_solution/doctype/security_attendance_details/security_attendance_details.py +++ b/propms/property_management_solution/doctype/security_attendance_details/security_attendance_details.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class SecurityAttendanceDetails(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/security_attendance_details/test_security_attendance_details.py b/propms/property_management_solution/doctype/security_attendance_details/test_security_attendance_details.py index f047a1f8..355e0f07 100755 --- a/propms/property_management_solution/doctype/security_attendance_details/test_security_attendance_details.py +++ b/propms/property_management_solution/doctype/security_attendance_details/test_security_attendance_details.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestSecurityAttendanceDetails(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/security_deposit_details/security_deposit_details.py b/propms/property_management_solution/doctype/security_deposit_details/security_deposit_details.py index ba9f161b..eeb12905 100755 --- a/propms/property_management_solution/doctype/security_deposit_details/security_deposit_details.py +++ b/propms/property_management_solution/doctype/security_deposit_details/security_deposit_details.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class SecurityDepositDetails(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/security_deposit_details/test_security_deposit_details.py b/propms/property_management_solution/doctype/security_deposit_details/test_security_deposit_details.py index 5df5dee8..a08795ce 100755 --- a/propms/property_management_solution/doctype/security_deposit_details/test_security_deposit_details.py +++ b/propms/property_management_solution/doctype/security_deposit_details/test_security_deposit_details.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestSecurityDepositDetails(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/tool_item/tool_item.py b/propms/property_management_solution/doctype/tool_item/tool_item.py index 1953a603..562630a1 100644 --- a/propms/property_management_solution/doctype/tool_item/tool_item.py +++ b/propms/property_management_solution/doctype/tool_item/tool_item.py @@ -6,4 +6,4 @@ class ToolItem(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/tool_item_record/test_tool_item_record.py b/propms/property_management_solution/doctype/tool_item_record/test_tool_item_record.py index b679d62f..6537e331 100644 --- a/propms/property_management_solution/doctype/tool_item_record/test_tool_item_record.py +++ b/propms/property_management_solution/doctype/tool_item_record/test_tool_item_record.py @@ -6,4 +6,4 @@ class TestToolItemRecord(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/tool_item_record/tool_item_record.js b/propms/property_management_solution/doctype/tool_item_record/tool_item_record.js index e1160168..cb1320c3 100644 --- a/propms/property_management_solution/doctype/tool_item_record/tool_item_record.js +++ b/propms/property_management_solution/doctype/tool_item_record/tool_item_record.js @@ -25,4 +25,3 @@ cur_frm.set_query("tool_item_set", function () { } } }); - diff --git a/propms/property_management_solution/doctype/tool_item_record/tool_item_record.py b/propms/property_management_solution/doctype/tool_item_record/tool_item_record.py index e8a2b490..24009ca1 100644 --- a/propms/property_management_solution/doctype/tool_item_record/tool_item_record.py +++ b/propms/property_management_solution/doctype/tool_item_record/tool_item_record.py @@ -6,4 +6,4 @@ class ToolItemRecord(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/tool_item_set/test_tool_item_set.py b/propms/property_management_solution/doctype/tool_item_set/test_tool_item_set.py index fb33d388..a6c72c80 100644 --- a/propms/property_management_solution/doctype/tool_item_set/test_tool_item_set.py +++ b/propms/property_management_solution/doctype/tool_item_set/test_tool_item_set.py @@ -6,4 +6,4 @@ class TestToolItemSet(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/tool_item_set/tool_item_set.py b/propms/property_management_solution/doctype/tool_item_set/tool_item_set.py index df1ba4c7..6e4bf5dd 100644 --- a/propms/property_management_solution/doctype/tool_item_set/tool_item_set.py +++ b/propms/property_management_solution/doctype/tool_item_set/tool_item_set.py @@ -6,4 +6,4 @@ class ToolItemSet(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/unit_assets/unit_assets.py b/propms/property_management_solution/doctype/unit_assets/unit_assets.py index d3e3709d..ee1851f6 100755 --- a/propms/property_management_solution/doctype/unit_assets/unit_assets.py +++ b/propms/property_management_solution/doctype/unit_assets/unit_assets.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class UnitAssets(Document): - pass + pass diff --git a/propms/property_management_solution/doctype/unit_type/test_unit_type.py b/propms/property_management_solution/doctype/unit_type/test_unit_type.py index b255391c..5471a2e1 100755 --- a/propms/property_management_solution/doctype/unit_type/test_unit_type.py +++ b/propms/property_management_solution/doctype/unit_type/test_unit_type.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and Contributors # See license.txt -from __future__ import unicode_literals + import unittest class TestUnitType(unittest.TestCase): - pass + pass diff --git a/propms/property_management_solution/doctype/unit_type/unit_type.py b/propms/property_management_solution/doctype/unit_type/unit_type.py index 3d20b5a0..e343de98 100755 --- a/propms/property_management_solution/doctype/unit_type/unit_type.py +++ b/propms/property_management_solution/doctype/unit_type/unit_type.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document class UnitType(Document): - pass + pass diff --git a/propms/property_management_solution/journal_entry_account.js b/propms/property_management_solution/journal_entry_account.js index 15801403..56d8b6e8 100644 --- a/propms/property_management_solution/journal_entry_account.js +++ b/propms/property_management_solution/journal_entry_account.js @@ -37,4 +37,4 @@ frappe.ui.form.on('Journal Entry Account', { frappe.model.set_value(cdt, cdn, "party", ""); } } -}) \ No newline at end of file +}) diff --git a/propms/property_management_solution/notification/daily_checkup_report/daily_checkup_report.md b/propms/property_management_solution/notification/daily_checkup_report/daily_checkup_report.md index b54ea659..09953ee4 100644 --- a/propms/property_management_solution/notification/daily_checkup_report/daily_checkup_report.md +++ b/propms/property_management_solution/notification/daily_checkup_report/daily_checkup_report.md @@ -1 +1 @@ -This email has been sent with data from daily report submission \ No newline at end of file +This email has been sent with data from daily report submission diff --git a/propms/property_management_solution/notification/daily_checkup_report/daily_checkup_report.py b/propms/property_management_solution/notification/daily_checkup_report/daily_checkup_report.py index 2f1b3d1b..02e3e933 100644 --- a/propms/property_management_solution/notification/daily_checkup_report/daily_checkup_report.py +++ b/propms/property_management_solution/notification/daily_checkup_report/daily_checkup_report.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals - - def get_context(context): - # do your magic here - pass + # do your magic here + pass diff --git a/propms/property_management_solution/notification/outsourcing_attendance/outsourcing_attendance.md b/propms/property_management_solution/notification/outsourcing_attendance/outsourcing_attendance.md index 5f1c19e1..fe91b8d2 100644 --- a/propms/property_management_solution/notification/outsourcing_attendance/outsourcing_attendance.md +++ b/propms/property_management_solution/notification/outsourcing_attendance/outsourcing_attendance.md @@ -1 +1 @@ -This email has been sent with data from security attendance submission \ No newline at end of file +This email has been sent with data from security attendance submission diff --git a/propms/property_management_solution/notification/outsourcing_attendance/outsourcing_attendance.py b/propms/property_management_solution/notification/outsourcing_attendance/outsourcing_attendance.py index 2f1b3d1b..02e3e933 100644 --- a/propms/property_management_solution/notification/outsourcing_attendance/outsourcing_attendance.py +++ b/propms/property_management_solution/notification/outsourcing_attendance/outsourcing_attendance.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals - - def get_context(context): - # do your magic here - pass + # do your magic here + pass diff --git a/propms/property_management_solution/notification/security_attendance/security_attendance.md b/propms/property_management_solution/notification/security_attendance/security_attendance.md index 5f1c19e1..fe91b8d2 100644 --- a/propms/property_management_solution/notification/security_attendance/security_attendance.md +++ b/propms/property_management_solution/notification/security_attendance/security_attendance.md @@ -1 +1 @@ -This email has been sent with data from security attendance submission \ No newline at end of file +This email has been sent with data from security attendance submission diff --git a/propms/property_management_solution/notification/security_attendance/security_attendance.py b/propms/property_management_solution/notification/security_attendance/security_attendance.py index 2f1b3d1b..02e3e933 100644 --- a/propms/property_management_solution/notification/security_attendance/security_attendance.py +++ b/propms/property_management_solution/notification/security_attendance/security_attendance.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals - - def get_context(context): - # do your magic here - pass + # do your magic here + pass diff --git a/propms/property_management_solution/report/debtors_report/debtors_report.js b/propms/property_management_solution/report/debtors_report/debtors_report.js index b015a25e..7cbc9326 100644 --- a/propms/property_management_solution/report/debtors_report/debtors_report.js +++ b/propms/property_management_solution/report/debtors_report/debtors_report.js @@ -34,4 +34,4 @@ frappe.query_reports["Debtors Report"] = { return value; }, -}; \ No newline at end of file +}; diff --git a/propms/property_management_solution/report/invoice_details/invoice_details.py b/propms/property_management_solution/report/invoice_details/invoice_details.py index f96aea47..7bbeeada 100644 --- a/propms/property_management_solution/report/invoice_details/invoice_details.py +++ b/propms/property_management_solution/report/invoice_details/invoice_details.py @@ -1,17 +1,15 @@ # Copyright (c) 2013, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe -from .other_methods import get_residential_columns -from .other_methods import get_sales_invoice +from .other_methods import get_residential_columns, get_sales_invoice def execute(filters=None): - columns, data = [], [] - if filters.get("rental") and filters.get("year"): - columns = get_residential_columns(filters.get("year")) - get_sales_invoice(filters, data) + columns, data = [], [] + if filters.get("rental") and filters.get("year"): + columns = get_residential_columns(filters.get("year")) + get_sales_invoice(filters, data) - return columns, data + return columns, data diff --git a/propms/property_management_solution/report/invoice_details/other_methods.py b/propms/property_management_solution/report/invoice_details/other_methods.py index eef08723..b5ab9611 100644 --- a/propms/property_management_solution/report/invoice_details/other_methods.py +++ b/propms/property_management_solution/report/invoice_details/other_methods.py @@ -1,216 +1,193 @@ -import frappe, calendar -from frappe import _ +import calendar from datetime import date, timedelta +import frappe +from frappe import _ + def get_residential_columns(year): - columns = [ - { - "fieldname": "apartment_no", - "label": _("Apartment No."), - "fieldtype": "Data", - "width": 150, - }, - { - "fieldname": "client", - "label": _("Client"), - "fieldtype": "Data", - "width": 150, - }, - { - "fieldname": "advance_prev_year", - "label": _("Advance RECD in 2019"), - "fieldtype": "Data", - "width": 150, - }, - { - "fieldname": "invoice_no", - "label": _("Invoice No."), - "fieldtype": "Link", - "options": "Sales Invoice", - "width": 150, - }, - {"fieldname": "from", "label": _("From"), "fieldtype": "Data", "width": 150}, - {"fieldname": "to", "label": _("To"), "fieldtype": "Data", "width": 150}, - { - "fieldname": "invoice_amount", - "label": _("Invoice Amount"), - "fieldtype": "Data", - "width": 150, - }, - ] - months = months_array() - for i in months: - columns.append( - { - "fieldname": i.lower(), - "label": i + " " + str(year), - "fieldtype": "Data", - "width": 150, - } - ) - - return columns + columns = [ + { + "fieldname": "apartment_no", + "label": _("Apartment No."), + "fieldtype": "Data", + "width": 150, + }, + { + "fieldname": "client", + "label": _("Client"), + "fieldtype": "Data", + "width": 150, + }, + { + "fieldname": "advance_prev_year", + "label": _("Advance RECD in 2019"), + "fieldtype": "Data", + "width": 150, + }, + { + "fieldname": "invoice_no", + "label": _("Invoice No."), + "fieldtype": "Link", + "options": "Sales Invoice", + "width": 150, + }, + {"fieldname": "from", "label": _("From"), "fieldtype": "Data", "width": 150}, + {"fieldname": "to", "label": _("To"), "fieldtype": "Data", "width": 150}, + { + "fieldname": "invoice_amount", + "label": _("Invoice Amount"), + "fieldtype": "Data", + "width": 150, + }, + ] + months = months_array() + for i in months: + columns.append( + { + "fieldname": i.lower(), + "label": i + " " + str(year), + "fieldtype": "Data", + "width": 150, + } + ) + + return columns def get_sales_invoice(filters, data, from_other=None, months=None): - total = {} - lease_item = "'" + filters.get("rental") + "' " - print(lease_item) - if filters.get("maintenance"): - lease_item = "'Service Charge - " + filters.get("rental").split()[0] + "'" + total = {} + lease_item = "'" + filters.get("rental") + "' " + print(lease_item) + if filters.get("maintenance"): + lease_item = "'Service Charge - " + filters.get("rental").split()[0] + "'" - query = """ SELECT * FROM `tabSales Invoice` AS SI WHERE EXISTS (SELECT * FROM `tabSales Invoice Item` AS SIT WHERE SIT.item_code = {0} and SIT.parent = SI.name ) + query = f""" SELECT * FROM `tabSales Invoice` AS SI WHERE EXISTS (SELECT * FROM `tabSales Invoice Item` AS SIT WHERE SIT.item_code = {lease_item} and SIT.parent = SI.name ) and SI.docstatus=%s - ORDER by SI.customer,SI.from_date ASC""".format( - lease_item - ) % ( - 1 - ) - - sales_invoices = frappe.db.sql(query, as_dict=True) - previuos_customer = "" - for i in sales_invoices: - lease = frappe.get_value("Lease", i.lease, "property") - obj = { - "apartment_no": lease or "", - "client": i.customer, - "advance_prev_year": "", - "invoice_no": i.name, - "from": i.from_date if i.from_date else i.posting_date, - "to": i.to_date - timedelta(days=1) if i.to_date else i.posting_date, - "invoice_amount": i.total, - } - set_monthly_amount( - i.from_date, - i.to_date - timedelta(days=1) if i.to_date else "", - obj, - filters, - total, - months, - ) - if previuos_customer != i.customer: - data.append({}) - previuos_customer = i.customer - data.append(obj) - if from_other: - data.append(total) + ORDER by SI.customer,SI.from_date ASC""" % (1) + + sales_invoices = frappe.db.sql(query, as_dict=True) + previuos_customer = "" + for i in sales_invoices: + lease = frappe.get_value("Lease", i.lease, "property") + obj = { + "apartment_no": lease or "", + "client": i.customer, + "advance_prev_year": "", + "invoice_no": i.name, + "from": i.from_date if i.from_date else i.posting_date, + "to": i.to_date - timedelta(days=1) if i.to_date else i.posting_date, + "invoice_amount": i.total, + } + set_monthly_amount( + i.from_date, + i.to_date - timedelta(days=1) if i.to_date else "", + obj, + filters, + total, + months, + ) + if previuos_customer != i.customer: + data.append({}) + previuos_customer = i.customer + data.append(obj) + if from_other: + data.append(total) def set_monthly_amount(start_date, end_date, obj, filters, total, months): - rate = get_rate(obj["invoice_no"], filters) - if end_date and rate: - check_dates(start_date, end_date, rate, obj, total, months) + rate = get_rate(obj["invoice_no"], filters) + if end_date and rate: + check_dates(start_date, end_date, rate, obj, total, months) def check_dates(start_date, end_date, rate, obj, total, months): - start = start_date - no_minus = 0 - - while start < end_date: - month_string = start.strftime("%b") - month_no_of_days = calendar.monthrange(start.year, start.month)[1] - last_date = date(start.year, start.month, month_no_of_days) - if (last_date - start).days >= 29 or ( - month_string == "Feb" and (last_date - start).days >= 27 - ): - if start.year == start_date.year: - obj[month_string.lower()] = round(rate, 2) - if months and month_string.lower() in months: - total[month_string.lower()] = ( - round(rate, 2) + round(total[month_string.lower()], 2) - if month_string.lower() in total - else round(rate, 2) - ) - else: - if start.year == start_date.year: - obj[month_string.lower()] = round( - round(rate / month_no_of_days, 2) - * (month_no_of_days - int(start.day)), - 2, - ) - if months and month_string.lower() in months: - total[month_string.lower()] = ( - round( - round(rate / month_no_of_days, 2) - * (month_no_of_days - int(start.day)), - 2, - ) - + round(total[month_string.lower()], 2) - if month_string.lower() in total - else round( - round(rate / month_no_of_days, 2) - * (month_no_of_days - int(start.day)), - 2, - ) - ) - no_minus = month_no_of_days - start += timedelta(days=month_no_of_days) - - start_last = start - timedelta(days=no_minus) - - if (end_date - start_last).days > 0 and start_last.month != end_date.month: - if start_last.year == start_date.year and start_last.year == end_date.year: - month_string = end_date.strftime("%b") - month_no_of_days = calendar.monthrange(end_date.year, end_date.month)[1] - if int(end_date.day) >= 29 or ( - month_string == "Feb" and (end_date - start_last).days >= 27 - ): - obj[month_string.lower()] = round(rate, 2) - if months and month_string.lower() in months: - total[month_string.lower()] = ( - round(rate, 2) + round(total[month_string.lower()], 2) - if month_string.lower() in total - else round(rate, 2) - ) - else: - obj[month_string.lower()] = round( - round(rate / month_no_of_days, 2) * (int(end_date.day)), 2 - ) - if months and month_string.lower() in months: - total[month_string.lower()] = ( - round( - round(rate / month_no_of_days, 2) * (int(end_date.day)), 2 - ) - + round(total[month_string.lower()], 2) - if month_string.lower() in total - else round( - round(rate / month_no_of_days, 2) * (int(end_date.day)), 2 - ) - ) + start = start_date + no_minus = 0 + + while start < end_date: + month_string = start.strftime("%b") + month_no_of_days = calendar.monthrange(start.year, start.month)[1] + last_date = date(start.year, start.month, month_no_of_days) + if (last_date - start).days >= 29 or (month_string == "Feb" and (last_date - start).days >= 27): + if start.year == start_date.year: + obj[month_string.lower()] = round(rate, 2) + if months and month_string.lower() in months: + total[month_string.lower()] = ( + round(rate, 2) + round(total[month_string.lower()], 2) + if month_string.lower() in total + else round(rate, 2) + ) + else: + if start.year == start_date.year: + obj[month_string.lower()] = round( + round(rate / month_no_of_days, 2) * (month_no_of_days - int(start.day)), + 2, + ) + if months and month_string.lower() in months: + total[month_string.lower()] = ( + round( + round(rate / month_no_of_days, 2) * (month_no_of_days - int(start.day)), + 2, + ) + + round(total[month_string.lower()], 2) + if month_string.lower() in total + else round( + round(rate / month_no_of_days, 2) * (month_no_of_days - int(start.day)), + 2, + ) + ) + no_minus = month_no_of_days + start += timedelta(days=month_no_of_days) + + start_last = start - timedelta(days=no_minus) + + if (end_date - start_last).days > 0 and start_last.month != end_date.month: + if start_last.year == start_date.year and start_last.year == end_date.year: + month_string = end_date.strftime("%b") + month_no_of_days = calendar.monthrange(end_date.year, end_date.month)[1] + if int(end_date.day) >= 29 or (month_string == "Feb" and (end_date - start_last).days >= 27): + obj[month_string.lower()] = round(rate, 2) + if months and month_string.lower() in months: + total[month_string.lower()] = ( + round(rate, 2) + round(total[month_string.lower()], 2) + if month_string.lower() in total + else round(rate, 2) + ) + else: + obj[month_string.lower()] = round(round(rate / month_no_of_days, 2) * (int(end_date.day)), 2) + if months and month_string.lower() in months: + total[month_string.lower()] = ( + round(round(rate / month_no_of_days, 2) * (int(end_date.day)), 2) + + round(total[month_string.lower()], 2) + if month_string.lower() in total + else round(round(rate / month_no_of_days, 2) * (int(end_date.day)), 2) + ) def months_array(): - return [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ] + return [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ] def get_rate(invoice_name, filters): - filters_value = " and item_code= '" + filters.get("rental") + "' " - if filters.get("maintenance"): - filters_value = ( - "and item_code = 'Service Charge - " - + filters.get("rental").split()[0] - + "'" - ) - query = """ SELECT rate FROM `tabSales Invoice Item` WHERE {0} {1}""".format( - "parent = '" + invoice_name + "' ", filters_value - ) - - return ( - frappe.db.sql(query, as_dict=True)[0].rate - if len(frappe.db.sql(query, as_dict=True)) > 0 - else "" - ) + filters_value = " and item_code= '" + filters.get("rental") + "' " + if filters.get("maintenance"): + filters_value = "and item_code = 'Service Charge - " + filters.get("rental").split()[0] + "'" + query = """ SELECT rate FROM `tabSales Invoice Item` WHERE {} {}""".format( + "parent = '" + invoice_name + "' ", filters_value + ) + + return frappe.db.sql(query, as_dict=True)[0].rate if len(frappe.db.sql(query, as_dict=True)) > 0 else "" diff --git a/propms/property_management_solution/report/mis_income_break_up/mis_income_break_up.py b/propms/property_management_solution/report/mis_income_break_up/mis_income_break_up.py index 13494643..a1fce66a 100644 --- a/propms/property_management_solution/report/mis_income_break_up/mis_income_break_up.py +++ b/propms/property_management_solution/report/mis_income_break_up/mis_income_break_up.py @@ -1,15 +1,13 @@ # Copyright (c) 2013, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals -from .other_methods import get_columns -from .other_methods import get_rentals -from .other_methods import get_rental_maintenance + +from .other_methods import get_columns, get_rental_maintenance, get_rentals def execute(filters=None): - columns, data = get_columns(filters), get_rentals(filters) + columns, data = get_columns(filters), get_rentals(filters) - get_rental_maintenance(filters, data) + get_rental_maintenance(filters, data) - return columns, data + return columns, data diff --git a/propms/property_management_solution/report/mis_income_break_up/other_methods.py b/propms/property_management_solution/report/mis_income_break_up/other_methods.py index 73ec6d8f..f45ced49 100644 --- a/propms/property_management_solution/report/mis_income_break_up/other_methods.py +++ b/propms/property_management_solution/report/mis_income_break_up/other_methods.py @@ -1,6 +1,7 @@ -from time import strptime import calendar from collections import Counter +from time import strptime + from ..invoice_details.other_methods import get_sales_invoice from ..utility_invoices.other_methods import get_utility_sales_invoice @@ -8,102 +9,93 @@ def get_rentals(filters): - return_data = [] - if filters.get("year"): - - return_data.append({"income": "RENTAL INCOME"}) - sum_monthly = Counter({}) - tax = {"income": "LESS: W.TAX 10%"} - net_rent = {"income": "NET RENT RECEIVED"} - rentals = ["Commercial Rent", "Residential Rent"] - for i in rentals: - data = [] - _filters = {"rental": i, "year": filters.get("year")} - get_sales_invoice(_filters, data, "Mis Income Break Up", months) - if len(data) > 0 and len(data[len(data) - 1]) > 0: - data[len(data) - 1]["total"] = sum(data[len(data) - 1].values()) / len( - data[len(data) - 1] - ) - - sum_monthly += Counter(data[len(data) - 1]) - - data[len(data) - 1]["income"] = i - return_data.append(data[len(data) - 1]) - - sum_monthly["income"] = "Total Rentals Received" - - return_data.append(sum_monthly) - return_data.append(tax) - return_data.append(net_rent) - for i in sum_monthly: - tax[i] = float(sum_monthly[i]) * float(0.10) - net_rent[i] = sum_monthly[i] - tax[i] - return return_data + return_data = [] + if filters.get("year"): + return_data.append({"income": "RENTAL INCOME"}) + sum_monthly = Counter({}) + tax = {"income": "LESS: W.TAX 10%"} + net_rent = {"income": "NET RENT RECEIVED"} + rentals = ["Commercial Rent", "Residential Rent"] + for i in rentals: + data = [] + _filters = {"rental": i, "year": filters.get("year")} + get_sales_invoice(_filters, data, "Mis Income Break Up", months) + if len(data) > 0 and len(data[len(data) - 1]) > 0: + data[len(data) - 1]["total"] = sum(data[len(data) - 1].values()) / len(data[len(data) - 1]) + + sum_monthly += Counter(data[len(data) - 1]) + + data[len(data) - 1]["income"] = i + return_data.append(data[len(data) - 1]) + + sum_monthly["income"] = "Total Rentals Received" + + return_data.append(sum_monthly) + return_data.append(tax) + return_data.append(net_rent) + for i in sum_monthly: + tax[i] = float(sum_monthly[i]) * 0.10 + net_rent[i] = sum_monthly[i] - tax[i] + return return_data def get_rental_maintenance(filters, return_data): + if filters.get("year"): + return_data.append({"income": "MAINTENANCE INCOME"}) + sum_monthly = Counter({}) - if filters.get("year"): - - return_data.append({"income": "MAINTENANCE INCOME"}) - sum_monthly = Counter({}) - - rentals = ["Commercial Rent", "Residential Rent", "Utility Charges"] - for ii in rentals: - data = [] - _filters = {"rental": ii, "year": filters.get("year"), "maintenance": 1} - if ii == "Utility Charges": - get_utility_sales_invoice(data, "Mis Income Break Up", months) - else: - get_sales_invoice(_filters, data, "Mis Income Break Up", months) - if len(data) > 0 and len(data[len(data) - 1]) > 0: - data[len(data) - 1]["total"] = sum(data[len(data) - 1].values()) / len( - data[len(data) - 1] - ) + rentals = ["Commercial Rent", "Residential Rent", "Utility Charges"] + for ii in rentals: + data = [] + _filters = {"rental": ii, "year": filters.get("year"), "maintenance": 1} + if ii == "Utility Charges": + get_utility_sales_invoice(data, "Mis Income Break Up", months) + else: + get_sales_invoice(_filters, data, "Mis Income Break Up", months) + if len(data) > 0 and len(data[len(data) - 1]) > 0: + data[len(data) - 1]["total"] = sum(data[len(data) - 1].values()) / len(data[len(data) - 1]) - sum_monthly += Counter(data[len(data) - 1]) + sum_monthly += Counter(data[len(data) - 1]) - data[len(data) - 1]["income"] = ( - ii + " Maintenance" if ii != "Utility Charges" else ii - ) - return_data.append(data[len(data) - 1]) + data[len(data) - 1]["income"] = ii + " Maintenance" if ii != "Utility Charges" else ii + return_data.append(data[len(data) - 1]) - sum_monthly["income"] = "Maintenance Total" + sum_monthly["income"] = "Maintenance Total" - return_data.append(sum_monthly) - return_data.append({}) - return return_data + return_data.append(sum_monthly) + return_data.append({}) + return return_data def get_columns(filters): - columns = [ - { - "label": "Income", - "fieldname": "income", - "fieldtype": "Data", - "width": 200, - } - ] - month_int_from = int(strptime(filters.get("from"), "%B").tm_mon) - month_int_to = int(strptime(filters.get("to"), "%B").tm_mon) - - while month_int_from <= month_int_to: - months.append(calendar.month_name[month_int_from].lower()[:3]) - columns.append( - { - "label": calendar.month_name[month_int_from], - "fieldname": calendar.month_name[month_int_from].lower()[:3], - "fieldtype": "Currency", - "width": 180, - } - ) - month_int_from += 1 - columns.append( - { - "label": "Total", - "fieldname": "total", - "fieldtype": "Currency", - "width": 180, - } - ) - return columns + columns = [ + { + "label": "Income", + "fieldname": "income", + "fieldtype": "Data", + "width": 200, + } + ] + month_int_from = int(strptime(filters.get("from"), "%B").tm_mon) + month_int_to = int(strptime(filters.get("to"), "%B").tm_mon) + + while month_int_from <= month_int_to: + months.append(calendar.month_name[month_int_from].lower()[:3]) + columns.append( + { + "label": calendar.month_name[month_int_from], + "fieldname": calendar.month_name[month_int_from].lower()[:3], + "fieldtype": "Currency", + "width": 180, + } + ) + month_int_from += 1 + columns.append( + { + "label": "Total", + "fieldname": "total", + "fieldtype": "Currency", + "width": 180, + } + ) + return columns diff --git a/propms/property_management_solution/report/rent_invoices_details/rent_invoices_details.py b/propms/property_management_solution/report/rent_invoices_details/rent_invoices_details.py index b8bf7439..182a5fc7 100644 --- a/propms/property_management_solution/report/rent_invoices_details/rent_invoices_details.py +++ b/propms/property_management_solution/report/rent_invoices_details/rent_invoices_details.py @@ -1,506 +1,468 @@ # Copyright (c) 2013, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe -from time import strptime -import calendar -from datetime import date, timedelta, datetime + from collections import OrderedDict +from datetime import datetime, timedelta + +import frappe +from erpnext import get_company_currency, get_default_company from frappe.utils import ( - getdate, - date_diff, - month_diff, - get_last_day, - get_first_day, - add_months, - floor, - add_days, - flt, - cint, + add_days, + add_months, + cint, + date_diff, + floor, + flt, + get_first_day, + get_last_day, + getdate, ) -from erpnext import get_company_currency, get_default_company def execute(filters=None): - data = get_data(filters) - columns = get_columns(filters) - return columns, data + data = get_data(filters) + columns = get_columns(filters) + return columns, data def get_data(filters): - rows = [] - _from_date = "'{from_date}'".format(from_date=filters["from_date"]) - _to_date = "'{to_date}'".format(to_date=filters["to_date"]) - _company = "'{company}'".format(company=filters["company"]) - _items_grupe = filters.get("type_name") - float_precision = cint(frappe.db.get_default("float_precision")) or 2 - if filters.get("company"): - default_currency = get_company_currency(filters["company"]) - else: - company = get_default_company() - default_currency = get_company_currency(company) - - conditions = "" - if not filters.get("extand"): - conditions = "AND DATE(posting_date) BETWEEN {start} AND {end}".format( - start=_from_date, end=_to_date - ) - - query = """ + rows = [] + _from_date = "'{from_date}'".format(from_date=filters["from_date"]) + _to_date = "'{to_date}'".format(to_date=filters["to_date"]) + _company = "'{company}'".format(company=filters["company"]) + _items_grupe = filters.get("type_name") + float_precision = cint(frappe.db.get_default("float_precision")) or 2 + if filters.get("company"): + default_currency = get_company_currency(filters["company"]) + else: + company = get_default_company() + default_currency = get_company_currency(company) + + conditions = "" + if not filters.get("extand"): + conditions = f"AND DATE(posting_date) BETWEEN {_from_date} AND {_to_date}" + + query = f""" SELECT - name as invoice_id, - customer, - base_net_total as total, + name as invoice_id, + customer, + base_net_total as total, net_total as foreign_total, - currency, - conversion_rate as exchange_rate, - posting_date as date, + currency, + conversion_rate as exchange_rate, + posting_date as date, lease FROM `tabSales Invoice` WHERE - docstatus = 1 - AND company = {company} + docstatus = 1 + AND company = {_company} AND lease != "" AND from_date != "" AND to_date != "" AND is_return != 1 {conditions} ORDER BY lease DESC, posting_date DESC - """.format( - conditions=conditions, company=_company - ) - - sales_invoices = frappe.db.sql(query, as_dict=True) - - for invoice in sales_invoices: - _items_rwos = [] - append = False - property_name = frappe.db.get_value("Lease", invoice["lease"], "property") - invoice["property_name"] = property_name - if invoice.total == invoice.foreign_total: - invoice.foreign_total, invoice.exchange_rate = "", "" - # if filters.get("foreign_currency") and get_company_currency(filters.company) != filters.foreign_currency: - # months_obj = calculate_monthly_ammount(invoice.foreign_total,invoice.from_date,invoice.to_date) - # else: - # months_obj = calculate_monthly_ammount(invoice.total,invoice.from_date,invoice.to_date) - # if months_obj: - # for key,value in months_obj.items(): - # invoice[key] = value - - invoice_id = "'{invoice_id}'".format(invoice_id=invoice["invoice_id"]) - - query_items = """ + """ + + sales_invoices = frappe.db.sql(query, as_dict=True) + + for invoice in sales_invoices: + _items_rwos = [] + append = False + property_name = frappe.db.get_value("Lease", invoice["lease"], "property") + invoice["property_name"] = property_name + if invoice.total == invoice.foreign_total: + invoice.foreign_total, invoice.exchange_rate = "", "" + # if filters.get("foreign_currency") and get_company_currency(filters.company) != filters.foreign_currency: + # months_obj = calculate_monthly_ammount(invoice.foreign_total,invoice.from_date,invoice.to_date) + # else: + # months_obj = calculate_monthly_ammount(invoice.total,invoice.from_date,invoice.to_date) + # if months_obj: + # for key,value in months_obj.items(): + # invoice[key] = value + + invoice_id = "'{invoice_id}'".format(invoice_id=invoice["invoice_id"]) + + query_items = f""" SELECT - item_code, - base_net_amount as item_total, + item_code, + base_net_amount as item_total, net_amount as item_foreign_total, - service_start_date as from_date, - service_end_date as to_date, - qty as quantity, + service_start_date as from_date, + service_end_date as to_date, + qty as quantity, net_rate FROM `tabSales Invoice Item` WHERE parent = {invoice_id} - """.format( - invoice_id=invoice_id - ) - - items = frappe.db.sql(query_items, as_dict=True) - for item in items: - item_group = frappe.db.get_value("Item", item["item_code"], "item_group") - item["item_group"] = item_group - item.item_foreign_total = flt(item.item_foreign_total, float_precision) - item.item_total = flt(item.item_total, float_precision) - months_obj = calculate_monthly_ammount( - item.item_total, - default_currency, - item.from_date, - item.to_date, - item.item_foreign_total, - filters.get("foreign_currency"), - filters, - ) - if months_obj: - for key, value in months_obj.items(): - item[key] = value - if _items_grupe == "All Item Groups": - _items_rwos.append(item) - append = True - elif _items_grupe == item_group: - _items_rwos.append(item) - append = True - if filters.get("foreign_currency"): - item.item_total = flt(item.item_foreign_total, float_precision) - else: - item.item_total = flt(item.item_total, float_precision) - if append and ( - filters.foreign_currency == invoice.currency or not filters.foreign_currency - ): - # rows.append(invoice) - for item in _items_rwos: - item.update(invoice) - rows.append(item) - # rows.append({}) - - return rows + """ + + items = frappe.db.sql(query_items, as_dict=True) + for item in items: + item_group = frappe.db.get_value("Item", item["item_code"], "item_group") + item["item_group"] = item_group + item.item_foreign_total = flt(item.item_foreign_total, float_precision) + item.item_total = flt(item.item_total, float_precision) + months_obj = calculate_monthly_ammount( + item.item_total, + default_currency, + item.from_date, + item.to_date, + item.item_foreign_total, + filters.get("foreign_currency"), + filters, + ) + if months_obj: + for key, value in months_obj.items(): + item[key] = value + if _items_grupe == "All Item Groups": + _items_rwos.append(item) + append = True + elif _items_grupe == item_group: + _items_rwos.append(item) + append = True + if filters.get("foreign_currency"): + item.item_total = flt(item.item_foreign_total, float_precision) + else: + item.item_total = flt(item.item_total, float_precision) + if append and (filters.foreign_currency == invoice.currency or not filters.foreign_currency): + # rows.append(invoice) + for item in _items_rwos: + item.update(invoice) + rows.append(item) + # rows.append({}) + + return rows def get_columns(filters): - if filters.get("company"): - currency = get_company_currency(filters["company"]) - else: - company = get_default_company() - currency = get_company_currency(company) - - if filters.get("foreign_currency"): - _foreign_currency = filters["foreign_currency"] - else: - _foreign_currency = currency - - if _foreign_currency == currency: - foreign_currency = "Foreign" - else: - foreign_currency = filters["foreign_currency"] - - columns = [ - { - "label": "Property", - "fieldname": "property_name", - "fieldtype": "Link", - "options": "Property", - "width": 100, - }, - { - "label": "Customer", - "fieldname": "customer", - "fieldtype": "Link", - "options": "Customer", - "width": 100, - }, - { - "label": "Lease", - "fieldname": "lease", - "fieldtype": "Link", - "options": "Lease", - "width": 100, - }, - { - "label": "Advance Before {0}".format(_foreign_currency), - "fieldname": "advance_before", - "fieldtype": "Float", - "width": 100, - }, - { - "label": "Invoice", - "fieldname": "invoice_id", - "fieldtype": "Link", - "options": "Sales Invoice", - "width": 150, - }, - { - "label": "Date", - "fieldname": "date", - "fieldtype": "date", - "width": 100, - }, - # { - # "label": "Total {0}".format(currency), - # "fieldname": "total", - # "fieldtype": "Float", - # "width": 100, - # }, - { - "label": "Exchange Rate", - "fieldname": "exchange_rate", - "fieldtype": "Float", - "width": 100, - }, - # { - # "label": "Total {0}".format(foreign_currency or "Foreign"), - # "fieldname": "foreign_total", - # "fieldtype": "Float", - # "width": 100, - # }, - { - "label": "Item", - "fieldname": "item_code", - "fieldtype": "Link", - "options": "Item", - "width": 100, - }, - { - "label": "Quantity", - "fieldname": "quantity", - "width": 75, - }, - { - "label": "Item Total {0}".format(_foreign_currency), - "fieldname": "item_total", - "fieldtype": "Float", - "width": 100, - }, - { - "label": "From Date", - "fieldname": "from_date", - "fieldtype": "date", - "width": 100, - }, - { - "label": "To Date", - "fieldname": "to_date", - "fieldtype": "date", - "width": 100, - }, - ] - - months_list = get_months(filters["from_date"], filters["to_date"]) - - for month in months_list: - columns.append( - { - "label": "{0} {1}".format(month, currency), - "fieldname": "{0} {1}".format(month.lower(), currency), - "fieldtype": "Float", - "width": 100, - } - ) - if ( - filters.get("foreign_currency") - and filters.get("foreign_currency") != currency - ): - columns.append( - { - "label": "{0} {1}".format(month, filters.get("foreign_currency")), - "fieldname": "{0} {1}".format( - month.lower(), filters.get("foreign_currency") - ), - "fieldtype": "Float", - "width": 100, - } - ) - - columns.append( - { - "label": "Advance After {0}".format(_foreign_currency), - "fieldname": "advance_after", - "fieldtype": "Float", - "width": 100, - } - ) - - return columns + if filters.get("company"): + currency = get_company_currency(filters["company"]) + else: + company = get_default_company() + currency = get_company_currency(company) + + if filters.get("foreign_currency"): + _foreign_currency = filters["foreign_currency"] + else: + _foreign_currency = currency + + columns = [ + { + "label": "Property", + "fieldname": "property_name", + "fieldtype": "Link", + "options": "Property", + "width": 100, + }, + { + "label": "Customer", + "fieldname": "customer", + "fieldtype": "Link", + "options": "Customer", + "width": 100, + }, + { + "label": "Lease", + "fieldname": "lease", + "fieldtype": "Link", + "options": "Lease", + "width": 100, + }, + { + "label": f"Advance Before {_foreign_currency}", + "fieldname": "advance_before", + "fieldtype": "Float", + "width": 100, + }, + { + "label": "Invoice", + "fieldname": "invoice_id", + "fieldtype": "Link", + "options": "Sales Invoice", + "width": 150, + }, + { + "label": "Date", + "fieldname": "date", + "fieldtype": "date", + "width": 100, + }, + # { + # "label": "Total {0}".format(currency), + # "fieldname": "total", + # "fieldtype": "Float", + # "width": 100, + # }, + { + "label": "Exchange Rate", + "fieldname": "exchange_rate", + "fieldtype": "Float", + "width": 100, + }, + # { + # "label": "Total {0}".format(foreign_currency or "Foreign"), + # "fieldname": "foreign_total", + # "fieldtype": "Float", + # "width": 100, + # }, + { + "label": "Item", + "fieldname": "item_code", + "fieldtype": "Link", + "options": "Item", + "width": 100, + }, + { + "label": "Quantity", + "fieldname": "quantity", + "width": 75, + }, + { + "label": f"Item Total {_foreign_currency}", + "fieldname": "item_total", + "fieldtype": "Float", + "width": 100, + }, + { + "label": "From Date", + "fieldname": "from_date", + "fieldtype": "date", + "width": 100, + }, + { + "label": "To Date", + "fieldname": "to_date", + "fieldtype": "date", + "width": 100, + }, + ] + + months_list = get_months(filters["from_date"], filters["to_date"]) + + for month in months_list: + columns.append( + { + "label": f"{month} {currency}", + "fieldname": f"{month.lower()} {currency}", + "fieldtype": "Float", + "width": 100, + } + ) + if filters.get("foreign_currency") and filters.get("foreign_currency") != currency: + columns.append( + { + "label": "{} {}".format(month, filters.get("foreign_currency")), + "fieldname": "{} {}".format(month.lower(), filters.get("foreign_currency")), + "fieldtype": "Float", + "width": 100, + } + ) + + columns.append( + { + "label": f"Advance After {_foreign_currency}", + "fieldname": "advance_after", + "fieldtype": "Float", + "width": 100, + } + ) + + return columns def get_months(from_date, to_date): - months_list = [] - dates = [from_date, to_date] - start, end = [datetime.strptime(_, "%Y-%m-%d") for _ in dates] - months_obj = OrderedDict( - ((start + timedelta(_)).strftime(r"%b-%y"), None) - for _ in range((end - start).days) - ) - for key, value in months_obj.items(): - months_list.append(key) - return months_list + months_list = [] + dates = [from_date, to_date] + start, end = [datetime.strptime(_, "%Y-%m-%d") for _ in dates] + months_obj = OrderedDict( + ((start + timedelta(_)).strftime(r"%b-%y"), None) for _ in range((end - start).days) + ) + for key, _value in months_obj.items(): + months_list.append(key) + return months_list def check_full_month(from_date, to_date): - month_start_day = get_first_day(from_date) - month_end_day = get_last_day(from_date) - if from_date == month_start_day and to_date == month_end_day: - return True - else: - return False + month_start_day = get_first_day(from_date) + month_end_day = get_last_day(from_date) + if from_date == month_start_day and to_date == month_end_day: + return True + else: + return False def calculate_monthly_ammount( - ammount, - default_currency, - from_date, - to_date, - foreign_ammount, - foreign_currency=None, - filters=None, + ammount, + default_currency, + from_date, + to_date, + foreign_ammount, + foreign_currency=None, + filters=None, ): - float_precision = cint(frappe.db.get_default("float_precision")) or 2 - months_report_list = [] - for month in get_months(filters["from_date"], filters["to_date"]): - months_report_list.append("{0} {1}".format(month.lower(), default_currency)) - if ( - filters.get("foreign_currency") - and filters.get("foreign_currency") != default_currency - ): - months_report_list.append( - "{0} {1}".format(month.lower(), filters.get("foreign_currency")) - ) - if ammount and from_date and to_date: - monthly_ammount_obj = {} - days = 0 - date = from_date - end_date = to_date - field_list = [] - field_list_foreign = [] - first_last = 0 - first_last_foreign = 0 - sub_ammount = 0 - sub_ammount_foreign = 0 - # days_list= [] - - while date <= end_date: - start_month = getdate(date).month - end_month = getdate(to_date).month - - if start_month == end_month: - last_day = end_date - days_diff = date_diff(last_day, date) + 1 - if check_full_month(date, last_day): - days_diff = 30 - days += days_diff - # days_list.append(days_diff) - if date == last_day: - last_day = add_days(last_day, 1) - month_filed = (get_months(str(date), str(last_day))[0]).lower() - month_len = date_diff(get_last_day(date), get_first_day(date)) - field_list.append( - { - "days_diff": days_diff, - "month_filed": "{0} {1}".format(month_filed, default_currency), - "month_len": month_len, - "foreign": False, - } - ) - if foreign_currency and foreign_currency != default_currency: - field_list_foreign.append( - { - "days_diff": days_diff, - "month_filed": "{0} {1}".format( - month_filed, foreign_currency - ), - "month_len": month_len, - "foreign": True, - } - ) - date = get_first_day(add_months(date, 1)) - - else: - last_day = get_last_day(date) - days_diff = date_diff(last_day, date) + 1 - if check_full_month(date, last_day): - days_diff = 30 - days += days_diff - # days_list.append(days_diff) - if date == last_day: - last_day = add_days(last_day, 1) - month_filed = (get_months(str(date), str(last_day))[0]).lower() - month_len = date_diff(get_last_day(date), get_first_day(date)) - field_list.append( - { - "days_diff": days_diff, - "month_filed": "{0} {1}".format(month_filed, default_currency), - "month_len": month_len, - "foreign": False, - } - ) - if foreign_currency and foreign_currency != default_currency: - field_list_foreign.append( - { - "days_diff": days_diff, - "month_filed": "{0} {1}".format( - month_filed, foreign_currency - ), - "month_len": month_len, - "foreign": True, - } - ) - date = get_first_day(add_months(date, 1)) - - if floor(days / 30) != (days / 30) and (floor(days / 30) * 30 + 6) < days: - days = (floor(days / 30) + 1) * 30 - elif floor(days / 30) != (days / 30) and floor(days / 30) * 30 < days: - days = floor(days / 30) * 30 - - daily_ammount = ammount / (days) - daily_ammount_foreign = foreign_ammount / (days) - - m = 1 - for i in field_list: - if m == 1 and i["days_diff"] < 30: - first_last += i["days_diff"] - elif m == len(field_list) and i["days_diff"] < 30: - first_last += i["days_diff"] - else: - sub_ammount += i["days_diff"] * daily_ammount - m += 1 - - m = 1 - for i in field_list_foreign: - if m == 1 and i["days_diff"] < 30: - first_last_foreign += i["days_diff"] - elif m == len(field_list) and i["days_diff"] < 30: - first_last_foreign += i["days_diff"] - else: - sub_ammount_foreign += i["days_diff"] * daily_ammount_foreign - m += 1 - - n = 1 - for i in field_list_foreign: - if n == 1 and i["days_diff"] < 30: - monthly_ammount_obj[i["month_filed"]] = flt( - i["days_diff"] - * ((foreign_ammount - sub_ammount_foreign) / first_last), - float_precision, - ) - elif n == len(field_list) and i["days_diff"] < 30: - monthly_ammount_obj[i["month_filed"]] = flt( - i["days_diff"] - * ((foreign_ammount - sub_ammount_foreign) / first_last), - float_precision, - ) - else: - monthly_ammount_obj[i["month_filed"]] = flt( - i["days_diff"] * daily_ammount_foreign, float_precision - ) - n += 1 - - n = 1 - for i in field_list: - if n == 1 and i["days_diff"] < 30: - monthly_ammount_obj[i["month_filed"]] = flt( - i["days_diff"] * ((ammount - sub_ammount) / first_last), - float_precision, - ) - elif n == len(field_list) and i["days_diff"] < 30: - monthly_ammount_obj[i["month_filed"]] = flt( - i["days_diff"] * ((ammount - sub_ammount) / first_last), - float_precision, - ) - else: - monthly_ammount_obj[i["month_filed"]] = flt( - i["days_diff"] * daily_ammount, float_precision - ) - n += 1 - advance_before = 0 - advance_after = 0 - first_month = datetime.strptime( - months_report_list[0][:6].replace("-", " 20"), "%b %Y" - ) - last_month = datetime.strptime( - months_report_list[-1][:6].replace("-", " 20"), "%b %Y" - ) - for key, value in monthly_ammount_obj.items(): - key_currency = key[7:] - currency = foreign_currency or default_currency - if key not in months_report_list and key_currency == currency: - mydate = datetime.strptime(str(key)[:6].replace("-", " 20"), "%b %Y") - if mydate > last_month: - advance_after += value - elif mydate < first_month: - advance_before += value - if advance_after: - monthly_ammount_obj["advance_after"] = advance_after - if advance_before: - monthly_ammount_obj["advance_before"] = advance_before - return monthly_ammount_obj + float_precision = cint(frappe.db.get_default("float_precision")) or 2 + months_report_list = [] + for month in get_months(filters["from_date"], filters["to_date"]): + months_report_list.append(f"{month.lower()} {default_currency}") + if filters.get("foreign_currency") and filters.get("foreign_currency") != default_currency: + months_report_list.append("{} {}".format(month.lower(), filters.get("foreign_currency"))) + if ammount and from_date and to_date: + monthly_ammount_obj = {} + days = 0 + date = from_date + end_date = to_date + field_list = [] + field_list_foreign = [] + first_last = 0 + first_last_foreign = 0 + sub_ammount = 0 + sub_ammount_foreign = 0 + # days_list= [] + + while date <= end_date: + start_month = getdate(date).month + end_month = getdate(to_date).month + + if start_month == end_month: + last_day = end_date + days_diff = date_diff(last_day, date) + 1 + if check_full_month(date, last_day): + days_diff = 30 + days += days_diff + # days_list.append(days_diff) + if date == last_day: + last_day = add_days(last_day, 1) + month_filed = (get_months(str(date), str(last_day))[0]).lower() + month_len = date_diff(get_last_day(date), get_first_day(date)) + field_list.append( + { + "days_diff": days_diff, + "month_filed": f"{month_filed} {default_currency}", + "month_len": month_len, + "foreign": False, + } + ) + if foreign_currency and foreign_currency != default_currency: + field_list_foreign.append( + { + "days_diff": days_diff, + "month_filed": f"{month_filed} {foreign_currency}", + "month_len": month_len, + "foreign": True, + } + ) + date = get_first_day(add_months(date, 1)) + + else: + last_day = get_last_day(date) + days_diff = date_diff(last_day, date) + 1 + if check_full_month(date, last_day): + days_diff = 30 + days += days_diff + # days_list.append(days_diff) + if date == last_day: + last_day = add_days(last_day, 1) + month_filed = (get_months(str(date), str(last_day))[0]).lower() + month_len = date_diff(get_last_day(date), get_first_day(date)) + field_list.append( + { + "days_diff": days_diff, + "month_filed": f"{month_filed} {default_currency}", + "month_len": month_len, + "foreign": False, + } + ) + if foreign_currency and foreign_currency != default_currency: + field_list_foreign.append( + { + "days_diff": days_diff, + "month_filed": f"{month_filed} {foreign_currency}", + "month_len": month_len, + "foreign": True, + } + ) + date = get_first_day(add_months(date, 1)) + + if floor(days / 30) != (days / 30) and (floor(days / 30) * 30 + 6) < days: + days = (floor(days / 30) + 1) * 30 + elif floor(days / 30) != (days / 30) and floor(days / 30) * 30 < days: + days = floor(days / 30) * 30 + + daily_ammount = ammount / (days) + daily_ammount_foreign = foreign_ammount / (days) + + m = 1 + for i in field_list: + if m == 1 and i["days_diff"] < 30: + first_last += i["days_diff"] + elif m == len(field_list) and i["days_diff"] < 30: + first_last += i["days_diff"] + else: + sub_ammount += i["days_diff"] * daily_ammount + m += 1 + + m = 1 + for i in field_list_foreign: + if m == 1 and i["days_diff"] < 30: + first_last_foreign += i["days_diff"] + elif m == len(field_list) and i["days_diff"] < 30: + first_last_foreign += i["days_diff"] + else: + sub_ammount_foreign += i["days_diff"] * daily_ammount_foreign + m += 1 + + n = 1 + for i in field_list_foreign: + if n == 1 and i["days_diff"] < 30: + monthly_ammount_obj[i["month_filed"]] = flt( + i["days_diff"] * ((foreign_ammount - sub_ammount_foreign) / first_last), + float_precision, + ) + elif n == len(field_list) and i["days_diff"] < 30: + monthly_ammount_obj[i["month_filed"]] = flt( + i["days_diff"] * ((foreign_ammount - sub_ammount_foreign) / first_last), + float_precision, + ) + else: + monthly_ammount_obj[i["month_filed"]] = flt( + i["days_diff"] * daily_ammount_foreign, float_precision + ) + n += 1 + + n = 1 + for i in field_list: + if n == 1 and i["days_diff"] < 30: + monthly_ammount_obj[i["month_filed"]] = flt( + i["days_diff"] * ((ammount - sub_ammount) / first_last), + float_precision, + ) + elif n == len(field_list) and i["days_diff"] < 30: + monthly_ammount_obj[i["month_filed"]] = flt( + i["days_diff"] * ((ammount - sub_ammount) / first_last), + float_precision, + ) + else: + monthly_ammount_obj[i["month_filed"]] = flt(i["days_diff"] * daily_ammount, float_precision) + n += 1 + advance_before = 0 + advance_after = 0 + first_month = datetime.strptime(months_report_list[0][:6].replace("-", " 20"), "%b %Y") + last_month = datetime.strptime(months_report_list[-1][:6].replace("-", " 20"), "%b %Y") + for key, value in monthly_ammount_obj.items(): + key_currency = key[7:] + currency = foreign_currency or default_currency + if key not in months_report_list and key_currency == currency: + mydate = datetime.strptime(str(key)[:6].replace("-", " 20"), "%b %Y") + if mydate > last_month: + advance_after += value + elif mydate < first_month: + advance_before += value + if advance_after: + monthly_ammount_obj["advance_after"] = advance_after + if advance_before: + monthly_ammount_obj["advance_before"] = advance_before + return monthly_ammount_obj diff --git a/propms/property_management_solution/report/rent_invoices_details_usd/rent_invoices_details_usd.py b/propms/property_management_solution/report/rent_invoices_details_usd/rent_invoices_details_usd.py index 9fd8a510..d3fef271 100644 --- a/propms/property_management_solution/report/rent_invoices_details_usd/rent_invoices_details_usd.py +++ b/propms/property_management_solution/report/rent_invoices_details_usd/rent_invoices_details_usd.py @@ -1,501 +1,463 @@ # Copyright (c) 2013, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe -from time import strptime -import calendar -from datetime import date, timedelta, datetime + from collections import OrderedDict -from frappe.utils import ( - getdate, - date_diff, - month_diff, - get_last_day, - get_first_day, - add_months, - floor, - add_days, - flt, - cint, -) +from datetime import datetime, timedelta + +import frappe from erpnext import get_company_currency, get_default_company from erpnext.setup.utils import get_exchange_rate +from frappe.utils import ( + add_days, + add_months, + cint, + date_diff, + floor, + flt, + get_first_day, + get_last_day, + getdate, +) def execute(filters=None): - filters["foreign_currency"] = "USD" - data = get_data(filters) - columns = get_columns(filters) - return columns, data + filters["foreign_currency"] = "USD" + data = get_data(filters) + columns = get_columns(filters) + return columns, data def get_data(filters): - rows = [] - _from_date = "'{from_date}'".format(from_date=filters["from_date"]) - _to_date = "'{to_date}'".format(to_date=filters["to_date"]) - _company = "'{company}'".format(company=filters["company"]) - _items_grupe = filters.get("type_name") - float_precision = cint(frappe.db.get_default("float_precision")) or 2 - if filters.get("company"): - default_currency = get_company_currency(filters["company"]) - else: - company = get_default_company() - default_currency = get_company_currency(company) - - conditions = "" - if not filters.get("extand"): - conditions = "AND DATE(posting_date) BETWEEN {start} AND {end}".format( - start=_from_date, end=_to_date - ) - - query = """ + rows = [] + _from_date = "'{from_date}'".format(from_date=filters["from_date"]) + _to_date = "'{to_date}'".format(to_date=filters["to_date"]) + _company = "'{company}'".format(company=filters["company"]) + _items_grupe = filters.get("type_name") + float_precision = cint(frappe.db.get_default("float_precision")) or 2 + if filters.get("company"): + default_currency = get_company_currency(filters["company"]) + else: + company = get_default_company() + default_currency = get_company_currency(company) + + conditions = "" + if not filters.get("extand"): + conditions = f"AND DATE(posting_date) BETWEEN {_from_date} AND {_to_date}" + + query = f""" SELECT - name as invoice_id, - customer, - base_net_total as total, + name as invoice_id, + customer, + base_net_total as total, net_total as foreign_total, - currency, - conversion_rate as exchange_rate, - posting_date as date, + currency, + conversion_rate as exchange_rate, + posting_date as date, lease FROM `tabSales Invoice` WHERE - docstatus = 1 - AND company = {company} + docstatus = 1 + AND company = {_company} AND lease != "" AND from_date != "" AND to_date != "" {conditions} ORDER BY lease DESC, posting_date ASC - """.format( - conditions=conditions, company=_company - ) - - sales_invoices = frappe.db.sql(query, as_dict=True) - - for invoice in sales_invoices: - _items_rwos = [] - append = False - property_name = frappe.db.get_value("Lease", invoice["lease"], "property") - invoice["property_name"] = property_name - if invoice.total == invoice.foreign_total: - invoice.exchange_rate = get_exchange_rate( - "USD", default_currency, invoice.posting_date - ) - invoice.foreign_total = invoice.total / invoice.exchange_rate - - invoice_id = "'{invoice_id}'".format(invoice_id=invoice["invoice_id"]) - - query_items = """ + """ + + sales_invoices = frappe.db.sql(query, as_dict=True) + + for invoice in sales_invoices: + _items_rwos = [] + append = False + property_name = frappe.db.get_value("Lease", invoice["lease"], "property") + invoice["property_name"] = property_name + if invoice.total == invoice.foreign_total: + invoice.exchange_rate = get_exchange_rate("USD", default_currency, invoice.posting_date) + invoice.foreign_total = invoice.total / invoice.exchange_rate + + invoice_id = "'{invoice_id}'".format(invoice_id=invoice["invoice_id"]) + + query_items = f""" SELECT - item_code, - base_net_amount as item_total, + item_code, + base_net_amount as item_total, net_amount as item_foreign_total, - service_start_date as from_date, - service_end_date as to_date, - qty as quantity, + service_start_date as from_date, + service_end_date as to_date, + qty as quantity, net_rate FROM `tabSales Invoice Item` WHERE parent = {invoice_id} - """.format( - invoice_id=invoice_id - ) - - items = frappe.db.sql(query_items, as_dict=True) - for item in items: - item_group = frappe.db.get_value("Item", item["item_code"], "item_group") - item["item_group"] = item_group - item.item_foreign_total = flt(item.item_foreign_total, float_precision) - item.item_total = flt(item.item_total, float_precision) - if item.item_foreign_total == item.item_total: - item.item_foreign_total = item.item_total / invoice.exchange_rate - months_obj = calculate_monthly_ammount( - item.item_total, - default_currency, - item.from_date, - item.to_date, - item.item_foreign_total, - filters.get("foreign_currency"), - filters, - ) - if months_obj: - for key, value in months_obj.items(): - item[key] = value - if _items_grupe == "All Item Groups": - _items_rwos.append(item) - append = True - elif _items_grupe == item_group: - _items_rwos.append(item) - append = True - if filters.get("foreign_currency"): - item.item_total = flt(item.item_foreign_total, float_precision) - else: - item.item_total = flt(item.item_total, float_precision) - if append: - for item in _items_rwos: - item.update(invoice) - rows.append(item) - - return rows + """ + + items = frappe.db.sql(query_items, as_dict=True) + for item in items: + item_group = frappe.db.get_value("Item", item["item_code"], "item_group") + item["item_group"] = item_group + item.item_foreign_total = flt(item.item_foreign_total, float_precision) + item.item_total = flt(item.item_total, float_precision) + if item.item_foreign_total == item.item_total: + item.item_foreign_total = item.item_total / invoice.exchange_rate + months_obj = calculate_monthly_ammount( + item.item_total, + default_currency, + item.from_date, + item.to_date, + item.item_foreign_total, + filters.get("foreign_currency"), + filters, + ) + if months_obj: + for key, value in months_obj.items(): + item[key] = value + if _items_grupe == "All Item Groups": + _items_rwos.append(item) + append = True + elif _items_grupe == item_group: + _items_rwos.append(item) + append = True + if filters.get("foreign_currency"): + item.item_total = flt(item.item_foreign_total, float_precision) + else: + item.item_total = flt(item.item_total, float_precision) + if append: + for item in _items_rwos: + item.update(invoice) + rows.append(item) + + return rows def get_columns(filters): - if filters.get("company"): - currency = get_company_currency(filters["company"]) - else: - company = get_default_company() - currency = get_company_currency(company) - - if filters.get("foreign_currency"): - _foreign_currency = filters["foreign_currency"] - else: - _foreign_currency = currency - - if _foreign_currency == currency: - foreign_currency = "Foreign" - else: - foreign_currency = filters["foreign_currency"] - - columns = [ - { - "label": "Property", - "fieldname": "property_name", - "fieldtype": "Link", - "options": "Property", - "width": 100, - }, - { - "label": "Customer", - "fieldname": "customer", - "fieldtype": "Link", - "options": "Customer", - "width": 100, - }, - { - "label": "Lease", - "fieldname": "lease", - "fieldtype": "Link", - "options": "Lease", - "width": 100, - }, - { - "label": "Advance Before {0}".format(_foreign_currency), - "fieldname": "advance_before", - "fieldtype": "Float", - "width": 100, - }, - { - "label": "Invoice", - "fieldname": "invoice_id", - "fieldtype": "Link", - "options": "Sales Invoice", - "width": 150, - }, - { - "label": "Date", - "fieldname": "date", - "fieldtype": "date", - "width": 100, - }, - # { - # "label": "Total {0}".format(currency), - # "fieldname": "total", - # "fieldtype": "Float", - # "width": 100, - # }, - { - "label": "Exchange Rate", - "fieldname": "exchange_rate", - "fieldtype": "Float", - "width": 100, - }, - # { - # "label": "Total {0}".format(foreign_currency or "Foreign"), - # "fieldname": "foreign_total", - # "fieldtype": "Float", - # "width": 100, - # }, - { - "label": "Item", - "fieldname": "item_code", - "fieldtype": "Link", - "options": "Item", - "width": 100, - }, - { - "label": "Quantity", - "fieldname": "quantity", - "width": 75, - }, - { - "label": "Item Total {0}".format(_foreign_currency), - "fieldname": "item_total", - "fieldtype": "Float", - "width": 100, - }, - { - "label": "From Date", - "fieldname": "from_date", - "fieldtype": "date", - "width": 100, - }, - { - "label": "To Date", - "fieldname": "to_date", - "fieldtype": "date", - "width": 100, - }, - ] - - months_list = get_months(filters["from_date"], filters["to_date"]) - - for month in months_list: - columns.append( - { - "label": "{0} {1}".format(month, currency), - "fieldname": "{0} {1}".format(month.lower(), currency), - "fieldtype": "Float", - "width": 100, - } - ) - if ( - filters.get("foreign_currency") - and filters.get("foreign_currency") != currency - ): - columns.append( - { - "label": "{0} {1}".format(month, filters.get("foreign_currency")), - "fieldname": "{0} {1}".format( - month.lower(), filters.get("foreign_currency") - ), - "fieldtype": "Float", - "width": 100, - } - ) - - columns.append( - { - "label": "Advance After {0}".format(_foreign_currency), - "fieldname": "advance_after", - "fieldtype": "Float", - "width": 100, - } - ) - - return columns + if filters.get("company"): + currency = get_company_currency(filters["company"]) + else: + company = get_default_company() + currency = get_company_currency(company) + + if filters.get("foreign_currency"): + _foreign_currency = filters["foreign_currency"] + else: + _foreign_currency = currency + + columns = [ + { + "label": "Property", + "fieldname": "property_name", + "fieldtype": "Link", + "options": "Property", + "width": 100, + }, + { + "label": "Customer", + "fieldname": "customer", + "fieldtype": "Link", + "options": "Customer", + "width": 100, + }, + { + "label": "Lease", + "fieldname": "lease", + "fieldtype": "Link", + "options": "Lease", + "width": 100, + }, + { + "label": f"Advance Before {_foreign_currency}", + "fieldname": "advance_before", + "fieldtype": "Float", + "width": 100, + }, + { + "label": "Invoice", + "fieldname": "invoice_id", + "fieldtype": "Link", + "options": "Sales Invoice", + "width": 150, + }, + { + "label": "Date", + "fieldname": "date", + "fieldtype": "date", + "width": 100, + }, + # { + # "label": "Total {0}".format(currency), + # "fieldname": "total", + # "fieldtype": "Float", + # "width": 100, + # }, + { + "label": "Exchange Rate", + "fieldname": "exchange_rate", + "fieldtype": "Float", + "width": 100, + }, + # { + # "label": "Total {0}".format(foreign_currency or "Foreign"), + # "fieldname": "foreign_total", + # "fieldtype": "Float", + # "width": 100, + # }, + { + "label": "Item", + "fieldname": "item_code", + "fieldtype": "Link", + "options": "Item", + "width": 100, + }, + { + "label": "Quantity", + "fieldname": "quantity", + "width": 75, + }, + { + "label": f"Item Total {_foreign_currency}", + "fieldname": "item_total", + "fieldtype": "Float", + "width": 100, + }, + { + "label": "From Date", + "fieldname": "from_date", + "fieldtype": "date", + "width": 100, + }, + { + "label": "To Date", + "fieldname": "to_date", + "fieldtype": "date", + "width": 100, + }, + ] + + months_list = get_months(filters["from_date"], filters["to_date"]) + + for month in months_list: + columns.append( + { + "label": f"{month} {currency}", + "fieldname": f"{month.lower()} {currency}", + "fieldtype": "Float", + "width": 100, + } + ) + if filters.get("foreign_currency") and filters.get("foreign_currency") != currency: + columns.append( + { + "label": "{} {}".format(month, filters.get("foreign_currency")), + "fieldname": "{} {}".format(month.lower(), filters.get("foreign_currency")), + "fieldtype": "Float", + "width": 100, + } + ) + + columns.append( + { + "label": f"Advance After {_foreign_currency}", + "fieldname": "advance_after", + "fieldtype": "Float", + "width": 100, + } + ) + + return columns def get_months(from_date, to_date): - months_list = [] - dates = [from_date, to_date] - start, end = [datetime.strptime(_, "%Y-%m-%d") for _ in dates] - months_obj = OrderedDict( - ((start + timedelta(_)).strftime(r"%b-%y"), None) - for _ in range((end - start).days) - ) - for key, value in months_obj.items(): - months_list.append(key) - return months_list + months_list = [] + dates = [from_date, to_date] + start, end = [datetime.strptime(_, "%Y-%m-%d") for _ in dates] + months_obj = OrderedDict( + ((start + timedelta(_)).strftime(r"%b-%y"), None) for _ in range((end - start).days) + ) + for key, _value in months_obj.items(): + months_list.append(key) + return months_list def check_full_month(from_date, to_date): - month_start_day = get_first_day(from_date) - month_end_day = get_last_day(from_date) - if from_date == month_start_day and to_date == month_end_day: - return True - else: - return False + month_start_day = get_first_day(from_date) + month_end_day = get_last_day(from_date) + if from_date == month_start_day and to_date == month_end_day: + return True + else: + return False def calculate_monthly_ammount( - ammount, - default_currency, - from_date, - to_date, - foreign_ammount, - foreign_currency=None, - filters=None, + ammount, + default_currency, + from_date, + to_date, + foreign_ammount, + foreign_currency=None, + filters=None, ): - float_precision = cint(frappe.db.get_default("float_precision")) or 2 - months_report_list = [] - for month in get_months(filters["from_date"], filters["to_date"]): - months_report_list.append("{0} {1}".format(month.lower(), default_currency)) - if ( - filters.get("foreign_currency") - and filters.get("foreign_currency") != default_currency - ): - months_report_list.append( - "{0} {1}".format(month.lower(), filters.get("foreign_currency")) - ) - if ammount and from_date and to_date: - monthly_ammount_obj = {} - days = 0 - date = from_date - end_date = to_date - field_list = [] - field_list_foreign = [] - first_last = 0 - first_last_foreign = 0 - sub_ammount = 0 - sub_ammount_foreign = 0 - # days_list= [] - - while date <= end_date: - start_month = getdate(date).month - end_month = getdate(to_date).month - - if start_month == end_month: - last_day = end_date - days_diff = date_diff(last_day, date) + 1 - if check_full_month(date, last_day): - days_diff = 30 - days += days_diff - # days_list.append(days_diff) - if date == last_day: - last_day = add_days(last_day, 1) - month_filed = (get_months(str(date), str(last_day))[0]).lower() - month_len = date_diff(get_last_day(date), get_first_day(date)) - field_list.append( - { - "days_diff": days_diff, - "month_filed": "{0} {1}".format(month_filed, default_currency), - "month_len": month_len, - "foreign": False, - } - ) - if foreign_currency and foreign_currency != default_currency: - field_list_foreign.append( - { - "days_diff": days_diff, - "month_filed": "{0} {1}".format( - month_filed, foreign_currency - ), - "month_len": month_len, - "foreign": True, - } - ) - date = get_first_day(add_months(date, 1)) - - else: - last_day = get_last_day(date) - days_diff = date_diff(last_day, date) + 1 - if check_full_month(date, last_day): - days_diff = 30 - days += days_diff - # days_list.append(days_diff) - if date == last_day: - last_day = add_days(last_day, 1) - month_filed = (get_months(str(date), str(last_day))[0]).lower() - month_len = date_diff(get_last_day(date), get_first_day(date)) - field_list.append( - { - "days_diff": days_diff, - "month_filed": "{0} {1}".format(month_filed, default_currency), - "month_len": month_len, - "foreign": False, - } - ) - if foreign_currency and foreign_currency != default_currency: - field_list_foreign.append( - { - "days_diff": days_diff, - "month_filed": "{0} {1}".format( - month_filed, foreign_currency - ), - "month_len": month_len, - "foreign": True, - } - ) - date = get_first_day(add_months(date, 1)) - - if floor(days / 30) != (days / 30) and (floor(days / 30) * 30 + 6) < days: - days = (floor(days / 30) + 1) * 30 - elif floor(days / 30) != (days / 30) and floor(days / 30) * 30 < days: - days = floor(days / 30) * 30 - - daily_ammount = 0 if days == 0 else ammount / (days) - daily_ammount_foreign = 0 if days == 0 else foreign_ammount / (days) - - m = 1 - for i in field_list: - if m == 1 and i["days_diff"] < 30: - first_last += i["days_diff"] - elif m == len(field_list) and i["days_diff"] < 30: - first_last += i["days_diff"] - else: - sub_ammount += i["days_diff"] * daily_ammount - m += 1 - - m = 1 - for i in field_list_foreign: - if m == 1 and i["days_diff"] < 30: - first_last_foreign += i["days_diff"] - elif m == len(field_list) and i["days_diff"] < 30: - first_last_foreign += i["days_diff"] - else: - sub_ammount_foreign += i["days_diff"] * daily_ammount_foreign - m += 1 - - n = 1 - for i in field_list_foreign: - if n == 1 and i["days_diff"] < 30: - monthly_ammount_obj[i["month_filed"]] = flt( - i["days_diff"] - * ((foreign_ammount - sub_ammount_foreign) / first_last), - float_precision, - ) - elif n == len(field_list) and i["days_diff"] < 30: - monthly_ammount_obj[i["month_filed"]] = flt( - i["days_diff"] - * ((foreign_ammount - sub_ammount_foreign) / first_last), - float_precision, - ) - else: - monthly_ammount_obj[i["month_filed"]] = flt( - i["days_diff"] * daily_ammount_foreign, float_precision - ) - n += 1 - - n = 1 - for i in field_list: - if n == 1 and i["days_diff"] < 30: - monthly_ammount_obj[i["month_filed"]] = flt( - i["days_diff"] * ((ammount - sub_ammount) / first_last), - float_precision, - ) - elif n == len(field_list) and i["days_diff"] < 30: - monthly_ammount_obj[i["month_filed"]] = flt( - i["days_diff"] * ((ammount - sub_ammount) / first_last), - float_precision, - ) - else: - monthly_ammount_obj[i["month_filed"]] = flt( - i["days_diff"] * daily_ammount, float_precision - ) - n += 1 - advance_before = 0 - advance_after = 0 - first_month = datetime.strptime( - months_report_list[0][:6].replace("-", " 20"), "%b %Y" - ) - last_month = datetime.strptime( - months_report_list[-1][:6].replace("-", " 20"), "%b %Y" - ) - for key, value in monthly_ammount_obj.items(): - key_currency = key[7:] - currency = foreign_currency or default_currency - if key not in months_report_list and key_currency == currency: - mydate = datetime.strptime(str(key)[:6].replace("-", " 20"), "%b %Y") - if mydate > last_month: - advance_after += value - elif mydate < first_month: - advance_before += value - if advance_after: - monthly_ammount_obj["advance_after"] = advance_after - if advance_before: - monthly_ammount_obj["advance_before"] = advance_before - return monthly_ammount_obj + float_precision = cint(frappe.db.get_default("float_precision")) or 2 + months_report_list = [] + for month in get_months(filters["from_date"], filters["to_date"]): + months_report_list.append(f"{month.lower()} {default_currency}") + if filters.get("foreign_currency") and filters.get("foreign_currency") != default_currency: + months_report_list.append("{} {}".format(month.lower(), filters.get("foreign_currency"))) + if ammount and from_date and to_date: + monthly_ammount_obj = {} + days = 0 + date = from_date + end_date = to_date + field_list = [] + field_list_foreign = [] + first_last = 0 + first_last_foreign = 0 + sub_ammount = 0 + sub_ammount_foreign = 0 + # days_list= [] + + while date <= end_date: + start_month = getdate(date).month + end_month = getdate(to_date).month + + if start_month == end_month: + last_day = end_date + days_diff = date_diff(last_day, date) + 1 + if check_full_month(date, last_day): + days_diff = 30 + days += days_diff + # days_list.append(days_diff) + if date == last_day: + last_day = add_days(last_day, 1) + month_filed = (get_months(str(date), str(last_day))[0]).lower() + month_len = date_diff(get_last_day(date), get_first_day(date)) + field_list.append( + { + "days_diff": days_diff, + "month_filed": f"{month_filed} {default_currency}", + "month_len": month_len, + "foreign": False, + } + ) + if foreign_currency and foreign_currency != default_currency: + field_list_foreign.append( + { + "days_diff": days_diff, + "month_filed": f"{month_filed} {foreign_currency}", + "month_len": month_len, + "foreign": True, + } + ) + date = get_first_day(add_months(date, 1)) + + else: + last_day = get_last_day(date) + days_diff = date_diff(last_day, date) + 1 + if check_full_month(date, last_day): + days_diff = 30 + days += days_diff + # days_list.append(days_diff) + if date == last_day: + last_day = add_days(last_day, 1) + month_filed = (get_months(str(date), str(last_day))[0]).lower() + month_len = date_diff(get_last_day(date), get_first_day(date)) + field_list.append( + { + "days_diff": days_diff, + "month_filed": f"{month_filed} {default_currency}", + "month_len": month_len, + "foreign": False, + } + ) + if foreign_currency and foreign_currency != default_currency: + field_list_foreign.append( + { + "days_diff": days_diff, + "month_filed": f"{month_filed} {foreign_currency}", + "month_len": month_len, + "foreign": True, + } + ) + date = get_first_day(add_months(date, 1)) + + if floor(days / 30) != (days / 30) and (floor(days / 30) * 30 + 6) < days: + days = (floor(days / 30) + 1) * 30 + elif floor(days / 30) != (days / 30) and floor(days / 30) * 30 < days: + days = floor(days / 30) * 30 + + daily_ammount = 0 if days == 0 else ammount / (days) + daily_ammount_foreign = 0 if days == 0 else foreign_ammount / (days) + + m = 1 + for i in field_list: + if m == 1 and i["days_diff"] < 30: + first_last += i["days_diff"] + elif m == len(field_list) and i["days_diff"] < 30: + first_last += i["days_diff"] + else: + sub_ammount += i["days_diff"] * daily_ammount + m += 1 + + m = 1 + for i in field_list_foreign: + if m == 1 and i["days_diff"] < 30: + first_last_foreign += i["days_diff"] + elif m == len(field_list) and i["days_diff"] < 30: + first_last_foreign += i["days_diff"] + else: + sub_ammount_foreign += i["days_diff"] * daily_ammount_foreign + m += 1 + + n = 1 + for i in field_list_foreign: + if n == 1 and i["days_diff"] < 30: + monthly_ammount_obj[i["month_filed"]] = flt( + i["days_diff"] * ((foreign_ammount - sub_ammount_foreign) / first_last), + float_precision, + ) + elif n == len(field_list) and i["days_diff"] < 30: + monthly_ammount_obj[i["month_filed"]] = flt( + i["days_diff"] * ((foreign_ammount - sub_ammount_foreign) / first_last), + float_precision, + ) + else: + monthly_ammount_obj[i["month_filed"]] = flt( + i["days_diff"] * daily_ammount_foreign, float_precision + ) + n += 1 + + n = 1 + for i in field_list: + if n == 1 and i["days_diff"] < 30: + monthly_ammount_obj[i["month_filed"]] = flt( + i["days_diff"] * ((ammount - sub_ammount) / first_last), + float_precision, + ) + elif n == len(field_list) and i["days_diff"] < 30: + monthly_ammount_obj[i["month_filed"]] = flt( + i["days_diff"] * ((ammount - sub_ammount) / first_last), + float_precision, + ) + else: + monthly_ammount_obj[i["month_filed"]] = flt(i["days_diff"] * daily_ammount, float_precision) + n += 1 + advance_before = 0 + advance_after = 0 + first_month = datetime.strptime(months_report_list[0][:6].replace("-", " 20"), "%b %Y") + last_month = datetime.strptime(months_report_list[-1][:6].replace("-", " 20"), "%b %Y") + for key, value in monthly_ammount_obj.items(): + key_currency = key[7:] + currency = foreign_currency or default_currency + if key not in months_report_list and key_currency == currency: + mydate = datetime.strptime(str(key)[:6].replace("-", " 20"), "%b %Y") + if mydate > last_month: + advance_after += value + elif mydate < first_month: + advance_before += value + if advance_after: + monthly_ammount_obj["advance_after"] = advance_after + if advance_before: + monthly_ammount_obj["advance_before"] = advance_before + return monthly_ammount_obj diff --git a/propms/property_management_solution/report/security_attendance_report/security_attendance_report.html b/propms/property_management_solution/report/security_attendance_report/security_attendance_report.html index b5eb3622..76cbf855 100755 --- a/propms/property_management_solution/report/security_attendance_report/security_attendance_report.html +++ b/propms/property_management_solution/report/security_attendance_report/security_attendance_report.html @@ -45,4 +45,4 @@

    - \ No newline at end of file + diff --git a/propms/property_management_solution/report/security_deposit/security_deposit.js b/propms/property_management_solution/report/security_deposit/security_deposit.js index a2c3f683..c4f18372 100644 --- a/propms/property_management_solution/report/security_deposit/security_deposit.js +++ b/propms/property_management_solution/report/security_deposit/security_deposit.js @@ -10,7 +10,7 @@ frappe.query_reports["Security Deposit"] = { "label": __("Account"), "fieldtype": "Link", "options": "Account", - "default": "21401 - Security Deposit Commercial - VPL", + "default": "21401 - Security Deposit Commercial - VPL", "get_query": function() { return { "query": "erpnext.controllers.queries.get_account_list", @@ -22,4 +22,4 @@ frappe.query_reports["Security Deposit"] = { } }, ] -} \ No newline at end of file +} diff --git a/propms/property_management_solution/report/subscription_service_report/subscription_service_report.js b/propms/property_management_solution/report/subscription_service_report/subscription_service_report.js index 76e00b25..fe7e5e6b 100644 --- a/propms/property_management_solution/report/subscription_service_report/subscription_service_report.js +++ b/propms/property_management_solution/report/subscription_service_report/subscription_service_report.js @@ -18,7 +18,7 @@ frappe.query_reports["Subscription Service Report"] = { } } } - }, + }, { "fieldname":"to_date", "label": __("To Date"), diff --git a/propms/property_management_solution/report/utility_invoices/other_methods.py b/propms/property_management_solution/report/utility_invoices/other_methods.py index b3edd4f5..8b3fd06e 100644 --- a/propms/property_management_solution/report/utility_invoices/other_methods.py +++ b/propms/property_management_solution/report/utility_invoices/other_methods.py @@ -1,205 +1,186 @@ -import frappe, calendar -from frappe import _ +import calendar from datetime import date, timedelta +import frappe +from frappe import _ + def get_residential_columns(year): - columns = [ - { - "fieldname": "apartment_no", - "label": _("Apartment No."), - "fieldtype": "Data", - "width": 150, - }, - { - "fieldname": "client", - "label": _("Client"), - "fieldtype": "Data", - "width": 150, - }, - { - "fieldname": "advance_prev_year", - "label": _("Advance RECD in 2019"), - "fieldtype": "Data", - "width": 150, - }, - { - "fieldname": "invoice_no", - "label": _("Invoice No."), - "fieldtype": "Link", - "options": "Sales Invoice", - "width": 150, - }, - {"fieldname": "from", "label": _("From"), "fieldtype": "Data", "width": 150}, - {"fieldname": "to", "label": _("To"), "fieldtype": "Data", "width": 150}, - { - "fieldname": "invoice_amount", - "label": _("Invoice Amount"), - "fieldtype": "Data", - "width": 150, - }, - ] - months = months_array() - for i in months: - columns.append( - { - "fieldname": i.lower(), - "label": i + " " + str(year), - "fieldtype": "Data", - "width": 150, - } - ) - - return columns + columns = [ + { + "fieldname": "apartment_no", + "label": _("Apartment No."), + "fieldtype": "Data", + "width": 150, + }, + { + "fieldname": "client", + "label": _("Client"), + "fieldtype": "Data", + "width": 150, + }, + { + "fieldname": "advance_prev_year", + "label": _("Advance RECD in 2019"), + "fieldtype": "Data", + "width": 150, + }, + { + "fieldname": "invoice_no", + "label": _("Invoice No."), + "fieldtype": "Link", + "options": "Sales Invoice", + "width": 150, + }, + {"fieldname": "from", "label": _("From"), "fieldtype": "Data", "width": 150}, + {"fieldname": "to", "label": _("To"), "fieldtype": "Data", "width": 150}, + { + "fieldname": "invoice_amount", + "label": _("Invoice Amount"), + "fieldtype": "Data", + "width": 150, + }, + ] + months = months_array() + for i in months: + columns.append( + { + "fieldname": i.lower(), + "label": i + " " + str(year), + "fieldtype": "Data", + "width": 150, + } + ) + + return columns def get_utility_sales_invoice(data, from_other=None, months=None): - total = {} - lease_item = "'Utility Charges' " - query = """ SELECT * FROM `tabSales Invoice` AS SI WHERE EXISTS (SELECT * FROM `tabSales Invoice Item` AS SIT WHERE SIT.item_code = {0} and SIT.parent = SI.name ) + total = {} + lease_item = "'Utility Charges' " + query = f""" SELECT * FROM `tabSales Invoice` AS SI WHERE EXISTS (SELECT * FROM `tabSales Invoice Item` AS SIT WHERE SIT.item_code = {lease_item} and SIT.parent = SI.name ) and SI.docstatus=%s - ORDER by SI.customer,SI.from_date ASC""".format( - lease_item - ) % ( - 1 - ) - - sales_invoices = frappe.db.sql(query, as_dict=True) - previuos_customer = "" - for i in sales_invoices: - lease = frappe.get_value("Lease", i.lease, "property") - obj = { - "apartment_no": lease, - "client": i.customer, - "advance_prev_year": "", - "invoice_no": i.name, - "from": i.from_date if i.from_date else i.posting_date, - "to": i.to_date - timedelta(days=1) if i.to_date else i.posting_date, - "invoice_amount": i.total, - } - set_monthly_amount( - i.from_date, - i.to_date - timedelta(days=1) if i.to_date else "", - obj, - total, - months, - ) - if previuos_customer != i.customer: - data.append({}) - previuos_customer = i.customer - data.append(obj) - if from_other: - data.append(total) + ORDER by SI.customer,SI.from_date ASC""" % (1) + + sales_invoices = frappe.db.sql(query, as_dict=True) + previuos_customer = "" + for i in sales_invoices: + lease = frappe.get_value("Lease", i.lease, "property") + obj = { + "apartment_no": lease, + "client": i.customer, + "advance_prev_year": "", + "invoice_no": i.name, + "from": i.from_date if i.from_date else i.posting_date, + "to": i.to_date - timedelta(days=1) if i.to_date else i.posting_date, + "invoice_amount": i.total, + } + set_monthly_amount( + i.from_date, + i.to_date - timedelta(days=1) if i.to_date else "", + obj, + total, + months, + ) + if previuos_customer != i.customer: + data.append({}) + previuos_customer = i.customer + data.append(obj) + if from_other: + data.append(total) def set_monthly_amount(start_date, end_date, obj, total, months): - rate = get_rate(obj["invoice_no"]) - if end_date and rate: - check_dates(start_date, end_date, rate, obj, total, months) + rate = get_rate(obj["invoice_no"]) + if end_date and rate: + check_dates(start_date, end_date, rate, obj, total, months) def check_dates(start_date, end_date, rate, obj, total, months): - start = start_date - no_minus = 0 - while start < end_date: - month_string = start.strftime("%b") - month_no_of_days = calendar.monthrange(start.year, start.month)[1] - last_date = date(start.year, start.month, month_no_of_days) - if (last_date - start).days >= 29 or ( - month_string == "Feb" and (last_date - start).days >= 27 - ): - if start.year == start_date.year: - obj[month_string.lower()] = round(rate, 2) - if months and month_string.lower() in months: - total[month_string.lower()] = ( - round(rate, 2) + round(total[month_string.lower()], 2) - if month_string.lower() in total - else round(rate, 2) - ) - else: - if start.year == start_date.year: - obj[month_string.lower()] = round( - round(rate / month_no_of_days, 2) - * (month_no_of_days - int(start.day)), - 2, - ) - if months and month_string.lower() in months: - total[month_string.lower()] = ( - round( - round(rate / month_no_of_days, 2) - * (month_no_of_days - int(start.day)), - 2, - ) - + round(total[month_string.lower()], 2) - if month_string.lower() in total - else round( - round(rate / month_no_of_days, 2) - * (month_no_of_days - int(start.day)), - 2, - ) - ) - no_minus = month_no_of_days - start += timedelta(days=month_no_of_days) - - start_last = start - timedelta(days=no_minus) - - if (end_date - start_last).days > 0: - if start_last.year == start_date.year: - month_string = end_date.strftime("%b") - month_no_of_days = calendar.monthrange(end_date.year, end_date.month)[1] - if int(end_date.day) >= 29 or ( - month_string == "Feb" and (end_date - start_last).days >= 27 - ): - obj[month_string.lower()] = round(rate, 2) - if months and month_string.lower() in months: - total[month_string.lower()] = ( - round(rate, 2) + round(total[month_string.lower()], 2) - if month_string.lower() in total - else round(rate, 2) - ) - else: - obj[month_string.lower()] = round( - round(rate / month_no_of_days, 2) * (int(end_date.day)), 2 - ) - if months and month_string.lower() in months: - total[month_string.lower()] = ( - round( - round(rate / month_no_of_days, 2) * (int(end_date.day)), 2 - ) - + round(total[month_string.lower()], 2) - if month_string.lower() in total - else round( - round(rate / month_no_of_days, 2) * (int(end_date.day)), 2 - ) - ) + start = start_date + no_minus = 0 + while start < end_date: + month_string = start.strftime("%b") + month_no_of_days = calendar.monthrange(start.year, start.month)[1] + last_date = date(start.year, start.month, month_no_of_days) + if (last_date - start).days >= 29 or (month_string == "Feb" and (last_date - start).days >= 27): + if start.year == start_date.year: + obj[month_string.lower()] = round(rate, 2) + if months and month_string.lower() in months: + total[month_string.lower()] = ( + round(rate, 2) + round(total[month_string.lower()], 2) + if month_string.lower() in total + else round(rate, 2) + ) + else: + if start.year == start_date.year: + obj[month_string.lower()] = round( + round(rate / month_no_of_days, 2) * (month_no_of_days - int(start.day)), + 2, + ) + if months and month_string.lower() in months: + total[month_string.lower()] = ( + round( + round(rate / month_no_of_days, 2) * (month_no_of_days - int(start.day)), + 2, + ) + + round(total[month_string.lower()], 2) + if month_string.lower() in total + else round( + round(rate / month_no_of_days, 2) * (month_no_of_days - int(start.day)), + 2, + ) + ) + no_minus = month_no_of_days + start += timedelta(days=month_no_of_days) + + start_last = start - timedelta(days=no_minus) + + if (end_date - start_last).days > 0: + if start_last.year == start_date.year: + month_string = end_date.strftime("%b") + month_no_of_days = calendar.monthrange(end_date.year, end_date.month)[1] + if int(end_date.day) >= 29 or (month_string == "Feb" and (end_date - start_last).days >= 27): + obj[month_string.lower()] = round(rate, 2) + if months and month_string.lower() in months: + total[month_string.lower()] = ( + round(rate, 2) + round(total[month_string.lower()], 2) + if month_string.lower() in total + else round(rate, 2) + ) + else: + obj[month_string.lower()] = round(round(rate / month_no_of_days, 2) * (int(end_date.day)), 2) + if months and month_string.lower() in months: + total[month_string.lower()] = ( + round(round(rate / month_no_of_days, 2) * (int(end_date.day)), 2) + + round(total[month_string.lower()], 2) + if month_string.lower() in total + else round(round(rate / month_no_of_days, 2) * (int(end_date.day)), 2) + ) def months_array(): - return [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ] + return [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ] def get_rate(invoice_name): - filters_value = "and item_code = 'Utility Charges'" - query = """ SELECT rate FROM `tabSales Invoice Item` WHERE {0} {1}""".format( - "parent = '" + invoice_name + "' ", filters_value - ) - print(query) - - return ( - frappe.db.sql(query, as_dict=True)[0].rate - if len(frappe.db.sql(query, as_dict=True)) > 0 - else "" - ) + filters_value = "and item_code = 'Utility Charges'" + query = """ SELECT rate FROM `tabSales Invoice Item` WHERE {} {}""".format( + "parent = '" + invoice_name + "' ", filters_value + ) + print(query) + + return frappe.db.sql(query, as_dict=True)[0].rate if len(frappe.db.sql(query, as_dict=True)) > 0 else "" diff --git a/propms/property_management_solution/report/utility_invoices/utility_invoices.py b/propms/property_management_solution/report/utility_invoices/utility_invoices.py index 141dff09..0f90f704 100644 --- a/propms/property_management_solution/report/utility_invoices/utility_invoices.py +++ b/propms/property_management_solution/report/utility_invoices/utility_invoices.py @@ -1,15 +1,14 @@ # Copyright (c) 2013, Aakvatech and contributors # For license information, please see license.txt -from __future__ import unicode_literals -from .other_methods import get_residential_columns -from .other_methods import get_utility_sales_invoice + +from .other_methods import get_residential_columns, get_utility_sales_invoice def execute(filters=None): - columns, data = [], [] - if filters.get("year"): - columns = get_residential_columns(filters.get("year")) - get_utility_sales_invoice(data) + columns, data = [], [] + if filters.get("year"): + columns = get_residential_columns(filters.get("year")) + get_utility_sales_invoice(data) - return columns, data + return columns, data diff --git a/propms/property_management_solution/report/withholding_tax_summary_on_sales_(properties)/withholding_tax_summary_on_sales_(properties).js b/propms/property_management_solution/report/withholding_tax_summary_on_sales_(properties)/withholding_tax_summary_on_sales_(properties).js index 550d84d7..557a30d8 100644 --- a/propms/property_management_solution/report/withholding_tax_summary_on_sales_(properties)/withholding_tax_summary_on_sales_(properties).js +++ b/propms/property_management_solution/report/withholding_tax_summary_on_sales_(properties)/withholding_tax_summary_on_sales_(properties).js @@ -17,4 +17,4 @@ frappe.query_reports["Withholding Tax Summary on Sales (Properties)"] = { "default": frappe.datetime.get_today(), }, ] -}; \ No newline at end of file +}; diff --git a/propms/property_management_solution/sales_invoice.js b/propms/property_management_solution/sales_invoice.js index 911d804d..6735c05d 100644 --- a/propms/property_management_solution/sales_invoice.js +++ b/propms/property_management_solution/sales_invoice.js @@ -37,4 +37,4 @@ frappe.ui.form.on('Sales Invoice', { frappe.model.set_value(cdt, cdn, "customer", ""); } } -}) \ No newline at end of file +}) diff --git a/propms/propms-gitlab.sh b/propms/propms-gitlab.sh index fcfe2b22..20b396ca 100755 --- a/propms/propms-gitlab.sh +++ b/propms/propms-gitlab.sh @@ -13,4 +13,3 @@ cd apps/propms git add . git commit -m "$1" git push upstream master - diff --git a/propms/utils/create_custom_fields.py b/propms/utils/create_custom_fields.py index dcbe1a01..16ac3ac4 100644 --- a/propms/utils/create_custom_fields.py +++ b/propms/utils/create_custom_fields.py @@ -8,71 +8,65 @@ def load_json(file): - CURR_DIR = os.path.abspath(os.path.dirname(__file__)) - json_file_path = os.path.join(CURR_DIR, folder, file) - # TODO do not load the file if already applied - with open(json_file_path, "r") as file: - data = json.load(file) - return data + CURR_DIR = os.path.abspath(os.path.dirname(__file__)) + json_file_path = os.path.join(CURR_DIR, folder, file) + # TODO do not load the file if already applied + with open(json_file_path) as file: + data = json.load(file) + return data def create_fields_from_json(custom_fields_obj): - disallowed_fields = [ - "name", - "owner", - "creation", - "modified", - "modified_by", - "docstatus", - "idx", - "is_system_generated", - "__last_sync_on", - ] - doctype_custom_fields_dict = {} - - for custom_field in custom_fields_obj: - doctype = custom_field["dt"] - all_fields = frappe.get_meta("Custom Field").get_valid_columns() - field_list = set(all_fields).difference(disallowed_fields) - custom_field_dict = {} - for field_name in field_list: - custom_field_dict[field_name] = custom_field.get(field_name) - - # Ensure the list for the doctype is initialized - if doctype not in doctype_custom_fields_dict: - doctype_custom_fields_dict[doctype] = [] - - doctype_custom_fields_dict[doctype].append(custom_field_dict) - - create_custom_fields(doctype_custom_fields_dict, update=False) + disallowed_fields = [ + "name", + "owner", + "creation", + "modified", + "modified_by", + "docstatus", + "idx", + "is_system_generated", + "__last_sync_on", + ] + doctype_custom_fields_dict = {} + + for custom_field in custom_fields_obj: + doctype = custom_field["dt"] + all_fields = frappe.get_meta("Custom Field").get_valid_columns() + field_list = set(all_fields).difference(disallowed_fields) + custom_field_dict = {} + for field_name in field_list: + custom_field_dict[field_name] = custom_field.get(field_name) + + # Ensure the list for the doctype is initialized + if doctype not in doctype_custom_fields_dict: + doctype_custom_fields_dict[doctype] = [] + + doctype_custom_fields_dict[doctype].append(custom_field_dict) + + create_custom_fields(doctype_custom_fields_dict, update=False) def execute(): - # read names of only json files in this folder and put it into files list - files = list( - filter( - lambda x: x.endswith(".json"), - os.listdir( - os.path.join(os.path.abspath(os.path.dirname(__file__)), folder) - ), - ) - ) - for file in files: - data = load_json(file) - create_fields_from_json(data) + # read names of only json files in this folder and put it into files list + files = list( + filter( + lambda x: x.endswith(".json"), + os.listdir(os.path.join(os.path.abspath(os.path.dirname(__file__)), folder)), + ) + ) + for file in files: + data = load_json(file) + create_fields_from_json(data) @frappe.whitelist() def export_custom_fields(docnames): - docnames = frappe.parse_json(docnames) - custom_fields = [] - - for docname in docnames: - doc = frappe.get_doc("Custom Field", docname) - custom_fields.append( - doc.as_dict( - convert_dates_to_str=True, no_default_fields=True, no_nulls=True - ) - ) - - return str(custom_fields) \ No newline at end of file + docnames = frappe.parse_json(docnames) + custom_fields = [] + + for docname in docnames: + doc = frappe.get_doc("Custom Field", docname) + custom_fields.append(doc.as_dict(convert_dates_to_str=True, no_default_fields=True, no_nulls=True)) + + return str(custom_fields) diff --git a/propms/utils/create_property_setter.py b/propms/utils/create_property_setter.py index 6110fa57..fc214e49 100644 --- a/propms/utils/create_property_setter.py +++ b/propms/utils/create_property_setter.py @@ -8,88 +8,94 @@ def load_json(file): - CURR_DIR = os.path.abspath(os.path.dirname(__file__)) - json_file_path = os.path.join(CURR_DIR, folder, file) - with open(json_file_path, "r") as file: - data = json.load(file) - return data + CURR_DIR = os.path.abspath(os.path.dirname(__file__)) + json_file_path = os.path.join(CURR_DIR, folder, file) + with open(json_file_path) as file: + data = json.load(file) + return data def create_property_setter_from_json(property_setters_obj): - disallowed_fields = [ - "name", - "owner", - "creation", - "modified", - "modified_by", - "docstatus", - "idx", - "is_system_generated", - "__last_sync_on", - ] - - # Fetching existing setters using composite key (DocType, Field, Property) - existing_data = frappe.db.get_all("Property Setter", - fields=["name", "doc_type", "field_name", "property", "value", "property_type"], - page_length=20000 - ) - - # Create a mapping: {(doc_type, field_name, property): record} - existing_map = {} - for d in existing_data: - key = (d.doc_type, d.field_name or "", d.property) - existing_map[key] = d - - for property_setter in property_setters_obj: - doc_type = property_setter.get('doc_type') - field_name = property_setter.get('field_name') - prop = property_setter.get('property') - key = (doc_type, field_name or "", prop) - - name_in_json = property_setter.get('name') - - if key in existing_map: - existing = existing_map[key] - - # Normalizing values for comparison - old_val = str(existing.get('value') if existing.get('value') is not None else "") - new_val = str(property_setter.get('value') if property_setter.get('value') is not None else "") - - old_prop_type = str(existing.get('property_type') if existing.get('property_type') is not None else "") - new_prop_type = str(property_setter.get('property_type') if property_setter.get('property_type') is not None else "") - - if old_val.strip() == new_val.strip() and old_prop_type.strip() == new_prop_type.strip(): - continue - - if property_setter.get('doctype_or_field') == "DocType": - for_doctype = True - else: - for_doctype = False - - all_fields = frappe.get_meta("Property Setter").get_valid_columns() - field_list = set(all_fields).difference(disallowed_fields) - - property_setter_dict = {field: property_setter.get(field) for field in field_list if field in property_setter} - - make_property_setter( - doctype=property_setter_dict['doc_type'], - fieldname=property_setter_dict.get('field_name', None), - property=property_setter_dict['property'], - value=property_setter_dict['value'], - property_type=property_setter_dict['property_type'], - for_doctype=for_doctype - ) + disallowed_fields = [ + "name", + "owner", + "creation", + "modified", + "modified_by", + "docstatus", + "idx", + "is_system_generated", + "__last_sync_on", + ] + + # Fetching existing setters using composite key (DocType, Field, Property) + existing_data = frappe.db.get_all( + "Property Setter", + fields=["name", "doc_type", "field_name", "property", "value", "property_type"], + page_length=20000, + ) + + # Create a mapping: {(doc_type, field_name, property): record} + existing_map = {} + for d in existing_data: + key = (d.doc_type, d.field_name or "", d.property) + existing_map[key] = d + + for property_setter in property_setters_obj: + doc_type = property_setter.get("doc_type") + field_name = property_setter.get("field_name") + prop = property_setter.get("property") + key = (doc_type, field_name or "", prop) + + if key in existing_map: + existing = existing_map[key] + + # Normalizing values for comparison + old_val = str(existing.get("value") if existing.get("value") is not None else "") + new_val = str(property_setter.get("value") if property_setter.get("value") is not None else "") + + old_prop_type = str( + existing.get("property_type") if existing.get("property_type") is not None else "" + ) + new_prop_type = str( + property_setter.get("property_type") + if property_setter.get("property_type") is not None + else "" + ) + + if old_val.strip() == new_val.strip() and old_prop_type.strip() == new_prop_type.strip(): + continue + + if property_setter.get("doctype_or_field") == "DocType": + for_doctype = True + else: + for_doctype = False + + all_fields = frappe.get_meta("Property Setter").get_valid_columns() + field_list = set(all_fields).difference(disallowed_fields) + + property_setter_dict = { + field: property_setter.get(field) for field in field_list if field in property_setter + } + + make_property_setter( + doctype=property_setter_dict["doc_type"], + fieldname=property_setter_dict.get("field_name", None), + property=property_setter_dict["property"], + value=property_setter_dict["value"], + property_type=property_setter_dict["property_type"], + for_doctype=for_doctype, + ) + def execute(): - # read names of only json files in this folder and put it into files list - files = list( - filter( - lambda x: x.endswith(".json"), - os.listdir( - os.path.join(os.path.abspath(os.path.dirname(__file__)), folder) - ), - ) - ) - for file in files: - data = load_json(file) - create_property_setter_from_json(data) \ No newline at end of file + # read names of only json files in this folder and put it into files list + files = list( + filter( + lambda x: x.endswith(".json"), + os.listdir(os.path.join(os.path.abspath(os.path.dirname(__file__)), folder)), + ) + ) + for file in files: + data = load_json(file) + create_property_setter_from_json(data) diff --git a/pyproject.toml b/pyproject.toml index 041d7671..06898420 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,32 +9,35 @@ readme = "README.md" dynamic = ["version"] dependencies = [] +[project.optional-dependencies] +dev = [ + "pre-commit", +] + [build-system] requires = ["flit_core >=3.4,<4"] build-backend = "flit_core.buildapi" [tool.bench.frappe-dependencies] -frappe = ">=15.0.0,<16.0.0" -erpnext = ">=15.0.0,<16.0.0" +frappe = ">=16.0.0,<17.0.0" +erpnext = ">=16.0.0,<17.0.0" [tool.ruff] -line-length = 120 +line-length = 110 target-version = "py310" +[tool.ruff.format] +quote-style = "double" +indent-style = "tab" +docstring-code-format = true + [tool.ruff.lint] -select = [ - "F", - "E", - "W", - "I", - "UP", - "B", -] -ignore = [ - "E501", - "E402", - "E741", -] +select = ["F", "E", "W", "I", "UP", "B"] +ignore = ["E101", "E402", "E501", "E741", "W191"] + +# These dependencies are only installed when developer mode is enabled +[tool.bench.dev-dependencies] +# package_name = "~=1.1.0" [tool.setuptools.dynamic] version = {attr = "propms.__version__"} diff --git a/scripts/setup-git-hooks.sh b/scripts/setup-git-hooks.sh new file mode 100755 index 00000000..58aabf1f --- /dev/null +++ b/scripts/setup-git-hooks.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Installs pre-commit and wires up the commit and push hooks for this clone. +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" + +if ! command -v pre-commit >/dev/null 2>&1; then + echo "pre-commit not found, installing..." + if command -v uv >/dev/null 2>&1; then + uv tool install pre-commit + elif command -v pipx >/dev/null 2>&1; then + pipx install pre-commit + else + python3 -m pip install --user pre-commit + fi +fi + +pre-commit install --install-hooks --overwrite + +echo +echo "Hooks installed:" +echo " pre-commit staged files only, fast" +echo " commit-msg conventional commit message check" +echo " pre-push pre-commit run --all-files, blocks the push on any failure"