From d0cb28e4dae6f1b7275b1a0e9d570044c37f9496 Mon Sep 17 00:00:00 2001 From: Anoop Date: Sun, 7 May 2023 09:35:49 +0530 Subject: [PATCH] Initial --- .gitignore | 6 + MANIFEST.in | 18 + README.md | 7 + license.txt | 1 + muezzin/__init__.py | 5 + muezzin/api/__init__.py | 0 muezzin/api/property_contract.py | 76 ++ muezzin/api/property_dashboard.py | 24 + muezzin/config/__init__.py | 0 muezzin/config/desktop.py | 14 + muezzin/config/docs.py | 11 + muezzin/config/property_management.py | 141 ++++ muezzin/events/sales_invoice.py | 25 + muezzin/fixtures/accounting_dimension.json | 30 + muezzin/fixtures/custom_field.json | 462 +++++++++++ muezzin/fixtures/property_setter.json | 18 + muezzin/hooks.py | 161 ++++ muezzin/modules.txt | 1 + muezzin/patches.txt | 0 muezzin/property_management/__init__.py | 0 .../property_management/doctype/__init__.py | 0 .../doctype/auction/__init__.py | 0 .../doctype/auction/auction.js | 31 + .../doctype/auction/auction.json | 118 +++ .../doctype/auction/auction.py | 10 + .../doctype/auction/test_auction.py | 10 + .../doctype/auction_free_card/__init__.py | 0 .../auction_free_card/auction_free_card.js | 37 + .../auction_free_card/auction_free_card.json | 190 +++++ .../auction_free_card/auction_free_card.py | 24 + .../test_auction_free_card.py | 10 + .../doctype/auction_unit/__init__.py | 0 .../doctype/auction_unit/auction_unit.json | 29 + .../doctype/auction_unit/auction_unit.py | 10 + .../doctype/contract_annual_raise/__init__.py | 0 .../contract_annual_raise.json | 43 + .../contract_annual_raise.py | 10 + .../doctype/contract_payments/__init__.py | 0 .../contract_payments/contract_payments.json | 92 +++ .../contract_payments/contract_payments.py | 10 + .../doctype/court/__init__.py | 0 .../doctype/court/court.js | 8 + .../doctype/court/court.json | 52 ++ .../doctype/court/court.py | 10 + .../doctype/court/test_court.py | 10 + .../doctype/discount_request/__init__.py | 0 .../discount_request/discount_request.js | 39 + .../discount_request/discount_request.json | 153 ++++ .../discount_request/discount_request.py | 52 ++ .../discount_request/test_discount_request.py | 10 + .../doctype/document/__init__.py | 0 .../doctype/document/document.json | 60 ++ .../doctype/document/document.py | 10 + .../doctype/document_type/__init__.py | 0 .../doctype/document_type/document_type.js | 8 + .../doctype/document_type/document_type.json | 43 + .../doctype/document_type/document_type.py | 10 + .../document_type/test_document_type.py | 10 + .../doctype/maintenance_ticket/__init__.py | 0 .../maintenance_ticket/maintenance_ticket.js | 47 ++ .../maintenance_ticket.json | 173 ++++ .../maintenance_ticket/maintenance_ticket.py | 10 + .../test_maintenance_ticket.py | 10 + .../doctype/opening_phase/__init__.py | 0 .../doctype/opening_phase/opening_phase.js | 8 + .../doctype/opening_phase/opening_phase.json | 43 + .../doctype/opening_phase/opening_phase.py | 10 + .../opening_phase/test_opening_phase.py | 10 + .../doctype/property/__init__.py | 0 .../doctype/property/property.js | 58 ++ .../doctype/property/property.json | 499 ++++++++++++ .../doctype/property/property.py | 10 + .../doctype/property/test_property.py | 10 + .../doctype/property_contract/__init__.py | 0 .../property_contract (copy).py | 545 +++++++++++++ .../property_contract/property_contract.js | 351 +++++++++ .../property_contract/property_contract.json | 745 ++++++++++++++++++ .../property_contract/property_contract.py | 545 +++++++++++++ .../test_property_contract.py | 10 + .../doctype/property_type/__init__.py | 0 .../doctype/property_type/property_type.js | 8 + .../doctype/property_type/property_type.json | 41 + .../doctype/property_type/property_type.py | 10 + .../property_type/test_property_type.py | 10 + .../doctype/property_unit_detail/__init__.py | 0 .../property_unit_detail.json | 36 + .../property_unit_detail.py | 10 + .../doctype/real_estate_offer/__init__.py | 0 .../real_estate_offer/real_estate_offer.js | 8 + .../real_estate_offer/real_estate_offer.json | 273 +++++++ .../real_estate_offer/real_estate_offer.py | 10 + .../test_real_estate_offer.py | 10 + .../doctype/real_estate_order/__init__.py | 0 .../real_estate_order/real_estate_order.js | 8 + .../real_estate_order/real_estate_order.json | 275 +++++++ .../real_estate_order/real_estate_order.py | 10 + .../test_real_estate_order.py | 10 + .../__init__.py | 0 .../reservation_repayment_schedule.json | 82 ++ .../reservation_repayment_schedule.py | 10 + .../doctype/reservations/__init__.py | 0 .../doctype/reservations/reservations.js | 190 +++++ .../doctype/reservations/reservations.json | 487 ++++++++++++ .../doctype/reservations/reservations.py | 128 +++ .../doctype/reservations/test_reservations.py | 10 + .../doctype/script/__init__.py | 0 .../doctype/script/script.js | 8 + .../doctype/script/script.json | 61 ++ .../doctype/script/script.py | 10 + .../doctype/script/test_script.py | 10 + .../doctype/tenant_brand_name/__init__.py | 0 .../tenant_brand_name/tenant_brand_name.js | 8 + .../tenant_brand_name/tenant_brand_name.json | 29 + .../tenant_brand_name/tenant_brand_name.py | 10 + .../test_tenant_brand_name.py | 10 + .../doctype/unit/__init__.py | 0 .../doctype/unit/test_unit.py | 10 + .../property_management/doctype/unit/unit.js | 150 ++++ .../doctype/unit/unit.json | 547 +++++++++++++ .../property_management/doctype/unit/unit.py | 27 + .../doctype/unit/unit_dashboard.py | 31 + .../doctype/unit_activity/__init__.py | 0 .../unit_activity/test_unit_activity.py | 10 + .../doctype/unit_activity/unit_activity.js | 8 + .../doctype/unit_activity/unit_activity.json | 51 ++ .../doctype/unit_activity/unit_activity.py | 10 + .../doctype/unit_sale_contract/__init__.py | 0 .../test_unit_sale_contract.py | 10 + .../unit_sale_contract/unit_sale_contract.js | 35 + .../unit_sale_contract.json | 416 ++++++++++ .../unit_sale_contract/unit_sale_contract.py | 10 + .../doctype/unit_type/__init__.py | 0 .../doctype/unit_type/test_unit_type.py | 10 + .../doctype/unit_type/unit_type.js | 8 + .../doctype/unit_type/unit_type.json | 41 + .../doctype/unit_type/unit_type.py | 10 + muezzin/property_management/page/__init__.py | 0 .../page/property_dashboard/__init__.py | 0 .../property_dashboard/dashboard-tiles.html | 43 + .../property_dashboard/property_dashboard.js | 54 ++ .../property_dashboard.json | 24 + .../property_management/report/__init__.py | 0 .../property_management_revenue/__init__.py | 0 .../property_management_revenue.js | 34 + .../property_management_revenue.json | 32 + .../property_management_revenue.py | 383 +++++++++ .../report/rent_roll/__init__.py | 0 .../report/rent_roll/rent_roll.js | 33 + .../report/rent_roll/rent_roll.json | 27 + .../report/rent_roll/rent_roll.py | 377 +++++++++ .../report/service_charge_roll/__init__.py | 0 .../service_charge_roll.js | 34 + .../service_charge_roll.json | 31 + .../service_charge_roll.py | 386 +++++++++ .../report/sold_unit/__init__.py | 0 .../report/sold_unit/sold_unit.js | 9 + .../report/sold_unit/sold_unit.json | 24 + .../report/sold_unit/sold_unit.py | 180 +++++ muezzin/public/build.json | 1 + muezzin/public/js/payment_entry.js | 17 + muezzin/public/scss/style.scss | 16 + muezzin/templates/__init__.py | 0 muezzin/templates/pages/__init__.py | 0 requirements.txt | 1 + setup.py | 20 + 165 files changed, 10065 insertions(+) create mode 100644 .gitignore create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 license.txt create mode 100644 muezzin/__init__.py create mode 100644 muezzin/api/__init__.py create mode 100644 muezzin/api/property_contract.py create mode 100644 muezzin/api/property_dashboard.py create mode 100644 muezzin/config/__init__.py create mode 100644 muezzin/config/desktop.py create mode 100644 muezzin/config/docs.py create mode 100644 muezzin/config/property_management.py create mode 100644 muezzin/events/sales_invoice.py create mode 100644 muezzin/fixtures/accounting_dimension.json create mode 100644 muezzin/fixtures/custom_field.json create mode 100644 muezzin/fixtures/property_setter.json create mode 100644 muezzin/hooks.py create mode 100644 muezzin/modules.txt create mode 100644 muezzin/patches.txt create mode 100644 muezzin/property_management/__init__.py create mode 100644 muezzin/property_management/doctype/__init__.py create mode 100644 muezzin/property_management/doctype/auction/__init__.py create mode 100644 muezzin/property_management/doctype/auction/auction.js create mode 100644 muezzin/property_management/doctype/auction/auction.json create mode 100644 muezzin/property_management/doctype/auction/auction.py create mode 100644 muezzin/property_management/doctype/auction/test_auction.py create mode 100644 muezzin/property_management/doctype/auction_free_card/__init__.py create mode 100644 muezzin/property_management/doctype/auction_free_card/auction_free_card.js create mode 100644 muezzin/property_management/doctype/auction_free_card/auction_free_card.json create mode 100644 muezzin/property_management/doctype/auction_free_card/auction_free_card.py create mode 100644 muezzin/property_management/doctype/auction_free_card/test_auction_free_card.py create mode 100644 muezzin/property_management/doctype/auction_unit/__init__.py create mode 100644 muezzin/property_management/doctype/auction_unit/auction_unit.json create mode 100644 muezzin/property_management/doctype/auction_unit/auction_unit.py create mode 100644 muezzin/property_management/doctype/contract_annual_raise/__init__.py create mode 100644 muezzin/property_management/doctype/contract_annual_raise/contract_annual_raise.json create mode 100644 muezzin/property_management/doctype/contract_annual_raise/contract_annual_raise.py create mode 100644 muezzin/property_management/doctype/contract_payments/__init__.py create mode 100644 muezzin/property_management/doctype/contract_payments/contract_payments.json create mode 100644 muezzin/property_management/doctype/contract_payments/contract_payments.py create mode 100644 muezzin/property_management/doctype/court/__init__.py create mode 100644 muezzin/property_management/doctype/court/court.js create mode 100644 muezzin/property_management/doctype/court/court.json create mode 100644 muezzin/property_management/doctype/court/court.py create mode 100644 muezzin/property_management/doctype/court/test_court.py create mode 100644 muezzin/property_management/doctype/discount_request/__init__.py create mode 100644 muezzin/property_management/doctype/discount_request/discount_request.js create mode 100644 muezzin/property_management/doctype/discount_request/discount_request.json create mode 100644 muezzin/property_management/doctype/discount_request/discount_request.py create mode 100644 muezzin/property_management/doctype/discount_request/test_discount_request.py create mode 100644 muezzin/property_management/doctype/document/__init__.py create mode 100644 muezzin/property_management/doctype/document/document.json create mode 100644 muezzin/property_management/doctype/document/document.py create mode 100644 muezzin/property_management/doctype/document_type/__init__.py create mode 100644 muezzin/property_management/doctype/document_type/document_type.js create mode 100644 muezzin/property_management/doctype/document_type/document_type.json create mode 100644 muezzin/property_management/doctype/document_type/document_type.py create mode 100644 muezzin/property_management/doctype/document_type/test_document_type.py create mode 100644 muezzin/property_management/doctype/maintenance_ticket/__init__.py create mode 100644 muezzin/property_management/doctype/maintenance_ticket/maintenance_ticket.js create mode 100644 muezzin/property_management/doctype/maintenance_ticket/maintenance_ticket.json create mode 100644 muezzin/property_management/doctype/maintenance_ticket/maintenance_ticket.py create mode 100644 muezzin/property_management/doctype/maintenance_ticket/test_maintenance_ticket.py create mode 100644 muezzin/property_management/doctype/opening_phase/__init__.py create mode 100644 muezzin/property_management/doctype/opening_phase/opening_phase.js create mode 100644 muezzin/property_management/doctype/opening_phase/opening_phase.json create mode 100644 muezzin/property_management/doctype/opening_phase/opening_phase.py create mode 100644 muezzin/property_management/doctype/opening_phase/test_opening_phase.py create mode 100644 muezzin/property_management/doctype/property/__init__.py create mode 100644 muezzin/property_management/doctype/property/property.js create mode 100644 muezzin/property_management/doctype/property/property.json create mode 100644 muezzin/property_management/doctype/property/property.py create mode 100644 muezzin/property_management/doctype/property/test_property.py create mode 100644 muezzin/property_management/doctype/property_contract/__init__.py create mode 100644 muezzin/property_management/doctype/property_contract/property_contract (copy).py create mode 100644 muezzin/property_management/doctype/property_contract/property_contract.js create mode 100644 muezzin/property_management/doctype/property_contract/property_contract.json create mode 100644 muezzin/property_management/doctype/property_contract/property_contract.py create mode 100644 muezzin/property_management/doctype/property_contract/test_property_contract.py create mode 100644 muezzin/property_management/doctype/property_type/__init__.py create mode 100644 muezzin/property_management/doctype/property_type/property_type.js create mode 100644 muezzin/property_management/doctype/property_type/property_type.json create mode 100644 muezzin/property_management/doctype/property_type/property_type.py create mode 100644 muezzin/property_management/doctype/property_type/test_property_type.py create mode 100644 muezzin/property_management/doctype/property_unit_detail/__init__.py create mode 100644 muezzin/property_management/doctype/property_unit_detail/property_unit_detail.json create mode 100644 muezzin/property_management/doctype/property_unit_detail/property_unit_detail.py create mode 100644 muezzin/property_management/doctype/real_estate_offer/__init__.py create mode 100644 muezzin/property_management/doctype/real_estate_offer/real_estate_offer.js create mode 100644 muezzin/property_management/doctype/real_estate_offer/real_estate_offer.json create mode 100644 muezzin/property_management/doctype/real_estate_offer/real_estate_offer.py create mode 100644 muezzin/property_management/doctype/real_estate_offer/test_real_estate_offer.py create mode 100644 muezzin/property_management/doctype/real_estate_order/__init__.py create mode 100644 muezzin/property_management/doctype/real_estate_order/real_estate_order.js create mode 100644 muezzin/property_management/doctype/real_estate_order/real_estate_order.json create mode 100644 muezzin/property_management/doctype/real_estate_order/real_estate_order.py create mode 100644 muezzin/property_management/doctype/real_estate_order/test_real_estate_order.py create mode 100644 muezzin/property_management/doctype/reservation_repayment_schedule/__init__.py create mode 100644 muezzin/property_management/doctype/reservation_repayment_schedule/reservation_repayment_schedule.json create mode 100644 muezzin/property_management/doctype/reservation_repayment_schedule/reservation_repayment_schedule.py create mode 100644 muezzin/property_management/doctype/reservations/__init__.py create mode 100644 muezzin/property_management/doctype/reservations/reservations.js create mode 100644 muezzin/property_management/doctype/reservations/reservations.json create mode 100644 muezzin/property_management/doctype/reservations/reservations.py create mode 100644 muezzin/property_management/doctype/reservations/test_reservations.py create mode 100644 muezzin/property_management/doctype/script/__init__.py create mode 100644 muezzin/property_management/doctype/script/script.js create mode 100644 muezzin/property_management/doctype/script/script.json create mode 100644 muezzin/property_management/doctype/script/script.py create mode 100644 muezzin/property_management/doctype/script/test_script.py create mode 100644 muezzin/property_management/doctype/tenant_brand_name/__init__.py create mode 100644 muezzin/property_management/doctype/tenant_brand_name/tenant_brand_name.js create mode 100644 muezzin/property_management/doctype/tenant_brand_name/tenant_brand_name.json create mode 100644 muezzin/property_management/doctype/tenant_brand_name/tenant_brand_name.py create mode 100644 muezzin/property_management/doctype/tenant_brand_name/test_tenant_brand_name.py create mode 100644 muezzin/property_management/doctype/unit/__init__.py create mode 100644 muezzin/property_management/doctype/unit/test_unit.py create mode 100644 muezzin/property_management/doctype/unit/unit.js create mode 100644 muezzin/property_management/doctype/unit/unit.json create mode 100644 muezzin/property_management/doctype/unit/unit.py create mode 100644 muezzin/property_management/doctype/unit/unit_dashboard.py create mode 100644 muezzin/property_management/doctype/unit_activity/__init__.py create mode 100644 muezzin/property_management/doctype/unit_activity/test_unit_activity.py create mode 100644 muezzin/property_management/doctype/unit_activity/unit_activity.js create mode 100644 muezzin/property_management/doctype/unit_activity/unit_activity.json create mode 100644 muezzin/property_management/doctype/unit_activity/unit_activity.py create mode 100644 muezzin/property_management/doctype/unit_sale_contract/__init__.py create mode 100644 muezzin/property_management/doctype/unit_sale_contract/test_unit_sale_contract.py create mode 100644 muezzin/property_management/doctype/unit_sale_contract/unit_sale_contract.js create mode 100644 muezzin/property_management/doctype/unit_sale_contract/unit_sale_contract.json create mode 100644 muezzin/property_management/doctype/unit_sale_contract/unit_sale_contract.py create mode 100644 muezzin/property_management/doctype/unit_type/__init__.py create mode 100644 muezzin/property_management/doctype/unit_type/test_unit_type.py create mode 100644 muezzin/property_management/doctype/unit_type/unit_type.js create mode 100644 muezzin/property_management/doctype/unit_type/unit_type.json create mode 100644 muezzin/property_management/doctype/unit_type/unit_type.py create mode 100644 muezzin/property_management/page/__init__.py create mode 100644 muezzin/property_management/page/property_dashboard/__init__.py create mode 100644 muezzin/property_management/page/property_dashboard/dashboard-tiles.html create mode 100644 muezzin/property_management/page/property_dashboard/property_dashboard.js create mode 100644 muezzin/property_management/page/property_dashboard/property_dashboard.json create mode 100644 muezzin/property_management/report/__init__.py create mode 100644 muezzin/property_management/report/property_management_revenue/__init__.py create mode 100644 muezzin/property_management/report/property_management_revenue/property_management_revenue.js create mode 100644 muezzin/property_management/report/property_management_revenue/property_management_revenue.json create mode 100644 muezzin/property_management/report/property_management_revenue/property_management_revenue.py create mode 100644 muezzin/property_management/report/rent_roll/__init__.py create mode 100644 muezzin/property_management/report/rent_roll/rent_roll.js create mode 100644 muezzin/property_management/report/rent_roll/rent_roll.json create mode 100644 muezzin/property_management/report/rent_roll/rent_roll.py create mode 100644 muezzin/property_management/report/service_charge_roll/__init__.py create mode 100644 muezzin/property_management/report/service_charge_roll/service_charge_roll.js create mode 100644 muezzin/property_management/report/service_charge_roll/service_charge_roll.json create mode 100644 muezzin/property_management/report/service_charge_roll/service_charge_roll.py create mode 100644 muezzin/property_management/report/sold_unit/__init__.py create mode 100644 muezzin/property_management/report/sold_unit/sold_unit.js create mode 100644 muezzin/property_management/report/sold_unit/sold_unit.json create mode 100644 muezzin/property_management/report/sold_unit/sold_unit.py create mode 100644 muezzin/public/build.json create mode 100644 muezzin/public/js/payment_entry.js create mode 100644 muezzin/public/scss/style.scss create mode 100644 muezzin/templates/__init__.py create mode 100644 muezzin/templates/pages/__init__.py create mode 100644 requirements.txt create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf846ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +*.pyc +*.egg-info +*.swp +tags +property_management/docs/current \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..dd592f2 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,18 @@ +include MANIFEST.in +include requirements.txt +include *.json +include *.md +include *.py +include *.txt +recursive-include property_management *.css +recursive-include property_management *.csv +recursive-include property_management *.html +recursive-include property_management *.ico +recursive-include property_management *.js +recursive-include property_management *.json +recursive-include property_management *.md +recursive-include property_management *.png +recursive-include property_management *.py +recursive-include property_management *.svg +recursive-include property_management *.txt +recursive-exclude property_management *.pyc \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c5841ae --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +## Property Management + +Property Management + +#### License + +MIT \ No newline at end of file diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..2fdf7c7 --- /dev/null +++ b/license.txt @@ -0,0 +1 @@ +License: MIT \ No newline at end of file diff --git a/muezzin/__init__.py b/muezzin/__init__.py new file mode 100644 index 0000000..95d1338 --- /dev/null +++ b/muezzin/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +__version__ = '0.0.1' + diff --git a/muezzin/api/__init__.py b/muezzin/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/api/property_contract.py b/muezzin/api/property_contract.py new file mode 100644 index 0000000..7c2ab5d --- /dev/null +++ b/muezzin/api/property_contract.py @@ -0,0 +1,76 @@ +import frappe +import json + +@frappe.whitelist() +def change_contract_status(doc, status): + """ + Validates if property already on rent between the from and to dates + """ + doc = json.loads(doc) + + if status == "Hold": + doc = frappe.get_doc("Property Contract", doc["name"]) + doc.status = status + doc.save() + + unit_doc = frappe.get_doc("Unit", doc.unit) + unit_doc.status = "Vacant" + unit_doc.save() + return + + # Get Property Contaract if from date is between new from and new to date + filters = { + "rent_start_date": ["between", [doc["rent_start_date"], doc["rent_end_date"]]], + "docstatus": 1, + "unit_name": doc["unit_name"], + "status": ["!=", "Hold"] + } + + property_contract = frappe.get_list("Property Contract", + fields = ["name"], + filters = filters) + + if len(property_contract)> 0: + frappe.throw(f"This property unit already contain a contract {property_contract[0].name} that not ended yet.") + + + # Get Property Contaract if to date is between new from and new to date + filters = { + "rent_end_date": ["between", [doc["rent_start_date"], doc["rent_end_date"]]], + "docstatus": 1, + "unit_name": doc["unit_name"], + "status": ["!=", "Hold"] + } + + property_contract = frappe.get_list("Property Contract", + fields = ["name"], + filters = filters) + + if len(property_contract)> 0: + frappe.throw(f"This property unit already contain a contract {property_contract[0].name} that not ended yet.") + + + # Get Property Contaract if from date is after new from date and to date is before new to date + filters = { + "rent_start_date": ["<=", doc["rent_start_date"]], + "rent_end_date": [">=", doc["rent_end_date"]], + "docstatus": 1, + "unit_name": doc["unit_name"], + "status": ["!=", "Hold"] + } + + property_contract = frappe.get_list("Property Contract", + fields = ["name"], + filters = filters) + + if len(property_contract)> 0: + frappe.throw(f"This property unit already contain a contract {property_contract[0].name} that not ended yet.") + + + doc = frappe.get_doc("Property Contract", doc["name"]) + doc.status = status + doc.save() + + unit_doc = frappe.get_doc("Unit", doc.unit_name) + unit_doc.status = "Leased" + unit_doc.save() diff --git a/muezzin/api/property_dashboard.py b/muezzin/api/property_dashboard.py new file mode 100644 index 0000000..c881aec --- /dev/null +++ b/muezzin/api/property_dashboard.py @@ -0,0 +1,24 @@ +import frappe + + +@frappe.whitelist() +def get_tiles_data(): + ''' + function will use to get property dashboard's tiles data + ''' + + properties = frappe.db.count("Property") + units = frappe.db.count("Unit") + sold_units = frappe.db.count("Unit", {"status": "Sold"}) + rented_units = frappe.db.count("Unit", {"status": "Rented"}) + available_units = frappe.db.count("Unit", {"status": "Available"}) + + data = { + "total_property": properties, + "total_unit": units, + "total_sold_unit": sold_units, + "total_rented_unit": rented_units, + "total_available_unit": available_units, + } + + return data diff --git a/muezzin/config/__init__.py b/muezzin/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/config/desktop.py b/muezzin/config/desktop.py new file mode 100644 index 0000000..dc142a5 --- /dev/null +++ b/muezzin/config/desktop.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return [ + { + "module_name": "Property Management", + "color": "grey", + "icon": "octicon octicon-file-directory", + "type": "module", + "label": _("Property Management") + } + ] diff --git a/muezzin/config/docs.py b/muezzin/config/docs.py new file mode 100644 index 0000000..980a7be --- /dev/null +++ b/muezzin/config/docs.py @@ -0,0 +1,11 @@ +""" +Configuration for docs +""" + +# source_link = "https://github.com/[org_name]/property_management" +# docs_base_url = "https://[org_name].github.io/property_management" +# headline = "App that does everything" +# sub_heading = "Yes, you got that right the first time, everything" + +def get_context(context): + context.brand_html = "Property Management" diff --git a/muezzin/config/property_management.py b/muezzin/config/property_management.py new file mode 100644 index 0000000..eedbe0f --- /dev/null +++ b/muezzin/config/property_management.py @@ -0,0 +1,141 @@ +from __future__ import unicode_literals +from frappe import _ + + +def get_data(): + return [ + { + "label": _("Services"), + "items": [ + { + "type": "doctype", + "name": "Maintenance Ticket", + "onboard": 1 + }, + { + "type": "doctype", + "name": "Property Contract", + "onboard": 1 + }, + { + "type": "doctype", + "name": "Reservations", + "onboard": 1 + }, + { + "type": "doctype", + "name": "Auction", + "onboard": 1 + }, + { + "type": "doctype", + "name": "Auction Free Card", + "onboard": 1 + }, + { + "type": "doctype", + "name": "Real Estate Offer", + "onboard": 1 + }, + { + "type": "doctype", + "name": "Real Estate Order", + "onboard": 1 + } + ] + }, + { + "label": _("Setup"), + "items": [ + { + "type": "doctype", + "name": "Unit", + "onboard": 1 + }, + { + "type": "doctype", + "name": "Property", + "onboard": 1 + }, + { + "type": "doctype", + "name": "Unit Type", + "onboard": 1 + }, + { + "type": "doctype", + "name": "Property Type", + "onboard": 1 + }, + { + "type": "doctype", + "name": "Unit Activity", + "onboard": 1 + }, + { + "type": "doctype", + "name": "Opening Phase", + "onboard": 1 + }, + ] + }, + { + "label": _("People"), + "items": [ + { + "type": "doctype", + "name": "User", + "onboard": 1 + }, + { + "type": "doctype", + "name": "Customer", + "onboard": 1 + } + ] + }, + { + "label": _("Reports"), + "items": [ + { + "type": "report", + "is_query_report": True, + "name": "Rent Roll", + "doctype": "Property Contract", + "onboard": 1 + }, + { + "type": "report", + "is_query_report": True, + "name": "Service Charge Roll", + "doctype": "Property Contract", + "onboard": 1 + }, + { + "type": "report", + "is_query_report": True, + "name": "Sold Unit", + "doctype": "Unit", + "onboard": 1 + }, + { + "type": "report", + "is_query_report": True, + "name": "Property Management Revenue", + "doctype": "Property Contract", + "onboard": 1 + } + ] + }, + { + "label": _("Dashboards"), + "items": [ + { + "type": "page", + "name": "property-dashboard", + "label": _("Property Dashboard"), + "onboard": 1 + } + ] + }, + ] diff --git a/muezzin/events/sales_invoice.py b/muezzin/events/sales_invoice.py new file mode 100644 index 0000000..7f9c5f6 --- /dev/null +++ b/muezzin/events/sales_invoice.py @@ -0,0 +1,25 @@ +import frappe + +def submit(doc, method): + if hasattr(doc, 'property') and doc.property: + prop = frappe.get_doc('Property', doc.property) + prop.customer = doc.customer + prop.customer_name = doc.customer_name + prop.save() + if hasattr(doc, 'unit') and doc.unit: + unit = frappe.get_doc('Unit', doc.unit) + unit.customer = doc.customer + unit.customer_name = doc.customer_name + unit.save() + +def cancel(doc, method): + if hasattr(doc, 'property') and doc.property: + prop = frappe.get_doc('Property', doc.property) + prop.customer = None + prop.customer_name = None + prop.save() + if hasattr(doc, 'unit') and doc.unit: + unit = frappe.get_doc('Unit', doc.unit) + unit.customer = None + unit.customer_name = None + unit.save() \ No newline at end of file diff --git a/muezzin/fixtures/accounting_dimension.json b/muezzin/fixtures/accounting_dimension.json new file mode 100644 index 0000000..7f60fd9 --- /dev/null +++ b/muezzin/fixtures/accounting_dimension.json @@ -0,0 +1,30 @@ +[ + { + "dimension_defaults": [], + "disabled": 0, + "docstatus": 0, + "doctype": "Accounting Dimension", + "document_type": "Unit", + "fieldname": "unit", + "label": "Unit", + "modified": "2021-02-01 15:29:00.082653", + "name": "Unit", + "parent": null, + "parentfield": null, + "parenttype": null + }, + { + "dimension_defaults": [], + "disabled": 0, + "docstatus": 0, + "doctype": "Accounting Dimension", + "document_type": "Property", + "fieldname": "property", + "label": "Property", + "modified": "2021-02-01 15:29:09.706024", + "name": "Property", + "parent": null, + "parentfield": null, + "parenttype": null + } +] \ No newline at end of file diff --git a/muezzin/fixtures/custom_field.json b/muezzin/fixtures/custom_field.json new file mode 100644 index 0000000..1bffc35 --- /dev/null +++ b/muezzin/fixtures/custom_field.json @@ -0,0 +1,462 @@ +[ + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": "eval:doc.customer_type == \"Company\"", + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Customer", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "company_name_en", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "insert_after": "customer_name", + "label": "Company Name en", + "length": 0, + "modified": "2021-04-12 01:00:41.963015", + "name": "Customer-company_name_en", + "no_copy": 0, + "options": null, + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Lead", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "sales_persons", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "insert_after": "email_id", + "label": "Sales Persons", + "length": 0, + "modified": "2021-10-24 01:56:12.828482", + "name": "Lead-sales_persons", + "no_copy": 0, + "options": "Sales Person", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Customer", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "customer_documents", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "insert_after": "disabled", + "label": "Customer Documents", + "length": 0, + "modified": "2021-12-28 00:44:27.424715", + "name": "Customer-customer_documents", + "no_copy": 0, + "options": null, + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Sales Invoice", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "property_contract", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "insert_after": "project", + "label": "Property Contract", + "length": 0, + "modified": "2021-06-18 02:01:44.898355", + "name": "Sales Invoice-property_contract", + "no_copy": 0, + "options": "Property Contract", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Customer", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "documents", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "insert_after": "customer_documents", + "label": "Documents", + "length": 0, + "modified": "2021-12-28 00:44:27.946523", + "name": "Customer-documents", + "no_copy": 0, + "options": "Document", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Journal Entry", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "accounting_dimensions", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "insert_after": "select_print_heading", + "label": "Accounting Dimensions", + "length": 0, + "modified": "2021-02-01 17:24:28.183421", + "name": "Journal Entry-accounting_dimensions", + "no_copy": 0, + "options": null, + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Journal Entry", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "unit", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "insert_after": "accounting_dimensions", + "label": "Unit", + "length": 0, + "modified": "2021-02-01 17:24:28.342902", + "name": "Journal Entry-unit", + "no_copy": 0, + "options": "Unit", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Journal Entry", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "property", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "insert_after": "unit", + "label": "Property", + "length": 0, + "modified": "2021-02-01 17:24:28.545797", + "name": "Journal Entry-property", + "no_copy": 0, + "options": "Property", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Journal Entry", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "column_break_45", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "insert_after": "property", + "label": "", + "length": 0, + "modified": "2021-02-01 17:24:28.796911", + "name": "Journal Entry-column_break_45", + "no_copy": 0, + "options": "", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Journal Entry", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "cost_center", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "insert_after": "column_break_45", + "label": "Cost Center", + "length": 0, + "modified": "2021-02-01 17:24:28.955064", + "name": "Journal Entry-cost_center", + "no_copy": 0, + "options": "Cost Center", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0, + "width": null + } +] \ No newline at end of file diff --git a/muezzin/fixtures/property_setter.json b/muezzin/fixtures/property_setter.json new file mode 100644 index 0000000..9d5d965 --- /dev/null +++ b/muezzin/fixtures/property_setter.json @@ -0,0 +1,18 @@ +[ + { + "default_value": null, + "doc_type": "Customer", + "docstatus": 0, + "doctype": "Property Setter", + "doctype_or_field": "DocField", + "field_name": "customer_name", + "modified": "2021-04-12 01:03:25.733494", + "name": "Customer-customer_name-label", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "label", + "property_type": "Data", + "value": "Legal Name" + } +] \ No newline at end of file diff --git a/muezzin/hooks.py b/muezzin/hooks.py new file mode 100644 index 0000000..f45056d --- /dev/null +++ b/muezzin/hooks.py @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from . import __version__ as app_version + +app_name = "muezzin" +app_title = "Muezzin" +app_publisher = "Havenir Solutions" +app_description = "Property Management App for Muezzin" +app_icon = "octicon octicon-file-directory" +app_color = "grey" +app_email = "info@havenir.com" +app_license = "MIT" + +# Includes in +# ------------------ + +# include js, css files in header of desk.html +app_include_css = "/assets/css/muezzin.css" +# app_include_js = "/assets/muezzin/js/muezzin.js" + +# include js, css files in header of web template +# web_include_css = "/assets/muezzin/css/muezzin.css" +# web_include_js = "/assets/muezzin/js/muezzin.js" + +# include js in page +# page_js = {"page" : "public/js/file.js"} + +# include js in doctype views +doctype_js = { + "Payment Entry": "public/js/payment_entry.js" +} + +# doctype_list_js = {"doctype" : "public/js/doctype_list.js"} +# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} +# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} + +# Home Pages +# ---------- + +# application home page (will override Website Settings) +# home_page = "login" + +# website user home page (by Role) +# role_home_page = { +# "Role": "home_page" +# } + +# Website user home page (by function) +# get_website_user_home_page = "muezzin.utils.get_home_page" + +# Generators +# ---------- + +# automatically create page for each record of this doctype +# website_generators = ["Web Page"] + +# Installation +# ------------ + +# before_install = "muezzin.install.before_install" +# after_install = "muezzin.install.after_install" + +# Desk Notifications +# ------------------ +# See frappe.core.notifications.get_notification_config + +# notification_config = "muezzin.notifications.get_notification_config" + +# Permissions +# ----------- +# Permissions evaluated in scripted ways + +# permission_query_conditions = { +# "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions", +# } +# +# has_permission = { +# "Event": "frappe.desk.doctype.event.event.has_permission", +# } + +# Document Events +# --------------- +# Hook on document methods and events + +doc_events = { + "Sales Invoice": { + "on_submit": "muezzin.events.sales_invoice.submit", + "on_cancel": "muezzin.events.sales_invoice.cancel" + } +} + +# Scheduled Tasks +# --------------- + +scheduler_events = { + # "all": [ + # "muezzin.tasks.all" + # ], + "daily": [ + # "muezzin.property_management.doctype.property_contract.property_contract.create_sales_invoice", + "muezzin.property_management.doctype.property_contract.property_contract.update_unit_status" + ], + "hourly": [ + "muezzin.property_management.doctype.property_contract.property_contract.update_unit_status_basd_on_reservation" + ], + # "weekly": [ + # "muezzin.tasks.weekly" + # ] + # "monthly": [ + # "muezzin.tasks.monthly" + # ] +} + +# Testing +# ------- + +# before_tests = "muezzin.install.before_tests" + +# Overriding Methods +# ------------------------------ +# +# override_whitelisted_methods = { +# "frappe.desk.doctype.event.event.get_events": "muezzin.event.get_events" +# } +# +# each overriding function accepts a `data` argument; +# generated from the base implementation of the doctype dashboard, +# along with any modifications made in other Frappe apps +# override_doctype_dashboards = { +# "Task": "muezzin.task.get_dashboard_data" +# } +fixtures = [{ + "dt": "Custom Field", + "filters": [["name", "in", [ + 'Journal Entry-accounting_dimensions', + 'Journal Entry-unit', + 'Journal Entry-property', + 'Journal Entry-column_break_45', + 'Journal Entry-cost_center', + 'Customer-company_name_en', + 'Sales Invoice-property_contract', + 'Lead-sales_persons', + 'Customer-documents', + 'Customer-customer_documents' + ]]] +}, + { + "dt": "Property Setter", + "filters": [["name", "in", [ + 'Customer-customer_name-l abel', + ]]] +}, + { + "dt": "Accounting Dimension", + "filters": [["name", "in", [ + 'Property', + 'Unit' + ]]] +} + +] diff --git a/muezzin/modules.txt b/muezzin/modules.txt new file mode 100644 index 0000000..5420f19 --- /dev/null +++ b/muezzin/modules.txt @@ -0,0 +1 @@ +Property Management \ No newline at end of file diff --git a/muezzin/patches.txt b/muezzin/patches.txt new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/__init__.py b/muezzin/property_management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/__init__.py b/muezzin/property_management/doctype/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/auction/__init__.py b/muezzin/property_management/doctype/auction/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/auction/auction.js b/muezzin/property_management/doctype/auction/auction.js new file mode 100644 index 0000000..c21f82a --- /dev/null +++ b/muezzin/property_management/doctype/auction/auction.js @@ -0,0 +1,31 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Auction', { + refresh: function(frm) { + frm.set_query('property', () => { + return { + filters: { + property_type: frm.doc.property_type + } + } + }) + }, + + property_type: function(frm) { + frm.doc.property = null; + frm.refresh_field('property'); + }, + + property: function(frm) { + if (frm.doc.property) { + frappe.db.get_value('Property', frm.doc.property, 'property_type') + .then( r=> { + if (r.message) { + frm.doc.property_type = r.message.property_type; + frm.refresh_field('property_type'); + } + }) + } + } +}); diff --git a/muezzin/property_management/doctype/auction/auction.json b/muezzin/property_management/doctype/auction/auction.json new file mode 100644 index 0000000..225c1d9 --- /dev/null +++ b/muezzin/property_management/doctype/auction/auction.json @@ -0,0 +1,118 @@ +{ + "autoname": "format:{auction_subject}-{###}", + "creation": "2021-02-22 18:46:54.748490", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "auction_subject", + "property_type", + "property", + "unit", + "attach_a_file", + "column_break_4", + "date", + "number_of_pieces", + "auction_type", + "court", + "amended_from" + ], + "fields": [ + { + "fieldname": "auction_subject", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Auction Subject", + "reqd": 1 + }, + { + "fieldname": "property_type", + "fieldtype": "Link", + "label": "Property Type", + "options": "Property Type" + }, + { + "fieldname": "property", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Property", + "options": "Property", + "reqd": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "default": "Now", + "fieldname": "date", + "fieldtype": "Date", + "label": "Date" + }, + { + "fieldname": "number_of_pieces", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Number of Pieces", + "reqd": 1 + }, + { + "fieldname": "auction_type", + "fieldtype": "Select", + "label": "Auction Type", + "options": "Ministry of Justice\nIndividual" + }, + { + "fieldname": "court", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Court", + "options": "Court", + "reqd": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Auction", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "attach_a_file", + "fieldtype": "Attach", + "label": "Attach a File" + }, + { + "fieldname": "unit", + "fieldtype": "Table MultiSelect", + "label": "Unit", + "options": "Auction Unit", + "reqd": 1 + } + ], + "is_submittable": 1, + "modified": "2021-02-25 11:58:26.923370", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Auction", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/auction/auction.py b/muezzin/property_management/doctype/auction/auction.py new file mode 100644 index 0000000..7db6da4 --- /dev/null +++ b/muezzin/property_management/doctype/auction/auction.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class Auction(Document): + pass diff --git a/muezzin/property_management/doctype/auction/test_auction.py b/muezzin/property_management/doctype/auction/test_auction.py new file mode 100644 index 0000000..469204e --- /dev/null +++ b/muezzin/property_management/doctype/auction/test_auction.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestAuction(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/auction_free_card/__init__.py b/muezzin/property_management/doctype/auction_free_card/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/auction_free_card/auction_free_card.js b/muezzin/property_management/doctype/auction_free_card/auction_free_card.js new file mode 100644 index 0000000..c88e026 --- /dev/null +++ b/muezzin/property_management/doctype/auction_free_card/auction_free_card.js @@ -0,0 +1,37 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Auction Free Card', { + refresh: function(frm) { + frm.set_query('units', () => { + if (frm.doc.auction) { + return { + query: 'muezzin.property_management.doctype.auction_free_card.auction_free_card.get_unit', + filters: { + 'parent': frm.doc.auction + } + } + } else { + return { + filters: { + "parent": 'Empty' + } + } + } + }) + }, + + auction: function(frm) { + if(frm.doc.auction) { + frm.call('set_unit') + .then( r => { + refresh_field('units'); + }) + } else { + frm.doc.units = []; + frm.doc.property_type = null; + frm.doc.property = null; + frm.refresh_fields('units', 'property', 'property_type'); + } + } +}); diff --git a/muezzin/property_management/doctype/auction_free_card/auction_free_card.json b/muezzin/property_management/doctype/auction_free_card/auction_free_card.json new file mode 100644 index 0000000..f20aaff --- /dev/null +++ b/muezzin/property_management/doctype/auction_free_card/auction_free_card.json @@ -0,0 +1,190 @@ +{ + "autoname": "format:AFC-{auction}-{###}", + "creation": "2021-02-23 14:11:09.113084", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "auction", + "property", + "customer", + "column_break_4", + "units", + "property_type", + "mobile_number", + "national_id", + "section_break_9", + "bank", + "check_number", + "check_value", + "vat_on_commission", + "quest_value", + "total_quest", + "column_break_16", + "opening_price", + "plan", + "notes", + "tax_on_property", + "attach_files", + "amended_from" + ], + "fields": [ + { + "fieldname": "auction", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Auction", + "options": "Auction", + "reqd": 1 + }, + { + "fetch_from": "auction.property", + "fieldname": "property", + "fieldtype": "Data", + "label": "Property", + "read_only": 1 + }, + { + "fieldname": "mobile_number", + "fieldtype": "Data", + "label": "Mobile Number" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fetch_from": "auction.property_type", + "fieldname": "property_type", + "fieldtype": "Data", + "label": "Property Type", + "read_only": 1 + }, + { + "fieldname": "units", + "fieldtype": "Table MultiSelect", + "label": "Units", + "options": "Auction Unit", + "reqd": 1 + }, + { + "fieldname": "customer", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Customer", + "options": "Customer", + "reqd": 1 + }, + { + "fieldname": "national_id", + "fieldtype": "Data", + "label": "National Id" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break" + }, + { + "fieldname": "bank", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Bank", + "options": "Bank", + "reqd": 1 + }, + { + "fieldname": "check_number", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Check Number", + "reqd": 1 + }, + { + "fieldname": "check_value", + "fieldtype": "Float", + "label": "Check Value", + "reqd": 1 + }, + { + "fieldname": "vat_on_commission", + "fieldtype": "Float", + "label": "Vat On Commission", + "reqd": 1 + }, + { + "fieldname": "total_quest", + "fieldtype": "Float", + "label": "Total Quest", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "tax_on_property", + "fieldtype": "Check", + "label": "Tax on Property" + }, + { + "fieldname": "column_break_16", + "fieldtype": "Column Break" + }, + { + "fieldname": "opening_price", + "fieldtype": "Float", + "label": "Opening Price" + }, + { + "fieldname": "quest_value", + "fieldtype": "Float", + "label": "Quest Value", + "reqd": 1 + }, + { + "fieldname": "plan", + "fieldtype": "Data", + "label": "Plan", + "reqd": 1 + }, + { + "fieldname": "notes", + "fieldtype": "Data", + "label": "Notes" + }, + { + "fieldname": "attach_files", + "fieldtype": "Attach", + "label": "Attach Files" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Auction Free Card", + "print_hide": 1, + "read_only": 1 + } + ], + "is_submittable": 1, + "modified": "2021-02-25 11:57:24.514506", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Auction Free Card", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/auction_free_card/auction_free_card.py b/muezzin/property_management/doctype/auction_free_card/auction_free_card.py new file mode 100644 index 0000000..40ce817 --- /dev/null +++ b/muezzin/property_management/doctype/auction_free_card/auction_free_card.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class AuctionFreeCard(Document): + def set_unit(self): + result = frappe.db.get_list('Auction Unit', {'parent': self.auction}, 'unit') + for row in result: + self.append('units', { + 'unit': row.unit + }) + +@frappe.whitelist() +def get_unit(doctype, txt, searchfield, start, page_len, filters): + return frappe.db.sql("""select unit + from `tabAuction Unit` + where + parent = {auction}""" + .format(auction = frappe.db.escape(filters.get("auction")) + )) \ No newline at end of file diff --git a/muezzin/property_management/doctype/auction_free_card/test_auction_free_card.py b/muezzin/property_management/doctype/auction_free_card/test_auction_free_card.py new file mode 100644 index 0000000..cac967d --- /dev/null +++ b/muezzin/property_management/doctype/auction_free_card/test_auction_free_card.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestAuctionFreeCard(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/auction_unit/__init__.py b/muezzin/property_management/doctype/auction_unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/auction_unit/auction_unit.json b/muezzin/property_management/doctype/auction_unit/auction_unit.json new file mode 100644 index 0000000..999cf73 --- /dev/null +++ b/muezzin/property_management/doctype/auction_unit/auction_unit.json @@ -0,0 +1,29 @@ +{ + "creation": "2021-02-24 18:10:02.718182", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "unit" + ], + "fields": [ + { + "fieldname": "unit", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Unit", + "options": "Unit" + } + ], + "istable": 1, + "modified": "2021-02-24 18:35:03.788544", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Auction Unit", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/auction_unit/auction_unit.py b/muezzin/property_management/doctype/auction_unit/auction_unit.py new file mode 100644 index 0000000..4dab8d5 --- /dev/null +++ b/muezzin/property_management/doctype/auction_unit/auction_unit.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class AuctionUnit(Document): + pass diff --git a/muezzin/property_management/doctype/contract_annual_raise/__init__.py b/muezzin/property_management/doctype/contract_annual_raise/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/contract_annual_raise/contract_annual_raise.json b/muezzin/property_management/doctype/contract_annual_raise/contract_annual_raise.json new file mode 100644 index 0000000..2ced3b7 --- /dev/null +++ b/muezzin/property_management/doctype/contract_annual_raise/contract_annual_raise.json @@ -0,0 +1,43 @@ +{ + "creation": "2021-02-02 12:45:10.043925", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "date", + "annual_raise_type", + "annual_raise_amount" + ], + "fields": [ + { + "fieldname": "date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date" + }, + { + "fieldname": "annual_raise_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Annual Raise Type", + "options": "\nAmount\nPercentage" + }, + { + "fieldname": "annual_raise_amount", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Annual Raise Amount" + } + ], + "istable": 1, + "modified": "2021-06-30 03:36:03.351219", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Contract Annual Raise", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/contract_annual_raise/contract_annual_raise.py b/muezzin/property_management/doctype/contract_annual_raise/contract_annual_raise.py new file mode 100644 index 0000000..7d77653 --- /dev/null +++ b/muezzin/property_management/doctype/contract_annual_raise/contract_annual_raise.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class ContractAnnualRaise(Document): + pass diff --git a/muezzin/property_management/doctype/contract_payments/__init__.py b/muezzin/property_management/doctype/contract_payments/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/contract_payments/contract_payments.json b/muezzin/property_management/doctype/contract_payments/contract_payments.json new file mode 100644 index 0000000..7a2cf4a --- /dev/null +++ b/muezzin/property_management/doctype/contract_payments/contract_payments.json @@ -0,0 +1,92 @@ +{ + "creation": "2021-02-02 13:38:41.441710", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "date", + "amount", + "reason", + "invoice", + "create_invoice", + "discount_applied", + "discount_refrence", + "remarks" + ], + "fields": [ + { + "columns": 1, + "fieldname": "date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date", + "read_only": 1 + }, + { + "allow_on_submit": 1, + "columns": 1, + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "options": "tenant_name_currency" + }, + { + "columns": 2, + "fieldname": "reason", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Reason" + }, + { + "columns": 2, + "fieldname": "invoice", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Invoice", + "options": "Sales Invoice", + "read_only": 1 + }, + { + "allow_on_submit": 1, + "columns": 1, + "fieldname": "create_invoice", + "fieldtype": "Button", + "in_list_view": 1, + "label": "Create Invoice" + }, + { + "columns": 2, + "fieldname": "remarks", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Remarks" + }, + { + "columns": 1, + "default": "0", + "fieldname": "discount_applied", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Discount Applied", + "read_only": 1 + }, + { + "fieldname": "discount_refrence", + "fieldtype": "Data", + "label": "Discount Refrence", + "read_only": 1 + } + ], + "istable": 1, + "modified": "2021-12-25 18:08:34.469555", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Contract Payments", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/contract_payments/contract_payments.py b/muezzin/property_management/doctype/contract_payments/contract_payments.py new file mode 100644 index 0000000..973d682 --- /dev/null +++ b/muezzin/property_management/doctype/contract_payments/contract_payments.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class ContractPayments(Document): + pass diff --git a/muezzin/property_management/doctype/court/__init__.py b/muezzin/property_management/doctype/court/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/court/court.js b/muezzin/property_management/doctype/court/court.js new file mode 100644 index 0000000..5669a66 --- /dev/null +++ b/muezzin/property_management/doctype/court/court.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Court', { + // refresh: function(frm) { + + // } +}); diff --git a/muezzin/property_management/doctype/court/court.json b/muezzin/property_management/doctype/court/court.json new file mode 100644 index 0000000..b4921a4 --- /dev/null +++ b/muezzin/property_management/doctype/court/court.json @@ -0,0 +1,52 @@ +{ + "autoname": "field:court_name", + "creation": "2021-02-22 18:46:46.022348", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "court_name", + "amended_from" + ], + "fields": [ + { + "fieldname": "court_name", + "fieldtype": "Data", + "label": "Court Name", + "unique": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Court", + "print_hide": 1, + "read_only": 1 + } + ], + "is_submittable": 1, + "modified": "2021-02-22 18:46:46.022348", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Court", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/court/court.py b/muezzin/property_management/doctype/court/court.py new file mode 100644 index 0000000..6cb5929 --- /dev/null +++ b/muezzin/property_management/doctype/court/court.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class Court(Document): + pass diff --git a/muezzin/property_management/doctype/court/test_court.py b/muezzin/property_management/doctype/court/test_court.py new file mode 100644 index 0000000..d0d77f7 --- /dev/null +++ b/muezzin/property_management/doctype/court/test_court.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestCourt(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/discount_request/__init__.py b/muezzin/property_management/doctype/discount_request/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/discount_request/discount_request.js b/muezzin/property_management/doctype/discount_request/discount_request.js new file mode 100644 index 0000000..0429675 --- /dev/null +++ b/muezzin/property_management/doctype/discount_request/discount_request.js @@ -0,0 +1,39 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Discount Request', { + refresh: function (frm) { + get_brand_names(frm) + }, + onload: function (frm) { + get_brand_names(frm) + }, + customer: function (frm) { + get_brand_names(frm) + frm.set_query('property_contract', function (doc) { + return { + filters: { + renter: frm.doc.customer, + docstatus: 1 + } + } + }); + } +}); + + +let get_brand_names = function (frm) { + if (frm.doc.customer) { + frappe.call({ + method: "get_brand_names", + doc: frm.doc, + callback: function (data) { + if (data.message) { + let options = data.message; + let options_new = options.join("\n"); + frm.set_df_property('brand_name', 'options', options_new); + } + } + }) + } +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/discount_request/discount_request.json b/muezzin/property_management/doctype/discount_request/discount_request.json new file mode 100644 index 0000000..dc076c5 --- /dev/null +++ b/muezzin/property_management/doctype/discount_request/discount_request.json @@ -0,0 +1,153 @@ +{ + "autoname": "format:{property_contract}-{YYYY}-{#####}", + "creation": "2021-12-25 15:32:33.360291", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "customer", + "customer_name", + "brand_name", + "column_break_4", + "start_date", + "end_date", + "section_break_7", + "property_contract", + "unit_name", + "column_break_10", + "apply_discount_on", + "discount_type", + "percentage", + "amount", + "section_break_14", + "description", + "amended_from" + ], + "fields": [ + { + "fieldname": "customer", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Customer", + "options": "Customer", + "reqd": 1 + }, + { + "fetch_from": "customer.customer_name", + "fieldname": "customer_name", + "fieldtype": "Read Only", + "label": "Customer Name" + }, + { + "fieldname": "brand_name", + "fieldtype": "Select", + "label": "Tenant Brand Name" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "label": "Start Date", + "reqd": 1 + }, + { + "fieldname": "end_date", + "fieldtype": "Date", + "label": "End Date", + "reqd": 1 + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, + { + "fieldname": "property_contract", + "fieldtype": "Link", + "label": "Property Contract", + "options": "Property Contract" + }, + { + "fetch_from": "property_contract.unit", + "fieldname": "unit_name", + "fieldtype": "Link", + "label": "Unit Name", + "options": "Unit", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Discount Request", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, + { + "fieldname": "discount_type", + "fieldtype": "Select", + "label": "Discount Type", + "options": "\nPercentage\nAmount" + }, + { + "depends_on": "eval:doc.discount_type=='Percentage'", + "fieldname": "percentage", + "fieldtype": "Percent", + "label": "Percentage" + }, + { + "depends_on": "eval:doc.discount_type=='Amount'", + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount" + }, + { + "fieldname": "section_break_14", + "fieldtype": "Section Break" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + }, + { + "fieldname": "apply_discount_on", + "fieldtype": "Select", + "label": "Apply Discount On", + "options": "\nRent\nServices Charges\nAll", + "reqd": 1 + } + ], + "is_submittable": 1, + "modified": "2021-12-25 16:28:30.028665", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Discount Request", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 1, + "track_views": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/discount_request/discount_request.py b/muezzin/property_management/doctype/discount_request/discount_request.py new file mode 100644 index 0000000..0cc95fc --- /dev/null +++ b/muezzin/property_management/doctype/discount_request/discount_request.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions 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 flt + + +class DiscountRequest(Document): + def on_submit(self): + self.add_remove_discount(type='Add') + + def on_cancel(self): + self.add_remove_discount(type='Remove') + + def get_brand_names(self): + list_items = [] + brands = frappe.get_all("Tenant Brand Name", filters={"parent": self.customer}, fields=['tenant_brand_name'], + order_by="tenant_brand_name") + if brands: + for item in brands: + list_items.append(item["tenant_brand_name"]) + + return list_items + + def add_remove_discount(self, type): + filters = {"parent": self.property_contract, "date": ["between", [self.start_date, self.end_date]], + "invoice": ["=", '']} + if self.apply_discount_on != "All": + filters['reason'] = self.apply_discount_on + payments = frappe.db.get_all("Contract Payments", filters=filters, order_by="date") + for row in payments: + doc = frappe.get_doc("Contract Payments", row["name"]) + if type == 'Add': + doc.amount -= ( + ((flt(self.percentage) / 100) * flt(doc.amount)) if self.discount_type == 'Percentage' else flt( + self.amount)) + doc.remarks = ' تم تطبيق خصم على هذا الشهريقيمة
' + str((((flt(self.percentage) / 100) * flt( + doc.amount)) if self.discount_type == 'Percentage' else flt(self.amount))) + "
" + str(self.name) + doc.discount_applied = 1 + doc.discount_refrence = self.name + else: + doc.amount = (( (flt(doc.amount) * 100) / flt(100 - flt(self.percentage)) ) if self.discount_type == 'Percentage' else (flt(self.amount)+flt(doc.amount))) + doc.remarks = '' + doc.discount_applied = 0 + doc.discount_refrence = '' + + doc.flags.ignore_validate_update_after_submit = True + doc.save() + frappe.db.commit() diff --git a/muezzin/property_management/doctype/discount_request/test_discount_request.py b/muezzin/property_management/doctype/discount_request/test_discount_request.py new file mode 100644 index 0000000..2cb40d7 --- /dev/null +++ b/muezzin/property_management/doctype/discount_request/test_discount_request.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestDiscountRequest(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/document/__init__.py b/muezzin/property_management/doctype/document/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/document/document.json b/muezzin/property_management/doctype/document/document.json new file mode 100644 index 0000000..3af315d --- /dev/null +++ b/muezzin/property_management/doctype/document/document.json @@ -0,0 +1,60 @@ +{ + "creation": "2021-12-28 00:43:37.531121", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "document_type", + "attach", + "start_date", + "end_date" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "document_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Document Type", + "options": "Document Type", + "reqd": 1 + }, + { + "fieldname": "attach", + "fieldtype": "Attach", + "in_list_view": 1, + "label": "Attach", + "reqd": 1 + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Start Date" + }, + { + "fieldname": "end_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "End Date" + } + ], + "istable": 1, + "modified": "2021-12-28 00:45:09.880101", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Document", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/document/document.py b/muezzin/property_management/doctype/document/document.py new file mode 100644 index 0000000..87e6956 --- /dev/null +++ b/muezzin/property_management/doctype/document/document.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class Document(Document): + pass diff --git a/muezzin/property_management/doctype/document_type/__init__.py b/muezzin/property_management/doctype/document_type/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/document_type/document_type.js b/muezzin/property_management/doctype/document_type/document_type.js new file mode 100644 index 0000000..697af09 --- /dev/null +++ b/muezzin/property_management/doctype/document_type/document_type.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Document Type', { + // refresh: function(frm) { + + // } +}); diff --git a/muezzin/property_management/doctype/document_type/document_type.json b/muezzin/property_management/doctype/document_type/document_type.json new file mode 100644 index 0000000..55212d0 --- /dev/null +++ b/muezzin/property_management/doctype/document_type/document_type.json @@ -0,0 +1,43 @@ +{ + "autoname": "field:type", + "creation": "2021-12-28 00:40:08.546784", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "type" + ], + "fields": [ + { + "fieldname": "type", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Type", + "reqd": 1, + "unique": 1 + } + ], + "modified": "2021-12-28 00:40:14.109297", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Document Type", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/document_type/document_type.py b/muezzin/property_management/doctype/document_type/document_type.py new file mode 100644 index 0000000..ddb4afb --- /dev/null +++ b/muezzin/property_management/doctype/document_type/document_type.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class DocumentType(Document): + pass diff --git a/muezzin/property_management/doctype/document_type/test_document_type.py b/muezzin/property_management/doctype/document_type/test_document_type.py new file mode 100644 index 0000000..c66a673 --- /dev/null +++ b/muezzin/property_management/doctype/document_type/test_document_type.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestDocumentType(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/maintenance_ticket/__init__.py b/muezzin/property_management/doctype/maintenance_ticket/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/maintenance_ticket/maintenance_ticket.js b/muezzin/property_management/doctype/maintenance_ticket/maintenance_ticket.js new file mode 100644 index 0000000..ce7fae8 --- /dev/null +++ b/muezzin/property_management/doctype/maintenance_ticket/maintenance_ticket.js @@ -0,0 +1,47 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Maintenance Ticket', { + refresh: function(frm) { + queries(frm); + }, + + onload_post_render(frm) { + set_location(frm); + }, + + property(frm) { + if (frm.doc.property) { + // setTimeout(set_location(frm), 500); + } else { + frm.doc.unit = null; + frm.doc.property_location = null; + frm.doc.latitude = null; + frm.doc.longitude = null; + frm.refresh_fields(['unit', 'property_location', 'latitude', 'longitude']); + } + }, + property_location(frm) { + set_location(frm); + } +}); + +function queries(frm) { + frm.set_query('unit', () => { + return { + filters: { + property: frm.doc.property + } + } + }) +} + +// function set_location(frm) { +// if (!frm.doc.property_location && frm.doc.latitude && frm.doc.longitude) { +// frm.fields_dict.property_location.map.setView([frm.doc.latitude, frm.doc.longitude], 13); +// } +// else { +// frm.doc.latitude = frm.fields_dict.property_location.map.getCenter()['lat']; +// frm.doc.longitude = frm.fields_dict.property_location.map.getCenter()['lng']; +// } +// } \ No newline at end of file diff --git a/muezzin/property_management/doctype/maintenance_ticket/maintenance_ticket.json b/muezzin/property_management/doctype/maintenance_ticket/maintenance_ticket.json new file mode 100644 index 0000000..f999542 --- /dev/null +++ b/muezzin/property_management/doctype/maintenance_ticket/maintenance_ticket.json @@ -0,0 +1,173 @@ +{ + "autoname": "naming_series:", + "creation": "2021-01-15 17:58:51.656150", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "property", + "unit", + "category", + "property_location", + "column_break_5", + "client", + "mobile_number", + "service_provider", + "section_break_11", + "notes", + "section_break_13", + "latitude", + "responsible_user", + "column_break_15", + "longitude", + "send_sms_alert_to_responsible_user", + "section_break_18", + "location", + "amended_from" + ], + "fields": [ + { + "fieldname": "naming_series", + "fieldtype": "Data", + "label": "Serial Number", + "options": "MT-" + }, + { + "fieldname": "property", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Property", + "options": "Property", + "reqd": 1 + }, + { + "fieldname": "category", + "fieldtype": "Select", + "label": "Category", + "options": "Electrical Maintenance\nWater Maintenance\nA / C Maintenance\nElevators\nCleaning\nOther" + }, + { + "fieldname": "mobile_number", + "fieldtype": "Data", + "label": "Mobile Number" + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "fieldname": "service_provider", + "fieldtype": "Link", + "label": "Service Provider", + "options": "Supplier" + }, + { + "fieldname": "client", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Client", + "options": "Customer", + "reqd": 1 + }, + { + "fetch_from": "property.property_location", + "fieldname": "property_location", + "fieldtype": "Data", + "label": "Property Location", + "read_only": 1 + }, + { + "fieldname": "notes", + "fieldtype": "Small Text", + "label": "Notes" + }, + { + "default": "0", + "fieldname": "send_sms_alert_to_responsible_user", + "fieldtype": "Check", + "label": "Send SMS Alert to Responsible User" + }, + { + "fetch_from": "property.latitude", + "fieldname": "latitude", + "fieldtype": "Data", + "label": "Latitude", + "read_only": 1 + }, + { + "fieldname": "column_break_15", + "fieldtype": "Column Break" + }, + { + "fieldname": "responsible_user", + "fieldtype": "Link", + "label": "Responsible User", + "options": "User" + }, + { + "fetch_from": "property.longitude", + "fieldname": "longitude", + "fieldtype": "Data", + "label": "Longitude", + "read_only": 1 + }, + { + "fieldname": "section_break_18", + "fieldtype": "Section Break" + }, + { + "fieldname": "location", + "fieldtype": "Geolocation", + "label": "Location" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Maintenance Ticket", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "unit", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Unit", + "options": "Unit", + "reqd": 1 + }, + { + "fieldname": "section_break_11", + "fieldtype": "Section Break" + }, + { + "fieldname": "section_break_13", + "fieldtype": "Section Break" + } + ], + "is_submittable": 1, + "modified": "2021-01-18 12:35:14.883674", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Maintenance Ticket", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/maintenance_ticket/maintenance_ticket.py b/muezzin/property_management/doctype/maintenance_ticket/maintenance_ticket.py new file mode 100644 index 0000000..c17c7ad --- /dev/null +++ b/muezzin/property_management/doctype/maintenance_ticket/maintenance_ticket.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class MaintenanceTicket(Document): + pass diff --git a/muezzin/property_management/doctype/maintenance_ticket/test_maintenance_ticket.py b/muezzin/property_management/doctype/maintenance_ticket/test_maintenance_ticket.py new file mode 100644 index 0000000..2e7c599 --- /dev/null +++ b/muezzin/property_management/doctype/maintenance_ticket/test_maintenance_ticket.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestMaintenanceTicket(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/opening_phase/__init__.py b/muezzin/property_management/doctype/opening_phase/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/opening_phase/opening_phase.js b/muezzin/property_management/doctype/opening_phase/opening_phase.js new file mode 100644 index 0000000..1fbcbe0 --- /dev/null +++ b/muezzin/property_management/doctype/opening_phase/opening_phase.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Opening Phase', { + // refresh: function(frm) { + + // } +}); diff --git a/muezzin/property_management/doctype/opening_phase/opening_phase.json b/muezzin/property_management/doctype/opening_phase/opening_phase.json new file mode 100644 index 0000000..314a6fd --- /dev/null +++ b/muezzin/property_management/doctype/opening_phase/opening_phase.json @@ -0,0 +1,43 @@ +{ + "autoname": "field:phase_name", + "creation": "2021-04-12 01:36:49.755823", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "phase_name" + ], + "fields": [ + { + "fieldname": "phase_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Phase Name", + "reqd": 1, + "unique": 1 + } + ], + "modified": "2021-04-12 01:36:58.923784", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Opening Phase", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/opening_phase/opening_phase.py b/muezzin/property_management/doctype/opening_phase/opening_phase.py new file mode 100644 index 0000000..6995e2b --- /dev/null +++ b/muezzin/property_management/doctype/opening_phase/opening_phase.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class OpeningPhase(Document): + pass diff --git a/muezzin/property_management/doctype/opening_phase/test_opening_phase.py b/muezzin/property_management/doctype/opening_phase/test_opening_phase.py new file mode 100644 index 0000000..262c120 --- /dev/null +++ b/muezzin/property_management/doctype/opening_phase/test_opening_phase.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestOpeningPhase(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/property/__init__.py b/muezzin/property_management/doctype/property/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/property/property.js b/muezzin/property_management/doctype/property/property.js new file mode 100644 index 0000000..59867df --- /dev/null +++ b/muezzin/property_management/doctype/property/property.js @@ -0,0 +1,58 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Property', { + refresh: function(frm) { + if (!frm.is_dirty()) { + create_custom_buttons(frm); + } + } +}); + +const create_custom_buttons = function(frm) { + frm.add_custom_button(__('Journal Entry'), () => { + let doc = frm.doc + frappe.run_serially([ + () => frappe.new_doc('Journal Entry'), + () => { + cur_frm.doc.cost_center = doc.cost_center; + cur_frm.doc.property = doc.name; + cur_frm.refresh(); + } + ]); + }, __('Create')); + + frm.add_custom_button(__('Sales Invoice'), () => { + let doc = frm.doc + frappe.run_serially([ + () => frappe.new_doc('Sales Invoice'), + () => { + cur_frm.doc.cost_center = doc.cost_center; + cur_frm.doc.property = doc.name; + cur_frm.refresh(); + } + ]); + }, __('Create')); + + frm.add_custom_button(__('Payment Entry'), () => { + let doc = frm.doc + frappe.run_serially([ + () => frappe.new_doc('Payment Entry'), + () => { + cur_frm.doc.cost_center = doc.cost_center; + cur_frm.doc.property = doc.name; + if (doc.customer) { + cur_frm.doc.party_type = 'Customer'; + cur_frm.doc.party = doc.customer; + cur_frm.doc.party_name = doc.customer_name; + } + if (doc.supplier) { + cur_frm.doc.party_type = 'Supplier'; + cur_frm.doc.party = doc.supplier; + cur_frm.doc.party_name = doc.supplier_name; + } + cur_frm.refresh(); + } + ]); + }, __('Create')); +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/property/property.json b/muezzin/property_management/doctype/property/property.json new file mode 100644 index 0000000..71b28ed --- /dev/null +++ b/muezzin/property_management/doctype/property/property.json @@ -0,0 +1,499 @@ +{ + "autoname": "field:property_name", + "creation": "2021-01-15 17:09:32.926540", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "customer", + "customer_name", + "column_break_2", + "supplier", + "supplier_name", + "basic_information_section", + "property_name", + "property_type", + "property_code_or_plate", + "property_number", + "property_management_commission", + "subject_to_vat", + "property_location", + "about_him", + "column_break_9", + "property_address", + "area_of_property", + "construction_year", + "status", + "governorate", + "city", + "contact", + "type_of_commission_for_property_management", + "calculated_as_a_percentage", + "accounting_dimensions_section", + "cost_center", + "property_data_section", + "property_owner", + "property_owner_company", + "property_owner_supplier", + "property_owner_customer", + "instrument_number", + "land__real_estate_number", + "bank", + "column_break_21", + "instrument_history", + "instrument_case", + "instrument_image_section", + "attach_instrument_image", + "property_evaluation_data_section", + "real_estate_evaluation", + "amount_of_evaluation", + "purchasing_price", + "column_break_30", + "date_of_evaluation", + "resident_agency", + "date_of_purchase", + "picture_of_property_section", + "attach_property_image", + "additional_data_section", + "number_of_elevators", + "national_address", + "number_of_positions", + "real_estate_facilities", + "electricity_account_number", + "cleanliness_for_common_entrance", + "column_break_43", + "number_of_floors", + "flat_construction", + "property_features", + "water_account_number", + "architecture_guard_data_section", + "building_guard_name", + "architecture_guard_identity", + "column_break_51", + "building_guards_mobile_number", + "adding_units_to_property_section", + "units", + "location_on_the_map_section", + "latitude", + "amended_from" + ], + "fields": [ + { + "fieldname": "basic_information_section", + "fieldtype": "Section Break", + "label": "Basic Information" + }, + { + "fieldname": "property_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Property Name", + "no_copy": 1, + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "property_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Property Type", + "options": "Property Type", + "reqd": 1 + }, + { + "fieldname": "governorate", + "fieldtype": "Link", + "label": "Governorate", + "options": "Territory" + }, + { + "fieldname": "property_management_commission", + "fieldtype": "Data", + "label": "Property Management Commission" + }, + { + "default": "0", + "fieldname": "subject_to_vat", + "fieldtype": "Check", + "label": "Subject to VAT" + }, + { + "fieldname": "about_him", + "fieldtype": "Small Text", + "label": "About Him" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "fieldname": "property_address", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Property Address", + "reqd": 1 + }, + { + "fieldname": "area_of_property", + "fieldtype": "Data", + "label": "Area of Property" + }, + { + "fieldname": "construction_year", + "fieldtype": "Data", + "label": "Construction Year" + }, + { + "fieldname": "city", + "fieldtype": "Link", + "label": "City", + "options": "Territory" + }, + { + "fieldname": "type_of_commission_for_property_management", + "fieldtype": "Select", + "label": "Type of Commission for Property Management", + "options": "With Payments Made\nAnnual\nMid Term\nQuarterly" + }, + { + "default": "0", + "fieldname": "calculated_as_a_percentage", + "fieldtype": "Check", + "label": "Calculated as a Percentage" + }, + { + "fieldname": "property_data_section", + "fieldtype": "Section Break", + "label": "Property Data" + }, + { + "fieldname": "instrument_number", + "fieldtype": "Data", + "label": "Instrument Number" + }, + { + "fieldname": "land__real_estate_number", + "fieldtype": "Data", + "label": "Land / Real Estate Number" + }, + { + "fieldname": "bank", + "fieldtype": "Link", + "label": "Bank", + "options": "Bank" + }, + { + "fieldname": "column_break_21", + "fieldtype": "Column Break" + }, + { + "fieldname": "instrument_history", + "fieldtype": "Date", + "label": "Instrument History" + }, + { + "fieldname": "instrument_case", + "fieldtype": "Select", + "label": "Instrument Case", + "options": "Mortgaged\nNot Mortgaged" + }, + { + "fieldname": "instrument_image_section", + "fieldtype": "Section Break", + "label": "Instrument Image" + }, + { + "fieldname": "property_evaluation_data_section", + "fieldtype": "Section Break", + "label": "Property Evaluation Data" + }, + { + "fieldname": "real_estate_evaluation", + "fieldtype": "Data", + "label": "Real Estate Evaluation" + }, + { + "fieldname": "amount_of_evaluation", + "fieldtype": "Data", + "label": "Amount of Evaluation" + }, + { + "fieldname": "purchasing_price", + "fieldtype": "Float", + "label": "Purchasing Price" + }, + { + "fieldname": "column_break_30", + "fieldtype": "Column Break" + }, + { + "fieldname": "date_of_evaluation", + "fieldtype": "Date", + "label": "Date of Evaluation" + }, + { + "fieldname": "resident_agency", + "fieldtype": "Data", + "label": "Resident Agency" + }, + { + "fieldname": "date_of_purchase", + "fieldtype": "Date", + "label": "Date of Purchase" + }, + { + "fieldname": "picture_of_property_section", + "fieldtype": "Section Break", + "label": "Picture of Property" + }, + { + "fieldname": "additional_data_section", + "fieldtype": "Section Break", + "label": "Additional Data" + }, + { + "fieldname": "number_of_elevators", + "fieldtype": "Int", + "label": "Number of Elevators" + }, + { + "fieldname": "national_address", + "fieldtype": "Data", + "label": "National Address" + }, + { + "fieldname": "number_of_positions", + "fieldtype": "Int", + "label": "Number of Positions" + }, + { + "fieldname": "real_estate_facilities", + "fieldtype": "Small Text", + "label": "Real Estate Facilities" + }, + { + "fieldname": "electricity_account_number", + "fieldtype": "Data", + "label": "Electricity Account Number" + }, + { + "default": "0", + "fieldname": "cleanliness_for_common_entrance", + "fieldtype": "Check", + "label": "Cleanliness for Common Entrance" + }, + { + "fieldname": "column_break_43", + "fieldtype": "Column Break" + }, + { + "fieldname": "number_of_floors", + "fieldtype": "Int", + "label": "Number of Floors" + }, + { + "fieldname": "flat_construction", + "fieldtype": "Data", + "label": "Flat Construction" + }, + { + "fieldname": "property_features", + "fieldtype": "Small Text", + "label": "Property Features" + }, + { + "fieldname": "water_account_number", + "fieldtype": "Data", + "label": "Water Account Number" + }, + { + "fieldname": "architecture_guard_data_section", + "fieldtype": "Section Break", + "label": "Architecture Guard Data" + }, + { + "fieldname": "building_guard_name", + "fieldtype": "Data", + "label": "Building Guard Name" + }, + { + "fieldname": "architecture_guard_identity", + "fieldtype": "Data", + "label": "Architecture Guard Identity" + }, + { + "fieldname": "column_break_51", + "fieldtype": "Column Break" + }, + { + "fieldname": "building_guards_mobile_number", + "fieldtype": "Data", + "label": "Building Guard's Mobile Number" + }, + { + "fieldname": "adding_units_to_property_section", + "fieldtype": "Section Break", + "label": "Adding Units to Property" + }, + { + "fieldname": "units", + "fieldtype": "Table", + "label": "Units", + "options": "Property Unit Detail" + }, + { + "fieldname": "location_on_the_map_section", + "fieldtype": "Section Break", + "label": "Location on the map" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Property", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "attach_instrument_image", + "fieldtype": "Attach Image", + "label": "Image" + }, + { + "fieldname": "attach_property_image", + "fieldtype": "Attach Image", + "label": "Image" + }, + { + "fieldname": "contact", + "fieldtype": "Link", + "label": "Contact", + "options": "Contact" + }, + { + "fieldname": "latitude", + "fieldtype": "Data", + "label": "Latitude" + }, + { + "fieldname": "property_location", + "fieldtype": "Select", + "label": "Property Location", + "options": "Arkan Extention\nArkan Existing" + }, + { + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Available\nRented\nSold" + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "customer", + "fieldtype": "Link", + "label": "Customer", + "no_copy": 1, + "options": "Customer", + "read_only": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "supplier", + "fieldtype": "Link", + "label": "Supplier", + "no_copy": 1, + "options": "Supplier", + "read_only": 1 + }, + { + "fieldname": "property_code_or_plate", + "fieldtype": "Int", + "label": "Property Code / Plate" + }, + { + "fieldname": "property_number", + "fieldtype": "Int", + "label": "Property Number" + }, + { + "fieldname": "customer_name", + "fieldtype": "Data", + "label": "Customer Name", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "supplier_name", + "fieldtype": "Data", + "label": "Supplier Name", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "property_owner", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Property Owner", + "options": "\nCompany\nSupplier\nCustomer", + "reqd": 1 + }, + { + "depends_on": "eval:doc.property_owner=='Company'", + "fieldname": "property_owner_company", + "fieldtype": "Link", + "label": "Property Owner Company", + "options": "Company" + }, + { + "depends_on": "eval:doc.property_owner=='Supplier'", + "fieldname": "property_owner_supplier", + "fieldtype": "Link", + "label": "Property Owner Supplier", + "options": "Supplier" + }, + { + "depends_on": "eval:doc.property_owner=='Customer'", + "fieldname": "property_owner_customer", + "fieldtype": "Link", + "label": "Property Owner Customer", + "options": "Customer" + } + ], + "modified": "2021-03-01 16:41:28.607988", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Property", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/property/property.py b/muezzin/property_management/doctype/property/property.py new file mode 100644 index 0000000..0088cd4 --- /dev/null +++ b/muezzin/property_management/doctype/property/property.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class Property(Document): + pass diff --git a/muezzin/property_management/doctype/property/test_property.py b/muezzin/property_management/doctype/property/test_property.py new file mode 100644 index 0000000..c1982b9 --- /dev/null +++ b/muezzin/property_management/doctype/property/test_property.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestProperty(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/property_contract/__init__.py b/muezzin/property_management/doctype/property_contract/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/property_contract/property_contract (copy).py b/muezzin/property_management/doctype/property_contract/property_contract (copy).py new file mode 100644 index 0000000..7ad8ed2 --- /dev/null +++ b/muezzin/property_management/doctype/property_contract/property_contract (copy).py @@ -0,0 +1,545 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +import datetime +# from datetime import datetime, timedelta +import dateutil +import calendar +from calendar import weekday, monthrange +from collections import Counter +from dateutil.relativedelta import relativedelta +from dateutil.rrule import rrule, MONTHLY, YEARLY +from frappe.model.document import Document +from frappe.utils import flt, date_diff, getdate, cint, nowdate,get_datetime + + +class PropertyContract(Document): + def validate(self): + if self.is_new(): + self.add_payment() + # self.add_contract_annual_raise() + + def before_submit(self): + """ + Validates if property already on rent between the from and to dates + """ + + # Get Property Contaract if from date is between new from and new to date + filters = { + "rent_start_date": ["between", [self.rent_start_date, self.rent_end_date]], + "docstatus": 1, + "unit": self.unit, + "status": ["!=", "Hold"] + } + + property_contract = frappe.get_list("Property Contract", + fields = ["name"], + filters = filters) + + if len(property_contract)> 0: + frappe.throw(f"This property unit already contain a contract {property_contract[0].name} that not ended yet.") + + + # Get Property Contaract if to date is between new from and new to date + filters = { + "rent_end_date": ["between", [self.rent_start_date, self.rent_end_date]], + "docstatus": 1, + "unit": self.unit, + "status": ["!=", "Hold"] + } + + property_contract = frappe.get_list("Property Contract", + fields = ["name"], + filters = filters) + + if len(property_contract)> 0: + frappe.throw(f"This property unit already contain a contract {property_contract[0].name} that not ended yet.") + + + # Get Property Contaract if from date is after new from date and to date is before new to date + filters = { + "rent_start_date": ["<=", self.rent_start_date], + "rent_end_date": [">=", self.rent_end_date], + "docstatus": 1, + "unit": self.unit, + "status": ["!=", "Hold"] + } + + property_contract = frappe.get_list("Property Contract", + fields = ["name"], + filters = filters) + + if len(property_contract)> 0: + frappe.throw(f"This property unit already contain a contract {property_contract[0].name} that not ended yet.") + + diff_days = date_diff(self.rent_end_date,getdate()) + if flt(diff_days) > 0: + frappe.db.set_value("Unit", self.unit, "status", "Leased") + + def add_contract_annual_raise(self): + self.contract_annual_raise = [] + start_date = datetime.datetime.strptime(self.rent_start_date, "%Y-%m-%d") + duration = self.duration - 1 if self.duration > 1 else self.duration + month = dateutil.relativedelta.relativedelta(months=duration) + end_date = start_date + month + + for row in rrule(YEARLY, dtstart=start_date, until=end_date): + self.append('contract_annual_raise', { + 'date': row.date(), + 'amount': 0, + }) + + def add_payment(self): + + self.payments = [] + start_date = datetime.datetime.strptime(self.rent_start_date, "%Y-%m-%d") + duration = self.duration - 1 if self.duration > 1 else self.duration + month = dateutil.relativedelta.relativedelta(months=duration) + end_date = start_date + month + + if not self.annual_raise_start_date: + duration_to_add = 13 - start_date.month + months = dateutil.relativedelta.relativedelta(months = duration_to_add) + self.annual_raise_start_date = start_date + months + self.annual_raise_start_date = self.annual_raise_start_date.strftime('%Y-%m-%d') + + annual_raise_start_date = datetime.datetime.strptime(str(self.annual_raise_start_date), "%Y-%m-%d") + service_charge_start_date = datetime.datetime.strptime(str(self.service_charge_start_date), "%Y-%m-%d") + + frequency = 0 + if self.type == 'Customized c': + if self.payments_scheduling: + frequency = self.payments_scheduling + else: + if self.type == 'Monthly': + frequency = 1 + if self.type == 'Quarterly': + frequency = 4 + if self.type == 'Half Yearly': + frequency = 6 + if self.type == 'Annually': + frequency = 12 + + annual_raise_amount = self.annual_raise_amount + rent = (self.total_rent_per_month if not self.split_indoor_from_outdoor_rent else self.indoor_rent_month) + year = annual_raise_start_date.year + service_year = start_date.year + service_charge =0.0 + + if self.split_indoor_from_outdoor_service: + service_charge = self.service_charge_per_month or 0 + else: + service_charge = self.total_services_rent or 0 + + flag=1 + + service_charge_frequency = 1 + if self.service_charges_type == 'Annually': + service_charge_frequency = 12 + if flt(service_charge) > 0 : + for row in rrule(MONTHLY, interval=service_charge_frequency, dtstart=start_date, until=end_date): + if (self.service_raise_type == "Annual Regular"): + if row.month == service_charge_start_date.month and row.year >= service_charge_start_date.year: + # if row.year >service_year: + service_year=row.year + if self.annual_service_charges == "Percentage": + service_charge += service_charge / 100 * int((self.amount if self.amount else "0")) + else: + service_charge += int(self.amount) + + if (self.service_raise_type == "Annual Irregular"): + ourdoor_service_dict = {} + for sub_row in self.service_raise_details: + date = datetime.datetime.strptime(sub_row.date, "%Y-%m-%d") + date = date.strftime("%d-%m-%Y") + ourdoor_service_dict[date] = f'{sub_row.annual_raise_type}_{sub_row.annual_raise_amount}' + + month = '{:02d}'.format(row.month) + day = '{:02d}'.format(row.day) + + fieldname = f'{day}-{month}-{row.year}' + + if fieldname in ourdoor_service_dict and ourdoor_service_dict[fieldname]: + splitted_field = ourdoor_service_dict[fieldname].split("_") + annual_raise_type = splitted_field[0] + annual_raise_amount = splitted_field[1] + # print("===================>" + str(annual_raise_amount)) + if annual_raise_type == "Percentage": + service_charge += service_charge / 100 * int(annual_raise_amount) + else: + service_charge += int(annual_raise_amount) + + self.append('payments', { + 'date': row.date(), + 'amount': service_charge or 0, + 'reason':'Services Charges'+("" if not self.split_indoor_from_outdoor_service else " - InDoor") + }) + + if self.split_indoor_from_outdoor_service: + service_charge = self.outdoor_service_charge_per_month + year = service_charge_start_date.year + service_year = start_date.year + if service_charge > 0: + for row in rrule(MONTHLY, interval=service_charge_frequency, dtstart=start_date, until=end_date): + if (self.service_raise_type == "Annual Regular"): + if row.month == service_charge_start_date.month and row.year >= service_charge_start_date.year: + # if row.year > service_year: + service_year = row.year + if self.annual_service_charges == "Percentage": + service_charge += service_charge / 100 * int(self.amount) + else: + service_charge += int(self.amount) + + if (self.service_raise_type == "Annual Irregular"): + ourdoor_service_dict = {} + for sub_row in self.service_raise_details: + date = datetime.datetime.strptime(sub_row.date, "%Y-%m-%d") + date = date.strftime("%d-%m-%Y") + ourdoor_service_dict[date] = f'{sub_row.annual_raise_type}_{sub_row.annual_raise_amount}' + + month = '{:02d}'.format(row.month) + day = '{:02d}'.format(row.day) + + fieldname = f'{day}-{month}-{row.year}' + + if fieldname in ourdoor_service_dict and ourdoor_service_dict[fieldname]: + splitted_field = ourdoor_service_dict[fieldname].split("_") + annual_raise_type = splitted_field[0] + annual_raise_amount = splitted_field[1] + # print("===================>" + str(annual_raise_amount)) + if annual_raise_type == "Percentage": + service_charge += service_charge / 100 * int(annual_raise_amount) + else: + service_charge += int(annual_raise_amount) + + self.append('payments', { + 'date': row.date(), + 'amount': service_charge or 0, + 'reason': 'Services Charges'+" - " +"OutDoor" + }) + + for row in rrule(MONTHLY, interval=frequency, dtstart=start_date, until=end_date): + if(self.annual_raise == "Annual Regular"): + if row.year >= annual_raise_start_date.year: + #if row.month >= annual_raise_start_date.month or row.year > annual_raise_start_date.year: + if row.month == annual_raise_start_date.month and row.year >= annual_raise_start_date.year: + if row.year > year : + year=row.year + flag=1 + + if flag == 1: + if self.annual_raise_type == "Percentage": + rent += rent / 100 * int(annual_raise_amount) + else: + rent += int(annual_raise_amount) + flag = 0 + + if(self.annual_raise == "Annual Irregular"): + contract_dict = {} + for sub_row in self.contract_annual_raise: + date = datetime.datetime.strptime(sub_row.date, "%Y-%m-%d") + date = date.strftime("%d-%m-%Y") + contract_dict[date] = f'{sub_row.annual_raise_type}_{sub_row.annual_raise_amount}' + + + month = '{:02d}'.format(row.month) + day = '{:02d}'.format(row.day) + + fieldname = f'{day}-{month}-{row.year}' + + if fieldname in contract_dict and contract_dict[fieldname]: + splitted_field = contract_dict[fieldname].split("_") + annual_raise_type = splitted_field[0] + annual_raise_amount = splitted_field[1] + # print("===================>" + str(annual_raise_amount)) + if annual_raise_type == "Percentage": + rent += rent / 100 * int(annual_raise_amount) + else: + rent += int(annual_raise_amount) + + self.append('payments', { + 'date': row.date(), + 'amount': rent or 0, + 'reason':'Rent'+(' - indoor' if self.split_indoor_from_outdoor_rent else '') + }) + + if self.split_indoor_from_outdoor_rent: + flag = 1 + rent = self.outdoor_rent_month + if rent > 0: + for row in rrule(MONTHLY, interval=frequency, dtstart=start_date, until=end_date): + if (self.annual_raise == "Annual Regular"): + if row.year >= annual_raise_start_date.year: + #if row.month >= annual_raise_start_date.month or row.year > annual_raise_start_date.year: + if row.month == annual_raise_start_date.month and row.year >= annual_raise_start_date.year: + if row.year > year: + year = row.year + flag = 1 + + if flag == 1: + if self.annual_raise_type == "Percentage": + rent += rent / 100 * int(annual_raise_amount) + else: + rent += int(annual_raise_amount) + flag = 0 + + if (self.annual_raise == "Annual Irregular"): + contract_dict = {} + for sub_row in self.contract_annual_raise: + date = datetime.datetime.strptime(sub_row.date, "%Y-%m-%d") + date = date.strftime("%d-%m-%Y") + contract_dict[date] = f'{sub_row.annual_raise_type}_{sub_row.annual_raise_amount}' + + month = '{:02d}'.format(row.month) + day = '{:02d}'.format(row.day) + + fieldname = f'{day}-{month}-{row.year}' + + if fieldname in contract_dict and contract_dict[fieldname]: + splitted_field = contract_dict[fieldname].split("_") + annual_raise_type = splitted_field[0] + annual_raise_amount = splitted_field[1] + + if annual_raise_type == "Percentage": + rent += rent / 100 * int(annual_raise_amount) + else: + rent += int(annual_raise_amount) + + self.append('payments', { + 'date': row.date(), + 'amount': rent or 0, + 'reason': 'Rent' + ' - outdoor' + }) + + if not self.is_new(): + self.save() + + def on_cancel(self): + frappe.db.set_value("Unit", self.unit, "status", "Vacant") + + def create_row_invoice(self,rowid): + create_sales_invoice(self.name,rowid) + + def get_brand_names(self): + list_items = [] + brands = frappe.get_all("Tenant Brand Name", filters={"parent": self.renter}, fields=['tenant_brand_name'], order_by="tenant_brand_name") + if brands: + for item in brands: + list_items.append(item["tenant_brand_name"]) + + return list_items + + def update_duration(self): + duration = self.get_dates_diff(self.rent_start_date,self.rent_end_date) + # frappe.msgprint(str(duration)) + + duration_months = (cint(duration["years"]) * 12) + cint(duration["months"]) + (1 if cint(duration["days"]) > 0 or getdate(self.rent_end_date).day == 1 else 0) + # frappe.msgprint(str(duration_months)) + self.duration = duration_months + + return duration_months + + def get_dates_diff(self,a, b): + a = getdate(a) + b = getdate(b) + diff_dict = {} + # a = datetime(a.year, a.month, a.day) + # b = datetime(b.year, b.month, b.day) + delta = dateutil.relativedelta.relativedelta(b, a) + diff_dict["years"] = delta.years + diff_dict["months"] = delta.months + diff_dict["days"] = delta.days + + return diff_dict + +def create_sales_invoice(docname="",rowid=""): + print("Starting Creation..........") + condiction = "" + if docname and rowid: + row_doc = frappe.get_doc("Contract Payments",rowid) + condiction = " and C.parent = '%s' " % docname + # condiction += " and C.`name` = '%s' " % rowid + condiction += " and C.date = '%s' " % row_doc.date + invoices = frappe.db.sql(""" + select *,P.`name` contract_name,C.date invoice_date,C.amount row_amount + from `tabProperty Contract` P + inner JOIN + `tabContract Payments` C + on P.`name` = C.parent + -- and CURRENT_DATE() >= C.date + and P.rent_item is not NULL + and P.service_item is not NULL + -- and P.docstatus = 1 + and C.invoice is Null + and C.amount > 0 + {0} + order by date + """.format(condiction),as_dict=True,debug=True) + + if invoices: + # for invoice in invoices: + # s_name = create_sales_invoices(invoice) + # print(str(s_name.name)+" Invoice Created.") + s_name = create_sales_invoices(invoices) + print("Finished.") + +def create_sales_invoices(invoices): + # frappe.throw(str(args)) + default_company = frappe.db.get_default("Company") + si = frappe.new_doc("Sales Invoice") + + if isinstance(invoices,list): + args = frappe._dict(invoices[0]) + else: + args = frappe._dict(invoices) + + invoice_date = args.date or nowdate() + si.posting_date = invoice_date + + debit_to = frappe.db.get_value("Company", default_company, 'default_receivable_account') + if debit_to: + si.debit_to = debit_to # Default Receivable Account + else: + frappe.throw(_("Please set Default Receivable Account in Company " + default_company)) + + si.set_posting_time = 1 + si.postint_date = args.invoice_date + si.due_date = args.invoice_date + + si.customer = args.renter + si.tax_id = frappe.db.get_value("Customer",args.renter,"tax_id") + si.debit_to = debit_to + + print(str(si.postint_date)+"================================================="+str(si.due_date)) + + #Accounting Dimensions + si.property = args.property_name + si.unit = args.unit + si.property_contract = args.contract_name + invoice_curerncy = frappe.db.get_value('Customer', args.renter, "default_currency") or frappe.get_cached_value('Company', default_company, "default_currency") + + si.currency=invoice_curerncy + conversion_rate = 1 + if invoice_curerncy !="EGP": + conversion_rate = get_conversion_rate(invoice_curerncy,invoice_date) + if conversion_rate == 0: + message = _("Please Insert Currency Exchange for day {0}".format(invoice_date)) + frappe.throw(frappe.bold(message)) + + si.conversion_rate = conversion_rate + tax_list = [] + old_template = "" + for item in invoices: + default_income_account = None + item_income_account = frappe.db.get_value("Item Default", {"parent":(item.rent_item if item.reason =="Rent" else item.service_item)}, 'income_account') + company_default_income_account = frappe.db.get_value("Company", default_company, 'default_income_account') + + if item_income_account: + default_income_account = item_income_account + elif company_default_income_account: + default_income_account = company_default_income_account + else: + frappe.throw(_("Please set Default Income Account in Company " + default_company)) + + cost_center = frappe.db.get_value("Company", default_company, 'cost_center') + if not cost_center: + frappe.throw(_("Please set Default Cost Center in Company " + default_company)) + + + tax_template_name = "" + if item.reason =="Services Charges" and item.service_sales_taxes_and_charges_template: + tax_template_name = item.service_sales_taxes_and_charges_template + elif item.reason =="Rent" and item.rent_sales_taxes_and_charges_template: + tax_template_name = item.rent_sales_taxes_and_charges_template + + si.append("items", { + "item_code": (item.rent_item if item.reason =="Rent" else item.service_item) , + "item_name": (item.rent_item_name if item.reason =="Rent" else item.service_item), + "qty": 1, + "item_tax_template": (tax_template_name if tax_template_name else "") , + "rate": item.row_amount , + "uom": item.uom or "Unit", + "stock_uom": item.uom or "Unit", + "income_account": default_income_account,# Default Income Account + "cost_center": cost_center , + "conversion_factor": 1, + "description" : item.reason+" for contract "+ str(item.contract_name) + }) + + + if tax_template_name: + if old_template=="": + old_template = tax_template_name + + tax_data = frappe.db.sql(""" + select * + from `tabItem Tax Template Detail` + where parent='{tax_template_name}' + + """.format(tax_template_name=tax_template_name),as_dict=True,debug=False) + + if tax_data: + tax_data = tax_data[0] + if old_template == tax_template_name: + tax_list = [] + + tax_list.append({ + "charge_type":"On Net Total", + "account_head":tax_data["tax_type"], + "description":tax_data["parent"], + "rate": 0 , #tax_data["tax_rate"], + "tax_amount":flt(tax_data["tax_rate"]) * flt(item.row_amount), + "total":(flt(tax_data["tax_rate"]) * flt(item.row_amount)) + flt(item.row_amount), + "base_tax_amount":flt(tax_data["tax_rate"]) * flt(item.row_amount), + "base_total":(flt(tax_data["tax_rate"]) * flt(item.row_amount)) + flt(item.row_amount) + }) + + for item in tax_list: + si.append("taxes",item) + + Sinv_ID = None + try: + Sinv_ID = si.insert(ignore_mandatory = True) + for row in invoices: + frappe.db.sql("""update `tabContract Payments` set invoice = '{0}' where `name` = '{1}' """.format(Sinv_ID.name,row.name),debug=True) + + return Sinv_ID + except Exception as e: + print("Error......"+str(e)) + frappe.throw(_(e)) + +def get_conversion_rate(invoice_curerncy,invoice_date): + conversion_rate = 0 + exchange_rate = frappe.db.sql(""" + select exchange_rate + from `tabCurrency Exchange` + where from_currency ='{invoice_curerncy}' + and to_currency='EGP' + and date ='{invoice_date}' + """.format(invoice_curerncy=invoice_curerncy,invoice_date=invoice_date),as_dict=True) + if exchange_rate: + conversion_rate = exchange_rate[0]["exchange_rate"] + + return conversion_rate + + + +def update_unit_status(): + units = frappe.db.get_list("Property Contract",filters={"docstatus":1,"rent_end_date":getdate()},fields=["unit"]) + if units: + for unit in units: + frappe.db.set_value("Unit", unit["unit"], "status", "Vacant") + + + +def update_unit_status_basd_on_reservation(): + print("==========================starting============================") + units = frappe.db.get_list("Reservations",filters={"docstatus":1,"reservation_end":["<",get_datetime()]},fields=["unit"]) + if units: + for unit in units: + frappe.db.set_value("Unit", unit["unit"], "status", "Vacant") + print("==========================Finished============================") \ No newline at end of file diff --git a/muezzin/property_management/doctype/property_contract/property_contract.js b/muezzin/property_management/doctype/property_contract/property_contract.js new file mode 100644 index 0000000..bab9bd6 --- /dev/null +++ b/muezzin/property_management/doctype/property_contract/property_contract.js @@ -0,0 +1,351 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Property Contract', { + onload: function (frm) { + get_brand_names(frm) + }, + generate_payment_scheduale: function (frm) { + frm.doc.payment = [] + frappe.call({ + method: 'add_payment', + doc: frm.doc, + callback: function (res) { + frappe.show_alert('Generated Successfully') + + cur_frm.reload_doc() + } + }) + }, + renter: function (frm) { + get_brand_names(frm) + }, + refresh: function (frm) { + get_brand_names(frm) + frm.set_query('property_name', () => { + return { + filters: { + property_type: frm.doc.property_type + } + } + }) + frm.set_query('unit', () => { + return { + filters: { + unit_type: frm.doc.unit_type, + property: frm.doc.property_name + } + } + }) + frm.set_query('rent_item', () => { + return { + filters: { + is_sales_item: 1 + } + } + }) + frm.set_query('service_item', () => { + return { + filters: { + is_sales_item: 1 + } + } + }) + frm.clear_custom_buttons() + + if (frm.doc.docstatus == 0 && !frm.is_new()) { + frm.add_custom_button(__("Generate Payment Scheduale"), function () { + frm.trigger('generate_payment_scheduale'); + }); + } + + if (frm.doc.docstatus == 1 && frm.doc.status != "Hold") { + frm.add_custom_button("Hold Contract", () => { + frappe.call({ + method: "muezzin.api.property_contract.change_contract_status", + args: { + doc: frm.doc, + status: "Hold" + }, + callback: () => { + frm.reload_doc(); + } + }) + }) + } + if (frm.doc.docstatus == 1 && frm.doc.status == "Hold") { + frm.add_custom_button("Release Contract", () => { + frappe.call({ + method: "muezzin.api.property_contract.change_contract_status", + args: { + doc: frm.doc, + status: "Active" + }, + callback: () => { + frm.reload_doc(); + } + }) + }) + } + }, + + rent_start_date: function (frm) { + update_duration(frm) + // if (frm.doc.docstatus == 0 && !frm.is_new()) { + // frm.trigger('generate_payment_scheduale'); + // } + }, + rent_end_date: function (frm) { + update_duration(frm) + // if (frm.doc.docstatus == 0 && !frm.is_new()) { + // frm.trigger('generate_payment_scheduale'); + // } + }, + + // annual_raise_start_date: function (frm) { + // if (frm.doc.docstatus == 0 && !frm.is_new()) { + // frm.trigger('generate_payment_scheduale'); + // } + // }, + // service_charge_start_date: function (frm) { + // if (frm.doc.docstatus == 0 && !frm.is_new()) { + // frm.trigger('generate_payment_scheduale'); + // } + // }, + + property_type: function (frm) { + frm.doc.property_name = null; + frm.refresh_field('property_name'); + }, + + unit_type: function (frm) { + frm.doc.property_name = null; + frm.refresh_field('unit'); + }, + + // duration: function(frm) { + // update_payment(frm); + // update_annual_raise_amount(frm); + // }, + + // type: function(frm) { + // update_payment(frm); + // }, + + // payments_scheduling: function(frm) { + // update_payment(frm); + // }, + + unit: function (frm) { + update_total_unit_area(frm); + }, + service_indoor_area: function (frm) { + update_service_charge_per_month(frm); + }, + service_charge_sqm: function (frm) { + update_service_charge_per_month(frm); + }, + outdoor_service_charge_per_month: function (frm) { + + update_total_services_rent(frm) + update_total_rent(frm); + + // update_total_rent_month(frm); + update_total_services_rent(frm) + update_outdoor_service_charge_sqm(frm); + }, + + service_outdoor_area: function (frm) { + // update_outdoor_service_charge_sqm(frm); + }, + outdoor_service_charge_sqm: function (frm) { + update_outdoor_service_charge_sqm(frm); + }, + indoor_area: function (frm) { + update_indoor_rent_month(frm); + }, + + rent_per_sqm: function (frm) { + update_indoor_rent_month(frm); + }, + + indoor_rent_month: function (frm) { + update_total_rent_month(frm); + }, + + // outdoor_service_charge_per_month: function(frm) { + // update_total_rent_month(frm); + // }, + outdoor_area: function (frm) { + update_outdoor_rent_month(frm); + }, + + outdoor_rent_sqm: function (frm) { + update_outdoor_rent_month(frm); + }, + + outdoor_rent_month: function (frm) { + update_total_rent_month(frm); + }, + + total_rent_per_month: function (frm) { + update_total_rent(frm); + }, + + service_charge_per_month: function (frm) { + update_total_services_rent(frm) + update_total_rent(frm); + }, + + total_rent: function (frm) { + if (frm.doc.total_rent) { + frm.set_value('annualized_rent', frm.doc.total_rent * 12); + } + } +}); +frappe.ui.form.on('Contract Payments', { + create_invoice: function (frm, cdt, cdn) { + var row = locals[cdt][cdn]; + if (!row.invoice) { + frm.call({ + method: 'create_row_invoice', + doc: frm.doc, + args: {rowid: cdn}, + callback: function (res) { + frm.reload_doc(); + } + }) + } else { + frappe.throw(__('Invoice Already Created..')) + } + } +}) + +const update_payment = function (frm) { + if (!frm.doc.rent_start_date) { + frm.doc.duration = null; + frm.refresh_field('duration'); + frappe.msgprint('Enter Rent Start Date.') + } else { + if (frm.doc.type && frm.doc.duration) { + frm.call('add_payment'); + frm.refresh_field('payments'); + } else { + frm.doc.payments = []; + frm.refresh_field('payments'); + } + } +} + +const update_annual_raise_amount = function (frm) { + if (!frm.doc.rent_start_date) { + frm.doc.duration = null; + frm.refresh_field('duration'); + frappe.msgprint('Enter Rent Start Date.') + } else { + if (frm.doc.annual_raise === 'Annual Irregular' && frm.doc.duration) { + frm.call('add_contract_annual_raise'); + frm.refresh_field('contract_annual_raise'); + } else { + frm.doc.contract_annual_raise = []; + frm.refresh_field('contract_annual_raise'); + } + } +} + +// const update_service_charge = function(frm) { +// if(frm.doc.gla && frm.doc.service_charge_sqm) { +// frm.set_value('service_charge_per_month', frm.doc.gla * frm.doc.service_charge_sqm) +// } +// } + +const update_total_rent_month = function (frm) { + if (frm.doc.indoor_rent_month >= 0 && frm.doc.outdoor_rent_month >= 0) { + frm.set_value('total_rent_per_month', flt(frm.doc.indoor_rent_month) + flt(frm.doc.outdoor_rent_month) + flt(frm.doc.outdoor_service_charge_per_month)); + } +} + +const update_total_rent = function (frm) { + if (frm.doc.total_rent_per_month >= 0 && frm.doc.service_charge_per_month >= 0) { + frm.set_value('total_rent', flt(frm.doc.total_rent_per_month) + flt(frm.doc.service_charge_per_month) + flt(frm.doc.outdoor_service_charge_per_month)); + } +} + +const update_outdoor_rent_month = function (frm) { + // if (frm.doc.outdoor_area && frm.doc.outdoor_rent_sqm) { + frm.set_value('outdoor_rent_month', frm.doc.outdoor_area * frm.doc.outdoor_rent_sqm); + // } +} + +const update_indoor_rent_month = function (frm) { + if (frm.doc.indoor_area && frm.doc.rent_per_sqm) { + frm.set_value('indoor_rent_month', frm.doc.indoor_area * frm.doc.rent_per_sqm); + } +} +const update_total_unit_area = function (frm) { + // if (frm.doc.gla && frm.doc.service_charge_sqm) { + frm.set_value('gla', frm.doc.outdoor_area + frm.doc.indoor_area); + refresh_field('gla') + // } +} +const update_service_charge_per_month = function (frm) { + // if (frm.doc.gla && frm.doc.service_charge_sqm) { + frm.set_value('service_charge_per_month', frm.doc.service_indoor_area * frm.doc.service_charge_sqm); + // } +} +const update_total_services_rent = function (frm) { + frm.set_value('total_services_rent', flt(frm.doc.outdoor_service_charge_per_month) + flt(frm.doc.service_charge_per_month)); +} +const update_outdoor_service_charge_sqm = function (frm) { + // if (frm.doc.outdoor_service_charge_sqm && frm.doc.service_outdoor_area) { + // frm.set_value('outdoor_service_charge_sqm',0) + // frm.set_value('outdoor_rent_sqm',0) + frm.set_value('outdoor_service_charge_per_month', frm.doc.outdoor_service_charge_sqm * frm.doc.service_outdoor_area); + // refresh_field('outdoor_rent_sqm') + // refresh_field('outdoor_service_charge_per_month') + // } +} +const update_duration = function (frm) { + if (frm.doc.rent_start_date && frm.doc.rent_end_date) { + // let duration = get_time_diff(frm.doc.rent_end_date, frm.doc.rent_start_date) + // frm.set_value('duration', duration) + + frappe.call({ + method:'update_duration', + doc:frm.doc, + callback:function(r){ + if(r.message){ + frm.set_value('duration', r.message) + refresh_field('duration') + } + } + }) + } +} + +const get_time_diff = (end, start) => { + const end_datetime = new Date(end) + const start_datetime = new Date(start) + const time_diff = end_datetime - start_datetime + const months = time_diff / 2592000000 + return Math.round(months) +} + + +let get_brand_names = function (frm) { + if (frm.doc.renter) { + frappe.call({ + method: "muezzin.property_management.doctype.property_contract.property_contract.get_brand_names", + args: { + renter:frm.doc.renter + }, + callback: function (data) { + if (data.message) { + let options = data.message; + let options_new = options.join("\n"); + frm.set_df_property('brand_name', 'options', options_new); + } + } + }) + } +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/property_contract/property_contract.json b/muezzin/property_management/doctype/property_contract/property_contract.json new file mode 100644 index 0000000..c0e2c86 --- /dev/null +++ b/muezzin/property_management/doctype/property_contract/property_contract.json @@ -0,0 +1,745 @@ +{ + "autoname": "field:lease", + "creation": "2021-02-02 11:52:23.747179", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "main_data_section", + "lease", + "status", + "date_type", + "opening_date", + "rent_start_date", + "renter", + "brand_name", + "tenant_name_currency", + "is_vat_applied", + "column_break_7", + "writing_date", + "script", + "rent_end_date", + "migration_migrate_to_accounts", + "grace_period_day", + "revenue_sharing", + "insurance_policy", + "choose_units_section", + "property_type", + "unit_type", + "column_break_14", + "property_name", + "unit", + "items_section", + "rent_item", + "rent_item_name", + "rent_sales_taxes_and_charges_template", + "column_break_22", + "service_item", + "service_item_name", + "service_sales_taxes_and_charges_template", + "rent_details_section", + "indoor_area", + "rent_per_sqm", + "indoor_rent_month", + "column_break_24", + "service_indoor_area", + "service_charge_sqm", + "service_charge_per_month", + "section_break_28", + "outdoor_area", + "outdoor_rent_sqm", + "outdoor_rent_month", + "column_break_32", + "service_outdoor_area", + "outdoor_service_charge_sqm", + "outdoor_service_charge_per_month", + "totals_section", + "total_rent_per_month", + "total_rent", + "annualized_rent", + "column_break_25", + "gla", + "total_services_rent", + "lease_sent", + "lease_signed", + "split_indoor_from_outdoor_rent", + "split_indoor_from_outdoor_service", + "payments_scheduling_section", + "duration", + "payments_scheduling", + "annual_raise_start_date", + "annual_raise_type", + "column_break_19", + "type", + "annual_raise", + "annual_raise_amount", + "section_break_50", + "contract_annual_raise", + "section_break_23", + "service_raise_type", + "service_raise_details", + "annual_service_charges", + "amount", + "column_break_48", + "service_charges_type", + "service_charge_start_date", + "section_break_25", + "payments", + "expenses_section", + "maintenance_services_payment", + "water_payment", + "clining", + "owner_management", + "electricity_payment", + "lease_payment", + "column_break_29", + "ms_type", + "w_type", + "c_type", + "om_type", + "e_type", + "insurance_payment", + "column_break_36", + "ms_service", + "w_service", + "c_service", + "om_service", + "e_service", + "marketing_fees", + "amended_from" + ], + "fields": [ + { + "fieldname": "main_data_section", + "fieldtype": "Section Break", + "label": "Main Data" + }, + { + "fieldname": "lease", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Lease", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "date_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Date Type", + "options": "Hijri\nGregorian", + "reqd": 1 + }, + { + "fieldname": "rent_start_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Rent start date", + "reqd": 1 + }, + { + "fieldname": "renter", + "fieldtype": "Link", + "label": "Legal Tenant Name", + "options": "Customer" + }, + { + "default": "0", + "fieldname": "is_vat_applied", + "fieldtype": "Check", + "label": "Is Vat Applied" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "fieldname": "writing_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Writing Date", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "migration_migrate_to_accounts", + "fieldtype": "Check", + "label": "Migration (Migrate to Accounts)" + }, + { + "fieldname": "script", + "fieldtype": "Link", + "label": "Script", + "options": "Script" + }, + { + "fieldname": "grace_period_day", + "fieldtype": "Data", + "label": "Grace period (day)" + }, + { + "fieldname": "choose_units_section", + "fieldtype": "Section Break", + "label": "Choose Units" + }, + { + "fieldname": "property_type", + "fieldtype": "Link", + "label": "Property Type", + "options": "Property Type" + }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break" + }, + { + "fieldname": "property_name", + "fieldtype": "Link", + "label": "Property Name", + "options": "Property", + "reqd": 1 + }, + { + "fieldname": "payments_scheduling_section", + "fieldtype": "Section Break", + "label": "Payments Scheduling" + }, + { + "fieldname": "duration", + "fieldtype": "Int", + "label": "Duration", + "read_only": 1 + }, + { + "fieldname": "annual_raise", + "fieldtype": "Select", + "label": "Annual raise", + "options": "\nAnnual Regular\nAnnual Irregular" + }, + { + "depends_on": "eval:doc.service_raise_type=='Annual Regular'", + "fieldname": "column_break_19", + "fieldtype": "Column Break" + }, + { + "fieldname": "type", + "fieldtype": "Select", + "label": "Type", + "options": "Monthly\nQuarterly\nHalf Yearly\nAnnually\nCustomized c" + }, + { + "depends_on": "eval:doc.type=='Customized c'", + "description": "Ex: 3, means new payment every 3 months among lease duration", + "fieldname": "payments_scheduling", + "fieldtype": "Int", + "label": "Payments Scheduling" + }, + { + "fieldname": "expenses_section", + "fieldtype": "Section Break", + "label": "Expenses" + }, + { + "fieldname": "lease_payment", + "fieldtype": "Currency", + "label": "Lease Payment", + "options": "tenant_name_currency" + }, + { + "fieldname": "maintenance_services_payment", + "fieldtype": "Currency", + "label": "Maintenance Services Payment", + "options": "tenant_name_currency" + }, + { + "fieldname": "water_payment", + "fieldtype": "Currency", + "label": "Water Payment", + "options": "tenant_name_currency" + }, + { + "fieldname": "clining", + "fieldtype": "Currency", + "label": "Clining", + "options": "tenant_name_currency" + }, + { + "fieldname": "owner_management", + "fieldtype": "Currency", + "label": "Owner Management", + "options": "tenant_name_currency" + }, + { + "fieldname": "electricity_payment", + "fieldtype": "Currency", + "label": "Electricity Payment", + "options": "tenant_name_currency" + }, + { + "fieldname": "column_break_29", + "fieldtype": "Column Break" + }, + { + "fieldname": "insurance_payment", + "fieldtype": "Currency", + "label": "Insurance Payment", + "options": "Currency" + }, + { + "fieldname": "ms_type", + "fieldtype": "Select", + "label": "Type", + "options": "Annually\nWith Payments\nMonthly\nJust Once" + }, + { + "fieldname": "w_type", + "fieldtype": "Select", + "label": "Type", + "options": "Annually\nWith Payments\nMonthly\nJust Once" + }, + { + "fieldname": "c_type", + "fieldtype": "Select", + "label": "Type", + "options": "Annually\nWith Payments\nMonthly\nJust Once" + }, + { + "fieldname": "om_type", + "fieldtype": "Select", + "label": "Type", + "options": "Annually\nWith Payments\nMonthly\nJust Once" + }, + { + "fieldname": "e_type", + "fieldtype": "Select", + "label": "Type", + "options": "Annually\nWith Payments\nMonthly\nJust Once" + }, + { + "fieldname": "column_break_36", + "fieldtype": "Column Break" + }, + { + "fieldname": "ms_service", + "fieldtype": "Select", + "label": "Service Provider", + "options": "\nSarabi\nMaqawal\nMisry" + }, + { + "fieldname": "w_service", + "fieldtype": "Select", + "label": "Service Provider", + "options": "\nSarabi\nMaqawal\nMisry" + }, + { + "fieldname": "c_service", + "fieldtype": "Select", + "label": "Service Provider", + "options": "\nSarabi\nMaqawal\nMisry" + }, + { + "fieldname": "om_service", + "fieldtype": "Select", + "label": "Service Provider", + "options": "\nSarabi\nMaqawal\nMisry" + }, + { + "fieldname": "e_service", + "fieldtype": "Select", + "label": "Service Provider", + "options": "\nSarabi\nMaqawal\nMisry" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Property Contract", + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "eval:doc.annual_raise=='Annual Regular';", + "fieldname": "annual_raise_amount", + "fieldtype": "Data", + "label": "Annual Raise Amount" + }, + { + "collapsible": 1, + "fieldname": "section_break_23", + "fieldtype": "Section Break", + "label": "SERVICE CHARGES" + }, + { + "fieldname": "contract_annual_raise", + "fieldtype": "Table", + "label": "Annual Raise Details", + "options": "Contract Annual Raise" + }, + { + "depends_on": "eval:doc.duration>0;", + "fieldname": "section_break_25", + "fieldtype": "Section Break" + }, + { + "allow_on_submit": 1, + "fieldname": "payments", + "fieldtype": "Table", + "label": "Payments", + "options": "Contract Payments" + }, + { + "fieldname": "rent_details_section", + "fieldtype": "Section Break", + "label": "Rent Details" + }, + { + "fieldname": "rent_per_sqm", + "fieldtype": "Currency", + "label": "Rent (Per sqm)", + "options": "tenant_name_currency" + }, + { + "fieldname": "outdoor_rent_sqm", + "fieldtype": "Currency", + "label": "Outdoor Rent (sqm)", + "options": "tenant_name_currency" + }, + { + "fieldname": "total_rent_per_month", + "fieldtype": "Currency", + "label": "Total Rent per Month", + "options": "tenant_name_currency", + "read_only": 1 + }, + { + "fieldname": "service_charge_sqm", + "fieldtype": "Currency", + "label": "Service Charge sqm", + "options": "tenant_name_currency" + }, + { + "fieldname": "service_charge_per_month", + "fieldtype": "Currency", + "label": "Service Charge per Month", + "options": "tenant_name_currency" + }, + { + "fieldname": "total_rent", + "fieldtype": "Currency", + "label": "Total Rent", + "options": "tenant_name_currency", + "read_only": 1 + }, + { + "fieldname": "annualized_rent", + "fieldtype": "Currency", + "label": "Annualized Rent", + "options": "tenant_name_currency", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "lease_sent", + "fieldtype": "Check", + "label": "Lease Sent" + }, + { + "default": "0", + "fieldname": "lease_signed", + "fieldtype": "Check", + "label": "Lease Signed" + }, + { + "fieldname": "column_break_22", + "fieldtype": "Column Break" + }, + { + "fieldname": "marketing_fees", + "fieldtype": "Currency", + "label": "Marketing Fees", + "options": "tenant_name_currency" + }, + { + "fieldname": "rent_end_date", + "fieldtype": "Date", + "label": "Rent End Date", + "reqd": 1 + }, + { + "fieldname": "indoor_rent_month", + "fieldtype": "Currency", + "label": "Indoor Rent (LE/Month)", + "options": "tenant_name_currency" + }, + { + "fieldname": "outdoor_rent_month", + "fieldtype": "Currency", + "label": "Outdoor Rent (LE/Month)", + "options": "tenant_name_currency" + }, + { + "fieldname": "unit_type", + "fieldtype": "Link", + "label": "Unit Type", + "options": "Unit Type" + }, + { + "fetch_from": "unit.unit_area", + "fieldname": "indoor_area", + "fieldtype": "Float", + "label": "Indoor Area", + "read_only": 1 + }, + { + "fetch_from": "unit.outdoor_area", + "fieldname": "outdoor_area", + "fieldtype": "Float", + "label": "Outdoor Area", + "read_only": 1 + }, + { + "fieldname": "gla", + "fieldtype": "Float", + "label": "GLA", + "read_only": 1 + }, + { + "depends_on": "eval:doc.annual_raise=='Annual Regular';", + "fieldname": "annual_raise_type", + "fieldtype": "Select", + "label": "Annual Raise Type", + "options": "\nAmount\nPercentage" + }, + { + "allow_on_submit": 1, + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "\nActive\nHold", + "read_only": 1 + }, + { + "depends_on": "eval:doc.annual_raise !='Annual Irregular'", + "fieldname": "annual_raise_start_date", + "fieldtype": "Date", + "label": "Annual Raise Start Date" + }, + { + "fieldname": "service_charges_type", + "fieldtype": "Select", + "label": "Type", + "options": "Monthly\nAnnually" + }, + { + "depends_on": "eval:doc.service_raise_type=='Annual Regular'", + "fieldname": "annual_service_charges", + "fieldtype": "Select", + "label": "Annual Raise Type", + "options": "Percentage\nAmount" + }, + { + "fieldname": "column_break_48", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.service_raise_type=='Annual Regular'", + "fieldname": "amount", + "fieldtype": "Float", + "label": "Amount" + }, + { + "depends_on": "eval:doc.annual_raise=='Annual Irregular'", + "fieldname": "section_break_50", + "fieldtype": "Section Break" + }, + { + "fieldname": "unit", + "fieldtype": "Link", + "label": "Unit Name", + "options": "Unit", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "split_indoor_from_outdoor_rent", + "fieldtype": "Check", + "label": "Split Indoor from Outdoor Rent" + }, + { + "fieldname": "column_break_24", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_28", + "fieldtype": "Section Break" + }, + { + "fetch_from": "unit.outdoor_area", + "fieldname": "service_outdoor_area", + "fieldtype": "Float", + "label": "Service Outdoor Area ", + "read_only": 1 + }, + { + "fieldname": "outdoor_service_charge_sqm", + "fieldtype": "Float", + "label": "Outdoor Service Charge SQM" + }, + { + "fieldname": "outdoor_service_charge_per_month", + "fieldtype": "Float", + "label": "Outdoor Service Charge per Month" + }, + { + "fieldname": "totals_section", + "fieldtype": "Section Break", + "label": "Totals" + }, + { + "fieldname": "column_break_32", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "split_indoor_from_outdoor_service", + "fieldtype": "Check", + "label": " Split Indoor from Outdoor Service" + }, + { + "fieldname": "total_services_rent", + "fieldtype": "Currency", + "label": "Total Services Rent", + "options": "tenant_name_currency", + "read_only": 1 + }, + { + "fieldname": "items_section", + "fieldtype": "Section Break", + "label": "Items" + }, + { + "fieldname": "rent_item", + "fieldtype": "Link", + "label": "Rent Item", + "options": "Item", + "reqd": 1 + }, + { + "fieldname": "service_item", + "fieldtype": "Link", + "label": "Service Item", + "options": "Item", + "reqd": 1 + }, + { + "fieldname": "column_break_25", + "fieldtype": "Column Break" + }, + { + "fetch_from": "rent_item.item_name", + "fieldname": "rent_item_name", + "fieldtype": "Read Only", + "label": "Rent Item Name" + }, + { + "fetch_from": "service_item.item_name", + "fieldname": "service_item_name", + "fieldtype": "Read Only", + "label": "Service Item Name" + }, + { + "fetch_from": "unit.unit_area", + "fieldname": "service_indoor_area", + "fieldtype": "Float", + "label": "Service Indoor Area ", + "read_only": 1 + }, + { + "default": "Today", + "depends_on": "eval:doc.service_raise_type=='Annual Regular'", + "fieldname": "service_charge_start_date", + "fieldtype": "Date", + "label": "Service Charge Start Date", + "options": "Today" + }, + { + "fieldname": "service_raise_type", + "fieldtype": "Select", + "label": "Service Raise Type", + "options": "\nAnnual Regular\nAnnual Irregular" + }, + { + "depends_on": "eval:doc.service_raise_type=='Annual Irregular'", + "fieldname": "service_raise_details", + "fieldtype": "Table", + "label": "Service Raise Details", + "options": "Contract Annual Raise" + }, + { + "default": "Today", + "fieldname": "opening_date", + "fieldtype": "Date", + "label": "Opening date" + }, + { + "fieldname": "revenue_sharing", + "fieldtype": "Data", + "label": "Revenue sharing" + }, + { + "fieldname": "insurance_policy", + "fieldtype": "Data", + "label": "Insurance policy" + }, + { + "fieldname": "rent_sales_taxes_and_charges_template", + "fieldtype": "Link", + "label": "Rent Sales Taxes and Charges Template", + "options": "Sales Taxes and Charges Template" + }, + { + "fieldname": "service_sales_taxes_and_charges_template", + "fieldtype": "Link", + "label": "Service Sales Taxes and Charges Template", + "options": "Sales Taxes and Charges Template" + }, + { + "fieldname": "brand_name", + "fieldtype": "Select", + "label": "Tenant Brand Name" + }, + { + "default": "EGP", + "fetch_from": "renter.default_currency", + "fetch_if_empty": 1, + "fieldname": "tenant_name_currency", + "fieldtype": "Link", + "label": "Tenant Name Currency", + "options": "Currency" + } + ], + "is_submittable": 1, + "modified": "2021-12-25 01:52:49.738569", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Property Contract", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/property_contract/property_contract.py b/muezzin/property_management/doctype/property_contract/property_contract.py new file mode 100644 index 0000000..75d4715 --- /dev/null +++ b/muezzin/property_management/doctype/property_contract/property_contract.py @@ -0,0 +1,545 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +import datetime +# from datetime import datetime, timedelta +import dateutil +import calendar +from calendar import weekday, monthrange +from collections import Counter +from dateutil.relativedelta import relativedelta +from dateutil.rrule import rrule, MONTHLY, YEARLY +from frappe.model.document import Document +from frappe.utils import flt, date_diff, getdate, cint, nowdate + + +class PropertyContract(Document): + def validate(self): + if self.is_new(): + self.add_payment() + # self.add_contract_annual_raise() + + def before_submit(self): + """ + Validates if property already on rent between the from and to dates + """ + + # Get Property Contaract if from date is between new from and new to date + filters = { + "rent_start_date": ["between", [self.rent_start_date, self.rent_end_date]], + "docstatus": 1, + "unit": self.unit, + "status": ["!=", "Hold"] + } + + property_contract = frappe.get_list("Property Contract", + fields=["name"], + filters=filters) + + if len(property_contract) > 0: + frappe.throw( + f"This property unit already contain a contract {property_contract[0].name} that not ended yet.") + + # Get Property Contaract if to date is between new from and new to date + filters = { + "rent_end_date": ["between", [self.rent_start_date, self.rent_end_date]], + "docstatus": 1, + "unit": self.unit, + "status": ["!=", "Hold"] + } + + property_contract = frappe.get_list("Property Contract", + fields=["name"], + filters=filters) + + if len(property_contract) > 0: + frappe.throw( + f"This property unit already contain a contract {property_contract[0].name} that not ended yet.") + + # Get Property Contaract if from date is after new from date and to date is before new to date + filters = { + "rent_start_date": ["<=", self.rent_start_date], + "rent_end_date": [">=", self.rent_end_date], + "docstatus": 1, + "unit": self.unit, + "status": ["!=", "Hold"] + } + + property_contract = frappe.get_list("Property Contract", + fields=["name"], + filters=filters) + + if len(property_contract) > 0: + frappe.throw( + f"This property unit already contain a contract {property_contract[0].name} that not ended yet.") + + diff_days = date_diff(self.rent_end_date, getdate()) + if flt(diff_days) > 0: + frappe.db.set_value("Unit", self.unit, "status", "Leased") + + @frappe.whitelist() + def add_contract_annual_raise(self): + self.contract_annual_raise = [] + start_date = datetime.datetime.strptime(self.rent_start_date, "%Y-%m-%d") + duration = self.duration - 1 if self.duration > 1 else self.duration + month = dateutil.relativedelta.relativedelta(months=duration) + end_date = start_date + month + + for row in rrule(YEARLY, dtstart=start_date, until=end_date): + self.append('contract_annual_raise', { + 'date': row.date(), + 'amount': 0, + }) + + @frappe.whitelist() + def add_payment(self): + + self.payments = [] + start_date = datetime.datetime.strptime(self.rent_start_date, "%Y-%m-%d") + duration = self.duration - 1 if self.duration > 1 else self.duration + month = dateutil.relativedelta.relativedelta(months=duration) + end_date = start_date + month + + if not self.annual_raise_start_date: + duration_to_add = 13 - start_date.month + months = dateutil.relativedelta.relativedelta(months=duration_to_add) + self.annual_raise_start_date = start_date + months + self.annual_raise_start_date = self.annual_raise_start_date.strftime('%Y-%m-%d') + + annual_raise_start_date = datetime.datetime.strptime(str(self.annual_raise_start_date), "%Y-%m-%d") + service_charge_start_date = datetime.datetime.strptime(str(self.service_charge_start_date), "%Y-%m-%d") + + frequency = 0 + if self.type == 'Customized c': + if self.payments_scheduling: + frequency = self.payments_scheduling + else: + if self.type == 'Monthly': + frequency = 1 + if self.type == 'Quarterly': + frequency = 4 + if self.type == 'Half Yearly': + frequency = 6 + if self.type == 'Annually': + frequency = 12 + + annual_raise_amount = self.annual_raise_amount + rent = (self.total_rent_per_month if not self.split_indoor_from_outdoor_rent else self.indoor_rent_month) + year = annual_raise_start_date.year + service_year = start_date.year + service_charge = 0.0 + + if self.split_indoor_from_outdoor_service: + service_charge = self.service_charge_per_month or 0 + else: + service_charge = self.total_services_rent or 0 + + flag = 1 + + service_charge_frequency = 1 + if self.service_charges_type == 'Annually': + service_charge_frequency = 12 + if flt(service_charge) > 0: + for row in rrule(MONTHLY, interval=service_charge_frequency, dtstart=start_date, until=end_date): + if (self.service_raise_type == "Annual Regular"): + if row.month == service_charge_start_date.month and row.year >= service_charge_start_date.year: + # if row.year >service_year: + service_year = row.year + if self.annual_service_charges == "Percentage": + service_charge += service_charge / 100 * int((self.amount if self.amount else "0")) + else: + service_charge += int(self.amount) + + if (self.service_raise_type == "Annual Irregular"): + ourdoor_service_dict = {} + for sub_row in self.service_raise_details: + date = datetime.datetime.strptime(sub_row.date, "%Y-%m-%d") + date = date.strftime("%d-%m-%Y") + ourdoor_service_dict[date] = f'{sub_row.annual_raise_type}_{sub_row.annual_raise_amount}' + + month = '{:02d}'.format(row.month) + day = '{:02d}'.format(row.day) + + fieldname = f'{day}-{month}-{row.year}' + + if fieldname in ourdoor_service_dict and ourdoor_service_dict[fieldname]: + splitted_field = ourdoor_service_dict[fieldname].split("_") + annual_raise_type = splitted_field[0] + annual_raise_amount = splitted_field[1] + # print("===================>" + str(annual_raise_amount)) + if annual_raise_type == "Percentage": + service_charge += service_charge / 100 * int(annual_raise_amount) + else: + service_charge += int(annual_raise_amount) + + self.append('payments', { + 'date': row.date(), + 'amount': service_charge or 0, + 'reason': 'Services Charges' + ("" if not self.split_indoor_from_outdoor_service else " - InDoor") + }) + + if self.split_indoor_from_outdoor_service: + service_charge = self.outdoor_service_charge_per_month + year = service_charge_start_date.year + service_year = start_date.year + if service_charge > 0: + for row in rrule(MONTHLY, interval=service_charge_frequency, dtstart=start_date, until=end_date): + if (self.service_raise_type == "Annual Regular"): + if row.month == service_charge_start_date.month and row.year >= service_charge_start_date.year: + # if row.year > service_year: + service_year = row.year + if self.annual_service_charges == "Percentage": + service_charge += service_charge / 100 * int(self.amount) + else: + service_charge += int(self.amount) + + if (self.service_raise_type == "Annual Irregular"): + ourdoor_service_dict = {} + for sub_row in self.service_raise_details: + date = datetime.datetime.strptime(sub_row.date, "%Y-%m-%d") + date = date.strftime("%d-%m-%Y") + ourdoor_service_dict[date] = f'{sub_row.annual_raise_type}_{sub_row.annual_raise_amount}' + + month = '{:02d}'.format(row.month) + day = '{:02d}'.format(row.day) + + fieldname = f'{day}-{month}-{row.year}' + + if fieldname in ourdoor_service_dict and ourdoor_service_dict[fieldname]: + splitted_field = ourdoor_service_dict[fieldname].split("_") + annual_raise_type = splitted_field[0] + annual_raise_amount = splitted_field[1] + # print("===================>" + str(annual_raise_amount)) + if annual_raise_type == "Percentage": + service_charge += service_charge / 100 * int(annual_raise_amount) + else: + service_charge += int(annual_raise_amount) + + self.append('payments', { + 'date': row.date(), + 'amount': service_charge or 0, + 'reason': 'Services Charges' + " - " + "OutDoor" + }) + + for row in rrule(MONTHLY, interval=frequency, dtstart=start_date, until=end_date): + if (self.annual_raise == "Annual Regular"): + if row.year >= annual_raise_start_date.year: + # if row.month >= annual_raise_start_date.month or row.year > annual_raise_start_date.year: + if row.month == annual_raise_start_date.month and row.year >= annual_raise_start_date.year: + if row.year > year: + year = row.year + flag = 1 + + if flag == 1: + if self.annual_raise_type == "Percentage": + rent += rent / 100 * int(annual_raise_amount) + else: + rent += int(annual_raise_amount) + flag = 0 + + if (self.annual_raise == "Annual Irregular"): + contract_dict = {} + for sub_row in self.contract_annual_raise: + date = datetime.datetime.strptime(sub_row.date, "%Y-%m-%d") + date = date.strftime("%d-%m-%Y") + contract_dict[date] = f'{sub_row.annual_raise_type}_{sub_row.annual_raise_amount}' + + month = '{:02d}'.format(row.month) + day = '{:02d}'.format(row.day) + + fieldname = f'{day}-{month}-{row.year}' + + if fieldname in contract_dict and contract_dict[fieldname]: + splitted_field = contract_dict[fieldname].split("_") + annual_raise_type = splitted_field[0] + annual_raise_amount = splitted_field[1] + # print("===================>" + str(annual_raise_amount)) + if annual_raise_type == "Percentage": + rent += rent / 100 * int(annual_raise_amount) + else: + rent += int(annual_raise_amount) + + self.append('payments', { + 'date': row.date(), + 'amount': rent or 0, + 'reason': 'Rent' + (' - indoor' if self.split_indoor_from_outdoor_rent else '') + }) + + if self.split_indoor_from_outdoor_rent: + flag = 1 + rent = self.outdoor_rent_month + if rent > 0: + for row in rrule(MONTHLY, interval=frequency, dtstart=start_date, until=end_date): + if (self.annual_raise == "Annual Regular"): + if row.year >= annual_raise_start_date.year: + # if row.month >= annual_raise_start_date.month or row.year > annual_raise_start_date.year: + if row.month == annual_raise_start_date.month and row.year >= annual_raise_start_date.year: + if row.year > year: + year = row.year + flag = 1 + + if flag == 1: + if self.annual_raise_type == "Percentage": + rent += rent / 100 * int(annual_raise_amount) + else: + rent += int(annual_raise_amount) + flag = 0 + + if (self.annual_raise == "Annual Irregular"): + contract_dict = {} + for sub_row in self.contract_annual_raise: + date = datetime.datetime.strptime(sub_row.date, "%Y-%m-%d") + date = date.strftime("%d-%m-%Y") + contract_dict[date] = f'{sub_row.annual_raise_type}_{sub_row.annual_raise_amount}' + + month = '{:02d}'.format(row.month) + day = '{:02d}'.format(row.day) + + fieldname = f'{day}-{month}-{row.year}' + + if fieldname in contract_dict and contract_dict[fieldname]: + splitted_field = contract_dict[fieldname].split("_") + annual_raise_type = splitted_field[0] + annual_raise_amount = splitted_field[1] + + if annual_raise_type == "Percentage": + rent += rent / 100 * int(annual_raise_amount) + else: + rent += int(annual_raise_amount) + + self.append('payments', { + 'date': row.date(), + 'amount': rent or 0, + 'reason': 'Rent' + ' - outdoor' + }) + + if not self.is_new(): + self.save() + + def on_cancel(self): + frappe.db.set_value("Unit", self.unit, "status", "Vacant") + + @frappe.whitelist() + def create_row_invoice(self, rowid): + create_sales_invoice(self.name, rowid) + + def get_brand_names(self): + list_items = [] + brands = frappe.get_all("Tenant Brand Name", filters={"parent": self.renter}, fields=['tenant_brand_name'], + order_by="tenant_brand_name") + if brands: + for item in brands: + list_items.append(item["tenant_brand_name"]) + + return list_items + + @frappe.whitelist() + def update_duration(self): + duration = self.get_dates_diff(self.rent_start_date, self.rent_end_date) + # frappe.msgprint(str(duration)) + + duration_months = (cint(duration["years"]) * 12) + cint(duration["months"]) + ( + 1 if cint(duration["days"]) > 0 or getdate(self.rent_end_date).day == 1 else 0) + # frappe.msgprint(str(duration_months)) + self.duration = duration_months + + return duration_months + + def get_dates_diff(self, a, b): + a = getdate(a) + b = getdate(b) + diff_dict = {} + # a = datetime(a.year, a.month, a.day) + # b = datetime(b.year, b.month, b.day) + delta = dateutil.relativedelta.relativedelta(b, a) + diff_dict["years"] = delta.years + diff_dict["months"] = delta.months + diff_dict["days"] = delta.days + + return diff_dict + + +def create_sales_invoice(docname="", rowid=""): + print("Starting Creation..........") + condiction = "" + if docname and rowid: + row_doc = frappe.get_doc("Contract Payments", rowid) + #condiction = " and P.`name` = '%s' " % docname + condiction += " and C.`name` = '%s' " % rowid + condiction += " and C.date = '%s' " % row_doc.date + invoices = frappe.db.sql(""" + select *,P.`name` contract_name,C.date invoice_date + from `tabProperty Contract` P + inner JOIN + `tabContract Payments` C + on P.`name` = C.parent + -- and CURRENT_DATE() >= C.date + and P.rent_item is not NULL + and P.service_item is not NULL + -- and P.docstatus = 1 + and C.invoice is Null + and C.amount > 0 + {0} + order by date + """.format(condiction), as_dict=True, debug=True) + # return + if invoices: + for invoice in invoices: + s_name = create_sales_invoices(invoice) + print(str(s_name.name) + " Invoice Created.") + print("Finished.") + + +def create_sales_invoices(args): + # frappe.throw(str(args)) + default_company = frappe.db.get_default("Company") + si = frappe.new_doc("Sales Invoice") + args = frappe._dict(args) + si.posting_date = args.date or nowdate() + + invoice_date = args.date or nowdate() + + debit_to = frappe.db.get_value("Company", default_company, 'default_receivable_account') + if debit_to: + si.debit_to = debit_to # Default Receivable Account + else: + frappe.throw(_("Please set Default Receivable Account in Company " + default_company)) + + si.set_posting_time = 1 + si.postint_date = args.invoice_date + si.due_date = args.invoice_date + + si.customer = args.renter + si.tax_id = frappe.db.get_value("Customer", args.renter, "tax_id") + si.debit_to = debit_to + + print(str(si.postint_date) + "=================================================" + str(si.due_date)) + + # Accounting Dimensions + si.property = args.property_name + si.unit = args.unit + si.property_contract = args.contract_name + invoice_curerncy = frappe.get_cached_value('Customer', args.renter, "default_currency") or frappe.get_cached_value( + 'Company', default_company, "default_currency") + si.currency = invoice_curerncy + conversion_rate = 1 + if invoice_curerncy != "EGP": + conversion_rate = get_conversion_rate(invoice_curerncy,invoice_date) + if conversion_rate == 0: + message = _("Please Insert Currency Exchange for day {0}".format(invoice_date)) + frappe.throw(frappe.bold(message)) + + si.conversion_rate = conversion_rate + + default_income_account = None + item_income_account = frappe.db.get_value("Item Default", { + "parent": (args.rent_item if args.reason == "Rent" else args.service_item)}, 'income_account') + company_default_income_account = frappe.db.get_value("Company", default_company, 'default_income_account') + + if item_income_account: + default_income_account = item_income_account + elif company_default_income_account: + default_income_account = company_default_income_account + else: + frappe.throw(_("Please set Default Income Account in Company " + default_company)) + + cost_center = frappe.db.get_value("Company", default_company, 'cost_center') + if not cost_center: + frappe.throw(_("Please set Default Cost Center in Company " + default_company)) + + si.append("items", { + "item_code": (args.rent_item if args.reason == "Rent" else args.service_item), + "item_name": (args.rent_item_name if args.reason == "Rent" else args.service_item), + "qty": 1, + "rate": args.amount, + "uom": args.uom or "Unit", + "stock_uom": args.uom or "Unit", + "income_account": default_income_account, # Default Income Account + "cost_center": cost_center, + "conversion_factor": 1, + "description": args.reason + " for contract " + str(args.contract_name) + }) + + tax_template_name = "" + if args.reason == "Services Charges" and args.service_sales_taxes_and_charges_template: + tax_template_name = args.service_sales_taxes_and_charges_template + elif args.reason == "Rent" and args.rent_sales_taxes_and_charges_template: + tax_template_name = args.rent_sales_taxes_and_charges_template + + if tax_template_name: + tax_data = frappe.db.sql(""" + select T.`name` taxes_and_charges,S.charge_type,S.description,S.rate,S.account_head + from `tabSales Taxes and Charges Template` T + INNER JOIN + `tabSales Taxes and Charges` S + on T.`name` = S.parent + and S.parenttype='Sales Taxes and Charges Template' + and T.`name`='{tax_template_name}' + """.format(tax_template_name=tax_template_name), as_dict=True, debug=False) + + # frappe.throw(str(args.service_sales_taxes_and_charges_template)) + if tax_data: + tax_data = tax_data[0] + si.taxes_and_charges = tax_data["taxes_and_charges"] + si.append("taxes", { + "charge_type": tax_data["charge_type"], + "account_head": tax_data["account_head"], + "description": tax_data["description"], + "rate": tax_data["rate"], + "tax_amount": flt(tax_data["rate"]) * flt(si.net_total), + "total": (flt(tax_data["rate"]) * flt(si.net_total)) + flt(si.net_total), + "base_tax_amount": flt(tax_data["rate"]) * flt(si.net_total), + "base_total": (flt(tax_data["rate"]) * flt(si.net_total)) + flt(si.net_total) + }) + + Sinv_ID = None + try: + Sinv_ID = si.insert(ignore_mandatory=True) + frappe.db.sql("""update `tabContract Payments` set invoice = '{0}' where `name` = '{1}' """.format(Sinv_ID.name, + args.name), + debug=True) + return Sinv_ID + except Exception as e: + print("Error......" + str(e)) + frappe.throw(_(e)) + + +def get_conversion_rate(invoice_curerncy,invoice_date): + conversion_rate = 0 + exchange_rate = frappe.db.sql(""" + select exchange_rate + from `tabCurrency Exchange` + where from_currency ='{invoice_curerncy}' + and to_currency='EGP' + -- and date ='{invoice_date}' + ORDER BY date desc + limit 1 + """.format(invoice_curerncy=invoice_curerncy,invoice_date=invoice_date),as_dict=True) + if exchange_rate: + conversion_rate = exchange_rate[0]["exchange_rate"] + + return conversion_rate + + +def update_unit_status(): + units = frappe.db.get_list("Property Contract", filters={"docstatus": 1, "rent_end_date": getdate()}, + fields=["unit"]) + if units: + for unit in units: + frappe.db.set_value("Unit", unit["unit"], "status", "Vacant") + + +@frappe.whitelist() +def get_brand_names(renter): + list_items = [] + brands = frappe.get_all("Tenant Brand Name", filters={"parent": renter}, fields=['tenant_brand_name'], + order_by="tenant_brand_name") + if brands: + for item in brands: + list_items.append(item["tenant_brand_name"]) + + return list_items \ No newline at end of file diff --git a/muezzin/property_management/doctype/property_contract/test_property_contract.py b/muezzin/property_management/doctype/property_contract/test_property_contract.py new file mode 100644 index 0000000..1a976d2 --- /dev/null +++ b/muezzin/property_management/doctype/property_contract/test_property_contract.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestPropertyContract(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/property_type/__init__.py b/muezzin/property_management/doctype/property_type/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/property_type/property_type.js b/muezzin/property_management/doctype/property_type/property_type.js new file mode 100644 index 0000000..4c78aae --- /dev/null +++ b/muezzin/property_management/doctype/property_type/property_type.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Property Type', { + // refresh: function(frm) { + + // } +}); diff --git a/muezzin/property_management/doctype/property_type/property_type.json b/muezzin/property_management/doctype/property_type/property_type.json new file mode 100644 index 0000000..8e14a62 --- /dev/null +++ b/muezzin/property_management/doctype/property_type/property_type.json @@ -0,0 +1,41 @@ +{ + "autoname": "field:type", + "creation": "2021-01-15 16:58:13.694397", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "type" + ], + "fields": [ + { + "fieldname": "type", + "fieldtype": "Data", + "label": "Type", + "unique": 1 + } + ], + "modified": "2021-01-15 18:13:28.102488", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Property Type", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/property_type/property_type.py b/muezzin/property_management/doctype/property_type/property_type.py new file mode 100644 index 0000000..32008bb --- /dev/null +++ b/muezzin/property_management/doctype/property_type/property_type.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class PropertyType(Document): + pass diff --git a/muezzin/property_management/doctype/property_type/test_property_type.py b/muezzin/property_management/doctype/property_type/test_property_type.py new file mode 100644 index 0000000..c32f541 --- /dev/null +++ b/muezzin/property_management/doctype/property_type/test_property_type.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestPropertyType(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/property_unit_detail/__init__.py b/muezzin/property_management/doctype/property_unit_detail/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/property_unit_detail/property_unit_detail.json b/muezzin/property_management/doctype/property_unit_detail/property_unit_detail.json new file mode 100644 index 0000000..4c288b7 --- /dev/null +++ b/muezzin/property_management/doctype/property_unit_detail/property_unit_detail.json @@ -0,0 +1,36 @@ +{ + "creation": "2021-01-15 18:18:54.805424", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "unit_type", + "unit_number" + ], + "fields": [ + { + "fieldname": "unit_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Unit Type", + "options": "Unit Type" + }, + { + "fieldname": "unit_number", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Unit Number" + } + ], + "istable": 1, + "modified": "2021-01-15 18:22:32.111757", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Property Unit Detail", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/property_unit_detail/property_unit_detail.py b/muezzin/property_management/doctype/property_unit_detail/property_unit_detail.py new file mode 100644 index 0000000..133a3cd --- /dev/null +++ b/muezzin/property_management/doctype/property_unit_detail/property_unit_detail.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class PropertyUnitDetail(Document): + pass diff --git a/muezzin/property_management/doctype/real_estate_offer/__init__.py b/muezzin/property_management/doctype/real_estate_offer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/real_estate_offer/real_estate_offer.js b/muezzin/property_management/doctype/real_estate_offer/real_estate_offer.js new file mode 100644 index 0000000..77b2a48 --- /dev/null +++ b/muezzin/property_management/doctype/real_estate_offer/real_estate_offer.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Real Estate Offer', { + // refresh: function(frm) { + + // } +}); diff --git a/muezzin/property_management/doctype/real_estate_offer/real_estate_offer.json b/muezzin/property_management/doctype/real_estate_offer/real_estate_offer.json new file mode 100644 index 0000000..cd0b7e8 --- /dev/null +++ b/muezzin/property_management/doctype/real_estate_offer/real_estate_offer.json @@ -0,0 +1,273 @@ +{ + "autoname": "format:{title}-{####}", + "creation": "2021-02-23 13:30:21.537656", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "details", + "basic_information_section", + "owner", + "property_type", + "age", + "city", + "street", + "postal_box", + "number_of_air_conditioners", + "the_role", + "owner_type", + "instrument_history", + "width_rating", + "column_break_15", + "plate_number", + "property", + "display_type", + "is_subject_to_installment", + "neighborhood", + "space", + "the_national_email", + "air_conditioner_type", + "the_number_of_windows", + "instrument_number", + "case", + "financial_information_section", + "price", + "limit", + "income", + "column_break_31", + "price_type", + "price_of_one_meter", + "total_price", + "mortgaged", + "amended_from", + "section_break_37", + "location" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title" + }, + { + "fieldname": "details", + "fieldtype": "Small Text", + "label": "Details" + }, + { + "fieldname": "basic_information_section", + "fieldtype": "Section Break", + "label": "Basic Information" + }, + { + "fieldname": "owner", + "fieldtype": "Link", + "label": "Owner", + "options": "Customer" + }, + { + "fieldname": "property_type", + "fieldtype": "Link", + "label": "Property Type", + "options": "Property Type" + }, + { + "fieldname": "age", + "fieldtype": "Int", + "label": "Age" + }, + { + "fieldname": "city", + "fieldtype": "Data", + "label": "City" + }, + { + "fieldname": "street", + "fieldtype": "Data", + "label": "Street" + }, + { + "fieldname": "postal_box", + "fieldtype": "Data", + "label": "Postal Box" + }, + { + "fieldname": "number_of_air_conditioners", + "fieldtype": "Data", + "label": "Number of Air Conditioners" + }, + { + "fieldname": "the_role", + "fieldtype": "Data", + "label": "The Role" + }, + { + "fieldname": "owner_type", + "fieldtype": "Select", + "label": "Owner type", + "options": "Owner\nMediator\nAgent\nReal Estate Office" + }, + { + "fieldname": "instrument_history", + "fieldtype": "Date", + "label": "Instrument history" + }, + { + "fieldname": "width_rating", + "fieldtype": "Data", + "label": "Width Rating", + "options": "Commercial\nResidential\nAgricultural\nIndustrial\nTourist" + }, + { + "fieldname": "column_break_15", + "fieldtype": "Column Break" + }, + { + "fieldname": "plate_number", + "fieldtype": "Data", + "label": "Plate Number" + }, + { + "fieldname": "property", + "fieldtype": "Link", + "label": "Property", + "options": "Property" + }, + { + "fieldname": "display_type", + "fieldtype": "Select", + "label": "Display Type", + "options": "For Rent\nFor Sale\nInvestment" + }, + { + "fieldname": "is_subject_to_installment", + "fieldtype": "Data", + "label": "Is Subject to Installment" + }, + { + "fieldname": "neighborhood", + "fieldtype": "Select", + "label": "Neighborhood" + }, + { + "fieldname": "space", + "fieldtype": "Data", + "label": "Space" + }, + { + "fieldname": "the_national_email", + "fieldtype": "Data", + "label": "The National Email" + }, + { + "fieldname": "air_conditioner_type", + "fieldtype": "Data", + "label": "Air Conditioner Type" + }, + { + "fieldname": "the_number_of_windows", + "fieldtype": "Data", + "label": "The Number of Windows" + }, + { + "fieldname": "instrument_number", + "fieldtype": "Data", + "label": "Instrument Number" + }, + { + "fieldname": "case", + "fieldtype": "Select", + "label": "Case", + "options": "Effective\nClosed" + }, + { + "fieldname": "financial_information_section", + "fieldtype": "Section Break", + "label": "Financial Information" + }, + { + "fieldname": "price", + "fieldtype": "Currency", + "label": "Price", + "options": "Currency" + }, + { + "fieldname": "limit", + "fieldtype": "Data", + "label": "Limit" + }, + { + "fieldname": "income", + "fieldtype": "Float", + "label": "Income" + }, + { + "fieldname": "column_break_31", + "fieldtype": "Column Break" + }, + { + "fieldname": "price_type", + "fieldtype": "Select", + "label": "Price Type", + "options": "Fixed\nVariable" + }, + { + "fieldname": "price_of_one_meter", + "fieldtype": "Float", + "label": "Price of One Meter" + }, + { + "fieldname": "total_price", + "fieldtype": "Float", + "label": "Total Price" + }, + { + "fieldname": "mortgaged", + "fieldtype": "Select", + "label": "Mortgaged", + "options": "\nReal Estate Bank\nA Commercial Bank\nInstallment Company" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Real Estate Offer", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_37", + "fieldtype": "Section Break" + }, + { + "fieldname": "location", + "fieldtype": "Geolocation", + "label": "Location" + } + ], + "is_submittable": 1, + "modified": "2021-02-25 11:58:56.669609", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Real Estate Offer", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/real_estate_offer/real_estate_offer.py b/muezzin/property_management/doctype/real_estate_offer/real_estate_offer.py new file mode 100644 index 0000000..519e89b --- /dev/null +++ b/muezzin/property_management/doctype/real_estate_offer/real_estate_offer.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class RealEstateOffer(Document): + pass diff --git a/muezzin/property_management/doctype/real_estate_offer/test_real_estate_offer.py b/muezzin/property_management/doctype/real_estate_offer/test_real_estate_offer.py new file mode 100644 index 0000000..cb5ac3b --- /dev/null +++ b/muezzin/property_management/doctype/real_estate_offer/test_real_estate_offer.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestRealEstateOffer(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/real_estate_order/__init__.py b/muezzin/property_management/doctype/real_estate_order/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/real_estate_order/real_estate_order.js b/muezzin/property_management/doctype/real_estate_order/real_estate_order.js new file mode 100644 index 0000000..bbf1245 --- /dev/null +++ b/muezzin/property_management/doctype/real_estate_order/real_estate_order.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Real Estate Order', { + // refresh: function(frm) { + + // } +}); diff --git a/muezzin/property_management/doctype/real_estate_order/real_estate_order.json b/muezzin/property_management/doctype/real_estate_order/real_estate_order.json new file mode 100644 index 0000000..5dfdde6 --- /dev/null +++ b/muezzin/property_management/doctype/real_estate_order/real_estate_order.json @@ -0,0 +1,275 @@ +{ + "creation": "2021-02-23 13:45:20.505685", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "address_of_request", + "details", + "section_break_3", + "customer", + "type_of_request", + "property_type", + "price", + "case", + "column_break_9", + "age", + "city", + "neighborhood", + "street", + "postal_box", + "additional_details_section", + "number_of_rooms", + "role_number", + "number_of_laundries", + "space", + "direction", + "security", + "central_air_conditioning", + "elevator", + "swimming_pool", + "laundry_room", + "column_break_26", + "number_of_toilets", + "number_of_halls", + "finishing_type", + "flooring", + "architecture", + "kitchen", + "car_parking", + "driver_room", + "working_room", + "amended_from" + ], + "fields": [ + { + "fieldname": "address_of_request", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Address of Request", + "reqd": 1 + }, + { + "fieldname": "details", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Details", + "reqd": 1 + }, + { + "fieldname": "section_break_3", + "fieldtype": "Section Break" + }, + { + "fieldname": "customer", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Customer", + "options": "Customer", + "reqd": 1 + }, + { + "fieldname": "type_of_request", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Type of Request", + "options": "For Rent\nFor Sale", + "reqd": 1 + }, + { + "fieldname": "property_type", + "fieldtype": "Link", + "label": "Property Type", + "options": "Property Type", + "reqd": 1 + }, + { + "fieldname": "price", + "fieldtype": "Currency", + "label": "Price", + "options": "Currency", + "reqd": 1 + }, + { + "fieldname": "case", + "fieldtype": "Select", + "label": "Case", + "options": "Effective\nClosed", + "reqd": 1 + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "fieldname": "age", + "fieldtype": "Int", + "label": "Age" + }, + { + "fieldname": "city", + "fieldtype": "Data", + "label": "City" + }, + { + "fieldname": "neighborhood", + "fieldtype": "Data", + "label": "Neighborhood" + }, + { + "fieldname": "street", + "fieldtype": "Data", + "label": "Street" + }, + { + "fieldname": "postal_box", + "fieldtype": "Data", + "label": "Postal Box" + }, + { + "fieldname": "additional_details_section", + "fieldtype": "Section Break", + "label": "Additional Details" + }, + { + "fieldname": "number_of_rooms", + "fieldtype": "Data", + "label": "Number of Rooms" + }, + { + "fieldname": "role_number", + "fieldtype": "Data", + "label": "Role number" + }, + { + "fieldname": "number_of_laundries", + "fieldtype": "Data", + "label": "Number of Laundries" + }, + { + "fieldname": "space", + "fieldtype": "Data", + "label": "Space" + }, + { + "fieldname": "direction", + "fieldtype": "Data", + "label": "Direction" + }, + { + "fieldname": "security", + "fieldtype": "Data", + "label": "Security" + }, + { + "default": "0", + "fieldname": "central_air_conditioning", + "fieldtype": "Check", + "label": "Central Air Conditioning" + }, + { + "default": "0", + "fieldname": "elevator", + "fieldtype": "Check", + "label": "Elevator" + }, + { + "default": "0", + "fieldname": "swimming_pool", + "fieldtype": "Check", + "label": "Swimming Pool" + }, + { + "default": "0", + "fieldname": "laundry_room", + "fieldtype": "Check", + "label": "Laundry Room" + }, + { + "fieldname": "column_break_26", + "fieldtype": "Column Break" + }, + { + "fieldname": "number_of_toilets", + "fieldtype": "Data", + "label": "Number of Toilets" + }, + { + "fieldname": "number_of_halls", + "fieldtype": "Data", + "label": "Number of Halls" + }, + { + "fieldname": "finishing_type", + "fieldtype": "Select", + "label": "Finishing Type", + "options": "Deluxe\nNormal\nSuper\nProfessional" + }, + { + "fieldname": "flooring", + "fieldtype": "Data", + "label": "Flooring" + }, + { + "fieldname": "architecture", + "fieldtype": "Data", + "label": "Architecture" + }, + { + "fieldname": "kitchen", + "fieldtype": "Select", + "label": "Kitchen", + "options": "Open\nEnvelope" + }, + { + "default": "0", + "fieldname": "car_parking", + "fieldtype": "Check", + "label": "Car Parking" + }, + { + "default": "0", + "fieldname": "driver_room", + "fieldtype": "Check", + "label": "Driver Room" + }, + { + "default": "0", + "fieldname": "working_room", + "fieldtype": "Check", + "label": "Working Room" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Real Estate Order", + "print_hide": 1, + "read_only": 1 + } + ], + "is_submittable": 1, + "modified": "2021-02-25 12:00:30.216679", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Real Estate Order", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/real_estate_order/real_estate_order.py b/muezzin/property_management/doctype/real_estate_order/real_estate_order.py new file mode 100644 index 0000000..26763c4 --- /dev/null +++ b/muezzin/property_management/doctype/real_estate_order/real_estate_order.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class RealEstateOrder(Document): + pass diff --git a/muezzin/property_management/doctype/real_estate_order/test_real_estate_order.py b/muezzin/property_management/doctype/real_estate_order/test_real_estate_order.py new file mode 100644 index 0000000..c645e95 --- /dev/null +++ b/muezzin/property_management/doctype/real_estate_order/test_real_estate_order.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestRealEstateOrder(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/reservation_repayment_schedule/__init__.py b/muezzin/property_management/doctype/reservation_repayment_schedule/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/reservation_repayment_schedule/reservation_repayment_schedule.json b/muezzin/property_management/doctype/reservation_repayment_schedule/reservation_repayment_schedule.json new file mode 100644 index 0000000..2af545a --- /dev/null +++ b/muezzin/property_management/doctype/reservation_repayment_schedule/reservation_repayment_schedule.json @@ -0,0 +1,82 @@ +{ + "creation": "2021-10-23 15:09:39.197026", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "payment_date", + "principal_amount", + "interest_amount", + "total_payment", + "balance_amount", + "paid" + ], + "fields": [ + { + "allow_on_submit": 1, + "columns": 2, + "fieldname": "payment_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Payment Date" + }, + { + "columns": 2, + "fieldname": "principal_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Principal Amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "columns": 2, + "fieldname": "interest_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Interest Amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "columns": 2, + "fieldname": "total_payment", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Total Payment", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "columns": 2, + "fieldname": "balance_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Balance Amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "paid", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Paid", + "read_only": 1 + } + ], + "istable": 1, + "modified": "2021-10-24 02:20:34.154918", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Reservation Repayment Schedule", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/reservation_repayment_schedule/reservation_repayment_schedule.py b/muezzin/property_management/doctype/reservation_repayment_schedule/reservation_repayment_schedule.py new file mode 100644 index 0000000..9c3e322 --- /dev/null +++ b/muezzin/property_management/doctype/reservation_repayment_schedule/reservation_repayment_schedule.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class ReservationRepaymentSchedule(Document): + pass diff --git a/muezzin/property_management/doctype/reservations/__init__.py b/muezzin/property_management/doctype/reservations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/reservations/reservations.js b/muezzin/property_management/doctype/reservations/reservations.js new file mode 100644 index 0000000..5df5fc3 --- /dev/null +++ b/muezzin/property_management/doctype/reservations/reservations.js @@ -0,0 +1,190 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Reservations', { + refresh: function (frm) { + if (frm.doc.docstatus == 1) { + frm.add_custom_button(__("Contract"), () => { + frm.events.create_new_contract(frm) + }, "Create"); + } + + frm.set_query('property_name', () => { + return { + filters: { + property_type: frm.doc.property_type + } + } + }) + + frm.set_query('unit', () => { + return { + filters: { + status: 'Vacant' + } + } + }) + }, + create_new_contract: function (frm) { + if (frm.doc.reservation_type == 'Selling') { + frappe.model.open_mapped_doc({ + method: "muezzin.property_management.doctype.reservations.reservations.make_unit_sale_contract", + frm: cur_frm + }) + } else { + frappe.model.open_mapped_doc({ + method: "muezzin.property_management.doctype.reservations.reservations.make_property_contract", + frm: cur_frm + }) + } + }, + property_type: function (frm) { + frm.doc.property_name = null; + frm.refresh_field('property_name'); + }, + duration: function (frm) { + if (frm.doc.start_date) { + let dt = frm.doc.start_date + let hours = flt(frm.doc.duration); + let details = SplitTime(hours) + // alert(details) + let slot_to_time = moment(dt).add(details).format('YYYY-MM-DD HH:mm:ss') + frm.set_value('reservation_end', slot_to_time) + refresh_field('reservation_end') + } + }, + customer_primary_address: function (frm) { + if (frm.doc.customer_primary_address) { + frappe.call({ + method: 'frappe.contacts.doctype.address.address.get_address_display', + args: { + "address_dict": frm.doc.customer_primary_address + }, + callback: function (r) { + frm.set_value("primary_address", r.message); + } + }); + } + if (!frm.doc.customer_primary_address) { + frm.set_value("primary_address", ""); + } + }, + unit: function (frm) { + update_total_unit_area(frm); + }, + service_indoor_area: function (frm) { + update_service_charge_per_month(frm); + }, + service_charge_sqm: function (frm) { + update_service_charge_per_month(frm); + }, + outdoor_service_charge_per_month: function (frm) { + + update_total_services_rent(frm) + update_total_rent(frm); + + // update_total_rent_month(frm); + update_total_services_rent(frm) + update_outdoor_service_charge_sqm(frm); + }, + + service_outdoor_area: function (frm) { + // update_outdoor_service_charge_sqm(frm); + }, + outdoor_service_charge_sqm: function (frm) { + update_outdoor_service_charge_sqm(frm); + }, + indoor_area: function (frm) { + update_indoor_rent_month(frm); + }, + + rent_per_sqm: function (frm) { + update_indoor_rent_month(frm); + }, + + indoor_rent_month: function (frm) { + update_total_rent_month(frm); + }, + + // outdoor_service_charge_per_month: function(frm) { + // update_total_rent_month(frm); + // }, + outdoor_area: function (frm) { + update_outdoor_rent_month(frm); + }, + + outdoor_rent_sqm: function (frm) { + update_outdoor_rent_month(frm); + }, + + outdoor_rent_month: function (frm) { + update_total_rent_month(frm); + }, + + total_rent_per_month: function (frm) { + update_total_rent(frm); + }, + + service_charge_per_month: function (frm) { + update_total_services_rent(frm) + update_total_rent(frm); + }, + + total_rent: function (frm) { + if (frm.doc.total_rent) { + frm.set_value('annualized_rent', frm.doc.total_rent * 12); + } + } +}); + + +let SplitTime = function (numberOfHours) { + var Days = Math.floor(numberOfHours / 24); + var Remainder = numberOfHours % 24; + var Hours = Math.floor(Remainder); + var Minutes = Math.floor(60 * (Remainder - Hours)); + return ({"days": Days, "hours": Hours, "minutes": Minutes}) +} + +const update_indoor_rent_month = function (frm) { + if (frm.doc.indoor_area && frm.doc.rent_per_sqm) { + frm.set_value('indoor_rent_month', frm.doc.indoor_area * frm.doc.rent_per_sqm); + } +} + +const update_service_charge_per_month = function (frm) { + // if (frm.doc.gla && frm.doc.service_charge_sqm) { + frm.set_value('service_charge_per_month', frm.doc.service_indoor_area * frm.doc.service_charge_sqm); + // } +} + +const update_outdoor_service_charge_sqm = function (frm) { + frm.set_value('outdoor_service_charge_per_month', frm.doc.outdoor_service_charge_sqm * frm.doc.service_outdoor_area); +} + +const update_outdoor_rent_month = function (frm) { + frm.set_value('outdoor_rent_month', frm.doc.outdoor_area * frm.doc.outdoor_rent_sqm); +} + +const update_total_rent = function (frm) { + if (frm.doc.total_rent_per_month >= 0 && frm.doc.service_charge_per_month >= 0) { + frm.set_value('total_rent', flt(frm.doc.total_rent_per_month) + flt(frm.doc.service_charge_per_month) + flt(frm.doc.outdoor_service_charge_per_month)); + } +} + +const update_total_services_rent = function (frm) { + frm.set_value('total_services_rent', flt(frm.doc.outdoor_service_charge_per_month) + flt(frm.doc.service_charge_per_month)); +} + +const update_total_unit_area = function (frm) { + // if (frm.doc.gla && frm.doc.service_charge_sqm) { + frm.set_value('gla', frm.doc.outdoor_area + frm.doc.indoor_area); + refresh_field('gla') + // } +} + +const update_total_rent_month = function (frm) { + if (frm.doc.indoor_rent_month >= 0 && frm.doc.outdoor_rent_month >= 0) { + frm.set_value('total_rent_per_month', flt(frm.doc.indoor_rent_month) + flt(frm.doc.outdoor_rent_month) + flt(frm.doc.outdoor_service_charge_per_month)); + } +} diff --git a/muezzin/property_management/doctype/reservations/reservations.json b/muezzin/property_management/doctype/reservations/reservations.json new file mode 100644 index 0000000..0d9b4a3 --- /dev/null +++ b/muezzin/property_management/doctype/reservations/reservations.json @@ -0,0 +1,487 @@ +{ + "autoname": "format:{property_name}-{######}", + "creation": "2021-02-22 16:44:09.584088", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "reservation_type", + "lead_name", + "person_name", + "renter", + "customer_name", + "brand_name", + "brand_description", + "column_break_8", + "start_date", + "reservation_end", + "duration", + "section_break_28", + "authorized_signatory_person", + "column_break_30", + "official_correspondence_email", + "primary_address_and_contact_detail_section", + "customer_primary_contact", + "mobile_no", + "email_id", + "column_break_36", + "customer_primary_address", + "primary_address", + "column_break_3", + "repayment_start_date", + "amount", + "advance_payment", + "rate_of_interest", + "repayment_method", + "repayment_periods", + "monthly_repayment_amount", + "property_name", + "property_type", + "unit", + "commencement_date", + "column_break_23", + "reservation_deposit_date", + "reservation_deposit_amount", + "other_charges", + "grace_period", + "yearly_increase", + "repayment_schedule_details_section", + "repayment_schedule", + "rent_details_section", + "indoor_area", + "rent_per_sqm", + "indoor_rent_month", + "column_break_39", + "outdoor_area", + "outdoor_rent_sqm", + "outdoor_rent_month", + "service_rent_details_section", + "service_indoor_area", + "service_charge_sqm", + "service_charge_per_month", + "column_break_46", + "service_outdoor_area", + "outdoor_service_charge_sqm", + "outdoor_service_charge_per_month", + "totals_section", + "gla", + "total_rent_per_month", + "total_services_rent", + "column_break_54", + "total_rent", + "annualized_rent", + "amended_from" + ], + "fields": [ + { + "fieldname": "start_date", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Start Date", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Section Break", + "label": "Unit Details" + }, + { + "description": "Number in Hours", + "fieldname": "duration", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Duration", + "reqd": 1 + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "options": "Currency" + }, + { + "fieldname": "property_type", + "fieldtype": "Link", + "label": "Property Type", + "options": "Property Type", + "reqd": 1 + }, + { + "fieldname": "property_name", + "fieldtype": "Link", + "label": "Property Name", + "options": "Property", + "reqd": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Reservations", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "reservation_end", + "fieldtype": "Datetime", + "label": "Reservation End", + "read_only": 1 + }, + { + "fieldname": "unit", + "fieldtype": "Link", + "label": "Unit", + "options": "Unit", + "reqd": 1 + }, + { + "depends_on": "eval:doc.reservation_type=='Selling'", + "fieldname": "repayment_method", + "fieldtype": "Select", + "label": "Repayment Method", + "options": "\nRepay Fixed Amount per Period\nRepay Over Number of Periods" + }, + { + "depends_on": "eval:doc.repayment_method =='Repay Over Number of Periods'", + "fieldname": "repayment_periods", + "fieldtype": "Int", + "label": "Repayment Period in Months" + }, + { + "depends_on": "eval:doc.repayment_method =='Repay Fixed Amount per Period'", + "fieldname": "monthly_repayment_amount", + "fieldtype": "Currency", + "label": "Monthly Repayment Amount" + }, + { + "fieldname": "repayment_schedule_details_section", + "fieldtype": "Section Break", + "label": "Repayment Schedule Details" + }, + { + "fieldname": "repayment_schedule", + "fieldtype": "Table", + "label": "Repayment Schedule", + "options": "Reservation Repayment Schedule" + }, + { + "default": "0", + "depends_on": "eval:doc.reservation_type=='Selling'", + "fieldname": "rate_of_interest", + "fieldtype": "Percent", + "label": "Rate of Interest" + }, + { + "default": "0", + "depends_on": "eval:doc.reservation_type=='Selling'", + "fieldname": "advance_payment", + "fieldtype": "Currency", + "label": "Advance Payment" + }, + { + "depends_on": "doc.reservation_type=='Selling'", + "fieldname": "repayment_start_date", + "fieldtype": "Date", + "label": "Repayment Start Date" + }, + { + "fetch_from": "customer.customer_name", + "fieldname": "customer_name", + "fieldtype": "Data", + "label": "Customer Name", + "read_only": 1 + }, + { + "fieldname": "lead_name", + "fieldtype": "Link", + "label": "Lead", + "options": "Lead" + }, + { + "fetch_from": "lead_name.lead_name", + "fieldname": "person_name", + "fieldtype": "Data", + "label": "Person Name", + "read_only": 1 + }, + { + "fieldname": "reservation_type", + "fieldtype": "Select", + "label": "Reservation Type", + "options": "Rent\nSelling" + }, + { + "fieldname": "brand_name", + "fieldtype": "Data", + "label": "Brand Name" + }, + { + "fieldname": "brand_description", + "fieldtype": "Small Text", + "label": "Brand description" + }, + { + "fieldname": "section_break_28", + "fieldtype": "Section Break" + }, + { + "fieldname": "authorized_signatory_person", + "fieldtype": "Data", + "label": "Authorized Signatory person" + }, + { + "fieldname": "column_break_30", + "fieldtype": "Column Break" + }, + { + "fieldname": "official_correspondence_email", + "fieldtype": "Data", + "label": "Official Correspondence Email", + "options": "Email" + }, + { + "collapsible": 1, + "fieldname": "primary_address_and_contact_detail_section", + "fieldtype": "Section Break", + "label": "Primary Address and Contact Detail" + }, + { + "fieldname": "customer_primary_contact", + "fieldtype": "Link", + "label": "Customer Primary Contact", + "options": "Contact" + }, + { + "fetch_from": "customer_primary_contact.mobile_no", + "fieldname": "mobile_no", + "fieldtype": "Read Only", + "label": "Mobile No" + }, + { + "fetch_from": "customer_primary_contact.email_id", + "fieldname": "email_id", + "fieldtype": "Read Only", + "label": "Email Id" + }, + { + "fieldname": "column_break_36", + "fieldtype": "Column Break" + }, + { + "fieldname": "customer_primary_address", + "fieldtype": "Link", + "label": "Customer Primary Address", + "options": "Address" + }, + { + "fieldname": "primary_address", + "fieldtype": "Text", + "label": "Primary Address", + "read_only": 1 + }, + { + "depends_on": "eval:doc.reservation_type=='Rent'", + "fieldname": "reservation_deposit_date", + "fieldtype": "Date", + "label": "Reservation Deposit Date" + }, + { + "depends_on": "eval:doc.reservation_type=='Rent'", + "fieldname": "reservation_deposit_amount", + "fieldtype": "Currency", + "label": "Reservation Deposit Amount" + }, + { + "depends_on": "eval:doc.reservation_type=='Rent'", + "fieldname": "other_charges", + "fieldtype": "Currency", + "label": "Other Charges" + }, + { + "depends_on": "eval:doc.reservation_type=='Rent'", + "description": "Period Allowed to Delay Rent", + "fieldname": "grace_period", + "fieldtype": "Int", + "label": "Grace Period" + }, + { + "depends_on": "eval:doc.reservation_type=='Rent'", + "fieldname": "commencement_date", + "fieldtype": "Date", + "label": "Commencement Date" + }, + { + "depends_on": "eval:doc.reservation_type=='Rent'", + "fieldname": "yearly_increase", + "fieldtype": "Percent", + "label": "Yearly Increase" + }, + { + "depends_on": "eval:doc.reservation_type=='Rent'", + "fieldname": "rent_per_sqm", + "fieldtype": "Currency", + "label": "Rent (Price / sqm)" + }, + { + "fieldname": "renter", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Customer", + "options": "Customer", + "reqd": 1 + }, + { + "fieldname": "rent_details_section", + "fieldtype": "Section Break", + "label": "Rent Details" + }, + { + "fetch_from": "unit.unit_area", + "fieldname": "indoor_area", + "fieldtype": "Float", + "label": "Indoor Area", + "read_only": 1 + }, + { + "fieldname": "indoor_rent_month", + "fieldtype": "Currency", + "label": "Indoor Rent (LE/Month)" + }, + { + "fetch_from": "unit.outdoor_area", + "fieldname": "outdoor_area", + "fieldtype": "Float", + "label": "Outdoor Area", + "read_only": 1 + }, + { + "fieldname": "column_break_39", + "fieldtype": "Column Break" + }, + { + "fieldname": "outdoor_rent_sqm", + "fieldtype": "Currency", + "label": "Outdoor Rent (sqm)" + }, + { + "fieldname": "outdoor_rent_month", + "fieldtype": "Currency", + "label": "Outdoor Rent (LE/Month)" + }, + { + "fieldname": "service_rent_details_section", + "fieldtype": "Section Break", + "label": "Service Rent Details" + }, + { + "fetch_from": "unit.unit_area", + "fieldname": "service_indoor_area", + "fieldtype": "Float", + "label": "Service Indoor Area ", + "read_only": 1 + }, + { + "fieldname": "service_charge_sqm", + "fieldtype": "Currency", + "label": "Service Charge sqm" + }, + { + "fieldname": "service_charge_per_month", + "fieldtype": "Currency", + "label": "Service Charge per Month" + }, + { + "fieldname": "column_break_46", + "fieldtype": "Column Break" + }, + { + "fetch_from": "unit.outdoor_area", + "fieldname": "service_outdoor_area", + "fieldtype": "Float", + "label": "Service Outdoor Area ", + "read_only": 1 + }, + { + "fieldname": "outdoor_service_charge_sqm", + "fieldtype": "Currency", + "label": "Outdoor Service Charge SQM" + }, + { + "fieldname": "outdoor_service_charge_per_month", + "fieldtype": "Currency", + "label": "Outdoor Service Charge per Month" + }, + { + "fieldname": "totals_section", + "fieldtype": "Section Break", + "label": "Totals" + }, + { + "fieldname": "gla", + "fieldtype": "Float", + "label": "GLA", + "read_only": 1 + }, + { + "fieldname": "total_rent_per_month", + "fieldtype": "Currency", + "label": "Total Rent per Month", + "read_only": 1 + }, + { + "fieldname": "column_break_54", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_rent", + "fieldtype": "Currency", + "label": "Total Revenue", + "read_only": 1 + }, + { + "fieldname": "annualized_rent", + "fieldtype": "Currency", + "label": "Annualized Revenue", + "read_only": 1 + }, + { + "fieldname": "total_services_rent", + "fieldtype": "Currency", + "label": "Total Services Charges", + "read_only": 1 + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_23", + "fieldtype": "Column Break" + } + ], + "is_submittable": 1, + "modified": "2022-01-01 04:49:20.952001", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Reservations", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/reservations/reservations.py b/muezzin/property_management/doctype/reservations/reservations.py new file mode 100644 index 0000000..55a63c8 --- /dev/null +++ b/muezzin/property_management/doctype/reservations/reservations.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe, math, json +from frappe import _ +import erpnext +from frappe.model.document import Document +from frappe.utils import flt, rounded, add_months, nowdate, getdate,get_datetime +from erpnext.controllers.accounts_controller import AccountsController +from frappe.model.mapper import get_mapped_doc + +class Reservations(Document): + + def validate(self): + validate_repayment_method(self.repayment_method, self.amount, self.monthly_repayment_amount, self.repayment_periods) + self.set_missing_fields() + if self.reservation_type=='Selling': + self.make_repayment_schedule() + self.set_repayment_period() + + def on_submit(self): + if(get_datetime() < get_datetime(self.reservation_end)): + frappe.db.set_value("Unit", self.unit, "status", "Reserved") + else: + frappe.db.set_value("Unit", self.unit, "status", "Vacant") + + def on_cancel(self): + frappe.db.set_value("Unit", self.unit, "status", "Vacant") + + def set_missing_fields(self): + if self.repayment_method == "Repay Over Number of Periods": + self.monthly_repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.amount, self.rate_of_interest, self.repayment_periods,self.advance_payment) + + def set_repayment_period(self): + if self.repayment_method == "Repay Fixed Amount per Period": + repayment_periods = len(self.repayment_schedule) + self.repayment_periods = repayment_periods + + def make_repayment_schedule(self): + self.repayment_schedule = [] + + payment_date = self.repayment_start_date + balance_amount = self.amount - self.advance_payment + while(balance_amount > 0): + interest_amount = (balance_amount * flt(self.rate_of_interest) / (12*100)) + principal_amount = self.monthly_repayment_amount - interest_amount + balance_amount = (balance_amount + interest_amount - self.monthly_repayment_amount) + + if balance_amount < 0: + principal_amount += balance_amount + balance_amount = 0.0 + + total_payment = principal_amount + interest_amount + self.append("repayment_schedule", { + "payment_date": payment_date, + "principal_amount": principal_amount, + "interest_amount": interest_amount, + "total_payment": total_payment, + "balance_amount": balance_amount + }) + next_payment_date = add_months(payment_date, 1) + payment_date = next_payment_date + +def validate_repayment_method(repayment_method, amount, monthly_repayment_amount, repayment_periods): + if repayment_method == "Repay Over Number of Periods" and not repayment_periods: + frappe.throw(_("Please enter Repayment Periods")) + + if repayment_method == "Repay Fixed Amount per Period": + if not monthly_repayment_amount: + frappe.throw(_("Please enter repayment Amount")) + if monthly_repayment_amount > amount: + frappe.throw(_("Monthly Repayment Amount cannot be greater than Amount")) + +def get_monthly_repayment_amount(repayment_method, amount, rate_of_interest, repayment_periods,advance_payment): + if rate_of_interest: + monthly_interest_rate = flt(rate_of_interest) / (12 *100) + monthly_repayment_amount = math.ceil(((amount-advance_payment) * monthly_interest_rate * + (1 + monthly_interest_rate)**repayment_periods) \ + / ((1 + monthly_interest_rate)**repayment_periods - 1)) + else: + monthly_repayment_amount = math.ceil(flt((amount-advance_payment)) / repayment_periods) + # frappe.msgprint(str(repayment_periods)) + return monthly_repayment_amount + + + +@frappe.whitelist() +def make_unit_sale_contract(source_name, target_doc=None): + doclist = get_mapped_doc("Reservations", source_name, { + "Reservations": { + "doctype": "Unit Sale Contract" + }, + "Reservation Repayment Schedule": { + "doctype": "Reservation Repayment Schedule", + "field_map": [ + ["payment_date", "payment_date"], + ["principal_amount", "principal_amount"], + ["interest_amount", "interest_amount"], + ["total_payment", "total_payment"], + ["balance_amount", "balance_amount"] + ] + } + }, target_doc) + + return doclist + + +@frappe.whitelist() +def make_property_contract(source_name, target_doc=None): + doclist = get_mapped_doc("Reservations", source_name, { + "Reservations": { + "doctype": "Property Contract" + }, + "Reservation Repayment Schedule": { + "doctype": "Reservation Repayment Schedule", + "field_map": [ + ["payment_date", "payment_date"], + ["principal_amount", "principal_amount"], + ["interest_amount", "interest_amount"], + ["total_payment", "total_payment"], + ["balance_amount", "balance_amount"] + ] + } + }, target_doc) + + return doclist \ No newline at end of file diff --git a/muezzin/property_management/doctype/reservations/test_reservations.py b/muezzin/property_management/doctype/reservations/test_reservations.py new file mode 100644 index 0000000..efc10dc --- /dev/null +++ b/muezzin/property_management/doctype/reservations/test_reservations.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestReservations(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/script/__init__.py b/muezzin/property_management/doctype/script/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/script/script.js b/muezzin/property_management/doctype/script/script.js new file mode 100644 index 0000000..9d22575 --- /dev/null +++ b/muezzin/property_management/doctype/script/script.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Script', { + // refresh: function(frm) { + + // } +}); diff --git a/muezzin/property_management/doctype/script/script.json b/muezzin/property_management/doctype/script/script.json new file mode 100644 index 0000000..187213c --- /dev/null +++ b/muezzin/property_management/doctype/script/script.json @@ -0,0 +1,61 @@ +{ + "creation": "2021-02-02 11:22:10.301953", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "section_break_2", + "text", + "amended_from" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title" + }, + { + "fieldname": "section_break_2", + "fieldtype": "Section Break" + }, + { + "fieldname": "text", + "fieldtype": "Text Editor", + "label": "Text" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Script", + "print_hide": 1, + "read_only": 1 + } + ], + "is_submittable": 1, + "modified": "2021-02-02 11:22:10.301953", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Script", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/script/script.py b/muezzin/property_management/doctype/script/script.py new file mode 100644 index 0000000..e33cd73 --- /dev/null +++ b/muezzin/property_management/doctype/script/script.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class Script(Document): + pass diff --git a/muezzin/property_management/doctype/script/test_script.py b/muezzin/property_management/doctype/script/test_script.py new file mode 100644 index 0000000..ffd8997 --- /dev/null +++ b/muezzin/property_management/doctype/script/test_script.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestScript(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/tenant_brand_name/__init__.py b/muezzin/property_management/doctype/tenant_brand_name/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/tenant_brand_name/tenant_brand_name.js b/muezzin/property_management/doctype/tenant_brand_name/tenant_brand_name.js new file mode 100644 index 0000000..ceb6ca5 --- /dev/null +++ b/muezzin/property_management/doctype/tenant_brand_name/tenant_brand_name.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Tenant Brand Name', { + // refresh: function(frm) { + + // } +}); diff --git a/muezzin/property_management/doctype/tenant_brand_name/tenant_brand_name.json b/muezzin/property_management/doctype/tenant_brand_name/tenant_brand_name.json new file mode 100644 index 0000000..742f5a1 --- /dev/null +++ b/muezzin/property_management/doctype/tenant_brand_name/tenant_brand_name.json @@ -0,0 +1,29 @@ +{ + "creation": "2021-09-06 06:12:32.716168", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "tenant_brand_name" + ], + "fields": [ + { + "fieldname": "tenant_brand_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Tenant Brand Name ", + "reqd": 1 + } + ], + "istable": 1, + "modified": "2021-09-06 06:12:44.502817", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Tenant Brand Name", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/tenant_brand_name/tenant_brand_name.py b/muezzin/property_management/doctype/tenant_brand_name/tenant_brand_name.py new file mode 100644 index 0000000..7b0c51a --- /dev/null +++ b/muezzin/property_management/doctype/tenant_brand_name/tenant_brand_name.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class TenantBrandName(Document): + pass diff --git a/muezzin/property_management/doctype/tenant_brand_name/test_tenant_brand_name.py b/muezzin/property_management/doctype/tenant_brand_name/test_tenant_brand_name.py new file mode 100644 index 0000000..c117f09 --- /dev/null +++ b/muezzin/property_management/doctype/tenant_brand_name/test_tenant_brand_name.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestTenantBrandName(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/unit/__init__.py b/muezzin/property_management/doctype/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/unit/test_unit.py b/muezzin/property_management/doctype/unit/test_unit.py new file mode 100644 index 0000000..61c61c8 --- /dev/null +++ b/muezzin/property_management/doctype/unit/test_unit.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestUnit(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/unit/unit.js b/muezzin/property_management/doctype/unit/unit.js new file mode 100644 index 0000000..37889fa --- /dev/null +++ b/muezzin/property_management/doctype/unit/unit.js @@ -0,0 +1,150 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Unit', { + onload(frm) { + frm.set_query("sub_activity", () => { + return { + filters: { + parent_activity: frm.doc.unit_activity + } + } + }) + }, + refresh: function (frm) { + if (!frm.is_dirty()) { + create_custom_buttons(frm); + } + }, + + unit_area: function (frm) { + update_gla(frm); + }, + + outdoor_area: function (frm) { + update_gla(frm); + }, + + gla: function (frm) { + if (frm.doc.gla < 0) { + frm.doc.unit_price = null; + frm.doc.gla = null; + frm.refresh_field('gla'); + frappe.msgprint('Unit Area must be greater than zero.') + } else if (frm.doc.gla && frm.doc.price_psm) { + frm.doc.unit_price = frm.doc.gla * frm.doc.price_psm; + } + frm.refresh_field('unit_price'); + }, + + price_psm: function (frm) { + if (frm.doc.price_psm < 0) { + frm.doc.price_psm = null; + frm.doc.unit_price = null; + frm.refresh_field('price_psm'); + frappe.msgprint('Price must be greater than zero.') + } else if (frm.doc.gla && frm.doc.price_psm) { + frm.doc.unit_price = frm.doc.gla * frm.doc.price_psm; + } + frm.refresh_field('unit_price'); + } +}); + +const create_custom_buttons = function(frm) { + frm.add_custom_button(__('Journal Entry'), () => { + let doc = frm.doc + frappe.run_serially([ + () => frappe.new_doc('Journal Entry'), + () => { + cur_frm.doc.cost_center = doc.cost_center; + cur_frm.doc.property = doc.property; + cur_frm.doc.unit = doc.name; + cur_frm.refresh(); + } + ]); + }, __('Create')); + + cur_frm.add_custom_button(__('Sales Invoice'), () => make_sales_invoice(), __('Create')); + + // frm.add_custom_button(__('Sales Invoice'), () => { + // let doc = frm.doc + // frappe.run_serially([ + // () => frappe.new_doc('Sales Invoice'), + // () => { + // cur_frm.doc.cost_center = doc.cost_center; + // cur_frm.doc.property = doc.property; + // cur_frm.doc.unit = doc.name; + // let customer = doc.property_owner_customer; + // frappe.model.set_value(cur_frm.doc.doctype, cur_frm.doc.name, "customer", customer); + // cur_frm.refresh(); + // } + // ]); + // }, __('Create')); + + frm.add_custom_button(__('Payment Entry'), () => { + let doc = frm.doc + frappe.run_serially([ + () => frappe.new_doc('Payment Entry'), + () => { + cur_frm.doc.cost_center = doc.cost_center; + cur_frm.doc.property = doc.property; + cur_frm.doc.unit = doc.name; + + if (doc.property_owner_customer) { + cur_frm.doc.party_type = 'Customer'; + let party = doc.property_owner_customer; + frappe.model.set_value("Payment Entry", cur_frm.doc.name, "party", party) + } + + if (doc.property_owner_supplier) { + cur_frm.doc.party_type = 'Supplier'; + let party = doc.property_owner_supplier; + frappe.model.set_value("Payment Entry", cur_frm.doc.name, "party", party) + } + cur_frm.refresh(); + } + ]); + + }, __('Create')); + + frm.add_custom_button(__('Purchase Invoice'), () => { + let doc = frm.doc + frappe.run_serially([ + () => frappe.new_doc('Purchase Invoice'), + () => { + cur_frm.doc.cost_center = doc.cost_center; + cur_frm.doc.property = doc.property; + cur_frm.doc.unit = doc.name; + let supplier = doc.property_owner_supplier; + frappe.model.set_value(cur_frm.doc.doctype, cur_frm.doc.name, "supplier", supplier); + cur_frm.refresh(); + } + ]); + }, __('Create')); + + frm.add_custom_button(__('Contract'), () => { + let doc = frm.doc + frappe.run_serially([ + () => frappe.new_doc('Property Contract'), + () => { + cur_frm.doc.property_name = doc.property; + cur_frm.doc.unit_name = doc.name; + cur_frm.refresh(); + } + ]); + }, __('Create')); +} + +const update_gla = function(frm) { + if (frm.doc.unit_area>=0 && frm.doc.outdoor_area>=0) { + frm.set_value('gla', frm.doc.unit_area+frm.doc.outdoor_area); + } +} + + +var make_sales_invoice = function () { + frappe.model.open_mapped_doc({ + method: "muezzin.property_management.doctype.unit.unit.make_sales_invoice", + frm: cur_frm + }); + } \ No newline at end of file diff --git a/muezzin/property_management/doctype/unit/unit.json b/muezzin/property_management/doctype/unit/unit.json new file mode 100644 index 0000000..cf77990 --- /dev/null +++ b/muezzin/property_management/doctype/unit/unit.json @@ -0,0 +1,547 @@ +{ + "autoname": "format:{property}-{unit_number}", + "creation": "2021-01-15 17:13:53.660671", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "property", + "column_break_2", + "status", + "section_break_4", + "customer", + "customer_name", + "column_break_6", + "supplier", + "supplier_name", + "section_break_8", + "unit_number", + "property_owner", + "property_owner_company", + "property_owner_supplier", + "property_owner_customer", + "company_name_en", + "annual_rental_price", + "column_break_11", + "unit_type", + "opening_phase", + "electricity_meter_number", + "apartment_number", + "section_break_14", + "unit_area", + "outdoor_area", + "gla", + "unit_price", + "column_break_14", + "price_psm", + "unit_valuation_rate", + "unit_image_section", + "image", + "accounting_dimension_section", + "cost_center", + "additional_data_section", + "role_floor_number", + "bathrooms", + "neighborhood", + "north_length_of_plot_in_meters", + "south_length_of_plot_in_meters", + "east_length_of_plot_in_meters", + "west_length_of_plot_in_meters", + "number_of_entries", + "unit_activity", + "sub_activity", + "water_meter_number", + "processing_mode", + "ground_floor_area", + "ground_floor_and_mezzanine", + "entrance_type", + "split_air_conditioner", + "sitting_rooms", + "additional_features", + "value_of_property_management_commission", + "column_break_28", + "bedrooms", + "no_planned", + "north_plot_no", + "south_plot_no", + "east_plot_no", + "west_plot_no", + "unit_description", + "rooms__openings_number", + "toilets", + "unit_form", + "unit_area_in_sq_meter", + "plate_number", + "mezzanine_space", + "minimal_rental_price", + "minimal_price_per_sq_meter", + "key_number", + "window_conditioner", + "kitchens", + "halls", + "yard", + "installed_kitchen", + "car_entrance", + "commission_type_for_property_management", + "activating_tax_on_property_management_commission" + ], + "fields": [ + { + "fieldname": "property", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Property", + "options": "Property", + "reqd": 1 + }, + { + "fieldname": "unit_number", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Unit Number", + "reqd": 1 + }, + { + "fieldname": "annual_rental_price", + "fieldtype": "Float", + "label": "Annual Rental Price" + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fieldname": "unit_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Unit Type", + "options": "Unit Type", + "reqd": 1 + }, + { + "fieldname": "electricity_meter_number", + "fieldtype": "Data", + "label": "Electricity Meter Number" + }, + { + "fieldname": "apartment_number", + "fieldtype": "Data", + "label": "Apartment Number" + }, + { + "collapsible": 1, + "fieldname": "additional_data_section", + "fieldtype": "Section Break", + "label": "Additional Data" + }, + { + "fieldname": "role_floor_number", + "fieldtype": "Data", + "label": "Role (Floor Number)" + }, + { + "fieldname": "bathrooms", + "fieldtype": "Int", + "label": "Bathrooms" + }, + { + "fieldname": "neighborhood", + "fieldtype": "Data", + "label": "Neighborhood" + }, + { + "fieldname": "north_length_of_plot_in_meters", + "fieldtype": "Float", + "label": "North (Length of Plot in Meters)" + }, + { + "fieldname": "south_length_of_plot_in_meters", + "fieldtype": "Float", + "label": "South (Length of Plot in Meters)" + }, + { + "fieldname": "east_length_of_plot_in_meters", + "fieldtype": "Float", + "label": "East (Length of Plot in Meters)" + }, + { + "fieldname": "west_length_of_plot_in_meters", + "fieldtype": "Float", + "label": "West (Length of Plot in Meters)" + }, + { + "fieldname": "number_of_entries", + "fieldtype": "Data", + "label": "Number of Entries" + }, + { + "fieldname": "unit_activity", + "fieldtype": "Link", + "label": "Unit Activity", + "options": "Unit Activity" + }, + { + "fieldname": "water_meter_number", + "fieldtype": "Data", + "label": "Water Meter Number" + }, + { + "fieldname": "processing_mode", + "fieldtype": "Select", + "label": "Processing Mode", + "options": "Normal\nConditioned\nAir-Conditioned and Furnished" + }, + { + "fieldname": "ground_floor_area", + "fieldtype": "Float", + "label": "Ground Floor Area" + }, + { + "fieldname": "entrance_type", + "fieldtype": "Select", + "label": "Entrance type", + "options": "Chose Type\nSpecial\nMutual" + }, + { + "fieldname": "split_air_conditioner", + "fieldtype": "Int", + "label": "Split Air Conditioner" + }, + { + "fieldname": "sitting_rooms", + "fieldtype": "Int", + "label": "Sitting Rooms" + }, + { + "fieldname": "additional_features", + "fieldtype": "Small Text", + "label": "Additional Features" + }, + { + "fieldname": "value_of_property_management_commission", + "fieldtype": "Data", + "label": "Value of Property Management Commission" + }, + { + "fieldname": "column_break_28", + "fieldtype": "Column Break" + }, + { + "fieldname": "bedrooms", + "fieldtype": "Data", + "label": "Bedrooms" + }, + { + "fieldname": "no_planned", + "fieldtype": "Data", + "label": "No. Planned" + }, + { + "fieldname": "north_plot_no", + "fieldtype": "Data", + "label": "North (Plot No.)" + }, + { + "fieldname": "south_plot_no", + "fieldtype": "Data", + "label": "South (Plot No.)" + }, + { + "fieldname": "east_plot_no", + "fieldtype": "Data", + "label": "East (Plot No.)" + }, + { + "fieldname": "west_plot_no", + "fieldtype": "Data", + "label": "West (Plot No.)" + }, + { + "fieldname": "unit_description", + "fieldtype": "Select", + "label": "Unit Description", + "options": "Chose Type\nResidential Families\nResidential Bachelors\nCommercial" + }, + { + "fieldname": "rooms__openings_number", + "fieldtype": "Int", + "label": "Rooms / Openings Number" + }, + { + "fieldname": "toilets", + "fieldtype": "Int", + "label": "Toilets" + }, + { + "fieldname": "unit_form", + "fieldtype": "Data", + "label": "Unit Form" + }, + { + "fieldname": "unit_area_in_sq_meter", + "fieldtype": "Float", + "label": "Unit Area (in sq meter)" + }, + { + "fieldname": "plate_number", + "fieldtype": "Data", + "label": "Plate Number" + }, + { + "fieldname": "mezzanine_space", + "fieldtype": "Float", + "label": "Mezzanine Space" + }, + { + "fieldname": "minimal_rental_price", + "fieldtype": "Data", + "label": "Minimal Rental price" + }, + { + "fieldname": "minimal_price_per_sq_meter", + "fieldtype": "Data", + "label": "Minimal Price (per sq meter)" + }, + { + "fieldname": "key_number", + "fieldtype": "Data", + "label": "Key Number" + }, + { + "fieldname": "window_conditioner", + "fieldtype": "Int", + "label": "Window Conditioner" + }, + { + "fieldname": "kitchens", + "fieldtype": "Int", + "label": "Kitchens" + }, + { + "fieldname": "halls", + "fieldtype": "Int", + "label": "Halls" + }, + { + "default": "0", + "fieldname": "yard", + "fieldtype": "Check", + "label": "Yard" + }, + { + "default": "0", + "fieldname": "installed_kitchen", + "fieldtype": "Check", + "label": "Installed Kitchen" + }, + { + "default": "0", + "fieldname": "car_entrance", + "fieldtype": "Check", + "label": "Car Entrance" + }, + { + "fieldname": "commission_type_for_property_management", + "fieldtype": "Select", + "label": "Commission Type for Property Management", + "options": "With Every Batch\nAnnual\nMid Term\nQuarterly" + }, + { + "default": "0", + "fieldname": "activating_tax_on_property_management_commission", + "fieldtype": "Check", + "label": "Activating Tax on Property Management Commission" + }, + { + "fieldname": "ground_floor_and_mezzanine", + "fieldtype": "Float", + "label": "Total Area of \u200b\u200bGround Floor and Mezzanine" + }, + { + "fieldname": "unit_image_section", + "fieldtype": "Section Break", + "label": "Unit Image" + }, + { + "fieldname": "image", + "fieldtype": "Attach Image", + "in_preview": 1, + "label": "Image", + "options": "Image" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "default": "Vacant", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Leased\nOperated\nReserved\nSold ( Business)\nSold ( Services & Retail)\nVacant" + }, + { + "collapsible": 1, + "fieldname": "accounting_dimension_section", + "fieldtype": "Section Break", + "label": "Accounting Dimension" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "unit_area", + "fieldtype": "Float", + "label": "Unit Area (per square meter)", + "reqd": 1 + }, + { + "fieldname": "unit_price", + "fieldtype": "Float", + "label": "Unit Price", + "read_only": 1 + }, + { + "fieldname": "section_break_14", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break" + }, + { + "fieldname": "price_psm", + "fieldtype": "Float", + "label": "Price (per Square Meter)" + }, + { + "fieldname": "customer", + "fieldtype": "Link", + "label": "Customer", + "options": "Customer", + "read_only": 1 + }, + { + "fieldname": "supplier", + "fieldtype": "Link", + "label": "Supplier", + "options": "Supplier", + "read_only": 1 + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "fieldname": "section_break_8", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "customer_name", + "fieldtype": "Data", + "label": "Customer Name", + "read_only": 1 + }, + { + "fieldname": "supplier_name", + "fieldtype": "Data", + "label": "Supplier Name", + "read_only": 1 + }, + { + "fieldname": "property_owner", + "fieldtype": "Select", + "label": "Property Owner", + "options": "\nCompany\nSupplier\nCustomer" + }, + { + "depends_on": "eval:doc.property_owner == \"Company\"", + "fieldname": "property_owner_company", + "fieldtype": "Link", + "label": "Property Owner Company", + "options": "Company" + }, + { + "depends_on": "eval:doc.property_owner == \"Supplier\"", + "fieldname": "property_owner_supplier", + "fieldtype": "Link", + "label": "Property Owner Supplier", + "options": "Supplier" + }, + { + "depends_on": "eval:doc.property_owner == \"Customer\"", + "fieldname": "property_owner_customer", + "fieldtype": "Link", + "label": "Property Owner Customer", + "options": "Customer" + }, + { + "fieldname": "sub_activity", + "fieldtype": "Link", + "label": "Sub Activity", + "options": "Unit Activity" + }, + { + "fieldname": "outdoor_area", + "fieldtype": "Float", + "label": "Outdoor Area", + "reqd": 1 + }, + { + "fieldname": "gla", + "fieldtype": "Float", + "label": "GLA", + "read_only": 1 + }, + { + "fieldname": "opening_phase", + "fieldtype": "Link", + "label": "Opening Phase", + "options": "Opening Phase" + }, + { + "depends_on": "eval:doc.property_owner_customer", + "fetch_from": "property_owner_customer.company_name_en", + "fieldname": "company_name_en", + "fieldtype": "Data", + "label": "Company Name en", + "read_only": 1 + }, + { + "fieldname": "unit_valuation_rate", + "fieldtype": "Float", + "label": "Unit Valuation Rate" + } + ], + "image_field": "image", + "modified": "2021-10-23 12:25:24.368007", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Unit", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/unit/unit.py b/muezzin/property_management/doctype/unit/unit.py new file mode 100644 index 0000000..00db3e4 --- /dev/null +++ b/muezzin/property_management/doctype/unit/unit.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class Unit(Document): + pass + + + +@frappe.whitelist() +def make_sales_invoice(source_name, target_doc=None): + from frappe.model.mapper import get_mapped_doc + + doclist = get_mapped_doc("Unit", source_name, { + "Unit": { + "doctype": "Sales Invoice", + "validation": { + "docstatus": ["=", 0] + } + } + }, target_doc) + + return doclist \ No newline at end of file diff --git a/muezzin/property_management/doctype/unit/unit_dashboard.py b/muezzin/property_management/doctype/unit/unit_dashboard.py new file mode 100644 index 0000000..0ca9836 --- /dev/null +++ b/muezzin/property_management/doctype/unit/unit_dashboard.py @@ -0,0 +1,31 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'unit', + 'non_standard_fieldnames': { + 'Purchase Invoice': 'unit', + 'Sales Invoice': 'unit', + 'Payment Entry':'unit', + 'Journal Entry': 'unit', + }, + 'transactions': [ + { + 'label': _('Related'), + 'items': ['Purchase Invoice', 'Sales Invoice'] + }, + { + 'label': _('Reference'), + 'items': ['Payment Entry','Journal Entry'] + }, + { + 'label': _('Property'), + 'items': ['Property Contract'] + }, + { + 'label': _('Reservations'), + 'items': ['Reservations'] + } + ] + } diff --git a/muezzin/property_management/doctype/unit_activity/__init__.py b/muezzin/property_management/doctype/unit_activity/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/unit_activity/test_unit_activity.py b/muezzin/property_management/doctype/unit_activity/test_unit_activity.py new file mode 100644 index 0000000..3ea0138 --- /dev/null +++ b/muezzin/property_management/doctype/unit_activity/test_unit_activity.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestUnitActivity(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/unit_activity/unit_activity.js b/muezzin/property_management/doctype/unit_activity/unit_activity.js new file mode 100644 index 0000000..1911001 --- /dev/null +++ b/muezzin/property_management/doctype/unit_activity/unit_activity.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Unit Activity', { + // refresh: function(frm) { + + // } +}); diff --git a/muezzin/property_management/doctype/unit_activity/unit_activity.json b/muezzin/property_management/doctype/unit_activity/unit_activity.json new file mode 100644 index 0000000..291e087 --- /dev/null +++ b/muezzin/property_management/doctype/unit_activity/unit_activity.json @@ -0,0 +1,51 @@ +{ + "autoname": "field:activity", + "creation": "2021-04-12 01:22:50.527594", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "activity", + "parent_activity" + ], + "fields": [ + { + "fieldname": "activity", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Activity", + "reqd": 1, + "unique": 1 + }, + { + "allow_in_quick_entry": 1, + "fieldname": "parent_activity", + "fieldtype": "Link", + "label": "Parent Activity", + "options": "Unit Activity" + } + ], + "modified": "2021-04-12 01:25:08.339500", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Unit Activity", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/unit_activity/unit_activity.py b/muezzin/property_management/doctype/unit_activity/unit_activity.py new file mode 100644 index 0000000..97e42b8 --- /dev/null +++ b/muezzin/property_management/doctype/unit_activity/unit_activity.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class UnitActivity(Document): + pass diff --git a/muezzin/property_management/doctype/unit_sale_contract/__init__.py b/muezzin/property_management/doctype/unit_sale_contract/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/unit_sale_contract/test_unit_sale_contract.py b/muezzin/property_management/doctype/unit_sale_contract/test_unit_sale_contract.py new file mode 100644 index 0000000..ea81219 --- /dev/null +++ b/muezzin/property_management/doctype/unit_sale_contract/test_unit_sale_contract.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestUnitSaleContract(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/unit_sale_contract/unit_sale_contract.js b/muezzin/property_management/doctype/unit_sale_contract/unit_sale_contract.js new file mode 100644 index 0000000..9387a6f --- /dev/null +++ b/muezzin/property_management/doctype/unit_sale_contract/unit_sale_contract.js @@ -0,0 +1,35 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Unit Sale Contract', { + refresh: function (frm) { + frm.add_custom_button(__("Print Cheque"), () => { + frm.events.print_cheques(frm) + }, "Print Cheque"); + }, + print_cheques:function (frm){ + show_dialog(frm) + } +}); + + +let show_dialog = function (frm) { + var d = new frappe.ui.Dialog({ + title: __('Select Bank'), + fields: [ + { + "label" : "Bank", + "fieldname": "bank", + "fieldtype": "Link", + "reqd": 1, + "options": "Bank" + } + ], + primary_action: function() { + var data = d.get_values(); + d.hide(); + }, + primary_action_label: __('Print Bank Cheque') + }); + d.show(); +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/unit_sale_contract/unit_sale_contract.json b/muezzin/property_management/doctype/unit_sale_contract/unit_sale_contract.json new file mode 100644 index 0000000..fdd1964 --- /dev/null +++ b/muezzin/property_management/doctype/unit_sale_contract/unit_sale_contract.json @@ -0,0 +1,416 @@ +{ + "autoname": "field:customer", + "creation": "2021-10-23 16:36:43.562800", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "main_data_section", + "status", + "opening_date", + "customer", + "column_break_7", + "writing_date", + "grace_period_day", + "choose_units_section", + "property_type", + "unit_type", + "column_break_14", + "property_name", + "unit", + "rent_details_section", + "indoor_area", + "rent_per_sqm", + "section_break_28", + "outdoor_area", + "outdoor_rent_sqm", + "totals_section", + "total_rent", + "gla", + "payment_terms_section", + "repayment_start_date", + "repayment_method", + "repayment_period_in_months", + "monthly_repayment_amount", + "column_break_28", + "amount", + "advance_payment", + "rate_of_interest", + "payments_scheduling_section", + "durations", + "repayment_schedule", + "expenses_section", + "maintenance_services_payment", + "water_payment", + "clining", + "owner_management", + "electricity_payment", + "lease_payment", + "column_break_29", + "ms_type", + "w_type", + "c_type", + "om_type", + "e_type", + "insurance_payment", + "column_break_36", + "ms_service", + "w_service", + "c_service", + "om_service", + "e_service", + "marketing_fees", + "amended_from" + ], + "fields": [ + { + "fieldname": "main_data_section", + "fieldtype": "Section Break", + "label": "Main Data" + }, + { + "allow_on_submit": 1, + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "\nActive\nHold", + "read_only": 1 + }, + { + "default": "Today", + "fieldname": "opening_date", + "fieldtype": "Date", + "label": "Opening date" + }, + { + "fieldname": "customer", + "fieldtype": "Link", + "label": "Customer", + "options": "Customer", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "fieldname": "writing_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Writing Date", + "reqd": 1 + }, + { + "fieldname": "grace_period_day", + "fieldtype": "Data", + "label": "Grace period (day)" + }, + { + "fieldname": "choose_units_section", + "fieldtype": "Section Break", + "label": "Choose Units" + }, + { + "fieldname": "property_type", + "fieldtype": "Link", + "label": "Property Type", + "options": "Property Type" + }, + { + "fieldname": "unit_type", + "fieldtype": "Link", + "label": "Unit Type", + "options": "Unit Type" + }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break" + }, + { + "fieldname": "property_name", + "fieldtype": "Link", + "label": "Property Name", + "options": "Property", + "reqd": 1 + }, + { + "fieldname": "unit", + "fieldtype": "Link", + "label": "Unit Name", + "options": "Unit", + "reqd": 1 + }, + { + "fieldname": "rent_details_section", + "fieldtype": "Section Break", + "label": "Unit Details" + }, + { + "fetch_from": "unit.unit_area", + "fieldname": "indoor_area", + "fieldtype": "Float", + "label": "Indoor Area", + "read_only": 1 + }, + { + "fieldname": "rent_per_sqm", + "fieldtype": "Currency", + "label": "Price (Per sqm)", + "options": "tenant_name_currency" + }, + { + "fieldname": "section_break_28", + "fieldtype": "Section Break" + }, + { + "fetch_from": "unit.outdoor_area", + "fieldname": "outdoor_area", + "fieldtype": "Float", + "label": "Outdoor Area", + "read_only": 1 + }, + { + "fieldname": "outdoor_rent_sqm", + "fieldtype": "Currency", + "label": "Outdoor Price (sqm)", + "options": "tenant_name_currency" + }, + { + "fieldname": "totals_section", + "fieldtype": "Section Break", + "label": "Totals" + }, + { + "fieldname": "total_rent", + "fieldtype": "Currency", + "label": "Total Price", + "options": "tenant_name_currency", + "read_only": 1 + }, + { + "fieldname": "gla", + "fieldtype": "Float", + "label": "GLA", + "read_only": 1 + }, + { + "fieldname": "payments_scheduling_section", + "fieldtype": "Section Break", + "label": "Payments Scheduling" + }, + { + "fieldname": "expenses_section", + "fieldtype": "Section Break", + "label": "Expenses" + }, + { + "fieldname": "maintenance_services_payment", + "fieldtype": "Currency", + "label": "Maintenance Services Payment", + "options": "tenant_name_currency" + }, + { + "fieldname": "water_payment", + "fieldtype": "Currency", + "label": "Water Payment", + "options": "tenant_name_currency" + }, + { + "fieldname": "clining", + "fieldtype": "Currency", + "label": "Clining", + "options": "tenant_name_currency" + }, + { + "fieldname": "owner_management", + "fieldtype": "Currency", + "label": "Owner Management", + "options": "tenant_name_currency" + }, + { + "fieldname": "electricity_payment", + "fieldtype": "Currency", + "label": "Electricity Payment", + "options": "tenant_name_currency" + }, + { + "fieldname": "lease_payment", + "fieldtype": "Currency", + "label": "Lease Payment", + "options": "tenant_name_currency" + }, + { + "fieldname": "column_break_29", + "fieldtype": "Column Break" + }, + { + "fieldname": "ms_type", + "fieldtype": "Select", + "label": "Type", + "options": "Annually\nWith Payments\nMonthly\nJust Once" + }, + { + "fieldname": "w_type", + "fieldtype": "Select", + "label": "Type", + "options": "Annually\nWith Payments\nMonthly\nJust Once" + }, + { + "fieldname": "c_type", + "fieldtype": "Select", + "label": "Type", + "options": "Annually\nWith Payments\nMonthly\nJust Once" + }, + { + "fieldname": "om_type", + "fieldtype": "Select", + "label": "Type", + "options": "Annually\nWith Payments\nMonthly\nJust Once" + }, + { + "fieldname": "e_type", + "fieldtype": "Select", + "label": "Type", + "options": "Annually\nWith Payments\nMonthly\nJust Once" + }, + { + "fieldname": "insurance_payment", + "fieldtype": "Currency", + "label": "Insurance Payment", + "options": "Currency" + }, + { + "fieldname": "column_break_36", + "fieldtype": "Column Break" + }, + { + "fieldname": "ms_service", + "fieldtype": "Select", + "label": "Service Provider", + "options": "\nSarabi\nMaqawal\nMisry" + }, + { + "fieldname": "w_service", + "fieldtype": "Select", + "label": "Service Provider", + "options": "\nSarabi\nMaqawal\nMisry" + }, + { + "fieldname": "c_service", + "fieldtype": "Select", + "label": "Service Provider", + "options": "\nSarabi\nMaqawal\nMisry" + }, + { + "fieldname": "om_service", + "fieldtype": "Select", + "label": "Service Provider", + "options": "\nSarabi\nMaqawal\nMisry" + }, + { + "fieldname": "e_service", + "fieldtype": "Select", + "label": "Service Provider", + "options": "\nSarabi\nMaqawal\nMisry" + }, + { + "fieldname": "marketing_fees", + "fieldtype": "Currency", + "label": "Marketing Fees", + "options": "tenant_name_currency" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Unit Sale Contract", + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "repayment_schedule", + "fieldtype": "Table", + "label": "Repayment Schedule", + "options": "Reservation Repayment Schedule" + }, + { + "fieldname": "payment_terms_section", + "fieldtype": "Section Break", + "label": "Payment Terms" + }, + { + "fieldname": "repayment_start_date", + "fieldtype": "Date", + "label": "Repayment Start Date" + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount" + }, + { + "fieldname": "advance_payment", + "fieldtype": "Currency", + "label": "Advance Payment" + }, + { + "fieldname": "rate_of_interest", + "fieldtype": "Percent", + "label": "Rate of Interest" + }, + { + "fieldname": "repayment_method", + "fieldtype": "Select", + "label": "Repayment Method", + "options": "\nRepay Fixed Amount per Period\nRepay Over Number of Periods" + }, + { + "depends_on": "eval:doc.repayment_method =='Repay Over Number of Periods'", + "fieldname": "repayment_period_in_months", + "fieldtype": "Int", + "label": "Repayment Period in Months" + }, + { + "depends_on": "eval:doc.repayment_method =='Repay Fixed Amount per Period'", + "fieldname": "monthly_repayment_amount", + "fieldtype": "Currency", + "label": "Monthly Repayment Amount" + }, + { + "fieldname": "column_break_28", + "fieldtype": "Column Break" + }, + { + "fieldname": "durations", + "fieldtype": "Int", + "label": "Duration", + "read_only": 1 + } + ], + "is_submittable": 1, + "modified": "2021-10-24 02:51:27.711004", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Unit Sale Contract", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/unit_sale_contract/unit_sale_contract.py b/muezzin/property_management/doctype/unit_sale_contract/unit_sale_contract.py new file mode 100644 index 0000000..8700242 --- /dev/null +++ b/muezzin/property_management/doctype/unit_sale_contract/unit_sale_contract.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class UnitSaleContract(Document): + pass diff --git a/muezzin/property_management/doctype/unit_type/__init__.py b/muezzin/property_management/doctype/unit_type/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/doctype/unit_type/test_unit_type.py b/muezzin/property_management/doctype/unit_type/test_unit_type.py new file mode 100644 index 0000000..7af2d8f --- /dev/null +++ b/muezzin/property_management/doctype/unit_type/test_unit_type.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestUnitType(unittest.TestCase): + pass diff --git a/muezzin/property_management/doctype/unit_type/unit_type.js b/muezzin/property_management/doctype/unit_type/unit_type.js new file mode 100644 index 0000000..f040cd6 --- /dev/null +++ b/muezzin/property_management/doctype/unit_type/unit_type.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Unit Type', { + // refresh: function(frm) { + + // } +}); diff --git a/muezzin/property_management/doctype/unit_type/unit_type.json b/muezzin/property_management/doctype/unit_type/unit_type.json new file mode 100644 index 0000000..4846d9a --- /dev/null +++ b/muezzin/property_management/doctype/unit_type/unit_type.json @@ -0,0 +1,41 @@ +{ + "autoname": "field:type", + "creation": "2021-01-15 17:11:27.407254", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "type" + ], + "fields": [ + { + "fieldname": "type", + "fieldtype": "Data", + "label": "Type", + "unique": 1 + } + ], + "modified": "2021-01-15 18:11:45.310001", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Unit Type", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/muezzin/property_management/doctype/unit_type/unit_type.py b/muezzin/property_management/doctype/unit_type/unit_type.py new file mode 100644 index 0000000..b624c03 --- /dev/null +++ b/muezzin/property_management/doctype/unit_type/unit_type.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class UnitType(Document): + pass diff --git a/muezzin/property_management/page/__init__.py b/muezzin/property_management/page/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/page/property_dashboard/__init__.py b/muezzin/property_management/page/property_dashboard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/page/property_dashboard/dashboard-tiles.html b/muezzin/property_management/page/property_dashboard/dashboard-tiles.html new file mode 100644 index 0000000..703dc9f --- /dev/null +++ b/muezzin/property_management/page/property_dashboard/dashboard-tiles.html @@ -0,0 +1,43 @@ +
+
+ +
+
+

{{ tiles["total_property"] }}

+

Number of Property

+
+
+ +
+ +
+
+

{{ tiles["total_unit"] }}

+

Number of Units

+
+
+ +
+
+

{{ tiles["total_sold_unit"] }}

+

Total of Sold Units

+
+
+ +
+
+

{{ tiles["total_rented_unit"] }}

+

Total of Rented Units

+
+
+ +
+
+

{{ tiles["total_available_unit"] }}

+

Total of Available Units

+
+
+ + +
+
diff --git a/muezzin/property_management/page/property_dashboard/property_dashboard.js b/muezzin/property_management/page/property_dashboard/property_dashboard.js new file mode 100644 index 0000000..f48193d --- /dev/null +++ b/muezzin/property_management/page/property_dashboard/property_dashboard.js @@ -0,0 +1,54 @@ +frappe.pages["property-dashboard"].on_page_load = function (wrapper) { + frappe.property_dashboard = new PropertyDashboard(wrapper); +}; + +class PropertyDashboard { + constructor(wrapper) { + this.page = wrapper.page; + + frappe.run_serially([ + () => this.make_page(wrapper), + () => this.make_action_bar(wrapper), + () => this.make_charts(wrapper), + ]); + } + + make_page(wrapper) { + this.page = frappe.ui.make_app_page({ + parent: wrapper, + title: "Property Dashboard", + single_column: true, + }); + } + + make_action_bar(wrapper) { + this.page.set_secondary_action( + "Refresh", + () => this.make_charts(wrapper), + "octicon octicon-sync" + ); + } + + make_charts(wrapper) { + const layout = $(wrapper).find(".layout-main-section"); + + // server call to get data + frappe.call({ + method: "muezzin.api.property_dashboard.get_tiles_data", + freeze: true, + callback: (r) => { + const data = r.message; + const context = { tiles: data }; + + const tiles = frappe.render_template("dashboard-tiles", context); + layout.html(""); + layout.html(tiles); + }, + error: (r) => { + const error = "

System error

"; + layout.append(error); + console.error(r); + }, + }); + } +} diff --git a/muezzin/property_management/page/property_dashboard/property_dashboard.json b/muezzin/property_management/page/property_dashboard/property_dashboard.json new file mode 100644 index 0000000..0793587 --- /dev/null +++ b/muezzin/property_management/page/property_dashboard/property_dashboard.json @@ -0,0 +1,24 @@ +{ + "content": null, + "creation": "2021-02-02 11:43:31.275397", + "docstatus": 0, + "doctype": "Page", + "icon": "octicon octicon-graph", + "idx": 0, + "modified": "2021-02-02 11:43:46.677402", + "modified_by": "Administrator", + "module": "Property Management", + "name": "property-dashboard", + "owner": "Administrator", + "page_name": "property-dashboard", + "roles": [ + { + "role": "Administrator" + } + ], + "script": null, + "standard": "Yes", + "style": null, + "system_page": 0, + "title": "Property Dashboard" +} \ No newline at end of file diff --git a/muezzin/property_management/report/__init__.py b/muezzin/property_management/report/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/report/property_management_revenue/__init__.py b/muezzin/property_management/report/property_management_revenue/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/report/property_management_revenue/property_management_revenue.js b/muezzin/property_management/report/property_management_revenue/property_management_revenue.js new file mode 100644 index 0000000..fdcafa9 --- /dev/null +++ b/muezzin/property_management/report/property_management_revenue/property_management_revenue.js @@ -0,0 +1,34 @@ +// Copyright (c) 2016, Havenir Solutions and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Property Management Revenue"] = { + "filters": [ + { + "label": "Company", + "fieldname": "company", + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + }, + { + "label": "From Date", + "fieldname": "from_date", + "fieldtype": "Date", + "default": frappe.datetime.year_start() + }, + { + "label": "To Date", + "fieldname": "to_date", + "fieldtype": "Date", + "default": frappe.datetime.year_end() + }, + { + "label": "Status", + "fieldname": "docstatus", + "fieldtype": "Select", + "options":"All\nSubmitted\nDraft", + "default": "Submitted" + } + ] +}; diff --git a/muezzin/property_management/report/property_management_revenue/property_management_revenue.json b/muezzin/property_management/report/property_management_revenue/property_management_revenue.json new file mode 100644 index 0000000..54c66a2 --- /dev/null +++ b/muezzin/property_management/report/property_management_revenue/property_management_revenue.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 1, + "creation": "2021-10-12 02:05:56.977753", + "disable_prepared_report": 1, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "letter_head": "AlBadr Letter Head Main", + "modified": "2021-10-12 02:05:56.977753", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Property Management Revenue", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Property Contract", + "reference_report": "", + "report_name": "Property Management Revenue", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Accountant" + } + ] +} \ No newline at end of file diff --git a/muezzin/property_management/report/property_management_revenue/property_management_revenue.py b/muezzin/property_management/report/property_management_revenue/property_management_revenue.py new file mode 100644 index 0000000..2d97529 --- /dev/null +++ b/muezzin/property_management/report/property_management_revenue/property_management_revenue.py @@ -0,0 +1,383 @@ +# Copyright (c) 2013, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from dateutil import relativedelta +import datetime +from dateutil.rrule import rrule, MONTHLY +from frappe.utils import date_diff, flt + +def execute(filters=None): + columns = [ + { + "fieldname": "location", + "label": _("Location"), + "fieldtype": "Data" + }, + { + "fieldname": "contract", + "label": _("Contract"), + "fieldtype": "Link", + "options": "Property Contract" + }, + { + "fieldname": "insurance_policy", + "label": _("Insurance policy"), + "fieldtype": "Data" + }, + { + "fieldname": "revenue_sharing", + "label": _("Revenue sharing"), + "fieldtype": "Data" + }, + { + "fieldname": "renter", + "label": _("Legal Tenant Name"), + "fieldtype": "Data" + }, + { + "fieldname": "brand_name", + "label": _("Tenant Brand Name"), + "fieldtype": "Data" + }, + # { + # "fieldname": "owner", + # "label": _("Owner"), + # "fieldtype": "Link", + # "options": "Customer" + # }, + { + "fieldname": "unit", + "label": _("Unit No."), + "fieldtype": "Link", + "options": "Unit" + }, + # { + # "fieldname": "unit_status", + # "label": _("Unit Status"), + # "fieldtype": "Data" + # }, + # { + # "fieldname": "unit_owner", + # "label": _("Tenant Name"), + # "fieldtype": "Link", + # "options": "Customer" + # }, + { + "fieldname": "property", + "label": _("Bldg. No"), + "fieldtype": "Link", + "options": "Property" + }, + { + "fieldname": "opening_date", + "label": _("Opening Date"), + "fieldtype": "Date" + }, + { + "fieldname": "role", + "label": _("Floor"), + "fieldtype": "Data" + }, + { + "fieldname": "unit_type", + "label": _("Unit Type"), + "fieldtype": "Data" + }, + # { + # "fieldname": "unit_activity", + # "label": _("Unit Activity"), + # "fieldtype": "Data" + # }, + # { + # "fieldname": "unit_subactivity", + # "label": _("Unit Subactivity"), + # "fieldtype": "Data" + # }, + { + "fieldname": "indoor_area", + "label": _("Indoor Area(m2)"), + "fieldtype": "Float" + }, + { + "fieldname": "outdoor_area", + "label": _("Outdoor Area"), + "fieldtype": "Float" + }, + { + "fieldname": "gla", + "label": _("GLA"), + "fieldtype": "Float" + }, + { + "fieldname": "rent_per_sqm", + "label": _("Rent Per Sqm"), + "fieldtype": "Float" + }, + { + "fieldname": "indoor_rent", + "label": _("Indoor Rent LE/month"), + "fieldtype": "Float" + }, + { + "fieldname": "outdoor_rent_per_sqm", + "label": _("Outdoor Rent SQM"), + "fieldtype": "Float" + }, + { + "fieldname": "outdoor_rent", + "label": _("Outdoor Rent LE / month"), + "fieldtype": "Float" + }, + { + "fieldname": "total_rent", + "label": _("TOTAL RENT/Month"), + "fieldtype": "Float" + }, + { + "fieldname": "service_charge_per_sqm", + "label": _("Service Charge SQM"), + "fieldtype": "Float" + }, + { + "fieldname": "service_charge", + "label": _("Service Charge Month"), + "fieldtype": "Float" + }, + { + "fieldname": "outdoor_service_charge_per_month", + "label": _("OutDoor Service Per Month"), + "fieldtype": "Float" + }, + { + "fieldname": "total_services_rent", + "label": _("Total Services Charges"), + "fieldtype": "Float" + }, + { + "fieldname": "total_rent_plus_service_charge", + "label": _("TOTAL RENT/Month + SC"), + "fieldtype": "Float" + }, + { + "fieldname": "annualized_rent", + "label": _("Annualized Rent"), + "fieldtype": "Float" + }, + { + "fieldname": "annual_raise_amount", + "label": _("Annual Raise"), + "fieldtype": "Float" + }, + { + "fieldname": "annual_raise_date", + "label": _("Date of Increase"), + "fieldtype": "Date" + }, + { + "fieldname": "opening_phase", + "label": _("Opening Phase"), + "fieldtype": "Float" + }, + { + "fieldname": "lease_sent", + "label": _("Lease Sent"), + "fieldtype": "Check" + }, + { + "fieldname": "lease_signed", + "label": _("Lease Signed"), + "fieldtype": "Check" + }, + { + "fieldname": "rf_deposit", + "label": _("RF Deposit"), + "fieldtype": "Float", + "default": 0 + }, + { + "fieldname": "rent_sold", + "label": _("Rent / Sold"), + "fieldtype": "Data" + }, + { + "fieldname": "marketing_fees", + "label": _("Marketing Fees"), + "fieldtype": "Data" + }, + { + "fieldname": "insurance_payment", + "label": _("Insurance Payment"), + "fieldtype": "Data" + }, + { + "fieldname": "lease_start", + "label": _("Lease Start"), + "fieldtype": "Date" + }, + { + "fieldname": "lease_term", + "label": _("Lease Term(in yrs)"), + "fieldtype": "Float", + "precision": "2" + }, + { + "fieldname": "lease_end", + "label": _("Lease End"), + "fieldtype": "Date" + }, + { + "fieldname": "months_left", + "label": _("Month Left"), + "fieldtype": "Data" + }, + { + "fieldname": "years_left", + "label": _("Year Left"), + "fieldtype": "Data" + } + ] + + start_date = datetime.datetime.strptime(str(filters.from_date), "%Y-%m-%d") + end_date = datetime.datetime.strptime(str(filters.to_date), "%Y-%m-%d") + + for row in rrule(MONTHLY, dtstart=start_date, until=end_date): + month = datetime.datetime.strptime(str(row.month), "%m") + month_name = month.strftime("%b") + + month = '{:02d}'.format(row.month) + day = '{:02d}'.format(row.day) + + label = f'{day}-{month_name}-{row.year}' + fieldname = f'{day}-{month}-{row.year}' + columns.append( + { + "fieldname": fieldname, + "label": _(label), + "fieldtype": "Data" + } + ) + data = get_data(filters) + return columns, data + + +def get_data(filters): + data = [] + q_filter = {} + if filters.docstatus == "Submitted": + q_filter = {"docstatus": 1} + elif filters.docstatus == "Draft": + q_filter = {"docstatus": 0} + else: + q_filter = {"docstatus": ["!=", "2"]} + + result = frappe.get_list('Property Contract', q_filter, ['name']) + for row in result: + contract = frappe.get_doc('Property Contract', row) + if not contract.unit: + continue + unit = frappe.get_doc('Unit', contract.unit) + prop = frappe.get_doc('Property', contract.property_name) + + # Calculate Month Left + date1 = datetime.datetime.strptime(str(contract.rent_end_date), '%Y-%m-%d') + date2 = datetime.datetime.today() + date_diffrance = date_diff(date2, date1) + + r = relativedelta.relativedelta(date1, date2) + # frappe.msgprint(str(r)) + months_left = abs(r.months) + years_left = abs(r.years) + + if date_diffrance > 0: + months_left = 0 + years_left = 0 + + col_data = { + 'location': prop.property_location, + 'brand_name': contract.brand_name, + 'insurance_policy': contract.insurance_policy, + 'revenue_sharing': contract.revenue_sharing, + 'contract': contract.name, + 'renter': contract.renter, + # 'owner': prop.property_owner, + 'unit': unit.name, + # 'unit_status': unit.status, + # 'unit_owner': unit.property_owner, + 'property': unit.property, + 'role': unit.role_floor_number, + 'unit_type': unit.unit_type, + # 'unit_activity': unit.unit_activity, + # 'unit_subactivity': unit.sub_activity, + 'indoor_area': unit.unit_area, + 'outdoor_area': unit.outdoor_area, + 'gla': unit.gla, + 'rent_per_sqm': contract.rent_per_sqm, + 'indoor_rent': contract.indoor_rent_month, + 'outdoor_rent_per_sqm': contract.outdoor_rent_sqm, + 'outdoor_rent': contract.outdoor_rent_month, + 'total_rent': contract.total_rent_per_month, + 'service_charge_per_sqm': contract.service_charge_sqm, + 'service_charge': contract.service_charge_per_month, + 'outdoor_service_charge_per_month': contract.outdoor_service_charge_per_month, + 'total_services_rent': contract.total_services_rent, + 'total_rent_plus_service_charge': contract.total_rent, + 'annualized_rent': flt(contract.total_rent_per_month) * 12, # contract.annualized_rent, + 'annual_raise_amount': contract.annual_raise_amount, + 'annual_raise_date': contract.annual_raise_start_date, + 'opening_date': contract.opening_date, + 'opening_phase': unit.opening_phase, + 'lease_sent': contract.lease_sent, + 'lease_signed': contract.lease_signed, + 'rf_deposit': 0, + 'rent_sold': unit.status, + 'marketing_fees': contract.marketing_fees, + 'insurance_payment': contract.insurance_payment, + 'lease_start': contract.rent_start_date, + 'lease_term': contract.duration / 12, + 'lease_end': contract.rent_end_date, + 'months_left': months_left, + 'years_left': years_left + } + + # Fetch monthly payments from payment table in property contract + # Add value in respective date column + + contract_months = {} + + for row in contract.payments: + date = row.date.strftime("%d-%m-%Y") + if date in contract_months: + contract_months[date] += row.amount + else: + contract_months[date] = row.amount # Make dict of all payments of current contract + # print(str(date)+"============="+str(contract_months[date])) + + start_date = datetime.datetime.strptime(str(filters.from_date), "%Y-%m-%d") + end_date = datetime.datetime.strptime(str(filters.to_date), "%Y-%m-%d") + + date_diffrance = date_diff(start_date, end_date) + if date_diffrance < 0: + end_date += relativedelta.relativedelta(days=abs(date_diffrance)) + + for row in rrule(MONTHLY, dtstart=start_date, until=end_date): + month = '{:02d}'.format(row.month) + day = '{:02d}'.format(row.day) + + fieldname = f'{day}-{month}-{row.year}' + + if fieldname in contract_months and contract_months[fieldname]: + # frappe.msgprint(str(contract_months[fieldname])) + col_data[fieldname] = contract_months[fieldname] + + # frappe.msgprint(str(col_data)) + ## NOTE + # check if this label exists in dict of payments made above + # if exists then payment for this contract is there so append it in columns + # all that's left is correct syntax to append in columns where date is same as column label + # And calculation of yearly payment if required + + data.append(col_data) + return data diff --git a/muezzin/property_management/report/rent_roll/__init__.py b/muezzin/property_management/report/rent_roll/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/report/rent_roll/rent_roll.js b/muezzin/property_management/report/rent_roll/rent_roll.js new file mode 100644 index 0000000..328ac08 --- /dev/null +++ b/muezzin/property_management/report/rent_roll/rent_roll.js @@ -0,0 +1,33 @@ +// Copyright (c) 2016, Havenir Solutions and contributors +// For license information, please see license.txt +/* eslint-disable */ +frappe.query_reports["Rent Roll"] = { + "filters": [ + { + "label": "Company", + "fieldname": "company", + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + }, + { + "label": "From Date", + "fieldname": "from_date", + "fieldtype": "Date", + "default": frappe.datetime.year_start() + }, + { + "label": "To Date", + "fieldname": "to_date", + "fieldtype": "Date", + "default": frappe.datetime.year_end() + }, + { + "label": "Status", + "fieldname": "docstatus", + "fieldtype": "Select", + "options":"All\nSubmitted\nDraft", + "default": "Submitted" + } + ] +}; diff --git a/muezzin/property_management/report/rent_roll/rent_roll.json b/muezzin/property_management/report/rent_roll/rent_roll.json new file mode 100644 index 0000000..94723e5 --- /dev/null +++ b/muezzin/property_management/report/rent_roll/rent_roll.json @@ -0,0 +1,27 @@ +{ + "add_total_row": 1, + "creation": "2021-03-01 16:44:05.088382", + "disable_prepared_report": 1, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2021-07-02 01:27:56.420018", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Rent Roll", + "owner": "Administrator", + "prepared_report": 1, + "ref_doctype": "Property Contract", + "report_name": "Rent Roll", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts Manager" + }, + { + "role": "Accounts User" + } + ] +} \ No newline at end of file diff --git a/muezzin/property_management/report/rent_roll/rent_roll.py b/muezzin/property_management/report/rent_roll/rent_roll.py new file mode 100644 index 0000000..7649476 --- /dev/null +++ b/muezzin/property_management/report/rent_roll/rent_roll.py @@ -0,0 +1,377 @@ +# Copyright (c) 2013, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from dateutil import relativedelta +import datetime +import dateutil +import calendar +from calendar import weekday, monthrange +from collections import Counter +from dateutil.rrule import rrule, MONTHLY, YEARLY +from frappe.utils import date_diff, flt + + +def execute(filters=None): + columns = [ + { + "fieldname": "location", + "label": _("Location"), + "fieldtype": "Data" + }, + { + "fieldname": "brand_name", + "label": _("Tenant Brand Name"), + "fieldtype": "Data" + }, + { + "fieldname": "insurance_policy", + "label": _("Insurance policy"), + "fieldtype": "Data" + }, + { + "fieldname": "revenue_sharing", + "label": _("Revenue sharing"), + "fieldtype": "Data" + }, + { + "fieldname": "contract", + "label": _("Contract"), + "fieldtype": "Link", + "options": "Property Contract" + }, + # { + # "fieldname": "owner", + # "label": _("Owner"), + # "fieldtype": "Link", + # "options": "Customer" + # }, + { + "fieldname": "unit", + "label": _("Unit No."), + "fieldtype": "Link", + "options": "Unit" + }, + # { + # "fieldname": "unit_status", + # "label": _("Unit Status"), + # "fieldtype": "Data" + # }, + # { + # "fieldname": "unit_owner", + # "label": _("Tenant Name"), + # "fieldtype": "Link", + # "options": "Customer" + # }, + { + "fieldname": "property", + "label": _("Bldg. No"), + "fieldtype": "Link", + "options": "Property" + }, + { + "fieldname": "role", + "label": _("Floor"), + "fieldtype": "Data" + }, + { + "fieldname": "unit_type", + "label": _("Unit Type"), + "fieldtype": "Data" + }, + # { + # "fieldname": "unit_activity", + # "label": _("Unit Activity"), + # "fieldtype": "Data" + # }, + # { + # "fieldname": "unit_subactivity", + # "label": _("Unit Subactivity"), + # "fieldtype": "Data" + # }, + { + "fieldname": "indoor_area", + "label": _("Indoor Area(m2)"), + "fieldtype": "Float" + }, + { + "fieldname": "outdoor_area", + "label": _("Outdoor Area"), + "fieldtype": "Float" + }, + { + "fieldname": "gla", + "label": _("GLA"), + "fieldtype": "Float" + }, + { + "fieldname": "rent_per_sqm", + "label": _("Rent Per Sqm"), + "fieldtype": "Float" + }, + { + "fieldname": "indoor_rent", + "label": _("Indoor Rent LE/month"), + "fieldtype": "Float" + }, + { + "fieldname": "outdoor_rent_per_sqm", + "label": _("Outdoor Rent SQM"), + "fieldtype": "Float" + }, + { + "fieldname": "outdoor_rent", + "label": _("Outdoor Rent LE / month"), + "fieldtype": "Float" + }, + { + "fieldname": "total_rent", + "label": _("TOTAL RENT/Month"), + "fieldtype": "Float" + }, + # { + # "fieldname": "service_charge_per_sqm", + # "label": _("Service Charge SQM"), + # "fieldtype": "Float" + # }, + # { + # "fieldname": "service_charge", + # "label": _("Service Charge Month"), + # "fieldtype": "Float" + # }, + # { + # "fieldname": "total_rent_plus_service_charge", + # "label": _("TOTAL RENT/Month + SC"), + # "fieldtype": "Float" + # }, + { + "fieldname": "annualized_rent", + "label": _("Annualized Rent"), + "fieldtype": "Float" + }, + { + "fieldname": "annual_raise_amount", + "label": _("Annual Raise"), + "fieldtype": "Float" + }, + { + "fieldname": "annual_raise_date", + "label": _("Date of Increase"), + "fieldtype": "Date" + }, + { + "fieldname": "opening_date", + "label": _("Opening Date"), + "fieldtype": "Date" + }, + # { + # "fieldname": "opening_phase", + # "label": _("Opening Phase"), + # "fieldtype": "Float" + # }, + # { + # "fieldname": "lease_sent", + # "label": _("Lease Sent"), + # "fieldtype": "Check" + # }, + # { + # "fieldname": "lease_signed", + # "label": _("Lease Signed"), + # "fieldtype": "Check" + # }, + # { + # "fieldname": "rf_deposit", + # "label": _("RF Deposit"), + # "fieldtype": "Float", + # "default": 0 + # }, + # { + # "fieldname": "rent_sold", + # "label": _("Rent / Sold"), + # "fieldtype": "Data" + # }, + { + "fieldname": "marketing_fees", + "label": _("Marketing Fees"), + "fieldtype": "Data" + }, + { + "fieldname": "insurance_payment", + "label": _("Insurance Payment"), + "fieldtype": "Data" + }, + { + "fieldname": "lease_start", + "label": _("Lease Start"), + "fieldtype": "Date" + }, + { + "fieldname": "lease_term", + "label": _("Lease Term(in yrs)"), + "fieldtype": "Float", + "precision": "2" + }, + { + "fieldname": "lease_end", + "label": _("Lease End"), + "fieldtype": "Date" + }, + { + "fieldname": "months_left", + "label": _("Month Left"), + "fieldtype": "Data" + }, + { + "fieldname": "years_left", + "label": _("Year Left"), + "fieldtype": "Data" + } + ] + + start_date = datetime.datetime.strptime(str(filters.from_date), "%Y-%m-%d") + end_date = datetime.datetime.strptime(str(filters.to_date), "%Y-%m-%d") + + # date_diffrance = date_diff(start_date, end_date) + # if date_diffrance < 0: + # end_date += relativedelta.relativedelta(days= abs(date_diffrance)) + + for row in rrule(MONTHLY, dtstart=start_date, until=end_date): + month = datetime.datetime.strptime(str(row.month), "%m") + month_name = month.strftime("%b") + + month = '{:02d}'.format(row.month) + day = '{:02d}'.format(row.day) + + label = f'{day}-{month_name}-{row.year}' + fieldname = f'{day}-{month}-{row.year}' + columns.append( + { + "fieldname": fieldname, + "label": _(label), + "fieldtype": "Data" + } + ) + data = get_data(filters) + return columns, data + +def get_data(filters): + data = [] + q_filter = {} + if filters.docstatus=="Submitted": + q_filter = { "docstatus" : 1} + elif filters.docstatus=="Draft": + q_filter = { "docstatus" : 0} + else: + q_filter = { "docstatus" : ["!=","2"]} + + result = frappe.get_list('Property Contract', q_filter, ['name']) + for row in result: + contract = frappe.get_doc('Property Contract', row) + if not contract.unit: + continue + unit = frappe.get_doc('Unit', contract.unit) + prop = frappe.get_doc('Property', contract.property_name) + + # Calculate Month Left + date1 = datetime.datetime.strptime(str(contract.rent_end_date), '%Y-%m-%d') + date2 = datetime.datetime.today() + date_diffrance = date_diff(date2, date1) + + r = relativedelta.relativedelta(date1, date2) + # frappe.msgprint(str(r)) + months_left = abs(r.months) + years_left = abs(r.years) + + if date_diffrance > 0: + months_left = 0 + years_left = 0 + + col_data = { + 'location': prop.property_location, + 'brand_name': contract.brand_name, + 'insurance_policy': contract.insurance_policy, + 'revenue_sharing': contract.revenue_sharing, + 'contract': contract.name, + # 'owner': prop.property_owner, + 'unit': unit.name, + # 'unit_status': unit.status, + # 'unit_owner': unit.property_owner, + 'property': unit.property, + 'role': unit.role_floor_number, + 'unit_type': unit.unit_type, + # 'unit_activity': unit.unit_activity, + # 'unit_subactivity': unit.sub_activity, + 'indoor_area': unit.unit_area, + 'outdoor_area': unit.outdoor_area, + 'gla': unit.gla, + 'rent_per_sqm': contract.rent_per_sqm, + 'indoor_rent': contract.indoor_rent_month, + 'outdoor_rent_per_sqm': contract.outdoor_rent_sqm, + 'outdoor_rent': contract.outdoor_rent_month, + 'total_rent': contract.total_rent_per_month, + # 'service_charge_per_sqm': contract.service_charge_sqm, + # 'service_charge': contract.service_charge_per_month, + # 'total_rent_plus_service_charge': contract.total_rent, + 'annualized_rent': flt(contract.total_rent_per_month) * 12 , #contract.annualized_rent, + 'annual_raise_amount':contract.annual_raise_amount, + 'annual_raise_date':contract.annual_raise_start_date, + 'opening_date':contract.opening_date, + # 'opening_phase': unit.opening_phase, + # 'lease_sent': contract.lease_sent, + # 'lease_signed': contract.lease_signed, + # 'rf_deposit': 0, + # 'rent_sold': unit.status, + 'marketing_fees': contract.marketing_fees, + 'insurance_payment': contract.insurance_payment, + 'lease_start': contract.rent_start_date, + 'lease_term': contract.duration/12, + 'lease_end': contract.rent_end_date, + 'months_left': months_left, + 'years_left': years_left + } + + # Fetch monthly payments from payment table in property contract + # Add value in respective date column + + + contract_months = {} + + for row in contract.payments: + if row.reason =='Rent': + date = row.date.strftime("%d-%m-%Y") + if date in contract_months: + contract_months[date] += row.amount + else: + contract_months[date] = row.amount # Make dict of all payments of current contract + + + start_date = datetime.datetime.strptime(str(filters.from_date), "%Y-%m-%d") + end_date = datetime.datetime.strptime(str(filters.to_date), "%Y-%m-%d") + + date_diffrance = date_diff(start_date, end_date) + if date_diffrance < 0: + end_date += relativedelta.relativedelta(days= abs(date_diffrance)) + + for row in rrule(MONTHLY, dtstart=start_date, until=end_date): + month = '{:02d}'.format(row.month) + day = '{:02d}'.format(row.day) + + fieldname = f'{day}-{month}-{row.year}' + + + if fieldname in contract_months and contract_months[fieldname]: + + #frappe.msgprint(str(contract_months[fieldname])) + col_data[fieldname] = contract_months[fieldname] + + # frappe.msgprint(str(col_data)) + ## NOTE + # check if this label exists in dict of payments made above + # if exists then payment for this contract is there so append it in columns + # all that's left is correct syntax to append in columns where date is same as column label + # And calculation of yearly payment if required + + data.append(col_data) + return data diff --git a/muezzin/property_management/report/service_charge_roll/__init__.py b/muezzin/property_management/report/service_charge_roll/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/report/service_charge_roll/service_charge_roll.js b/muezzin/property_management/report/service_charge_roll/service_charge_roll.js new file mode 100644 index 0000000..bacb744 --- /dev/null +++ b/muezzin/property_management/report/service_charge_roll/service_charge_roll.js @@ -0,0 +1,34 @@ +// Copyright (c) 2016, Havenir Solutions and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Service Charge Roll"] = { + "filters": [ + { + "label": "Company", + "fieldname": "company", + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + }, + { + "label": "From Date", + "fieldname": "from_date", + "fieldtype": "Date", + "default": frappe.datetime.year_start() + }, + { + "label": "To Date", + "fieldname": "to_date", + "fieldtype": "Date", + "default": frappe.datetime.year_end() + }, + { + "label": "Status", + "fieldname": "docstatus", + "fieldtype": "Select", + "options":"All\nSubmitted\nDraft", + "default": "Submitted" + } + ] +}; diff --git a/muezzin/property_management/report/service_charge_roll/service_charge_roll.json b/muezzin/property_management/report/service_charge_roll/service_charge_roll.json new file mode 100644 index 0000000..d3fcc78 --- /dev/null +++ b/muezzin/property_management/report/service_charge_roll/service_charge_roll.json @@ -0,0 +1,31 @@ +{ + "add_total_row": 1, + "creation": "2021-07-02 02:51:39.848334", + "disable_prepared_report": 1, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "letter_head": "AlBadr Letter Head Main", + "modified": "2021-07-02 02:51:42.977697", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Service Charge Roll", + "owner": "Administrator", + "prepared_report": 1, + "ref_doctype": "Property Contract", + "report_name": "Service Charge Roll", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Accounts User" + } + ] +} \ No newline at end of file diff --git a/muezzin/property_management/report/service_charge_roll/service_charge_roll.py b/muezzin/property_management/report/service_charge_roll/service_charge_roll.py new file mode 100644 index 0000000..12f1903 --- /dev/null +++ b/muezzin/property_management/report/service_charge_roll/service_charge_roll.py @@ -0,0 +1,386 @@ +# Copyright (c) 2013, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from dateutil import relativedelta +import datetime +import dateutil +import calendar +from calendar import weekday, monthrange +from collections import Counter +from dateutil.rrule import rrule, MONTHLY, YEARLY +from frappe.utils import date_diff + + +def execute(filters=None): + columns = [ + { + "fieldname": "location", + "label": _("Location"), + "fieldtype": "Data" + }, + { + "fieldname": "brand_name", + "label": _("Tenant Brand Name"), + "fieldtype": "Data" + }, + { + "fieldname": "contract", + "label": _("Contract"), + "fieldtype": "Link", + "options": "Property Contract" + }, + # { + # "fieldname": "owner", + # "label": _("Owner"), + # "fieldtype": "Link", + # "options": "Customer" + # }, + { + "fieldname": "unit", + "label": _("Unit No."), + "fieldtype": "Link", + "options": "Unit" + }, + # { + # "fieldname": "unit_status", + # "label": _("Unit Status"), + # "fieldtype": "Data" + # }, + # { + # "fieldname": "unit_owner", + # "label": _("Tenant Name"), + # "fieldtype": "Link", + # "options": "Customer" + # }, + { + "fieldname": "property", + "label": _("Bldg. No"), + "fieldtype": "Link", + "options": "Property" + }, + { + "fieldname": "role", + "label": _("Floor"), + "fieldtype": "Data" + }, + { + "fieldname": "unit_type", + "label": _("Unit Type"), + "fieldtype": "Data" + }, + # { + # "fieldname": "unit_activity", + # "label": _("Unit Activity"), + # "fieldtype": "Data" + # }, + # { + # "fieldname": "unit_subactivity", + # "label": _("Unit Subactivity"), + # "fieldtype": "Data" + # }, + { + "fieldname": "indoor_area", + "label": _("Indoor Area(m2)"), + "fieldtype": "Float" + }, + { + "fieldname": "outdoor_area", + "label": _("Outdoor Area"), + "fieldtype": "Float" + }, + { + "fieldname": "outdoor_service_charge_sqm", + "label": _("Outdoor Service Charge SQM"), + "fieldtype": "Float" + }, + { + "fieldname": "total_services_rent", + "label": _("Total Services Charges"), + "fieldtype": "Float" + }, + { + "fieldname": "gla", + "label": _("GLA"), + "fieldtype": "Float" + }, + # { + # "fieldname": "rent_per_sqm", + # "label": _("Rent Per Sqm"), + # "fieldtype": "Float" + # }, + # { + # "fieldname": "indoor_rent", + # "label": _("Indoor Rent LE/month"), + # "fieldtype": "Float" + # }, + # { + # "fieldname": "outdoor_rent_per_sqm", + # "label": _("Outdoor Rent SQM"), + # "fieldtype": "Float" + # }, + # { + # "fieldname": "outdoor_rent", + # "label": _("Outdoor Rent LE / month"), + # "fieldtype": "Float" + # }, + # { + # "fieldname": "total_rent", + # "label": _("TOTAL RENT/Month"), + # "fieldtype": "Float" + # }, + { + "fieldname": "service_charge_per_sqm", + "label": _("Service Charge SQM"), + "fieldtype": "Float" + }, + { + "fieldname": "service_charge_per_month", + "label": _("Service Charge per Month"), + "fieldtype": "Float" + }, + { + "fieldname": "outdoor_service_charge_per_month", + "label": _("Outdoor Service Charge per Month"), + "fieldtype": "Float" + }, + { + "fieldname": "opening_date", + "label": _("Opening Date"), + "fieldtype": "Date" + }, + # { + # "fieldname": "service_charge", + # "label": _("Service Charge Month"), + # "fieldtype": "Float" + # }, + # { + # "fieldname": "total_rent_plus_service_charge", + # "label": _("TOTAL RENT/Month + SC"), + # "fieldtype": "Float" + # }, + # { + # "fieldname": "annualized_rent", + # "label": _("Annualized Rent"), + # "fieldtype": "Float" + # }, + { + "fieldname": "annual_raise_amount", + "label": _("Annual Raise"), + "fieldtype": "Float" + }, + { + "fieldname": "annual_raise_date", + "label": _("Date of Increase"), + "fieldtype": "Date" + }, + # { + # "fieldname": "opening_phase", + # "label": _("Opening Phase"), + # "fieldtype": "Float" + # }, + # { + # "fieldname": "lease_sent", + # "label": _("Lease Sent"), + # "fieldtype": "Check" + # }, + # { + # "fieldname": "lease_signed", + # "label": _("Lease Signed"), + # "fieldtype": "Check" + # }, + # { + # "fieldname": "rf_deposit", + # "label": _("RF Deposit"), + # "fieldtype": "Float", + # "default": 0 + # }, + # { + # "fieldname": "rent_sold", + # "label": _("Rent / Sold"), + # "fieldtype": "Data" + # }, + { + "fieldname": "marketing_fees", + "label": _("Marketing Fees"), + "fieldtype": "Data" + }, + { + "fieldname": "insurance_payment", + "label": _("Insurance Payment"), + "fieldtype": "Data" + }, + { + "fieldname": "lease_start", + "label": _("Lease Start"), + "fieldtype": "Date" + }, + { + "fieldname": "lease_term", + "label": _("Lease Term(in yrs)"), + "fieldtype": "Float", + "precision": "2" + }, + { + "fieldname": "lease_end", + "label": _("Lease End"), + "fieldtype": "Date" + }, + { + "fieldname": "months_left", + "label": _("Month Left"), + "fieldtype": "Data" + }, + { + "fieldname": "years_left", + "label": _("Year Left"), + "fieldtype": "Data" + } + ] + + start_date = datetime.datetime.strptime(str(filters.from_date), "%Y-%m-%d") + end_date = datetime.datetime.strptime(str(filters.to_date), "%Y-%m-%d") + + # date_diffrance = date_diff(start_date, end_date) + # if date_diffrance < 0: + # end_date += relativedelta.relativedelta(days=abs(date_diffrance)) + + for row in rrule(MONTHLY, dtstart=start_date, until=end_date): + month = datetime.datetime.strptime(str(row.month), "%m") + month_name = month.strftime("%b") + + month = '{:02d}'.format(row.month) + day = '{:02d}'.format(row.day) + + label = f'{day}-{month_name}-{row.year}' + fieldname = f'{day}-{month}-{row.year}' + columns.append( + { + "fieldname": fieldname, + "label": _(label), + "fieldtype": "Data" + } + ) + data = get_data(filters) + return columns, data + + +def get_data(filters): + data = [] + + q_filter = {} + if filters.docstatus=="Submitted": + q_filter = { "docstatus" : 1} + elif filters.docstatus=="Draft": + q_filter = { "docstatus" : 0} + else: + q_filter = { "docstatus" : ["!=","2"]} + + result = frappe.get_list('Property Contract', q_filter, ['name']) + for row in result: + contract = frappe.get_doc('Property Contract', row) + if not contract.unit: + continue + unit = frappe.get_doc('Unit', contract.unit) + prop = frappe.get_doc('Property', contract.property_name) + + # Calculate Month Left + date1 = datetime.datetime.strptime(str(contract.rent_end_date), '%Y-%m-%d') + date2 = datetime.datetime.today() + date_diffrance = date_diff(date2, date1) + + r = relativedelta.relativedelta(date1, date2) + # frappe.msgprint(str(r)) + months_left = abs(r.months) + years_left = abs(r.years) + + if date_diffrance > 0: + months_left = 0 + years_left = 0 + + col_data = { + 'location': prop.property_location, + 'brand_name': contract.brand_name, + 'contract': contract.name, + # 'owner': prop.property_owner, + 'unit': unit.name, + # 'unit_status': unit.status, + # 'unit_owner': unit.property_owner, + 'property': unit.property, + 'role': unit.role_floor_number, + 'unit_type': unit.unit_type, + # 'unit_activity': unit.unit_activity, + # 'unit_subactivity': unit.sub_activity, + 'indoor_area': unit.unit_area, + 'outdoor_area': unit.outdoor_area, + 'outdoor_service_charge_sqm': contract.outdoor_service_charge_sqm, + 'total_services_rent': contract.total_services_rent, + 'gla': unit.gla, + # 'rent_per_sqm': contract.rent_per_sqm, + # 'indoor_rent': contract.indoor_rent_month, + # 'outdoor_rent_per_sqm': contract.outdoor_rent_sqm, + # 'outdoor_rent': contract.outdoor_rent_month, + # 'total_rent': contract.total_rent_per_month, + 'service_charge_per_sqm': contract.service_charge_sqm, + 'service_charge_per_month': contract.service_charge_per_month, + 'outdoor_service_charge_per_month': contract.outdoor_service_charge_per_month, + 'opening_date':contract.opening_date, + # 'service_charge': contract.service_charge_per_month, + # 'total_rent_plus_service_charge': contract.total_rent, + # 'annualized_rent': contract.annualized_rent, + 'annual_raise_amount': contract.annual_raise_amount, + 'annual_raise_date': contract.annual_raise_start_date, + # 'opening_phase': unit.opening_phase, + # 'lease_sent': contract.lease_sent, + # 'lease_signed': contract.lease_signed, + # 'rf_deposit': 0, + # 'rent_sold': unit.status, + 'marketing_fees': contract.marketing_fees, + 'insurance_payment': contract.insurance_payment, + 'lease_start': contract.rent_start_date, + 'lease_term': contract.duration / 12, + 'lease_end': contract.rent_end_date, + 'months_left': months_left, + 'years_left': years_left + } + + # Fetch monthly payments from payment table in property contract + # Add value in respective date column + + contract_months = {} + + for row in contract.payments: + if row.reason =='Services Charges': + date = row.date.strftime("%d-%m-%Y") + if date in contract_months: + contract_months[date] += row.amount + else: + contract_months[date] = row.amount # Make dict of all payments of current contract + + start_date = datetime.datetime.strptime(str(filters.from_date), "%Y-%m-%d") + end_date = datetime.datetime.strptime(str(filters.to_date), "%Y-%m-%d") + + date_diffrance = date_diff(start_date, end_date) + if date_diffrance < 0: + end_date += relativedelta.relativedelta(days=abs(date_diffrance)) + + for row in rrule(MONTHLY, dtstart=start_date, until=end_date): + month = '{:02d}'.format(row.month) + day = '{:02d}'.format(row.day) + + fieldname = f'{day}-{month}-{row.year}' + + if fieldname in contract_months and contract_months[fieldname]: + # frappe.msgprint(str(contract_months[fieldname])) + col_data[fieldname] = contract_months[fieldname] + # frappe.msgprint(str(col_data)) + ## NOTE + # check if this label exists in dict of payments made above + # if exists then payment for this contract is there so append it in columns + # all that's left is correct syntax to append in columns where date is same as column label + # And calculation of yearly payment if required + + data.append(col_data) + return data diff --git a/muezzin/property_management/report/sold_unit/__init__.py b/muezzin/property_management/report/sold_unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/property_management/report/sold_unit/sold_unit.js b/muezzin/property_management/report/sold_unit/sold_unit.js new file mode 100644 index 0000000..4007619 --- /dev/null +++ b/muezzin/property_management/report/sold_unit/sold_unit.js @@ -0,0 +1,9 @@ +// Copyright (c) 2016, Havenir Solutions and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Sold Unit"] = { + "filters": [ + + ] +}; diff --git a/muezzin/property_management/report/sold_unit/sold_unit.json b/muezzin/property_management/report/sold_unit/sold_unit.json new file mode 100644 index 0000000..2734a30 --- /dev/null +++ b/muezzin/property_management/report/sold_unit/sold_unit.json @@ -0,0 +1,24 @@ +{ + "add_total_row": 0, + "creation": "2021-04-12 15:43:07.126727", + "disable_prepared_report": 1, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2021-04-12 16:13:11.940231", + "modified_by": "Administrator", + "module": "Property Management", + "name": "Sold Unit", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Unit", + "report_name": "Sold Unit", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + } + ] +} \ No newline at end of file diff --git a/muezzin/property_management/report/sold_unit/sold_unit.py b/muezzin/property_management/report/sold_unit/sold_unit.py new file mode 100644 index 0000000..be9372b --- /dev/null +++ b/muezzin/property_management/report/sold_unit/sold_unit.py @@ -0,0 +1,180 @@ +# Copyright (c) 2013, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe + +def execute(filters=None): + columns = get_columns(filters) + data = build_data(filters) + return columns, data + +def get_columns(filters): + columns = [ + { + "label": "Sr", + "fieldname": "serial_number", + "fieldtype": "Int", + "width": 50 + }, + { + "label": "Name", + "fieldname": "unit", + "fieldtype": "Link", + "options": "Unit", + "width": 100 + }, + { + "label": "Docstatus", + "fieldname": "docstatus", + "fieldtype": "Int", + "width": 100 + }, + { + "label": "Unit Number", + "fieldname": "unit_number", + "fieldtype": "Data", + "width": 100 + }, + { + "label": "Role (Floor Number)", + "fieldname": "role_floor_number", + "fieldtype": "Data", + "width": 150 + }, + { + "label": "Contract Year", + "fieldname": "contract_year", + "fieldtype": "Data", + "width": 100 + }, + { + "label": "Unit Type", + "fieldname": "unit_type", + "fieldtype": "Link", + "options": "Unit Type", + "width": 100 + }, + { + "label": "Property Owner (Customer)", + "fieldname": "property_owner", + "fieldtype": "Data", + "width": 200 + }, + { + "label": "Unit Area (per Square Meter)", + "fieldname": "unit_area", + "fieldtype": "Int", + "width": 200 + }, + { + "label": "Price (per Square Meter)", + "fieldname": "price_psm", + "fieldtype": "Data", + "width": 200 + }, + { + "label": "Unit Price", + "fieldname": "unit_price", + "fieldtype": "Float", + "width": 100 + }, + { + "label": "Unit Total Cost", + "fieldname": "unit_valuation_rate", + "fieldtype": "Float", + "width": 100 + }, + { + "label": "Total Invoiced", + "fieldname": "total_invoiced", + "fieldtype": "Float", + "width": 100 + }, + { + "label": "Total Paid", + "fieldname": "total_paid", + "fieldtype": "Float", + "width": 100 + }, + { + "label": "Unit Profit", + "fieldname":"unit_profit", + "fieldtype": "Float", + "width": 100 + }, + { + "label": "Total Outstanding", + "fieldname": "total_outstanding", + "fieldtype": "Float", + "width": 150 + }, + ] + + return columns + +def build_data(filters): + data = [] + + sub_filters = {} + fields = [ + "name", + "docstatus", + "unit_number", + "unit_type", + "role_floor_number", + "property_owner_customer", + "unit_area", + "price_psm", + "unit_price", + "unit_valuation_rate", + ] + units = frappe.get_list("Unit", sub_filters, fields) + + i = 1 + for row in units: + sub_filters = { + "unit": row.name, + "docstatus": 1 + } + + fields = [ + "grand_total", + "outstanding_amount", + ] + + invoiced = 0 + outstanding = 0 + paid = 0 + + invoices = frappe.get_list("Sales Invoice", sub_filters, fields) + for invoice in invoices: + invoiced += invoice.grand_total + outstanding += invoice.outstanding_amount + + paid = invoiced - outstanding + + unit_profit = row.unit_valuation_rate-invoiced + + record = { + "serial_number": i, + "unit": row.name, + "docstatus": row.docstatus, + "unit_number": row.unit_number, + "role_floor_number": row.role_floor_number, + "unit_type": row.unit_type, + "property_owner": row.property_owner_customer, + "unit_area": row.unit_area, + "price_psm": row.price_psm, + "unit_price": row.unit_price, + 'unit_valuation_rate':row.unit_valuation_rate, + "total_invoiced": invoiced, + "total_paid": paid, + "unit_profit":unit_profit, + "total_outstanding": outstanding, + } + + i = i + 1 + + data.append(record) + return data \ No newline at end of file diff --git a/muezzin/public/build.json b/muezzin/public/build.json new file mode 100644 index 0000000..ad6c8a8 --- /dev/null +++ b/muezzin/public/build.json @@ -0,0 +1 @@ +{ "css/muezzin.css": ["public/scss/style.scss"] } diff --git a/muezzin/public/js/payment_entry.js b/muezzin/public/js/payment_entry.js new file mode 100644 index 0000000..e588e3e --- /dev/null +++ b/muezzin/public/js/payment_entry.js @@ -0,0 +1,17 @@ +frappe.ui.form.on("Payment Entry", { + onload(frm, dt, dn) { + if (frm.is_new()) { + const item = frm.doc.references[0]; + let doctype = "Sales Invoice"; + let doc_name = item.reference_name; + + frappe.db.get_value(doctype, doc_name, "unit").then((r) => { + frappe.model.set_value(dt, dn, "unit", r.message.unit); + }); + + frappe.db.get_value(doctype, doc_name, "property").then((r) => { + frappe.model.set_value(dt, dn, "property", r.message.property); + }); + } + }, +}); diff --git a/muezzin/public/scss/style.scss b/muezzin/public/scss/style.scss new file mode 100644 index 0000000..e0a80be --- /dev/null +++ b/muezzin/public/scss/style.scss @@ -0,0 +1,16 @@ +.tiles-wrapper { + padding: 20px; + + .tile { + height: 150px; + border: 1px solid #f5f7fa; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + border-radius: 6px; + margin-bottom: 20px; + background-color: #f5f7fa; + color: #8d99a6; + } +} diff --git a/muezzin/templates/__init__.py b/muezzin/templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muezzin/templates/pages/__init__.py b/muezzin/templates/pages/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5ac1c81 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +frappe \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..fe53dee --- /dev/null +++ b/setup.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from setuptools import setup, find_packages + +with open('requirements.txt') as f: + install_requires = f.read().strip().split('\n') + +# get version from __version__ variable in property_management/__init__.py +from muezzin import __version__ as version + +setup( + name='muezzin', + version=version, + description='Property Management', + author='Havenir Solutions', + author_email='info@havenir.com', + packages=find_packages(), + zip_safe=False, + include_package_data=True, + install_requires=install_requires +)