From acccfb1b92512f90f7a55b1ea8191e72d8389b34 Mon Sep 17 00:00:00 2001 From: av-dev2 Date: Thu, 27 Aug 2026 01:08:55 +0300 Subject: [PATCH 1/4] chore: adopt the standard pre-commit and ruff toolchain Install hooks at three stages. pre-commit checks staged files only so commits stay fast, commit-msg runs commitlint against the conventional commit rules, and pre-push re-enters pre-commit over the whole repository so a push cannot carry unchecked commits. Anchor the exclude regex. The old "node_modules|.git" pattern was unanchored, so ".git" also matched ".github" and no workflow YAML was ever validated, and it hid COMMIT_EDITMSG from the commit-msg stage. Add the frappe-semgrep-rules hook as language: python with semgrep in additional_dependencies, so it installs its own semgrep and clones the rules into the repo on first run instead of depending on a path that only exists on one machine. Ignore the clone target. Add no-commit-to-branch for main, master, production, and the version branches. Working branches such as version-15-hotfix stay open. Own the ruff config in this app. line-length moves from 120 to 110, target stays py310, and the missing [tool.ruff.format] section is added with double quotes and tab indentation. E101 and W191 join the ignore list because W is selected and the app indents with tabs. An explicit [tool.ruff.lint] section stops ruff resolving upward to a config outside the app. Add the dev extra and scripts/setup-git-hooks.sh so a fresh clone can install the hooks in one command. --- .gitignore | 4 ++- .pre-commit-config.yaml | 67 ++++++++++++++++++++++++++++++++++---- commitlint.config.js | 13 ++++++++ pyproject.toml | 14 +++++++- scripts/setup-git-hooks.sh | 24 ++++++++++++++ 5 files changed, 114 insertions(+), 8 deletions(-) create mode 100644 commitlint.config.js create mode 100755 scripts/setup-git-hooks.sh diff --git a/.gitignore b/.gitignore index 70899f1a..115e87b6 100755 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ *.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/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/pyproject.toml b/pyproject.toml index 041d7671..d361b0d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,11 @@ 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" @@ -18,7 +23,7 @@ frappe = ">=15.0.0,<16.0.0" erpnext = ">=15.0.0,<16.0.0" [tool.ruff] -line-length = 120 +line-length = 110 target-version = "py310" [tool.ruff.lint] @@ -31,10 +36,17 @@ select = [ "B", ] ignore = [ + "E101", + "W191", "E501", "E402", "E741", ] +[tool.ruff.format] +quote-style = "double" +indent-style = "tab" +docstring-code-format = true + [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" From d37cd07b73c0d190307477039d3ff5fca9afb8e3 Mon Sep 17 00:00:00 2001 From: av-dev2 Date: Thu, 27 Aug 2026 01:09:06 +0300 Subject: [PATCH 2/4] ci: drop bench builds and semantic-release, add static gates Remove ci.yml and ci-tests.yml. Both build a full bench on the runner (bench init, get-app, new-site, install-app, migrate) with MariaDB and two Redis services. Frappe tests run locally on the bench terminal, not in GitHub Actions, so these only added five to eight minutes per pull request and went red for upstream breakage unrelated to the diff. Remove release.yml and .releaserc.json. Tagging, releasing, and promotion belong to tag-and-promote-from-pr-label.yml. Running semantic-release alongside it tags a separate release commit, after which tag-and-promote finds the tag on a different commit and fails. Add pre-commit.yml so pre-commit runs over all files on every pull request, and semantic-commits.yml so commitlint re-checks the whole PR commit range. Both gates are server side and hold for contributors who never installed the local hooks. Rescope linter.yml to a full semgrep scan plus pip-audit. The blocking step now uses r/python.lang.security at ERROR severity, with a non-blocking WARNING pass for information. The duplicate pre-commit step is gone, since pre-commit.yml covers it over all files. --- .github/workflows/ci-tests.yml | 111 ------------------------- .github/workflows/ci.yml | 89 -------------------- .github/workflows/linter.yml | 45 +++++----- .github/workflows/pre-commit.yml | 31 +++++++ .github/workflows/release.yml | 43 ---------- .github/workflows/semantic-commits.yml | 31 +++++++ .releaserc.json | 29 ------- 7 files changed, 86 insertions(+), 293 deletions(-) delete mode 100644 .github/workflows/ci-tests.yml delete mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/pre-commit.yml delete mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/semantic-commits.yml delete mode 100644 .releaserc.json 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/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/.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" - ] -} From 50df577415a4967de9899a785445804749bbbedf Mon Sep 17 00:00:00 2001 From: av-dev2 Date: Thu, 27 Aug 2026 01:10:11 +0300 Subject: [PATCH 3/4] fix: clear the semgrep and ruff findings the new gates report Use frappe.db.get_single_value for the four reads of Property Management Settings in issue_hook. That doctype is a single, and frappe.db.get_value is not type safe against a single. The rest of the file already reads singles this way. Initialise invoice_item in leaseInvoiceAutoCreate. The flush branch reads the previous iteration's row, which the row_num guard makes safe at runtime, but the name had no binding at the top of the loop and ruff reported it undefined. Drop three dead locals: foreign_currency in both rent invoice reports, which nothing reads after the branch assigns it, and name_in_json in create_property_setter. Replace explicit positional format indices and rename an unused loop variable. --- propms/issue_hook.py | 595 ++++++------ propms/lease_invoice.py | 490 +++++----- .../rent_invoices_details.py | 886 +++++++++--------- .../rent_invoices_details_usd.py | 878 +++++++++-------- propms/utils/create_property_setter.py | 166 ++-- 5 files changed, 1455 insertions(+), 1560 deletions(-) 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 3c6a6e9b..22eeb947 100755 --- a/propms/lease_invoice.py +++ b/propms/lease_invoice.py @@ -1,286 +1,274 @@ -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) - doc = frappe.get_doc( - dict( - doctype=doctype, - company=company, - posting_date=today(), - items=json.loads(items), - customer=str(customer), - due_date=getDueDate(today(), 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) + doc = frappe.get_doc( + dict( + doctype=doctype, + company=company, + posting_date=today(), + items=json.loads(items), + customer=str(customer), + due_date=getDueDate(today(), 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", - ) - # 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 - # item_json["enable_deferred_revenue"] = 1 # Set it to true - 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) + """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", + ) + # 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 + # item_json["enable_deferred_revenue"] = 1 # Set it to true + 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/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/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) From 738bec3d8a837cff4b90d62be170d64d3358fe6a Mon Sep 17 00:00:00 2001 From: av-dev2 Date: Thu, 27 Aug 2026 01:10:25 +0300 Subject: [PATCH 4/4] style: reformat with the app ruff config Apply ruff at line-length 110 with tab indentation and double quotes across the app, together with the safe lint autofixes and the whitespace and end-of-file hooks. No behaviour changes. --- .deepsource.toml | 1 - LICENSE | 2 +- MANIFEST.in | 2 +- propms/__init__.py | 3 - propms/auto_custom.py | 1000 ++++++++-------- propms/config/desktop.py | 24 +- propms/config/docs.py | 2 +- propms/config/property_management_solution.py | 389 +++--- propms/hooks.py | 80 +- propms/lease_invoice_schedule.py | 281 ++--- propms/modules.txt | 2 +- propms/pos.js | 12 +- propms/pos.py | 27 +- propms/property_increment.py | 473 ++++---- .../apartment_status/apartment_status.py | 5 +- .../apartment_status/test_apartment_status.py | 5 +- .../checklist_checkup_area.py | 5 +- .../test_checklist_checkup_area.py | 5 +- .../checklist_checkup_area_task.py | 5 +- .../custom_error_log/custom_error_log.py | 5 +- .../custom_error_log/test_custom_error_log.py | 5 +- .../daily_checklist/daily_checklist.js | 2 +- .../daily_checklist/daily_checklist.py | 5 +- .../daily_checklist/test_daily_checklist.py | 5 +- .../daily_checklist_detail.py | 5 +- .../doctype/door/door.py | 5 +- .../doctype/exit/exit.py | 5 +- .../doctype/exit/test_exit.py | 5 +- .../doctype/flooring/flooring.py | 5 +- .../doctype/guard_shift/guard_shift.py | 5 +- .../doctype/guard_shift/test_guard_shift.py | 5 +- .../guard_shift_location.py | 5 +- .../doctype/insurance/insurance.py | 5 +- .../doctype/insurance/test_insurance.py | 5 +- .../issue_materials_billed.py | 4 +- .../issue_materials_detail.py | 5 +- .../doctype/key/key.py | 5 +- .../doctype/key_set/key_set.py | 5 +- .../doctype/key_set/test_key_set.py | 5 +- .../doctype/key_set_detail/key_set_detail.js | 1 - .../doctype/key_set_detail/key_set_detail.py | 5 +- .../key_set_detail/test_key_set_detail.py | 5 +- .../doctype/lease/lease.py | 1053 ++++++++--------- .../doctype/lease/test_lease.py | 5 +- .../lease_invoice_schedule.py | 5 +- .../test_lease_invoice_schedule.py | 5 +- .../doctype/lease_item/lease_item.py | 5 +- .../doctype/lease_item/test_lease_item.py | 5 +- .../doctype/meter/meter.js | 3 - .../doctype/meter/meter.py | 5 +- .../doctype/meter/test_meter.py | 5 +- .../doctype/meter_reading/meter_reading.py | 5 +- .../meter_reading/test_meter_reading.py | 5 +- .../meter_reading_detail.py | 5 +- .../test_meter_reading_detail.py | 5 +- .../multiselect_item_group.py | 4 +- .../outsource_contact/outsource_contact.py | 5 +- .../test_outsource_contact.py | 5 +- .../outsourcing_attendance.py | 5 +- .../test_outsourcing_attendance.py | 5 +- .../outsourcing_attendance_details.py | 5 +- .../outsourcing_category.py | 5 +- .../test_outsourcing_category.py | 5 +- .../outsourcing_shift/outsourcing_shift.py | 5 +- .../test_outsourcing_shift.py | 5 +- .../outsourcing_shift_location.py | 5 +- .../doctype/paint/paint.py | 5 +- .../doctype/property/property.py | 112 +- .../doctype/property/property_tree.js | 2 +- .../doctype/property/test_property.py | 5 +- .../property_amenity/property_amenity.py | 5 +- .../property_amenity/test_property_amenity.py | 5 +- .../property_management_settings.js | 2 +- .../property_management_settings.py | 5 +- .../test_property_management_settings.py | 5 +- .../property_meter_reading.py | 5 +- .../test_property_meter_reading.py | 5 +- .../doctype/property_unit/property_unit.py | 5 +- .../security_attendance.js | 2 +- .../security_attendance.py | 5 +- .../test_security_attendance.py | 5 +- .../security_attendance_details.js | 2 +- .../security_attendance_details.py | 5 +- .../test_security_attendance_details.py | 5 +- .../security_deposit_details.py | 5 +- .../test_security_deposit_details.py | 5 +- .../doctype/tool_item/tool_item.py | 2 +- .../tool_item_record/test_tool_item_record.py | 2 +- .../tool_item_record/tool_item_record.js | 1 - .../tool_item_record/tool_item_record.py | 2 +- .../tool_item_set/test_tool_item_set.py | 2 +- .../doctype/tool_item_set/tool_item_set.py | 2 +- .../doctype/unit_assets/unit_assets.py | 5 +- .../doctype/unit_type/test_unit_type.py | 5 +- .../doctype/unit_type/unit_type.py | 5 +- .../journal_entry_account.js | 2 +- .../daily_checkup_report.md | 2 +- .../daily_checkup_report.py | 7 +- .../outsourcing_attendance.md | 2 +- .../outsourcing_attendance.py | 7 +- .../security_attendance.md | 2 +- .../security_attendance.py | 7 +- .../report/debtors_report/debtors_report.js | 2 +- .../report/invoice_details/invoice_details.py | 14 +- .../report/invoice_details/other_methods.py | 367 +++--- .../mis_income_break_up.py | 12 +- .../mis_income_break_up/other_methods.py | 170 ++- .../security_attendance_report.html | 2 +- .../security_deposit/security_deposit.js | 4 +- .../subscription_service_report.js | 2 +- .../report/utility_invoices/other_methods.py | 351 +++--- .../utility_invoices/utility_invoices.py | 15 +- ...lding_tax_summary_on_sales_(properties).js | 2 +- .../sales_invoice.js | 2 +- propms/propms-gitlab.sh | 1 - propms/utils/create_custom_fields.py | 110 +- 116 files changed, 2355 insertions(+), 2544 deletions(-) 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/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/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/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 25e32546..61db1ec7 100755 --- a/propms/property_management_solution/doctype/lease/lease.py +++ b/propms/property_management_solution/doctype/lease/lease.py @@ -1,567 +1,564 @@ -# -*- 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): - 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 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): + 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 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/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)