updated payment service
This commit is contained in:
parent
88afa6b329
commit
9edc6cb6a0
|
|
@ -17,6 +17,7 @@ if __name__ == "__main__":
|
||||||
with get_db() as db_session:
|
with get_db() as db_session:
|
||||||
if set_alembic:
|
if set_alembic:
|
||||||
generate_alembic(session=db_session)
|
generate_alembic(session=db_session)
|
||||||
|
exit()
|
||||||
try:
|
try:
|
||||||
create_one_address(db_session=db_session)
|
create_one_address(db_session=db_session)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,26 @@ def init_api_enums_build_types(db_session):
|
||||||
"type_code": "BDT-I",
|
"type_code": "BDT-I",
|
||||||
"type_name": "Information",
|
"type_name": "Information",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"enum_class": "BuildDuesTypes",
|
||||||
|
"type_code": "BDT-CL",
|
||||||
|
"type_name": "Close Last Period Receipt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum_class": "BuildDuesTypes",
|
||||||
|
"type_code": "BDT-OP",
|
||||||
|
"type_name": "Open New Period Receipt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum_class": "BuildDuesTypes",
|
||||||
|
"type_code": "BDT-CT",
|
||||||
|
"type_name": "Commission Type",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum_class": "BuildDuesTypes",
|
||||||
|
"type_code": "BDT-FPR",
|
||||||
|
"type_name": "Fixation Payment Receipt",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"enum_class": "AccountingReceiptTypes",
|
"enum_class": "AccountingReceiptTypes",
|
||||||
"type_code": "ART-A",
|
"type_code": "ART-A",
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from Schemas.account.account import (
|
||||||
AccountDetail,
|
AccountDetail,
|
||||||
AccountRecordExchanges,
|
AccountRecordExchanges,
|
||||||
AccountRecords,
|
AccountRecords,
|
||||||
|
AccountDelayInterest,
|
||||||
)
|
)
|
||||||
from Schemas.account.iban import (
|
from Schemas.account.iban import (
|
||||||
BuildIbans,
|
BuildIbans,
|
||||||
|
|
@ -124,6 +125,7 @@ __all__ = [
|
||||||
"AccountDetail",
|
"AccountDetail",
|
||||||
"AccountRecordExchanges",
|
"AccountRecordExchanges",
|
||||||
"AccountRecords",
|
"AccountRecords",
|
||||||
|
"AccountDelayInterest",
|
||||||
"BuildIbans",
|
"BuildIbans",
|
||||||
"BuildIbanDescription",
|
"BuildIbanDescription",
|
||||||
"RelationshipEmployee2PostCode",
|
"RelationshipEmployee2PostCode",
|
||||||
|
|
|
||||||
|
|
@ -315,6 +315,45 @@ class AccountRecordExchanges(CrudCollection):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AccountDelayInterest(CrudCollection):
|
||||||
|
|
||||||
|
__tablename__ = "account_delay_interest"
|
||||||
|
__exclude__fields__ = []
|
||||||
|
|
||||||
|
interest_turn: Mapped[str] = mapped_column(String(10), nullable=False, server_default="akdi")
|
||||||
|
interest_rate: Mapped[float] = mapped_column(Numeric(10, 2), nullable=False, server_default="0")
|
||||||
|
delay_day: Mapped[int] = mapped_column(Integer, nullable=False, comment="Delay in days", server_default="0")
|
||||||
|
daily_rate: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, server_default="0")
|
||||||
|
interest: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, server_default="0")
|
||||||
|
bsmv: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, server_default="0")
|
||||||
|
kkdf: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, server_default="0")
|
||||||
|
total: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, server_default="0")
|
||||||
|
|
||||||
|
account_record_bank_date: Mapped[TIMESTAMP] = mapped_column(TIMESTAMP(timezone=True), nullable=True)
|
||||||
|
book_payment_process_date: Mapped[TIMESTAMP] = mapped_column(TIMESTAMP(timezone=True), nullable=True)
|
||||||
|
debt: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False)
|
||||||
|
|
||||||
|
approving_at: Mapped[TIMESTAMP] = mapped_column(TIMESTAMP(timezone=True), nullable=True)
|
||||||
|
approved_record: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default="0")
|
||||||
|
approving_person_id: Mapped[int] = mapped_column(Integer, nullable=True)
|
||||||
|
approving_person_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Approving Person UU ID")
|
||||||
|
|
||||||
|
bank_resp_company_id: Mapped[int] = mapped_column(Integer, nullable=True)
|
||||||
|
bank_resp_company_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Bank Response Company UU ID")
|
||||||
|
|
||||||
|
account_records_id: Mapped[int] = mapped_column(ForeignKey("account_records.id"), nullable=False)
|
||||||
|
account_records_uu_id: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||||
|
build_decision_book_payment_id: Mapped[int] = mapped_column(ForeignKey("build_decision_book_payments.id"), nullable=False)
|
||||||
|
build_decision_book_payment_uu_id: Mapped[str] = mapped_column(String(100), nullable=True)
|
||||||
|
new_build_decision_book_payment_id: Mapped[int] = mapped_column(Integer, nullable=True)
|
||||||
|
new_build_decision_book_payment_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="New Build Decision Book Payment UU ID")
|
||||||
|
|
||||||
|
__table_args__ = (
|
||||||
|
Index("_account_delay_interest_ndx_00", account_records_id, build_decision_book_payment_id),
|
||||||
|
{"comment": "Account Delay Interest Information"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AccountRecords(CrudCollection):
|
class AccountRecords(CrudCollection):
|
||||||
"""
|
"""
|
||||||
build_decision_book_id = kaydın sorumlu olduğu karar defteri
|
build_decision_book_id = kaydın sorumlu olduğu karar defteri
|
||||||
|
|
@ -324,48 +363,20 @@ class AccountRecords(CrudCollection):
|
||||||
|
|
||||||
__tablename__ = "account_records"
|
__tablename__ = "account_records"
|
||||||
__exclude__fields__ = []
|
__exclude__fields__ = []
|
||||||
__enum_list__ = [
|
__enum_list__ = [("receive_debit", "DebitTypes", "D"),("budget_type", "BudgetType", "B")]
|
||||||
("receive_debit", "DebitTypes", "D"),
|
|
||||||
("budget_type", "BudgetType", "B"),
|
|
||||||
]
|
|
||||||
|
|
||||||
iban: Mapped[str] = mapped_column(
|
iban: Mapped[str] = mapped_column(String(64), nullable=False, comment="IBAN Number of Bank")
|
||||||
String(64), nullable=False, comment="IBAN Number of Bank"
|
bank_date: Mapped[TIMESTAMP] = mapped_column(TIMESTAMP(timezone=True), nullable=False, comment="Bank Transaction Date")
|
||||||
)
|
currency_value: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, comment="Currency Value")
|
||||||
bank_date: Mapped[TIMESTAMP] = mapped_column(
|
bank_balance: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, comment="Bank Balance")
|
||||||
TIMESTAMP(timezone=True), nullable=False, comment="Bank Transaction Date"
|
currency: Mapped[str] = mapped_column(String(5), nullable=False, comment="Unit of Currency")
|
||||||
)
|
additional_balance: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, comment="Additional Balance")
|
||||||
|
channel_branch: Mapped[str] = mapped_column(String(120), nullable=False, comment="Branch Bank")
|
||||||
currency_value: Mapped[float] = mapped_column(
|
process_name: Mapped[str] = mapped_column(String, nullable=False, comment="Bank Process Type Name")
|
||||||
Numeric(20, 6), nullable=False, comment="Currency Value"
|
process_type: Mapped[str] = mapped_column(String, nullable=False, comment="Bank Process Type")
|
||||||
)
|
process_comment: Mapped[str] = mapped_column(String, nullable=False, comment="Transaction Record Comment")
|
||||||
bank_balance: Mapped[float] = mapped_column(
|
process_garbage: Mapped[str] = mapped_column(String, nullable=True, comment="Transaction Record Garbage")
|
||||||
Numeric(20, 6), nullable=False, comment="Bank Balance"
|
bank_reference_code: Mapped[str] = mapped_column(String, nullable=False, comment="Bank Reference Code")
|
||||||
)
|
|
||||||
currency: Mapped[str] = mapped_column(
|
|
||||||
String(5), nullable=False, comment="Unit of Currency"
|
|
||||||
)
|
|
||||||
additional_balance: Mapped[float] = mapped_column(
|
|
||||||
Numeric(20, 6), nullable=False, comment="Additional Balance"
|
|
||||||
)
|
|
||||||
channel_branch: Mapped[str] = mapped_column(
|
|
||||||
String(120), nullable=False, comment="Branch Bank"
|
|
||||||
)
|
|
||||||
process_name: Mapped[str] = mapped_column(
|
|
||||||
String, nullable=False, comment="Bank Process Type Name"
|
|
||||||
)
|
|
||||||
process_type: Mapped[str] = mapped_column(
|
|
||||||
String, nullable=False, comment="Bank Process Type"
|
|
||||||
)
|
|
||||||
process_comment: Mapped[str] = mapped_column(
|
|
||||||
String, nullable=False, comment="Transaction Record Comment"
|
|
||||||
)
|
|
||||||
process_garbage: Mapped[str] = mapped_column(
|
|
||||||
String, nullable=True, comment="Transaction Record Garbage"
|
|
||||||
)
|
|
||||||
bank_reference_code: Mapped[str] = mapped_column(
|
|
||||||
String, nullable=False, comment="Bank Reference Code"
|
|
||||||
)
|
|
||||||
|
|
||||||
add_comment_note: Mapped[str] = mapped_column(String, server_default="")
|
add_comment_note: Mapped[str] = mapped_column(String, server_default="")
|
||||||
is_receipt_mail_send: Mapped[bool] = mapped_column(Boolean, server_default="0")
|
is_receipt_mail_send: Mapped[bool] = mapped_column(Boolean, server_default="0")
|
||||||
|
|
@ -378,100 +389,45 @@ class AccountRecords(CrudCollection):
|
||||||
bank_date_w: Mapped[int] = mapped_column(SmallInteger)
|
bank_date_w: Mapped[int] = mapped_column(SmallInteger)
|
||||||
bank_date_d: Mapped[int] = mapped_column(SmallInteger)
|
bank_date_d: Mapped[int] = mapped_column(SmallInteger)
|
||||||
|
|
||||||
approving_accounting_record: Mapped[bool] = mapped_column(
|
approving_accounting_record: Mapped[bool] = mapped_column(Boolean, server_default="0")
|
||||||
Boolean, server_default="0"
|
accounting_receipt_date: Mapped[TIMESTAMP] = mapped_column(TIMESTAMP(timezone=True), server_default="1900-01-01 00:00:00")
|
||||||
)
|
|
||||||
accounting_receipt_date: Mapped[TIMESTAMP] = mapped_column(
|
|
||||||
TIMESTAMP(timezone=True), server_default="1900-01-01 00:00:00"
|
|
||||||
)
|
|
||||||
accounting_receipt_number: Mapped[int] = mapped_column(Integer, server_default="0")
|
accounting_receipt_number: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||||
status_id: Mapped[int] = mapped_column(SmallInteger, server_default="0")
|
status_id: Mapped[int] = mapped_column(SmallInteger, server_default="0")
|
||||||
|
|
||||||
approved_record: Mapped[bool] = mapped_column(Boolean, server_default="0")
|
approved_record: Mapped[bool] = mapped_column(Boolean, server_default="0")
|
||||||
import_file_name: Mapped[str] = mapped_column(
|
import_file_name: Mapped[str] = mapped_column(String, nullable=True, comment="XLS Key")
|
||||||
String, nullable=True, comment="XLS Key"
|
receive_debit: Mapped[int] = mapped_column(ForeignKey("api_enum_dropdown.id"), nullable=True)
|
||||||
)
|
receive_debit_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Debit UU ID")
|
||||||
|
budget_type: Mapped[int] = mapped_column(ForeignKey("api_enum_dropdown.id"), nullable=True)
|
||||||
receive_debit: Mapped[int] = mapped_column(
|
budget_type_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Budget Type UU ID")
|
||||||
ForeignKey("api_enum_dropdown.id"), nullable=True
|
|
||||||
)
|
|
||||||
receive_debit_uu_id: Mapped[str] = mapped_column(
|
|
||||||
String, nullable=True, comment="Debit UU ID"
|
|
||||||
)
|
|
||||||
budget_type: Mapped[int] = mapped_column(
|
|
||||||
ForeignKey("api_enum_dropdown.id"), nullable=True
|
|
||||||
)
|
|
||||||
budget_type_uu_id: Mapped[str] = mapped_column(
|
|
||||||
String, nullable=True, comment="Budget Type UU ID"
|
|
||||||
)
|
|
||||||
company_id: Mapped[int] = mapped_column(ForeignKey("companies.id"), nullable=True)
|
company_id: Mapped[int] = mapped_column(ForeignKey("companies.id"), nullable=True)
|
||||||
company_uu_id: Mapped[str] = mapped_column(
|
company_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Company UU ID")
|
||||||
String, nullable=True, comment="Company UU ID"
|
send_company_id: Mapped[int] = mapped_column(ForeignKey("companies.id"), nullable=True)
|
||||||
)
|
send_company_uu_id = mapped_column(String, nullable=True, comment="Send Company UU ID")
|
||||||
send_company_id: Mapped[int] = mapped_column(
|
|
||||||
ForeignKey("companies.id"), nullable=True
|
|
||||||
)
|
|
||||||
send_company_uu_id = mapped_column(
|
|
||||||
String, nullable=True, comment="Send Company UU ID"
|
|
||||||
)
|
|
||||||
|
|
||||||
send_person_id: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=True)
|
send_person_id: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=True)
|
||||||
send_person_uu_id: Mapped[str] = mapped_column(
|
send_person_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Send Person UU ID")
|
||||||
String, nullable=True, comment="Send Person UU ID"
|
approving_accounting_person: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=True)
|
||||||
)
|
approving_accounting_person_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Approving Accounting Person UU ID")
|
||||||
approving_accounting_person: Mapped[int] = mapped_column(
|
living_space_id: Mapped[int] = mapped_column(ForeignKey("build_living_space.id"), nullable=True)
|
||||||
ForeignKey("people.id"), nullable=True
|
living_space_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Living Space UU ID")
|
||||||
)
|
|
||||||
approving_accounting_person_uu_id: Mapped[str] = mapped_column(
|
|
||||||
String, nullable=True, comment="Approving Accounting Person UU ID"
|
|
||||||
)
|
|
||||||
|
|
||||||
living_space_id: Mapped[int] = mapped_column(
|
|
||||||
ForeignKey("build_living_space.id"), nullable=True
|
|
||||||
)
|
|
||||||
living_space_uu_id: Mapped[str] = mapped_column(
|
|
||||||
String, nullable=True, comment="Living Space UU ID"
|
|
||||||
)
|
|
||||||
customer_id: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=True)
|
customer_id: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=True)
|
||||||
customer_uu_id = mapped_column(String, nullable=True, comment="Customer UU ID")
|
customer_uu_id = mapped_column(String, nullable=True, comment="Customer UU ID")
|
||||||
|
|
||||||
build_id: Mapped[int] = mapped_column(ForeignKey("build.id"), nullable=True)
|
build_id: Mapped[int] = mapped_column(ForeignKey("build.id"), nullable=True)
|
||||||
build_uu_id: Mapped[str] = mapped_column(
|
build_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Build UU ID")
|
||||||
String, nullable=True, comment="Build UU ID"
|
build_parts_id: Mapped[int] = mapped_column(ForeignKey("build_parts.id"), nullable=True)
|
||||||
)
|
build_parts_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Build Parts UU ID")
|
||||||
build_parts_id: Mapped[int] = mapped_column(
|
build_decision_book_id: Mapped[int] = mapped_column(ForeignKey("build_decision_book.id"), nullable=True)
|
||||||
ForeignKey("build_parts.id"), nullable=True
|
build_decision_book_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Build Decision Book UU ID")
|
||||||
)
|
payment_result_type: Mapped[int] = mapped_column(ForeignKey("api_enum_dropdown.id"), nullable=True)
|
||||||
build_parts_uu_id: Mapped[str] = mapped_column(
|
payment_result_type_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Payment Result Type UU ID")
|
||||||
String, nullable=True, comment="Build Parts UU ID"
|
is_commission_applied: Mapped[bool] = mapped_column(Boolean, server_default="0")
|
||||||
)
|
|
||||||
build_decision_book_id: Mapped[int] = mapped_column(
|
|
||||||
ForeignKey("build_decision_book.id"), nullable=True
|
|
||||||
)
|
|
||||||
build_decision_book_uu_id: Mapped[str] = mapped_column(
|
|
||||||
String, nullable=True, comment="Build Decision Book UU ID"
|
|
||||||
)
|
|
||||||
# payment_result_type = Mapped[int] = mapped_column(
|
|
||||||
# ForeignKey("api_enum_dropdown.id"), nullable=True
|
|
||||||
# )
|
|
||||||
# payment_result_type_uu_id: Mapped[str] = mapped_column(
|
|
||||||
# String, nullable=True, comment="Payment Result Type UU ID"
|
|
||||||
# )
|
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
Index("_budget_records_ndx_00", is_receipt_mail_send, bank_date),
|
Index("_budget_records_ndx_00", is_receipt_mail_send, bank_date),
|
||||||
Index(
|
Index("_budget_records_ndx_01", iban, bank_date, bank_reference_code, bank_balance, unique=True),
|
||||||
"_budget_records_ndx_01",
|
|
||||||
iban,
|
|
||||||
bank_date,
|
|
||||||
bank_reference_code,
|
|
||||||
bank_balance,
|
|
||||||
unique=True,
|
|
||||||
),
|
|
||||||
Index("_budget_records_ndx_02", status_id, bank_date),
|
Index("_budget_records_ndx_02", status_id, bank_date),
|
||||||
{
|
{"comment": "Bank Records that are related to building and financial transactions"},
|
||||||
"comment": "Bank Records that are related to building and financial transactions"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# def payment_budget_record_close(self):
|
# def payment_budget_record_close(self):
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from Schemas.base_imports import (
|
||||||
mapped_column,
|
mapped_column,
|
||||||
Mapped,
|
Mapped,
|
||||||
)
|
)
|
||||||
|
from sqlalchemy import text
|
||||||
|
|
||||||
|
|
||||||
class BuildIbans(CrudCollection):
|
class BuildIbans(CrudCollection):
|
||||||
|
|
@ -44,6 +45,30 @@ class BuildIbans(CrudCollection):
|
||||||
{"comment": "IBANs related to money transactions due to building objects"},
|
{"comment": "IBANs related to money transactions due to building objects"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class CompanyDelayInterest(CrudCollection):
|
||||||
|
|
||||||
|
__tablename__ = "company_delay_interest"
|
||||||
|
|
||||||
|
company_id: Mapped[int] = mapped_column(Integer, ForeignKey("companies.id"), nullable=True)
|
||||||
|
company_uu_id: Mapped[str] = mapped_column(String, nullable=True)
|
||||||
|
|
||||||
|
build_id: Mapped[int] = mapped_column(Integer, ForeignKey("build.id"), nullable=True)
|
||||||
|
build_uu_id: Mapped[str] = mapped_column(String, nullable=True)
|
||||||
|
|
||||||
|
daily_interest_type: Mapped[str] = mapped_column(String(24), nullable=True)
|
||||||
|
daily_interest_rate: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, server_default=text("0"))
|
||||||
|
bsmv_rate: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, server_default=text("0"))
|
||||||
|
kkdf_rate: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, server_default=text("0"))
|
||||||
|
|
||||||
|
start_date: Mapped[TIMESTAMP] = mapped_column(TIMESTAMP(timezone=True), nullable=False)
|
||||||
|
stop_date: Mapped[TIMESTAMP] = mapped_column(TIMESTAMP(timezone=True), nullable=False, server_default=text("'2900-01-01 03:00:00+03'::timestamptz"))
|
||||||
|
|
||||||
|
__table_args__ = (
|
||||||
|
Index("_company_delay_interest_ndx_01", "company_id", "build_id", "start_date", unique=True),
|
||||||
|
Index("ix_company_delay_interest_build_uu_id", "company_id", "build_id"),
|
||||||
|
{"comment": "Company Delay Interest Information"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BuildIbanDescription(CrudCollection):
|
class BuildIbanDescription(CrudCollection):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -570,13 +570,14 @@ class BuildDecisionBookPayments(CrudCollection):
|
||||||
build_parts_id: Mapped[int] = mapped_column(ForeignKey("build_parts.id"), nullable=False)
|
build_parts_id: Mapped[int] = mapped_column(ForeignKey("build_parts.id"), nullable=False)
|
||||||
build_parts_uu_id: Mapped[str] = mapped_column(String, nullable=False, comment="Build Part UUID")
|
build_parts_uu_id: Mapped[str] = mapped_column(String, nullable=False, comment="Build Part UUID")
|
||||||
decision_book_project_id: Mapped[int] = mapped_column(
|
decision_book_project_id: Mapped[int] = mapped_column(
|
||||||
ForeignKey("build_decision_book_projects.id"),
|
ForeignKey("build_decision_book_projects.id"), nullable=True, comment="Decision Book Project ID",
|
||||||
nullable=True,
|
|
||||||
comment="Decision Book Project ID",
|
|
||||||
)
|
)
|
||||||
decision_book_project_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Decision Book Project UUID")
|
decision_book_project_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Decision Book Project UUID")
|
||||||
account_records_id: Mapped[int] = mapped_column(ForeignKey("account_records.id"), nullable=True)
|
account_records_id: Mapped[int] = mapped_column(ForeignKey("account_records.id"), nullable=True)
|
||||||
account_records_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Account Record UU ID")
|
account_records_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Account Record UU ID")
|
||||||
|
is_closed: Mapped[bool] = mapped_column(Boolean, server_default="0", comment="Is Decision Book Payment Closed")
|
||||||
|
debt_to_pay: Mapped[float] = mapped_column(Numeric(16, 2), server_default="0", comment="Debt To Pay")
|
||||||
|
debt_paid: Mapped[float] = mapped_column(Numeric(16, 2), server_default="0", comment="Debt Paid")
|
||||||
|
|
||||||
# budget_records_id: Mapped[int] = mapped_column(ForeignKey("account_records.id"), nullable=True)
|
# budget_records_id: Mapped[int] = mapped_column(ForeignKey("account_records.id"), nullable=True)
|
||||||
# budget_records_uu_id: Mapped[str] = mapped_column(
|
# budget_records_uu_id: Mapped[str] = mapped_column(
|
||||||
|
|
|
||||||
|
|
@ -7,38 +7,85 @@ end_time = perf_counter()
|
||||||
elapsed = end_time - start_time
|
elapsed = end_time - start_time
|
||||||
print(f'{elapsed:.3f} : seconds')
|
print(f'{elapsed:.3f} : seconds')
|
||||||
print('shallow_copy_list', len(shallow_copy_list))
|
print('shallow_copy_list', len(shallow_copy_list))
|
||||||
|
|
||||||
|
texts = [
|
||||||
|
"D 1 MÜBERRA BALTACI ŞUBAT 2016 O 4245N6000892 MÜBERRA BALTAC",
|
||||||
|
"BERAT VARNALI-GÜNEŞ APT NO-6 ŞUBAT 2016 ÖDEMESİ",
|
||||||
|
"BERAT VARNALI-GÜNEŞ APT NO-6 NİSAN 2016 ÖDEMESİ",
|
||||||
|
"GÖNÜL ARISOY- AİDAT*GÖNÜL ARISOY*H2406115923882",
|
||||||
|
"GÜLSER MAY - 8 NOLU DAIRENIN MAYIS 2016 ONARIM BEDELI",
|
||||||
|
"İREM YÜKSEKOL 12 NOLU DAİRE ARALIK AİDAT VE TAMİRAT PARASI",
|
||||||
|
"İREM YÜKSEKOL 12 NOLU DAİRE EKİM AİDAT VE TAMİRAT PARASI",
|
||||||
|
"GÜÇLÜ FATİH ERGÜN FATİH ERGÜN GÜÇLÜ D7 AİDAT",
|
||||||
|
"KASIM ARALIK 2015 OCAK 2016 TADILAT YUSUF EDEPLI DAIRE 10",
|
||||||
|
"muberra baltacı daıre 1 yakıt bedelı*MÜBERRA BALTACI*H2406175186422",
|
||||||
|
"YEŞİM ŞİMŞEK 5 NOLU DAİRE HAMDİYE AKAGÜNDÜZ",
|
||||||
|
"* HESAP İŞLETİM MASRAFI İADE (BTÇG) * 4245/ 76099902*MUH.HESA",
|
||||||
|
"4245 0500749 numarali hesap kapama",
|
||||||
|
"İREM YÜKSEKOL 12 NOLU DAİRE OCAK AİDAT VE TAMİRAT PARASI",
|
||||||
|
"GÜÇLÜ FATİH ERGÜN FATİH ERGÜN GÜÇLÜ D7 AİDAT",
|
||||||
|
"MUSTAFA EDEPLİ NO:11 MART AİDATI +20 TL ESKİ BORÇ *ALİ İHSAN EDEPLİ *4245X10Ç42",
|
||||||
|
"muberra baltaci daire 1 yakit bedeli*MUBERRA BALTACI*H2502245307227",
|
||||||
|
"OSMAN KILINÇ*0111*OSMAN KILINÇ - AİDAT - SUBAT*2238807",
|
||||||
|
"GÖNÜL ARISOY GÖNÜL ARISOY TARAFINDAN AKTA",
|
||||||
|
"AIDAT BEDELI*MEHMET KARATAY*H2504537864455",
|
||||||
|
"ELİFCAN DEMİRTAŞ*0062*CEP-EFTEMRİ-DAİRE 8 . AİDAT ÖDEMESİ*0130484",
|
||||||
|
"BALTACI MÜBERRA MÜBERRA BALTACI D:1YAKIT BED",
|
||||||
|
"SEZEN KONUR GÜNEŞ APARTMAN AİDATI 12 NUMARA",
|
||||||
|
"MEHMET KARATAY - HESABA AKTARILAN OCAK-SUBAT 2017 AIDAT",
|
||||||
|
"Osman Kilinc*0111*osman kilinc - 2024 - ekim*8614131*FAST",
|
||||||
|
"GÖNÜL ARISOY GÖNÜL ARISOY TARAFINDAN AKTA",
|
||||||
|
"GÖNÜL ARISOY GÖNÜL ARISOY TARAFINDAN AKTA",
|
||||||
|
"BALTACI MÜBERRA MÜBERRA BALTACI D:1YAKIT BED",
|
||||||
|
"SONGÜL VAR-2NOLU DAİRE KASIM 2015 ÖDEME",
|
||||||
|
"GÖNÜL ARISOY (9 NUMARA AİDAT)*GÖNÜL ARISOY*H2211022448099",
|
||||||
|
"BALTACI MÜBERRA MÜBERRA BALTACI D:1YAKIT BED",
|
||||||
|
"GÜÇLÜ FATİH ERGÜN FATİH ERGÜN GÜÇLÜ D7 AİDAT",
|
||||||
|
"2 nolu daire - TEMMUZ Ç4 TADİLAT*SONGÜL VAR*H2408373383590",
|
||||||
|
"GÜÇLÜ FATİH ERGÜN FATİH ERGÜN GÜÇLÜ D7 AİDAT",
|
||||||
|
"ELİFCAN DEMİRTAŞ*0062*ARALIK AYI AİDAT DAİRE 8*443Ç00*FAST",
|
||||||
|
"GÜÇLÜ FATİH ERGÜN FATİH ERGÜN GÜÇLÜ D7 AİDAT",
|
||||||
|
"ERİNÇ KARATAŞ 9 NOLU DAİRE AĞUSTOS AYI 15 GÜNLÜK AİDATI",
|
||||||
|
"HASAN CİHAN ŞENKÜÇÜK*0046*AİDAT*1690685*FAST",
|
||||||
|
"DAMLA GÖRMEZOĞLU*0099*6 nolu daire Kemal bey dava Ekim Kasım*2242091694*FAST",
|
||||||
|
"Müberra Baltacı Daire 1 Yakıt bedli*MÜBERRA BALTACI*H2107153811822",
|
||||||
|
"Osman Kılınç*0111*osman kılınç - aidat - Ç1- nisan*Ş12506*FAST",
|
||||||
|
"mübarra baltacı 1 nolu daire yakıt farkı şubat ayı dahil*MÜBERRA BALTACI*H210Ç5745006",
|
||||||
|
"ELİFCAN DEMİRTAŞ*0062*CEP-EFTEMRİ-DAİRE 8 EYLUL Ç0 AİDAT*0520317"
|
||||||
|
]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
|
|
||||||
1. Stage (Incoming Money)
|
1. Stage (Incoming Money)
|
||||||
# BuildDecisionBookPayments are reverse records of AccountRecords
|
|
||||||
|
# BuildDecisionBookPayments are reverse records of AccountRecords
|
||||||
|
|
||||||
AccountRecords.approved_record == True
|
AccountRecords.approved_record == True
|
||||||
AccountRecords.living_space_id is not None
|
AccountRecords.living_space_id is not None
|
||||||
# AccountRecords.receive_debit = Credit Receiver (Incoming money from client) / Debit Sender (Debt to be paid by system)
|
|
||||||
|
|
||||||
1.1
|
# AccountRecords.receive_debit = Credit Receiver (Incoming money from client) / Debit Sender (Debt to be paid by system)
|
||||||
AccountRecords.currency_value > 0 Received Money Transaction +
|
|
||||||
AccountRecords.currency_value > AccountRecords.remainder_balance () You have extra money in system account
|
1.1
|
||||||
Money consumed => AccountRecords.currency_value != abs(AccountRecords.remainder_balance) singluar iban
|
AccountRecords.currency_value > 0 Received Money Transaction +
|
||||||
Some payment done but money not yet all money is consumed => AccountRecords.currency_value + AccountRecords.remainder_balance != 0
|
AccountRecords.currency_value > AccountRecords.remainder_balance () You have extra money in system account
|
||||||
|
Money consumed => AccountRecords.currency_value != abs(AccountRecords.remainder_balance) singluar iban
|
||||||
|
Some payment done but money not yet all money is consumed => AccountRecords.currency_value + AccountRecords.remainder_balance != 0
|
||||||
|
|
||||||
|
|
||||||
AccountRecords.currency_value = AccountRecords.remainder_balance (There is no money that individual has in system)
|
AccountRecords.currency_value = AccountRecords.remainder_balance (There is no money that individual has in system)
|
||||||
AccountRecords.bank_date (Date money arrived)
|
AccountRecords.bank_date (Date money arrived)
|
||||||
AccountRecords.process_type (Type of bank transaction)
|
AccountRecords.process_type (Type of bank transaction)
|
||||||
|
|
||||||
1.2
|
1.2
|
||||||
AccountRecords.currency_value < 0 Sent Money Transaction -
|
AccountRecords.currency_value < 0 Sent Money Transaction -
|
||||||
|
|
||||||
|
|
||||||
2. Stage (Payment Match Process)
|
2. Stage (Payment Match Process)
|
||||||
Parse : BuildDecisionBookPayments.process_date (Year / Month / Day / Time)
|
Parse : BuildDecisionBookPayments.process_date (Year / Month / Day / Time)
|
||||||
BuildDecisionBookPayments.account_records_id == None ( Payment is not assigned to any account record)
|
BuildDecisionBookPayments.account_records_id == None ( Payment is not assigned to any account record)
|
||||||
BuildDecisionBookPayments.payment_types_id == debit_enum.id (Payment type is debit)
|
BuildDecisionBookPayments.payment_types_id == debit_enum.id (Payment type is debit)
|
||||||
|
|
||||||
2.1 Check current month has any payment to due Payment Month == Money Arrived Month
|
2.1 Check current month has any payment to due Payment Month == Money Arrived Month
|
||||||
2.2 Check previous months has any payment to due Payment Month < Money Arrived Month
|
2.2 Check previous months has any payment to due Payment Month < Money Arrived Month
|
||||||
|
|
||||||
3. Stage (Payment Assignment Process)
|
3. Stage (Payment Assignment Process)
|
||||||
Do payment set left money to account record as AccountRecords.remainder_balance
|
Do payment set left money to account record as AccountRecords.remainder_balance
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,385 @@
|
||||||
|
import arrow
|
||||||
|
import calendar
|
||||||
|
import time
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from Schemas import BuildDecisionBookPayments, AccountRecords, ApiEnumDropdown, Build, BuildDecisionBook
|
||||||
|
from time import perf_counter
|
||||||
|
from sqlalchemy import select, func, distinct, cast, Date, String, literal, desc, and_, or_, case
|
||||||
|
from Controllers.Postgres.engine import get_session_factory
|
||||||
|
#from ServicesApi.Schemas.account.account import AccountRecords
|
||||||
|
#from ServicesApi.Schemas.building.decision_book import BuildDecisionBookPayments
|
||||||
|
|
||||||
|
|
||||||
|
def find_last_day_of_month(date_value):
|
||||||
|
today = date_value.date()
|
||||||
|
_, last_day = calendar.monthrange(today.year, today.month)
|
||||||
|
return datetime(today.year, today.month, last_day, 23, 59, 59)
|
||||||
|
|
||||||
|
def find_first_day_of_month(date_value):
|
||||||
|
today = date_value.date()
|
||||||
|
return datetime(today.year, today.month, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def add_month_to_date(date_value):
|
||||||
|
month_to_process = date_value.month
|
||||||
|
year_to_process = date_value.year
|
||||||
|
if date_value.month == 12:
|
||||||
|
month_to_process = 1
|
||||||
|
year_to_process += 1
|
||||||
|
else:
|
||||||
|
month_to_process += 1
|
||||||
|
_, last_day = calendar.monthrange(year_to_process, month_to_process)
|
||||||
|
return datetime(year_to_process, month_to_process, 1), datetime(year_to_process, month_to_process, last_day)
|
||||||
|
|
||||||
|
|
||||||
|
class BuildDuesTypes:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.debit: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.add_debit: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.renovation: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.lawyer_expence: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.service_fee: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.information: ApiEnumDropdownShallowCopy = None
|
||||||
|
|
||||||
|
|
||||||
|
class ApiEnumDropdownShallowCopy:
|
||||||
|
id: int
|
||||||
|
uuid: str
|
||||||
|
enum_class: str
|
||||||
|
key: str
|
||||||
|
value: str
|
||||||
|
|
||||||
|
def __init__(self, id: int, uuid: str, enum_class: str, key: str, value: str):
|
||||||
|
self.id = id
|
||||||
|
self.uuid = uuid
|
||||||
|
self.enum_class = enum_class
|
||||||
|
self.key = key
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
|
||||||
|
def retrieve_remainder_balance_set_if_needed(account_record_id: int, session):
|
||||||
|
"""Update the remainder_balance of an account after spending money.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
account_record: The account record to update
|
||||||
|
amount_spent: The amount spent in this transaction
|
||||||
|
session: Database session
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if all money is spent, False otherwise
|
||||||
|
"""
|
||||||
|
AccountRecords.set_session(session)
|
||||||
|
|
||||||
|
account_record_remainder_balance = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter(
|
||||||
|
BuildDecisionBookPayments.account_records_id == account_record_id,
|
||||||
|
BuildDecisionBookPayments.account_is_debit == False
|
||||||
|
).scalar()
|
||||||
|
|
||||||
|
if not account_record_remainder_balance:
|
||||||
|
account_record_remainder_balance = 0
|
||||||
|
|
||||||
|
account_record = AccountRecords.query.filter_by(id=account_record_id).first()
|
||||||
|
account_record.remainder_balance = -1 * abs(account_record_remainder_balance) if account_record_remainder_balance != 0 else 0
|
||||||
|
account_record.save()
|
||||||
|
return account_record_remainder_balance
|
||||||
|
|
||||||
|
|
||||||
|
def retrieve_current_debt_to_pay_from_database(ref_id: int, session):
|
||||||
|
debt_to_pay = session.query(func.sum(BuildDecisionBookPayments.payment_amount)).filter(BuildDecisionBookPayments.ref_id == ref_id).scalar()
|
||||||
|
return abs(debt_to_pay)
|
||||||
|
|
||||||
|
|
||||||
|
def check_current_debt_to_pay_from_database_is_closed(ref_id: int, session):
|
||||||
|
session.commit()
|
||||||
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
payment_row_book = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.ref_id == str(ref_id), BuildDecisionBookPayments.account_is_debit == True).first()
|
||||||
|
debt_paid = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter(
|
||||||
|
BuildDecisionBookPayments.ref_id == str(ref_id), BuildDecisionBookPayments.account_is_debit == False
|
||||||
|
).scalar()
|
||||||
|
payment_row_book.debt_paid = abs(debt_paid) if debt_paid else 0 # Debt Reminder is how much money is needed to close record
|
||||||
|
payment_row_book.debt_to_pay = abs(payment_row_book.payment_amount) - abs(payment_row_book.debt_paid) # Debt To Pay is how much money is needed to close record
|
||||||
|
payment_row_book.is_closed = payment_row_book.debt_to_pay == 0
|
||||||
|
payment_row_book.save()
|
||||||
|
|
||||||
|
|
||||||
|
def close_payment_book(payment_row_book, account_record, value, session):
|
||||||
|
"""Create a credit entry in BuildDecisionBookPayments to close a debt.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
payment_row_book: The debit entry to be paid
|
||||||
|
account_record: The account record containing the funds
|
||||||
|
value: The amount to pay
|
||||||
|
session: Database session
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The newly created payment record
|
||||||
|
"""
|
||||||
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
account_record_remainder_balance = retrieve_remainder_balance_set_if_needed(account_record_id=account_record.id, session=session)
|
||||||
|
print(f'NOT Updated remainder balance: {account_record_remainder_balance} | Account record id: {account_record.id}')
|
||||||
|
|
||||||
|
# Create a new credit entry (payment)
|
||||||
|
new_row = BuildDecisionBookPayments.create(
|
||||||
|
ref_id=str(payment_row_book.uu_id),
|
||||||
|
payment_plan_time_periods=payment_row_book.payment_plan_time_periods,
|
||||||
|
period_time=payment_row_book.period_time,
|
||||||
|
currency=payment_row_book.currency,
|
||||||
|
account_records_id=account_record.id,
|
||||||
|
account_records_uu_id=str(account_record.uu_id),
|
||||||
|
build_parts_id=payment_row_book.build_parts_id,
|
||||||
|
build_parts_uu_id=str(payment_row_book.build_parts_uu_id),
|
||||||
|
payment_amount=abs(value), # Negative for credit entries
|
||||||
|
payment_types_id=payment_row_book.payment_types_id,
|
||||||
|
payment_types_uu_id=str(payment_row_book.payment_types_uu_id),
|
||||||
|
process_date_m=payment_row_book.process_date.month,
|
||||||
|
process_date_y=payment_row_book.process_date.year,
|
||||||
|
process_date=payment_row_book.process_date,
|
||||||
|
build_decision_book_item_id=payment_row_book.build_decision_book_item_id if payment_row_book.build_decision_book_item_id else None,
|
||||||
|
build_decision_book_item_uu_id=str(payment_row_book.build_decision_book_item_uu_id) if payment_row_book.build_decision_book_item_uu_id else None,
|
||||||
|
decision_book_project_id=payment_row_book.decision_book_project_id if payment_row_book.decision_book_project_id else None,
|
||||||
|
decision_book_project_uu_id=str(payment_row_book.decision_book_project_uu_id) if payment_row_book.decision_book_project_uu_id else None,
|
||||||
|
build_decision_book_id=payment_row_book.build_decision_book_id if payment_row_book.build_decision_book_id else None,
|
||||||
|
build_decision_book_uu_id=str(payment_row_book.build_decision_book_uu_id) if payment_row_book.build_decision_book_uu_id else None,
|
||||||
|
is_confirmed=True,
|
||||||
|
account_is_debit=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save the new payment record
|
||||||
|
saved_row = new_row.save()
|
||||||
|
session.commit()
|
||||||
|
session.refresh(saved_row)
|
||||||
|
account_record_remainder_balance = retrieve_remainder_balance_set_if_needed(account_record_id=account_record.id, session=session)
|
||||||
|
print(f'Updated remainder balance: {account_record_remainder_balance} | Account record id: {account_record.id}')
|
||||||
|
check_current_debt_to_pay_from_database_is_closed(ref_id=new_row.ref_id, session=session)
|
||||||
|
return account_record_remainder_balance
|
||||||
|
|
||||||
|
|
||||||
|
def get_enums_from_database():
|
||||||
|
build_dues_types = BuildDuesTypes()
|
||||||
|
with ApiEnumDropdown.new_session() as session:
|
||||||
|
|
||||||
|
ApiEnumDropdown.set_session(session)
|
||||||
|
|
||||||
|
debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-D").first() # Debit
|
||||||
|
add_debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-A").first() # Add Debit
|
||||||
|
renovation_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-R").first() # Renovation
|
||||||
|
late_payment_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-L").first() # Lawyer expence
|
||||||
|
service_fee_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-S").first() # Service fee
|
||||||
|
information_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-I").first() # Information
|
||||||
|
|
||||||
|
build_dues_types.debit = ApiEnumDropdownShallowCopy(
|
||||||
|
debit_enum_shallow.id, str(debit_enum_shallow.uu_id), debit_enum_shallow.enum_class, debit_enum_shallow.key, debit_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.add_debit = ApiEnumDropdownShallowCopy(
|
||||||
|
add_debit_enum_shallow.id, str(add_debit_enum_shallow.uu_id), add_debit_enum_shallow.enum_class, add_debit_enum_shallow.key, add_debit_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.renovation = ApiEnumDropdownShallowCopy(
|
||||||
|
renovation_enum_shallow.id, str(renovation_enum_shallow.uu_id), renovation_enum_shallow.enum_class, renovation_enum_shallow.key, renovation_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.lawyer_expence = ApiEnumDropdownShallowCopy(
|
||||||
|
late_payment_enum_shallow.id, str(late_payment_enum_shallow.uu_id), late_payment_enum_shallow.enum_class, late_payment_enum_shallow.key, late_payment_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.service_fee = ApiEnumDropdownShallowCopy(
|
||||||
|
service_fee_enum_shallow.id, str(service_fee_enum_shallow.uu_id), service_fee_enum_shallow.enum_class, service_fee_enum_shallow.key, service_fee_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.information = ApiEnumDropdownShallowCopy(
|
||||||
|
information_enum_shallow.id, str(information_enum_shallow.uu_id), information_enum_shallow.enum_class, information_enum_shallow.key, information_enum_shallow.value
|
||||||
|
)
|
||||||
|
return [build_dues_types.debit, build_dues_types.lawyer_expence, build_dues_types.add_debit, build_dues_types.renovation, build_dues_types.service_fee, build_dues_types.information]
|
||||||
|
|
||||||
|
|
||||||
|
def do_payments(build_id: int, work_date: datetime, run_now_bool: bool = False):
|
||||||
|
"""Process payments for the current month's unpaid debts.
|
||||||
|
This function retrieves account records with available funds and processes
|
||||||
|
payments for current month's unpaid debts in order of payment type priority.
|
||||||
|
"""
|
||||||
|
session_factory = get_session_factory()
|
||||||
|
session = session_factory()
|
||||||
|
|
||||||
|
# Set session for all models
|
||||||
|
AccountRecords.set_session(session)
|
||||||
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
Build.set_session(session)
|
||||||
|
BuildDecisionBook.set_session(session)
|
||||||
|
|
||||||
|
# Get payment types in priority order
|
||||||
|
payment_type_list = get_enums_from_database()
|
||||||
|
fund_finished = lambda money_spend, money_in_account: money_spend == money_in_account
|
||||||
|
payments_made, total_amount_paid, paid_count = 0, 0, 0
|
||||||
|
target_build = Build.query.filter(Build.id == build_id).first()
|
||||||
|
if not target_build:
|
||||||
|
raise ValueError(f"Build with id {build_id} not found")
|
||||||
|
|
||||||
|
decision_book = BuildDecisionBook.query.filter(
|
||||||
|
BuildDecisionBook.build_id == build_id, cast(BuildDecisionBook.expiry_starts, Date) <= work_date.date(),
|
||||||
|
cast(BuildDecisionBook.expiry_ends, Date) >= work_date.date(), BuildDecisionBook.decision_type == "RBM"
|
||||||
|
).first()
|
||||||
|
if not decision_book:
|
||||||
|
raise ValueError(f"Decision book not found for build with id {build_id}")
|
||||||
|
|
||||||
|
period_date_start = decision_book.expiry_starts
|
||||||
|
period_date_end = decision_book.expiry_ends
|
||||||
|
period_id = decision_book.id
|
||||||
|
|
||||||
|
first_date_of_process_date = find_first_day_of_month(datetime(work_date.year, work_date.month, 1))
|
||||||
|
last_date_of_process_date = find_last_day_of_month(datetime(work_date.year, work_date.month, 1))
|
||||||
|
if run_now_bool:
|
||||||
|
last_date_of_process_date = work_date
|
||||||
|
print('first_date_of_process_date', first_date_of_process_date)
|
||||||
|
print('last_date_of_process_date', last_date_of_process_date)
|
||||||
|
|
||||||
|
# Current month date filter
|
||||||
|
date_query_tuple = (
|
||||||
|
cast(BuildDecisionBookPayments.process_date, Date) >= first_date_of_process_date.date(), cast(BuildDecisionBookPayments.process_date, Date) <= last_date_of_process_date.date()
|
||||||
|
)
|
||||||
|
date_query_account_tuple = (
|
||||||
|
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(), cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date()
|
||||||
|
)
|
||||||
|
for payment_type in payment_type_list:
|
||||||
|
query_of_payments = BuildDecisionBookPayments.query.filter(*date_query_tuple)
|
||||||
|
query_of_payments = query_of_payments.filter(
|
||||||
|
BuildDecisionBookPayments.payment_types_id == payment_type.id, BuildDecisionBookPayments.account_is_debit == True,
|
||||||
|
BuildDecisionBookPayments.is_closed == False
|
||||||
|
).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||||
|
# priority_uuids = [payment_type.uuid, *[pt.uuid for pt in payment_type_list if pt.uuid != payment_type.uuid]]
|
||||||
|
# case_order = case(*[(AccountRecords.payment_result_type_uu_id == uuid, index) for index, uuid in enumerate(priority_uuids)], else_=len(priority_uuids))
|
||||||
|
|
||||||
|
for payment_row in query_of_payments:
|
||||||
|
money_to_pay_rows = AccountRecords.query.filter(
|
||||||
|
AccountRecords.build_parts_id == payment_row.build_parts_id, AccountRecords.payment_result_type == payment_type.id,
|
||||||
|
AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance), *date_query_account_tuple
|
||||||
|
).order_by(case_order, AccountRecords.bank_date.asc()).all()
|
||||||
|
for money_to_pay_row in money_to_pay_rows:
|
||||||
|
# Get remainder balance from database regardless trust over AccountRecords row from first query
|
||||||
|
remainder_balance_from_database = retrieve_remainder_balance_set_if_needed(money_to_pay_row.id, session)
|
||||||
|
available_funds = abs(money_to_pay_row.currency_value) - abs(remainder_balance_from_database)
|
||||||
|
# BuildDecisionBookPayments must be refreshed to check is there still money to be paid for this row
|
||||||
|
debt_to_pay = retrieve_current_debt_to_pay_from_database(ref_id=payment_row.ref_id, session=session)
|
||||||
|
if debt_to_pay == 0:
|
||||||
|
break # For session caused errors double check the database FOR multi process actions
|
||||||
|
if abs(available_funds) > abs(debt_to_pay):
|
||||||
|
payments_made += 1
|
||||||
|
total_amount_paid += debt_to_pay
|
||||||
|
paid_count += 1
|
||||||
|
close_payment_book(payment_row, money_to_pay_row, abs(debt_to_pay), session)
|
||||||
|
break # More fund to spend so go to next BuildDecisionBookPayments
|
||||||
|
else:
|
||||||
|
payments_made += 1
|
||||||
|
total_amount_paid += available_funds
|
||||||
|
paid_count += 1
|
||||||
|
close_payment_book(payment_row, money_to_pay_row, abs(available_funds), session)
|
||||||
|
continue # Fund has finished so go to next AccountRecords
|
||||||
|
|
||||||
|
print('payments_made', payments_made)
|
||||||
|
print('total_amount_paid', total_amount_paid)
|
||||||
|
print('paid_count', paid_count)
|
||||||
|
session.close()
|
||||||
|
session_factory.remove() # Clean up the session from the registry
|
||||||
|
|
||||||
|
|
||||||
|
def do_payments_of_previos_months(build_id: int):
|
||||||
|
"""Process payments for the previous month's unpaid debts.
|
||||||
|
This function retrieves account records with available funds and processes
|
||||||
|
payments for previous month's unpaid debts in order of payment type priority.
|
||||||
|
"""
|
||||||
|
session_factory = get_session_factory()
|
||||||
|
session = session_factory()
|
||||||
|
|
||||||
|
# Set session for all models
|
||||||
|
AccountRecords.set_session(session)
|
||||||
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
Build.set_session(session)
|
||||||
|
BuildDecisionBook.set_session(session)
|
||||||
|
|
||||||
|
# Get payment types in priority order
|
||||||
|
payment_type_list = get_enums_from_database()
|
||||||
|
print('payment_type_list', payment_type_list)
|
||||||
|
fund_finished = lambda money_spend, money_in_account: money_spend == money_in_account
|
||||||
|
payments_made, total_amount_paid, paid_count = 0, 0, 0
|
||||||
|
target_build = Build.query.filter(Build.id == build_id).first()
|
||||||
|
if not target_build:
|
||||||
|
raise ValueError(f"Build with id {build_id} not found")
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
decision_book = BuildDecisionBook.query.filter(
|
||||||
|
BuildDecisionBook.build_id == build_id, cast(BuildDecisionBook.expiry_starts, Date) <= now.date(),
|
||||||
|
cast(BuildDecisionBook.expiry_ends, Date) >= now.date(), BuildDecisionBook.decision_type == "RBM"
|
||||||
|
).first()
|
||||||
|
if not decision_book:
|
||||||
|
raise ValueError(f"Decision book not found for build with id {build_id}")
|
||||||
|
|
||||||
|
period_date_start = decision_book.expiry_starts
|
||||||
|
period_date_end = decision_book.expiry_ends
|
||||||
|
period_id = decision_book.id
|
||||||
|
|
||||||
|
print(dict(
|
||||||
|
period_date_start=str(period_date_start), period_date_end=str(period_date_end), period_id=period_id
|
||||||
|
))
|
||||||
|
|
||||||
|
first_date_of_process_date = find_first_day_of_month(datetime(period_date_start.year, period_date_start.month, 1))
|
||||||
|
last_date_of_process_date = find_first_day_of_month(datetime(now.year, now.month - 1, 1))
|
||||||
|
print('first_date_of_process_date', first_date_of_process_date)
|
||||||
|
print('last_date_of_process_date', last_date_of_process_date)
|
||||||
|
|
||||||
|
# Current month date filter
|
||||||
|
date_query_tuple = (
|
||||||
|
cast(BuildDecisionBookPayments.process_date, Date) >= first_date_of_process_date.date(), cast(BuildDecisionBookPayments.process_date, Date) <= last_date_of_process_date.date()
|
||||||
|
)
|
||||||
|
|
||||||
|
for payment_type in payment_type_list:
|
||||||
|
query_of_payments = BuildDecisionBookPayments.query.filter(
|
||||||
|
*date_query_tuple, BuildDecisionBookPayments.payment_types_id == payment_type.id, BuildDecisionBookPayments.account_is_debit == True
|
||||||
|
).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||||
|
print('length of debit', len(query_of_payments))
|
||||||
|
priority_uuids = [payment_type.uuid, *[pt.uuid for pt in payment_type_list if pt.uuid != payment_type.uuid]]
|
||||||
|
case_order = case(*[(AccountRecords.payment_result_type_uu_id == uuid, index) for index, uuid in enumerate(priority_uuids)], else_=len(priority_uuids))
|
||||||
|
for payment_row in query_of_payments:
|
||||||
|
print('-'* 100)
|
||||||
|
print('BuildDecisionBookPayments result: ',dict(
|
||||||
|
process_date=str(payment_row.process_date), ref_id=payment_row.ref_id, payment_amount=payment_row.payment_amount, build_parts_id=payment_row.build_parts_id,
|
||||||
|
payment_types_id=payment_row.payment_types_id, account_is_debit=payment_row.account_is_debit
|
||||||
|
))
|
||||||
|
print('-'* 100)
|
||||||
|
|
||||||
|
|
||||||
|
def do_regular_monthly_payers_payment_function(work_date: datetime, build_id: int, month_count: int = 23, year_count: int = 2):
|
||||||
|
start_date = work_date - timedelta(days=365 * year_count)
|
||||||
|
start_date = find_first_day_of_month(start_date)
|
||||||
|
for i in range(month_count):
|
||||||
|
start_date, _ = add_month_to_date(start_date)
|
||||||
|
do_payments(build_id, work_date=start_date, run_now_bool=False)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
start_time = perf_counter()
|
||||||
|
|
||||||
|
print("\n===== PROCESSING PAYMENTS =====\n")
|
||||||
|
print("Starting payment processing at:", datetime.now())
|
||||||
|
|
||||||
|
build_id = 1
|
||||||
|
# Do regular monthly payers payments
|
||||||
|
print("\n1. Processing regular monthly payers payments...")
|
||||||
|
do_regular_monthly_payers_payment_function(work_date=datetime.now(), build_id=build_id, month_count=23, year_count=2)
|
||||||
|
|
||||||
|
# Process payments for current month first
|
||||||
|
print("\n2. Processing current month payments...")
|
||||||
|
do_payments(build_id=build_id, work_date=datetime.now(), run_now_bool=True)
|
||||||
|
|
||||||
|
# Process payments for previous months
|
||||||
|
print("\n2. Processing previous months payments...")
|
||||||
|
# do_regular_monthly_payers_payment_function(work_date=datetime.now(), build_id=build_id, month_count=23, year_count=2)
|
||||||
|
|
||||||
|
print("\n===== PAYMENT PROCESSING COMPLETE =====\n")
|
||||||
|
print("Payment processing completed at:", datetime.now())
|
||||||
|
|
||||||
|
# Analyze the payment situation after processing payments
|
||||||
|
print("\n===== ANALYZING PAYMENT SITUATION AFTER PROCESSING =====\n")
|
||||||
|
# analyze_payment_function()
|
||||||
|
|
||||||
|
end_time = perf_counter()
|
||||||
|
print(f"\n{end_time - start_time:.3f} : seconds")
|
||||||
|
|
@ -0,0 +1,869 @@
|
||||||
|
import arrow
|
||||||
|
import calendar
|
||||||
|
import time
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from Schemas import BuildDecisionBookPayments, AccountRecords, ApiEnumDropdown, Build, BuildDecisionBook
|
||||||
|
from time import perf_counter
|
||||||
|
from sqlalchemy import select, func, distinct, cast, Date, String, literal, desc, and_, or_, case
|
||||||
|
from Controllers.Postgres.engine import get_session_factory
|
||||||
|
#from ServicesApi.Schemas.account.account import AccountRecords
|
||||||
|
#from ServicesApi.Schemas.building.decision_book import BuildDecisionBookPayments
|
||||||
|
|
||||||
|
|
||||||
|
def find_last_day_of_month(date_value):
|
||||||
|
today = date_value.date()
|
||||||
|
_, last_day = calendar.monthrange(today.year, today.month)
|
||||||
|
return datetime(today.year, today.month, last_day, 23, 59, 59)
|
||||||
|
|
||||||
|
def find_first_day_of_month(date_value):
|
||||||
|
today = date_value.date()
|
||||||
|
return datetime(today.year, today.month, 1)
|
||||||
|
|
||||||
|
|
||||||
|
class BuildDuesTypes:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.debit: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.add_debit: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.renovation: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.lawyer_expence: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.service_fee: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.information: ApiEnumDropdownShallowCopy = None
|
||||||
|
|
||||||
|
|
||||||
|
class ApiEnumDropdownShallowCopy:
|
||||||
|
id: int
|
||||||
|
uuid: str
|
||||||
|
enum_class: str
|
||||||
|
key: str
|
||||||
|
value: str
|
||||||
|
|
||||||
|
def __init__(self, id: int, uuid: str, enum_class: str, key: str, value: str):
|
||||||
|
self.id = id
|
||||||
|
self.uuid = uuid
|
||||||
|
self.enum_class = enum_class
|
||||||
|
self.key = key
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
|
||||||
|
def get_enums_from_database():
|
||||||
|
build_dues_types = BuildDuesTypes()
|
||||||
|
with ApiEnumDropdown.new_session() as session:
|
||||||
|
|
||||||
|
ApiEnumDropdown.set_session(session)
|
||||||
|
|
||||||
|
debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-D").first() # Debit
|
||||||
|
add_debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-A").first() # Add Debit
|
||||||
|
renovation_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-R").first() # Renovation
|
||||||
|
late_payment_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-L").first() # Lawyer expence
|
||||||
|
service_fee_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-S").first() # Service fee
|
||||||
|
information_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-I").first() # Information
|
||||||
|
|
||||||
|
build_dues_types.debit = ApiEnumDropdownShallowCopy(
|
||||||
|
debit_enum_shallow.id, str(debit_enum_shallow.uu_id), debit_enum_shallow.enum_class, debit_enum_shallow.key, debit_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.add_debit = ApiEnumDropdownShallowCopy(
|
||||||
|
add_debit_enum_shallow.id, str(add_debit_enum_shallow.uu_id), add_debit_enum_shallow.enum_class, add_debit_enum_shallow.key, add_debit_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.renovation = ApiEnumDropdownShallowCopy(
|
||||||
|
renovation_enum_shallow.id, str(renovation_enum_shallow.uu_id), renovation_enum_shallow.enum_class, renovation_enum_shallow.key, renovation_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.lawyer_expence = ApiEnumDropdownShallowCopy(
|
||||||
|
late_payment_enum_shallow.id, str(late_payment_enum_shallow.uu_id), late_payment_enum_shallow.enum_class, late_payment_enum_shallow.key, late_payment_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.service_fee = ApiEnumDropdownShallowCopy(
|
||||||
|
service_fee_enum_shallow.id, str(service_fee_enum_shallow.uu_id), service_fee_enum_shallow.enum_class, service_fee_enum_shallow.key, service_fee_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.information = ApiEnumDropdownShallowCopy(
|
||||||
|
information_enum_shallow.id, str(information_enum_shallow.uu_id), information_enum_shallow.enum_class, information_enum_shallow.key, information_enum_shallow.value
|
||||||
|
)
|
||||||
|
return [build_dues_types.debit, build_dues_types.lawyer_expence, build_dues_types.add_debit, build_dues_types.renovation, build_dues_types.service_fee, build_dues_types.information]
|
||||||
|
|
||||||
|
|
||||||
|
def generate_total_paid_amount_for_spesific_build_part_id(build_parts_id: int, session):
|
||||||
|
"""
|
||||||
|
Calculate the total amount paid for a specific build part ID.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
build_parts_id: The build part ID to calculate payments for
|
||||||
|
session: Database session
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: The total amount paid (absolute value)
|
||||||
|
"""
|
||||||
|
payment_query = session.query(func.sum(BuildDecisionBookPayments.payment_amount)).filter(
|
||||||
|
BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
||||||
|
BuildDecisionBookPayments.account_is_debit == False,
|
||||||
|
cast(BuildDecisionBookPayments.process_date, Date) >= '2022-01-01'
|
||||||
|
).scalar()
|
||||||
|
return payment_query if payment_query is not None else 0
|
||||||
|
|
||||||
|
|
||||||
|
def generate_total_debt_amount_for_spesific_build_part_id(build_parts_id: int, session):
|
||||||
|
|
||||||
|
# Use SQLAlchemy's func.sum to calculate the total debts
|
||||||
|
# For total debt, we want to include ALL debts, both processed and unprocessed
|
||||||
|
result = session.query(
|
||||||
|
func.sum(BuildDecisionBookPayments.payment_amount)
|
||||||
|
).filter(
|
||||||
|
BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
||||||
|
BuildDecisionBookPayments.account_is_debit == True,
|
||||||
|
cast(BuildDecisionBookPayments.process_date, Date) >= '2022-01-01'
|
||||||
|
).scalar()
|
||||||
|
|
||||||
|
# Return 0 if no debts found, otherwise return the absolute value of the sum
|
||||||
|
return abs(result) if result is not None else 0
|
||||||
|
|
||||||
|
|
||||||
|
def generate_total_amount_that_user_has_in_account(account_record: AccountRecords, session):
|
||||||
|
# Get total amount that user has in account
|
||||||
|
result = session.query(
|
||||||
|
func.sum(AccountRecords.currency_value)
|
||||||
|
).filter(
|
||||||
|
AccountRecords.build_parts_id == account_record.build_parts_id,
|
||||||
|
AccountRecords.currency_value > 0,
|
||||||
|
cast(AccountRecords.bank_date, Date) >= '2022-01-01'
|
||||||
|
).scalar()
|
||||||
|
|
||||||
|
# Return 0 if no payments found, otherwise return the absolute value of the sum
|
||||||
|
return abs(result)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _print_debt_details(debt, session):
|
||||||
|
"""Helper function to print detailed information about an unpaid debt.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
debt: The BuildDecisionBookPayments object representing the debt
|
||||||
|
session: Database session
|
||||||
|
"""
|
||||||
|
# Get the sum of payments for this debt
|
||||||
|
payments_sum = session.query(
|
||||||
|
func.sum(BuildDecisionBookPayments.payment_amount)
|
||||||
|
).filter(
|
||||||
|
BuildDecisionBookPayments.ref_id == debt.ref_id,
|
||||||
|
BuildDecisionBookPayments.account_is_debit == False
|
||||||
|
).scalar() or 0
|
||||||
|
|
||||||
|
# Calculate remaining amount
|
||||||
|
debit_amount = abs(debt.payment_amount)
|
||||||
|
remaining = debit_amount - abs(payments_sum)
|
||||||
|
payment_percentage = (abs(payments_sum) / debit_amount) * 100 if debit_amount > 0 else 0
|
||||||
|
|
||||||
|
# Format the date for display
|
||||||
|
date_str = debt.process_date.strftime('%Y-%m-%d') if debt.process_date else 'Unknown date'
|
||||||
|
|
||||||
|
|
||||||
|
def analyze_payment_function():
|
||||||
|
session_factory = get_session_factory()
|
||||||
|
session = session_factory()
|
||||||
|
# Set session for all models
|
||||||
|
AccountRecords.set_session(session)
|
||||||
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
|
||||||
|
order_pay = get_enums_from_database()
|
||||||
|
# Get distinct build_parts_id values from account records with positive currency_value
|
||||||
|
# This avoids redundant processing of the same build_parts_id
|
||||||
|
distinct_build_parts = session.query(
|
||||||
|
distinct(AccountRecords.build_parts_id)
|
||||||
|
).filter(
|
||||||
|
AccountRecords.build_parts_id.isnot(None),
|
||||||
|
AccountRecords.currency_value > 0,
|
||||||
|
AccountRecords.bank_date >= '2022-01-01'
|
||||||
|
).order_by(AccountRecords.build_parts_id.desc()).all()
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
for build_part_id_tuple in distinct_build_parts:
|
||||||
|
build_part_id = build_part_id_tuple[0] # Extract the ID from the tuple
|
||||||
|
process_date = datetime.now()
|
||||||
|
last_date_of_process_date = datetime(process_date.year, process_date.month, 1) - timedelta(days=1)
|
||||||
|
first_date_of_process_date = datetime(process_date.year, process_date.month, 1)
|
||||||
|
|
||||||
|
print(f"\n{'=' * 50}")
|
||||||
|
print(f"ACCOUNT ANALYSIS FOR BUILD PART ID: {build_part_id}")
|
||||||
|
print(f"{'=' * 50}")
|
||||||
|
|
||||||
|
# Calculate total paid amount for this build_part_id
|
||||||
|
total_amount_paid = generate_total_paid_amount_for_spesific_build_part_id(build_part_id, session)
|
||||||
|
|
||||||
|
# Calculate total debt amount for this build_part_id
|
||||||
|
total_debt_amount = generate_total_debt_amount_for_spesific_build_part_id(build_part_id, session)
|
||||||
|
|
||||||
|
# Get total amount in account for this build_part_id
|
||||||
|
account_record = AccountRecords()
|
||||||
|
account_record.build_parts_id = build_part_id
|
||||||
|
total_amount_in_account = generate_total_amount_that_user_has_in_account(account_record, session)
|
||||||
|
|
||||||
|
# Calculate remaining amount to be paid
|
||||||
|
amount_need_to_paid = total_debt_amount - total_amount_paid
|
||||||
|
total_amount_that_user_need_to_transfer = abs(amount_need_to_paid) - abs(total_amount_in_account)
|
||||||
|
|
||||||
|
# Print summary with clear descriptions
|
||||||
|
print(f"PAYMENT SUMMARY:")
|
||||||
|
print(f" • Total debt amount: {total_debt_amount:,.2f} TL")
|
||||||
|
print(f" • Amount already paid: {total_amount_paid:,.2f} TL")
|
||||||
|
print(f" • Remaining debt to be collected: {amount_need_to_paid:,.2f} TL")
|
||||||
|
print(f" • Current account balance: {total_amount_in_account:,.2f} TL")
|
||||||
|
|
||||||
|
if total_amount_that_user_need_to_transfer > 0:
|
||||||
|
print(f" • Additional funds needed: {total_amount_that_user_need_to_transfer:,.2f} TL")
|
||||||
|
elif amount_need_to_paid <= 0:
|
||||||
|
print(f" • Account is fully paid with no outstanding debt")
|
||||||
|
else:
|
||||||
|
print(f" • Sufficient funds available to close all debt")
|
||||||
|
# Show debt coverage percentage
|
||||||
|
if total_debt_amount > 0:
|
||||||
|
# Calculate current coverage (already paid)
|
||||||
|
current_coverage_percentage = (total_amount_paid / total_debt_amount) * 100
|
||||||
|
|
||||||
|
# Calculate potential coverage (including available funds)
|
||||||
|
potential_coverage = min(100, ((total_amount_paid + total_amount_in_account) / total_debt_amount) * 100)
|
||||||
|
|
||||||
|
# Display both percentages
|
||||||
|
print(f" • Current debt coverage: {current_coverage_percentage:.2f}%")
|
||||||
|
print(f" • Potential debt coverage with available funds: {potential_coverage:.2f}%")
|
||||||
|
|
||||||
|
# Analyze unpaid debts for each payment type
|
||||||
|
print("\nUNPAID DEBTS ANALYSIS BY PAYMENT TYPE:")
|
||||||
|
for payment_type in order_pay:
|
||||||
|
# Get unpaid debts for current month
|
||||||
|
date_query_current = (
|
||||||
|
BuildDecisionBookPayments.process_date >= first_date_of_process_date,
|
||||||
|
BuildDecisionBookPayments.process_date <= process_date
|
||||||
|
)
|
||||||
|
date_query_previous = (
|
||||||
|
BuildDecisionBookPayments.process_date < first_date_of_process_date,
|
||||||
|
)
|
||||||
|
|
||||||
|
current_unpaid_debts = get_unpaid_debts(build_parts_id=build_part_id, session=session, debit_type=payment_type, date_query=date_query_current)
|
||||||
|
|
||||||
|
# Get unpaid debts from previous months
|
||||||
|
previous_unpaid_debts = get_unpaid_debts(build_parts_id=build_part_id, session=session, debit_type=payment_type, date_query=date_query_previous)
|
||||||
|
|
||||||
|
# Calculate totals
|
||||||
|
current_total = sum(abs(debt[2]) for debt in current_unpaid_debts)
|
||||||
|
previous_total = sum(abs(debt[2]) for debt in previous_unpaid_debts)
|
||||||
|
grand_total = current_total + previous_total
|
||||||
|
|
||||||
|
# Print summary for this payment type
|
||||||
|
if current_unpaid_debts or previous_unpaid_debts:
|
||||||
|
print(f" • {payment_type.key}: Total unpaid: {grand_total:,.2f} TL")
|
||||||
|
|
||||||
|
# Current month details
|
||||||
|
if current_unpaid_debts:
|
||||||
|
print(f" - Current month: {len(current_unpaid_debts)} debts, {current_total:,.2f} TL")
|
||||||
|
# Show details of each unpaid debt if there aren't too many
|
||||||
|
# if len(current_unpaid_debts) <= 3:
|
||||||
|
# for debt in current_unpaid_debts:
|
||||||
|
# _print_debt_details(debt, session)
|
||||||
|
|
||||||
|
# Previous months details
|
||||||
|
if previous_unpaid_debts:
|
||||||
|
print(f" - Previous months: {len(previous_unpaid_debts)} debts, {previous_total:,.2f} TL")
|
||||||
|
# Show details of each unpaid debt if there aren't too many
|
||||||
|
# if len(previous_unpaid_debts) <= 3:
|
||||||
|
# for debt in previous_unpaid_debts:
|
||||||
|
# _print_debt_details(debt, session)
|
||||||
|
else:
|
||||||
|
print(f" • {payment_type.key}: All debts paid")
|
||||||
|
print(f"{'=' * 50}\n")
|
||||||
|
|
||||||
|
|
||||||
|
def close_payment_book(payment_row_book, account_record, value, session):
|
||||||
|
"""Create a credit entry in BuildDecisionBookPayments to close a debt.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
payment_row_book: The debit entry to be paid
|
||||||
|
account_record: The account record containing the funds
|
||||||
|
value: The amount to pay
|
||||||
|
session: Database session
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The newly created payment record
|
||||||
|
"""
|
||||||
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
|
||||||
|
# Create a new credit entry (payment)
|
||||||
|
new_row = BuildDecisionBookPayments.create(
|
||||||
|
ref_id=str(payment_row_book.uu_id),
|
||||||
|
payment_plan_time_periods=payment_row_book.payment_plan_time_periods,
|
||||||
|
period_time=payment_row_book.period_time,
|
||||||
|
currency=payment_row_book.currency,
|
||||||
|
account_records_id=account_record.id,
|
||||||
|
account_records_uu_id=str(account_record.uu_id),
|
||||||
|
build_parts_id=payment_row_book.build_parts_id,
|
||||||
|
build_parts_uu_id=str(payment_row_book.build_parts_uu_id),
|
||||||
|
payment_amount=abs(value), # Negative for credit entries
|
||||||
|
payment_types_id=payment_row_book.payment_types_id,
|
||||||
|
payment_types_uu_id=str(payment_row_book.payment_types_uu_id),
|
||||||
|
process_date_m=payment_row_book.process_date.month,
|
||||||
|
process_date_y=payment_row_book.process_date.year,
|
||||||
|
process_date=payment_row_book.process_date,
|
||||||
|
build_decision_book_item_id=payment_row_book.build_decision_book_item_id if payment_row_book.build_decision_book_item_id else None,
|
||||||
|
build_decision_book_item_uu_id=str(payment_row_book.build_decision_book_item_uu_id) if payment_row_book.build_decision_book_item_uu_id else None,
|
||||||
|
decision_book_project_id=payment_row_book.decision_book_project_id if payment_row_book.decision_book_project_id else None,
|
||||||
|
decision_book_project_uu_id=str(payment_row_book.decision_book_project_uu_id) if payment_row_book.decision_book_project_uu_id else None,
|
||||||
|
build_decision_book_id=payment_row_book.build_decision_book_id if payment_row_book.build_decision_book_id else None,
|
||||||
|
build_decision_book_uu_id=str(payment_row_book.build_decision_book_uu_id) if payment_row_book.build_decision_book_uu_id else None,
|
||||||
|
is_confirmed=True,
|
||||||
|
account_is_debit=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save the new payment record
|
||||||
|
saved_row = new_row.save()
|
||||||
|
session.commit()
|
||||||
|
session.refresh(saved_row)
|
||||||
|
session.flush()
|
||||||
|
return saved_row
|
||||||
|
|
||||||
|
|
||||||
|
def update_account_remainder_if_spent(account_record, ref_id: str, session):
|
||||||
|
"""Update the remainder_balance of an account after spending money.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
account_record: The account record to update
|
||||||
|
amount_spent: The amount spent in this transaction
|
||||||
|
session: Database session
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if all money is spent, False otherwise
|
||||||
|
"""
|
||||||
|
|
||||||
|
account_record_remainder_balance = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter(
|
||||||
|
BuildDecisionBookPayments.account_records_id == account_record.id,
|
||||||
|
BuildDecisionBookPayments.account_is_debit == False
|
||||||
|
).scalar()
|
||||||
|
|
||||||
|
if not account_record_remainder_balance:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if account_record_remainder_balance:
|
||||||
|
account_record.remainder_balance = account_record_remainder_balance
|
||||||
|
account_record.save()
|
||||||
|
return account_record_remainder_balance
|
||||||
|
|
||||||
|
|
||||||
|
def check_book_payment_is_still_has_debt_to_be_paid(ref_id: str, session):
|
||||||
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
debit_row_debt_to_pay = session.query(func.sum(BuildDecisionBookPayments.payment_amount)).filter(BuildDecisionBookPayments.ref_id == ref_id).scalar()
|
||||||
|
if not debit_row_debt_to_pay:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def update_all_spent_accounts(session):
|
||||||
|
"""Update remainder_balance for all accounts with payments.
|
||||||
|
|
||||||
|
This function finds account records in BuildDecisionBookPayments and updates
|
||||||
|
their remainder_balance based on the sum of payments made, regardless of whether
|
||||||
|
all funds have been spent or not.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
session: Database session
|
||||||
|
"""
|
||||||
|
with AccountRecords.new_session() as session:
|
||||||
|
# Set sessions for models
|
||||||
|
AccountRecords.set_session(session)
|
||||||
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
|
||||||
|
# Get distinct account_records_id values from BuildDecisionBookPayments
|
||||||
|
distinct_account_ids = session.query(BuildDecisionBookPayments.account_records_id).filter(
|
||||||
|
BuildDecisionBookPayments.account_records_id.isnot(None),
|
||||||
|
BuildDecisionBookPayments.account_is_debit == False # Credit entries (payments)
|
||||||
|
).distinct().all()
|
||||||
|
|
||||||
|
updated_count = 0
|
||||||
|
|
||||||
|
for account_id_tuple in distinct_account_ids:
|
||||||
|
account_id = account_id_tuple[0]
|
||||||
|
|
||||||
|
# Get the account record
|
||||||
|
account = AccountRecords.query.filter_by(id=account_id).first()
|
||||||
|
if not account or not account.build_parts_id or account.currency_value <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Calculate the sum of payments made using this account
|
||||||
|
# Note: payment_amount is negative for credit entries, so we need to use abs() to get the positive amount
|
||||||
|
payment_query = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter(
|
||||||
|
BuildDecisionBookPayments.account_records_id == account_id,
|
||||||
|
BuildDecisionBookPayments.account_is_debit == False # Credit entries (payments)
|
||||||
|
)
|
||||||
|
|
||||||
|
payment_sum = payment_query.scalar() or 0
|
||||||
|
|
||||||
|
# Update remainder_balance for ALL accounts, regardless of payment_sum value
|
||||||
|
threshold = Decimal('0.01')
|
||||||
|
fully_spent = abs(payment_sum) >= abs(account.currency_value) - threshold
|
||||||
|
status = "All funds spent" if fully_spent else "Partial payment"
|
||||||
|
|
||||||
|
# Store the positive value in remainder_balance
|
||||||
|
account.remainder_balance = -1 * abs(payment_sum)
|
||||||
|
account.save()
|
||||||
|
updated_count += 1
|
||||||
|
session.commit()
|
||||||
|
print(f"\nTotal accounts updated: {updated_count}")
|
||||||
|
|
||||||
|
|
||||||
|
# def find_amount_to_pay_by_ref_id(ref_id, session):
|
||||||
|
# """Calculate the remaining amount to pay for a specific debt reference ID.
|
||||||
|
|
||||||
|
# Args:
|
||||||
|
# ref_id: The reference ID of the debt (this is the uu_id of the debt record)
|
||||||
|
# session: Database session
|
||||||
|
|
||||||
|
# Returns:
|
||||||
|
# float: The remaining amount to pay
|
||||||
|
# """
|
||||||
|
# # Get the original debt amount - the debt is identified by its uu_id which is passed as ref_id
|
||||||
|
# debit = BuildDecisionBookPayments.query.filter(
|
||||||
|
# BuildDecisionBookPayments.uu_id == ref_id,
|
||||||
|
# BuildDecisionBookPayments.account_is_debit == True
|
||||||
|
# ).first()
|
||||||
|
# if not debit:
|
||||||
|
# return 0 # No debit found, nothing to pay
|
||||||
|
|
||||||
|
# debit_amount = abs(debit.payment_amount) # Ensure positive value for debit amount
|
||||||
|
|
||||||
|
# # Get the sum of payments already made for this debt
|
||||||
|
# # The ref_id in credit records points to the uu_id of the original debit
|
||||||
|
# # Note: payment_amount is negative for credit entries, so we use abs() to get positive values
|
||||||
|
# credit_amount = session.query(
|
||||||
|
# func.sum(func.abs(BuildDecisionBookPayments.payment_amount))
|
||||||
|
# ).filter(
|
||||||
|
# BuildDecisionBookPayments.ref_id == str(ref_id),
|
||||||
|
# BuildDecisionBookPayments.account_is_debit == False
|
||||||
|
# ).scalar() or 0
|
||||||
|
# # Calculate remaining amount to pay
|
||||||
|
# remaining = abs(debit_amount) - abs(credit_amount)
|
||||||
|
# # Ensure we don't return negative values
|
||||||
|
# if remaining < 0:
|
||||||
|
# return 0
|
||||||
|
|
||||||
|
# return remaining
|
||||||
|
|
||||||
|
def get_unpaid_debts_via_build_parts(build_parts_id: int, session, debit_type, date_query: tuple):
|
||||||
|
"""Find BuildDecisionBookPayments entries where the debt has NOT been fully paid for a specific debit type.
|
||||||
|
|
||||||
|
This function identifies payments where the sum of payments is less than the debit amount,
|
||||||
|
meaning the debt has not been fully closed.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
build_parts_id: The build part ID to check
|
||||||
|
session: Database session
|
||||||
|
debit_type: The specific debit type to check
|
||||||
|
date_query: Tuple of date filters to apply
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: List of unpaid debt entries (full BuildDecisionBookPayments objects)
|
||||||
|
|
||||||
|
query:
|
||||||
|
SELECT
|
||||||
|
bpf.ref_id,
|
||||||
|
bpf.process_date,
|
||||||
|
ABS(COALESCE(SUM(bpf.payment_amount), 0)) AS total_payments
|
||||||
|
FROM public.build_decision_book_payments AS bpf
|
||||||
|
GROUP BY
|
||||||
|
bpf.ref_id,
|
||||||
|
bpf.process_date
|
||||||
|
HAVING ABS(COALESCE(SUM(bpf.payment_amount), 0)) > 0
|
||||||
|
order by bpf.process_date
|
||||||
|
"""
|
||||||
|
# Create a subquery for the payment sums without executing it separately
|
||||||
|
payment_sums_subquery = select(
|
||||||
|
BuildDecisionBookPayments.ref_id,
|
||||||
|
BuildDecisionBookPayments.process_date,
|
||||||
|
func.abs(func.coalesce(func.sum(BuildDecisionBookPayments.payment_amount), 0)).label("total_payments")
|
||||||
|
).filter(
|
||||||
|
BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
||||||
|
BuildDecisionBookPayments.payment_types_id == debit_type.id,
|
||||||
|
*date_query
|
||||||
|
).group_by(
|
||||||
|
BuildDecisionBookPayments.ref_id, BuildDecisionBookPayments.process_date
|
||||||
|
).having(
|
||||||
|
func.abs(func.coalesce(func.sum(BuildDecisionBookPayments.payment_amount), 0)) > 0
|
||||||
|
).order_by(BuildDecisionBookPayments.process_date.desc())
|
||||||
|
|
||||||
|
# Use the subquery directly in the main query
|
||||||
|
payment_sums = session.execute(payment_sums_subquery).all()
|
||||||
|
payment_sums_list = []
|
||||||
|
for item in payment_sums:
|
||||||
|
payment_sums_list.append({"ref_id": item[0], "process_date": item[1], "total_payments": item[2]})
|
||||||
|
return payment_sums
|
||||||
|
|
||||||
|
|
||||||
|
def get_unpaid_debts(session, debit_type, date_query: tuple):
|
||||||
|
"""Find BuildDecisionBookPayments entries where the debt has NOT been fully paid for a specific debit type.
|
||||||
|
|
||||||
|
This function identifies payments where the sum of payments is less than the debit amount,
|
||||||
|
meaning the debt has not been fully closed.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
build_parts_id: The build part ID to check
|
||||||
|
session: Database session
|
||||||
|
debit_type: The specific debit type to check
|
||||||
|
date_query: Tuple of date filters to apply
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: List of unpaid debt entries (full BuildDecisionBookPayments objects)
|
||||||
|
|
||||||
|
query:
|
||||||
|
SELECT
|
||||||
|
bpf.ref_id,
|
||||||
|
bpf.process_date,
|
||||||
|
ABS(COALESCE(SUM(bpf.payment_amount), 0)) AS total_payments
|
||||||
|
FROM public.build_decision_book_payments AS bpf
|
||||||
|
GROUP BY
|
||||||
|
bpf.ref_id,
|
||||||
|
bpf.process_date
|
||||||
|
HAVING ABS(COALESCE(SUM(bpf.payment_amount), 0)) > 0
|
||||||
|
order by bpf.process_date
|
||||||
|
"""
|
||||||
|
# Create a subquery for the payment sums without executing it separately
|
||||||
|
do_payments_set = lambda ref, date, total: {"ref_id": ref, "process_date": date, "total_payments": total}
|
||||||
|
payment_sums_subquery = select(BuildDecisionBookPayments.ref_id, BuildDecisionBookPayments.process_date,
|
||||||
|
func.abs(func.coalesce(func.sum(BuildDecisionBookPayments.payment_amount), 0)).label("total_payments")
|
||||||
|
).filter(BuildDecisionBookPayments.payment_types_id == debit_type.id, *date_query
|
||||||
|
).group_by(BuildDecisionBookPayments.ref_id, BuildDecisionBookPayments.process_date
|
||||||
|
).having(func.abs(func.coalesce(func.sum(BuildDecisionBookPayments.payment_amount), 0)) > 0
|
||||||
|
).order_by(BuildDecisionBookPayments.process_date.asc())
|
||||||
|
|
||||||
|
payment_sums = session.execute(payment_sums_subquery).all()
|
||||||
|
return [do_payments_set(item[0], item[1], item[2]) for item in payment_sums]
|
||||||
|
|
||||||
|
|
||||||
|
def do_payments_of_this_month(build_id: int = 1):
|
||||||
|
"""Process payments for the current month's unpaid debts.
|
||||||
|
This function retrieves account records with available funds and processes
|
||||||
|
payments for current month's unpaid debts in order of payment type priority.
|
||||||
|
"""
|
||||||
|
session_factory = get_session_factory()
|
||||||
|
session = session_factory()
|
||||||
|
|
||||||
|
# Set session for all models
|
||||||
|
AccountRecords.set_session(session)
|
||||||
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
Build.set_session(session)
|
||||||
|
BuildDecisionBook.set_session(session)
|
||||||
|
|
||||||
|
# Get payment types in priority order
|
||||||
|
payment_type_list = get_enums_from_database()
|
||||||
|
fund_finished = lambda money_spend, money_in_account: money_spend == money_in_account
|
||||||
|
payments_made, total_amount_paid, paid_count = 0, 0, 0
|
||||||
|
target_build = Build.query.filter(Build.id == build_id).first()
|
||||||
|
if not target_build:
|
||||||
|
raise ValueError(f"Build with id {build_id} not found")
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
decision_book = BuildDecisionBook.query.filter(
|
||||||
|
BuildDecisionBook.build_id == build_id, cast(BuildDecisionBook.expiry_starts, Date) <= now.date(),
|
||||||
|
cast(BuildDecisionBook.expiry_ends, Date) >= now.date(), BuildDecisionBook.decision_type == "RBM"
|
||||||
|
).first()
|
||||||
|
if not decision_book:
|
||||||
|
raise ValueError(f"Decision book not found for build with id {build_id}")
|
||||||
|
|
||||||
|
period_date_start = decision_book.expiry_starts
|
||||||
|
period_date_end = decision_book.expiry_ends
|
||||||
|
period_id = decision_book.id
|
||||||
|
|
||||||
|
first_date_of_process_date = find_first_day_of_month(now)
|
||||||
|
last_date_of_process_date = find_last_day_of_month(now)
|
||||||
|
|
||||||
|
# Current month date filter
|
||||||
|
date_query_tuple = (
|
||||||
|
cast(BuildDecisionBookPayments.process_date, Date) >= first_date_of_process_date.date(), cast(BuildDecisionBookPayments.process_date, Date) <= last_date_of_process_date.date()
|
||||||
|
)
|
||||||
|
|
||||||
|
update_all_spent_accounts(session)
|
||||||
|
|
||||||
|
for payment_type in payment_type_list:
|
||||||
|
unpaid_debts = get_unpaid_debts(session, payment_type, date_query_tuple)
|
||||||
|
for unpaid_debt in unpaid_debts:
|
||||||
|
amount_to_pay = unpaid_debt["total_payments"]
|
||||||
|
ref_id = unpaid_debt["ref_id"]
|
||||||
|
process_date = unpaid_debt["process_date"]
|
||||||
|
debit_row = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.uu_id == ref_id).first()
|
||||||
|
build_parts_id = debit_row.build_parts_id
|
||||||
|
priority_uuids = [payment_type.uuid, *[pt.uuid for pt in payment_type_list if pt.uuid != payment_type.uuid]]
|
||||||
|
case_order = case(*[(AccountRecords.payment_result_type_uu_id == uuid, index) for index, uuid in enumerate(priority_uuids)], else_=len(priority_uuids))
|
||||||
|
money_to_pay_rows = AccountRecords.query.filter(
|
||||||
|
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
|
||||||
|
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(), cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date(),
|
||||||
|
).order_by(case_order, AccountRecords.bank_date.asc()).all()
|
||||||
|
for money_to_pay_row in money_to_pay_rows:
|
||||||
|
account_remainder_balance = update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
||||||
|
available_money = abs(money_to_pay_row.currency_value) - abs(account_remainder_balance)
|
||||||
|
if not available_money > 0:
|
||||||
|
continue
|
||||||
|
if available_money > amount_to_pay:
|
||||||
|
print('NOT All money is spent amount_to_pay:', amount_to_pay, 'debit_row payment_amount:', debit_row.payment_amount)
|
||||||
|
close_payment_book(debit_row, money_to_pay_row, amount_to_pay, session)
|
||||||
|
total_amount_paid += amount_to_pay
|
||||||
|
paid_count += 1
|
||||||
|
payments_made += 1
|
||||||
|
account_remainder_balance = update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
||||||
|
break
|
||||||
|
elif available_money <= amount_to_pay:
|
||||||
|
print('All money is spent amount_to_pay:', amount_to_pay, 'debit_row payment_amount:', debit_row.payment_amount)
|
||||||
|
close_payment_book(debit_row, money_to_pay_row, available_money, session)
|
||||||
|
total_amount_paid += available_money
|
||||||
|
paid_count += 1
|
||||||
|
payments_made += 1
|
||||||
|
account_remainder_balance = update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
print(f"Something else happened available_money: {available_money}, amount_to_pay: {amount_to_pay}")
|
||||||
|
|
||||||
|
update_all_spent_accounts(session)
|
||||||
|
|
||||||
|
print('payments_made', payments_made)
|
||||||
|
print('total_amount_paid', total_amount_paid)
|
||||||
|
print('paid_count', paid_count)
|
||||||
|
|
||||||
|
|
||||||
|
def do_payments_of_previos_months(build_id: int = 1):
|
||||||
|
"""Process payments for previous months' unpaid debts.
|
||||||
|
|
||||||
|
This function retrieves account records with available funds and processes
|
||||||
|
payments for previous months' unpaid debts in order of payment type priority.
|
||||||
|
"""
|
||||||
|
"""Process payments for the current month's unpaid debts.
|
||||||
|
This function retrieves account records with available funds and processes
|
||||||
|
payments for current month's unpaid debts in order of payment type priority.
|
||||||
|
"""
|
||||||
|
session_factory = get_session_factory()
|
||||||
|
session = session_factory()
|
||||||
|
|
||||||
|
# Set session for all models
|
||||||
|
AccountRecords.set_session(session)
|
||||||
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
Build.set_session(session)
|
||||||
|
BuildDecisionBook.set_session(session)
|
||||||
|
|
||||||
|
# Get payment types in priority order
|
||||||
|
payment_type_list = get_enums_from_database()
|
||||||
|
fund_finished = lambda money_spend, money_in_account: money_spend == money_in_account
|
||||||
|
payments_made, total_amount_paid, paid_count = 0, 0, 0
|
||||||
|
target_build = Build.query.filter(Build.id == build_id).first()
|
||||||
|
if not target_build:
|
||||||
|
raise ValueError(f"Build with id {build_id} not found")
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
decision_book = BuildDecisionBook.query.filter(
|
||||||
|
BuildDecisionBook.build_id == build_id,
|
||||||
|
cast(BuildDecisionBook.expiry_starts, Date) <= now.date(),
|
||||||
|
cast(BuildDecisionBook.expiry_ends, Date) >= now.date(),
|
||||||
|
BuildDecisionBook.decision_type == "RBM"
|
||||||
|
).first()
|
||||||
|
if not decision_book:
|
||||||
|
raise ValueError(f"Decision book not found for build with id {build_id}")
|
||||||
|
early_date = datetime(now.year - 1, now.month, now.day)
|
||||||
|
early_decision_book = BuildDecisionBook.query.filter(
|
||||||
|
BuildDecisionBook.build_id == build_id,
|
||||||
|
cast(BuildDecisionBook.expiry_starts, Date) <= early_date.date(),
|
||||||
|
cast(BuildDecisionBook.expiry_ends, Date) >= early_date.date(),
|
||||||
|
BuildDecisionBook.decision_type == "RBM"
|
||||||
|
).first()
|
||||||
|
|
||||||
|
period_date_start = decision_book.expiry_starts
|
||||||
|
period_date_end = decision_book.expiry_ends
|
||||||
|
period_id = decision_book.id
|
||||||
|
early_period_date_start = early_decision_book.expiry_starts
|
||||||
|
early_period_date_end = early_decision_book.expiry_ends
|
||||||
|
early_period_id = early_decision_book.id
|
||||||
|
|
||||||
|
first_date_of_process_date = arrow.get(period_date_start).datetime
|
||||||
|
last_date_of_process_date = now
|
||||||
|
|
||||||
|
# Current month date filter
|
||||||
|
date_query_tuple = (
|
||||||
|
cast(BuildDecisionBookPayments.process_date, Date) >= first_date_of_process_date.date(),
|
||||||
|
cast(BuildDecisionBookPayments.process_date, Date) <= last_date_of_process_date.date()
|
||||||
|
)
|
||||||
|
|
||||||
|
update_all_spent_accounts(session)
|
||||||
|
|
||||||
|
for payment_type in payment_type_list:
|
||||||
|
unpaid_debts = get_unpaid_debts(session, payment_type, date_query_tuple)
|
||||||
|
print('length unpaid debts: ', len(unpaid_debts))
|
||||||
|
for unpaid_debt in unpaid_debts:
|
||||||
|
amount_to_pay = unpaid_debt["total_payments"]
|
||||||
|
ref_id = unpaid_debt["ref_id"]
|
||||||
|
process_date = unpaid_debt["process_date"]
|
||||||
|
debit_row = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.uu_id == ref_id).first()
|
||||||
|
build_parts_id = debit_row.build_parts_id
|
||||||
|
priority_uuids = [payment_type.uuid, *[pt.uuid for pt in payment_type_list if pt.uuid != payment_type.uuid]]
|
||||||
|
case_order = case(*[(AccountRecords.payment_result_type_uu_id == uuid, index) for index, uuid in enumerate(priority_uuids)], else_=len(priority_uuids))
|
||||||
|
money_to_pay_rows = AccountRecords.query.filter(
|
||||||
|
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
|
||||||
|
cast(AccountRecords.bank_date, Date) >= find_first_day_of_month(process_date).date(), cast(AccountRecords.bank_date, Date) <= find_last_day_of_month(process_date).date(),
|
||||||
|
).order_by(case_order, AccountRecords.bank_date.asc()).all()
|
||||||
|
if not money_to_pay_rows:
|
||||||
|
money_to_pay_rows = AccountRecords.query.filter(
|
||||||
|
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
|
||||||
|
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(), cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date(),
|
||||||
|
).order_by(case_order, AccountRecords.bank_date.asc()).all()
|
||||||
|
|
||||||
|
for money_to_pay_row in money_to_pay_rows:
|
||||||
|
account_remainder_balance = update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
||||||
|
money_to_pay_row = AccountRecords.query.filter(AccountRecords.id == money_to_pay_row.id).first()
|
||||||
|
available_money = abs(money_to_pay_row.currency_value) - abs(account_remainder_balance)
|
||||||
|
if not available_money > 0:
|
||||||
|
continue
|
||||||
|
if available_money > amount_to_pay:
|
||||||
|
print('NOT All money is spent amount_to_pay:', amount_to_pay, 'debit_row payment_amount:', debit_row.payment_amount)
|
||||||
|
close_payment_book(debit_row, money_to_pay_row, amount_to_pay, session)
|
||||||
|
total_amount_paid += amount_to_pay
|
||||||
|
paid_count += 1
|
||||||
|
payments_made += 1
|
||||||
|
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
||||||
|
break
|
||||||
|
elif available_money <= amount_to_pay:
|
||||||
|
print('All money is spent amount_to_pay:', available_money, 'debit_row payment_amount:', debit_row.payment_amount)
|
||||||
|
close_payment_book(debit_row, money_to_pay_row, available_money, session)
|
||||||
|
total_amount_paid += available_money
|
||||||
|
paid_count += 1
|
||||||
|
payments_made += 1
|
||||||
|
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
print(f"Something else happened available_money: {available_money}, amount_to_pay: {amount_to_pay}")
|
||||||
|
|
||||||
|
print('This years decision book payments')
|
||||||
|
print('payments_made', payments_made)
|
||||||
|
print('total_amount_paid', total_amount_paid)
|
||||||
|
print('paid_count', paid_count)
|
||||||
|
payments_made, total_amount_paid, paid_count = 0, 0, 0
|
||||||
|
update_all_spent_accounts(session)
|
||||||
|
first_date_of_process_date = arrow.get(early_period_date_start).datetime
|
||||||
|
last_date_of_process_date = arrow.get(early_period_date_end).datetime
|
||||||
|
|
||||||
|
# Early month date filter
|
||||||
|
date_query_tuple = (
|
||||||
|
cast(BuildDecisionBookPayments.process_date, Date) >= first_date_of_process_date.date(), cast(BuildDecisionBookPayments.process_date, Date) <= last_date_of_process_date.date()
|
||||||
|
)
|
||||||
|
|
||||||
|
for payment_type in payment_type_list:
|
||||||
|
unpaid_debts = get_unpaid_debts(session, payment_type, date_query_tuple)
|
||||||
|
print('length unpaid debts: ', len(unpaid_debts))
|
||||||
|
for unpaid_debt in unpaid_debts:
|
||||||
|
amount_to_pay = unpaid_debt["total_payments"]
|
||||||
|
ref_id = unpaid_debt["ref_id"]
|
||||||
|
process_date = unpaid_debt["process_date"]
|
||||||
|
debit_row = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.uu_id == ref_id).first()
|
||||||
|
build_parts_id = debit_row.build_parts_id
|
||||||
|
first_date_of_process_date = find_first_day_of_month(process_date)
|
||||||
|
last_date_of_process_date = find_last_day_of_month(process_date)
|
||||||
|
priority_uuids = [payment_type.uuid, *[pt.uuid for pt in payment_type_list if pt.uuid != payment_type.uuid]]
|
||||||
|
case_order = case(*[(AccountRecords.payment_result_type_uu_id == uuid, index) for index, uuid in enumerate(priority_uuids)], else_=len(priority_uuids))
|
||||||
|
money_to_pay_rows = AccountRecords.query.filter(
|
||||||
|
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
|
||||||
|
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(), cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date(),
|
||||||
|
).order_by(case_order, AccountRecords.bank_date.asc()).all()
|
||||||
|
if not money_to_pay_rows:
|
||||||
|
money_to_pay_rows = AccountRecords.query.filter(
|
||||||
|
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
|
||||||
|
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(), cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date(),
|
||||||
|
).order_by(case_order, AccountRecords.bank_date.asc()).all()
|
||||||
|
for money_to_pay_row in money_to_pay_rows:
|
||||||
|
account_remainder_balance = update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
||||||
|
money_to_pay_row = AccountRecords.query.filter(AccountRecords.id == money_to_pay_row.id).first()
|
||||||
|
available_money = abs(money_to_pay_row.currency_value) - abs(account_remainder_balance)
|
||||||
|
if not available_money > 0:
|
||||||
|
continue
|
||||||
|
if available_money > amount_to_pay:
|
||||||
|
print('NOT All money is spent amount_to_pay:', amount_to_pay, 'debit_row payment_amount:', debit_row.payment_amount)
|
||||||
|
close_payment_book(debit_row, money_to_pay_row, amount_to_pay, session)
|
||||||
|
total_amount_paid += amount_to_pay
|
||||||
|
paid_count += 1
|
||||||
|
payments_made += 1
|
||||||
|
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
||||||
|
break
|
||||||
|
elif available_money <= amount_to_pay:
|
||||||
|
print('All money is spent amount_to_pay:', available_money, 'debit_row payment_amount:', debit_row.payment_amount)
|
||||||
|
close_payment_book(debit_row, money_to_pay_row, available_money, session)
|
||||||
|
total_amount_paid += available_money
|
||||||
|
paid_count += 1
|
||||||
|
payments_made += 1
|
||||||
|
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
print(f"Something else happened available_money: {available_money}, amount_to_pay: {amount_to_pay}")
|
||||||
|
|
||||||
|
update_all_spent_accounts(session)
|
||||||
|
print('Early years decision book payments')
|
||||||
|
print('payments_made', payments_made)
|
||||||
|
print('total_amount_paid', total_amount_paid)
|
||||||
|
print('paid_count', paid_count)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
start_time = perf_counter()
|
||||||
|
|
||||||
|
print("\n===== PROCESSING PAYMENTS =====\n")
|
||||||
|
print("Starting payment processing at:", datetime.now())
|
||||||
|
|
||||||
|
# Process payments for current month first
|
||||||
|
print("\n1. Processing current month payments...")
|
||||||
|
do_payments_of_this_month()
|
||||||
|
|
||||||
|
# Process payments for previous months
|
||||||
|
print("\n2. Processing previous months payments...")
|
||||||
|
do_payments_of_previos_months()
|
||||||
|
|
||||||
|
print("\n===== PAYMENT PROCESSING COMPLETE =====\n")
|
||||||
|
print("Payment processing completed at:", datetime.now())
|
||||||
|
|
||||||
|
# Analyze the payment situation after processing payments
|
||||||
|
print("\n===== ANALYZING PAYMENT SITUATION AFTER PROCESSING =====\n")
|
||||||
|
# analyze_payment_function()
|
||||||
|
|
||||||
|
end_time = perf_counter()
|
||||||
|
print(f"\n{end_time - start_time:.3f} : seconds")
|
||||||
|
|
||||||
|
|
||||||
|
# # Create a subquery to get the sum of payments for each debit's uu_id
|
||||||
|
# # For credit entries, ref_id points to the original debit's uu_id
|
||||||
|
# payment_sums = session.query(
|
||||||
|
# BuildDecisionBookPayments.ref_id.label('original_debt_id'),
|
||||||
|
# func.sum(func.abs(BuildDecisionBookPayments.payment_amount)).label('payment_sum')
|
||||||
|
# ).filter(
|
||||||
|
# BuildDecisionBookPayments.account_is_debit == False # Credit entries only
|
||||||
|
# ).group_by(BuildDecisionBookPayments.ref_id).subquery()
|
||||||
|
|
||||||
|
# # Main query to find debits with their payment sums
|
||||||
|
# query = session.query(BuildDecisionBookPayments)
|
||||||
|
|
||||||
|
# # Join with payment sums - cast uu_id to string to match ref_id type
|
||||||
|
# query = query.outerjoin(
|
||||||
|
# payment_sums,
|
||||||
|
# func.cast(BuildDecisionBookPayments.uu_id, String) == payment_sums.c.original_debt_id
|
||||||
|
# )
|
||||||
|
|
||||||
|
# # Filter for debits of the specified build part and payment type
|
||||||
|
# query = query.filter(
|
||||||
|
# BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
||||||
|
# BuildDecisionBookPayments.payment_types_id == debit_type.id,
|
||||||
|
# BuildDecisionBookPayments.account_is_debit == True, # Debit entries only
|
||||||
|
# )
|
||||||
|
|
||||||
|
# # Apply date filters if provided
|
||||||
|
# if date_query:
|
||||||
|
# for date_filter in date_query:
|
||||||
|
# query = query.filter(date_filter)
|
||||||
|
|
||||||
|
# # Filter for debits that are not fully paid
|
||||||
|
# # (payment_sum < debit_amount or payment_sum is NULL)
|
||||||
|
# query = query.filter(
|
||||||
|
# or_(
|
||||||
|
# payment_sums.c.payment_sum.is_(None),
|
||||||
|
# func.coalesce(payment_sums.c.payment_sum, 0) < func.abs(BuildDecisionBookPayments.payment_amount)
|
||||||
|
# )
|
||||||
|
# )
|
||||||
|
|
||||||
|
# # Execute the query and return the results
|
||||||
|
# results = query.order_by(BuildDecisionBookPayments.process_date).all()
|
||||||
|
|
@ -0,0 +1,199 @@
|
||||||
|
import arrow
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
from datetime import datetime
|
||||||
|
from time import perf_counter
|
||||||
|
from sqlalchemy import select, func, distinct, cast, Date, String, literal, desc, and_, or_, case, join, alias, text
|
||||||
|
from interest_calculate import hesapla_gecikme_faizi
|
||||||
|
from Controllers.Postgres.engine import get_session_factory
|
||||||
|
|
||||||
|
from Schemas import (
|
||||||
|
BuildDecisionBookPayments,
|
||||||
|
AccountRecords,
|
||||||
|
ApiEnumDropdown,
|
||||||
|
Build,
|
||||||
|
BuildDecisionBook,
|
||||||
|
AccountDelayInterest,
|
||||||
|
BuildParts,
|
||||||
|
)
|
||||||
|
|
||||||
|
# from ServicesApi.Schemas.account.account import AccountRecords, AccountDelayInterest
|
||||||
|
# from ServicesApi.Schemas.building.decision_book import BuildDecisionBookPayments, BuildDecisionBook
|
||||||
|
# from ServicesApi.Schemas.building.build import Build, BuildParts
|
||||||
|
# from ServicesApi.Schemas.others.enums import ApiEnumDropdown
|
||||||
|
|
||||||
|
"""
|
||||||
|
BuildDuesTypes BDT-S Service fee (Service fee)
|
||||||
|
BuildDuesTypes BDT-I Information (Information)
|
||||||
|
BuildDuesTypes BDT-D Bina Aidat (Debit)
|
||||||
|
BuildDuesTypes BDT-A Bina Ek Aidat (Add Debit)
|
||||||
|
BuildDuesTypes BDT-R Bina Tadilat (Renovation)
|
||||||
|
BuildDuesTypes BDT-L Bina Yasal Harcama (Lawyer expence)
|
||||||
|
BuildDuesTypes BDT-CL Close Last Period Receipt (Close Last Period Receipt)
|
||||||
|
BuildDuesTypes BDT-OP Open New Period Receipt (Open New Period Receipt)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def joined_decision_book_sql_query(build_decision_book_id: int=49):
|
||||||
|
subquery = select(
|
||||||
|
AccountRecords.build_parts_id,
|
||||||
|
BuildParts.part_code,
|
||||||
|
BuildParts.part_no,
|
||||||
|
AccountRecords.build_decision_book_id,
|
||||||
|
func.sum(AccountRecords.currency_value).label('paid')
|
||||||
|
).select_from(
|
||||||
|
AccountRecords.__table__.join(
|
||||||
|
BuildParts,
|
||||||
|
AccountRecords.build_parts_id == BuildParts.id
|
||||||
|
)
|
||||||
|
).where(
|
||||||
|
AccountRecords.build_decision_book_id == build_decision_book_id
|
||||||
|
).group_by(
|
||||||
|
AccountRecords.build_parts_id,
|
||||||
|
BuildParts.part_code,
|
||||||
|
BuildParts.part_no,
|
||||||
|
AccountRecords.build_decision_book_id
|
||||||
|
).alias('build_parts_to_account_records')
|
||||||
|
|
||||||
|
query = select(
|
||||||
|
subquery.c.build_decision_book_id,
|
||||||
|
subquery.c.build_parts_id,
|
||||||
|
subquery.c.part_code,
|
||||||
|
subquery.c.part_no,
|
||||||
|
subquery.c.paid,
|
||||||
|
func.sum(BuildDecisionBookPayments.payment_amount).label('debt'),
|
||||||
|
(func.sum(BuildDecisionBookPayments.payment_amount) + subquery.c.paid).label('total')
|
||||||
|
).select_from(
|
||||||
|
subquery.join(
|
||||||
|
BuildDecisionBookPayments,
|
||||||
|
(BuildDecisionBookPayments.build_parts_id == subquery.c.build_parts_id) &
|
||||||
|
(BuildDecisionBookPayments.build_decision_book_id == subquery.c.build_decision_book_id)
|
||||||
|
)
|
||||||
|
).group_by(
|
||||||
|
subquery.c.build_decision_book_id,
|
||||||
|
subquery.c.build_parts_id,
|
||||||
|
subquery.c.part_code,
|
||||||
|
subquery.c.part_no,
|
||||||
|
subquery.c.paid
|
||||||
|
).order_by(
|
||||||
|
subquery.c.part_no
|
||||||
|
)
|
||||||
|
return query
|
||||||
|
|
||||||
|
|
||||||
|
def print_query_result_like_dateabse_rows(results, book_id, expiry_starts, expiry_ends):
|
||||||
|
start_time = perf_counter()
|
||||||
|
print(f"\nResults for build_decision_book_id={book_id}:")
|
||||||
|
print(f"Selected decision book starts at: {expiry_starts} - ends at: {expiry_ends}")
|
||||||
|
print("-" * 80)
|
||||||
|
print(f"{'Build ID':<10} {'Parts ID':<10} {'Part Code':<15} {'Part No':<10} {'Paid (odenen)':<15} {'Debt (borc)':<15} {'Total':<15}")
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
for row in results:
|
||||||
|
print(f"{row.build_decision_book_id:<10} {row.build_parts_id:<10} {row.part_code:<15} {row.part_no:<10} {row.paid:<15.2f} {row.debt:<15.2f} {row.total:<15.2f}")
|
||||||
|
|
||||||
|
print("-" * 80)
|
||||||
|
print(f"Total rows: {len(results)}")
|
||||||
|
print(f"Query execution time: {perf_counter() - start_time:.4f} seconds")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
"""
|
||||||
|
Waiting for container to be ready...
|
||||||
|
Running the Python script inside the container...
|
||||||
|
Old book ID: 49
|
||||||
|
New book ID: 50
|
||||||
|
Old book starts at: 2024-07-01 - ends at: 2025-06-30
|
||||||
|
New book starts at: 2025-07-01 - ends at: 2026-06-30
|
||||||
|
Done! Check the output above.
|
||||||
|
"""
|
||||||
|
session_factory = get_session_factory()
|
||||||
|
session = session_factory()
|
||||||
|
start_time = perf_counter()
|
||||||
|
|
||||||
|
Build.set_session(session)
|
||||||
|
BuildDecisionBook.set_session(session)
|
||||||
|
|
||||||
|
build_decision_book_id, timezone = 49, "Europe/Istanbul"
|
||||||
|
selected_decision_book = BuildDecisionBook.query.filter_by(id=build_decision_book_id).first()
|
||||||
|
|
||||||
|
old_expiry_starts = arrow.get(selected_decision_book.expiry_starts).to(timezone).date()
|
||||||
|
old_expiry_ends = arrow.get(selected_decision_book.expiry_ends).to(timezone).date()
|
||||||
|
old_book_id = selected_decision_book.id
|
||||||
|
|
||||||
|
results = BuildDecisionBook.query.filter(
|
||||||
|
BuildDecisionBook.build_id == selected_decision_book.build_id,
|
||||||
|
BuildDecisionBook.expiry_ends > old_expiry_ends).order_by(BuildDecisionBook.expiry_ends.asc()
|
||||||
|
).all()
|
||||||
|
new_book = results[0] if len(results) > 0 else None
|
||||||
|
if not new_book:
|
||||||
|
raise ValueError(f"New book not found after => {build_decision_book_id}. Contant your admin for future updates")
|
||||||
|
|
||||||
|
new_expiry_starts = arrow.get(new_book.expiry_starts).to(timezone).date()
|
||||||
|
new_expiry_ends = arrow.get(new_book.expiry_ends).to(timezone).date()
|
||||||
|
new_book_id = new_book.id
|
||||||
|
|
||||||
|
joined_query = joined_decision_book_sql_query(build_decision_book_id=build_decision_book_id)
|
||||||
|
results = session.execute(joined_query).fetchall()
|
||||||
|
print_query_result_like_dateabse_rows(results=results, book_id=old_book_id, expiry_starts=old_expiry_starts, expiry_ends=old_expiry_ends)
|
||||||
|
|
||||||
|
for result in results:
|
||||||
|
if result.total > 0:
|
||||||
|
print(f'User has extra money in old book {result.total} | build part id : {result.build_parts_id} | part code : {result.part_code} | part no : {result.part_no}')
|
||||||
|
elif result.total < 0:
|
||||||
|
print(f'User has debt in new book {result.total} | build part id : {result.build_parts_id} | part code : {result.part_code} | part no : {result.part_no}')
|
||||||
|
else:
|
||||||
|
print(f'User has no debt or extra money in old book {result.total} | build part id : {result.build_parts_id} | part code : {result.part_code} | part no : {result.part_no}')
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
# print(f"Old book ID: {old_book_id}")
|
||||||
|
# print(f"New book ID: {new_book_id}")
|
||||||
|
# print(f"New book starts at: {new_expiry_starts} - ends at: {new_expiry_ends}")
|
||||||
|
|
||||||
|
# old_account_records = session.query(func.sum(AccountRecords.currency_value) - func.sum(BuildDecisionBookPayments.payment_amount)).filter(
|
||||||
|
# old_expiry_starts <= cast(AccountRecords.bank_date, Date), old_expiry_ends >= cast(AccountRecords.bank_date, Date),
|
||||||
|
# AccountRecords.currency_value > 0,
|
||||||
|
# func.abs(AccountRecords.currency_value) > func.abs(BuildDecisionBookPayments.payment_amount)
|
||||||
|
# ).scalar()
|
||||||
|
# new_account_records = session.query(func.sum(AccountRecords.currency_value) - func.sum(BuildDecisionBookPayments.payment_amount)).filter(
|
||||||
|
# new_expiry_starts <= cast(AccountRecords.bank_date, Date), new_expiry_ends >= cast(AccountRecords.bank_date, Date),
|
||||||
|
# AccountRecords.currency_value > 0,
|
||||||
|
# func.abs(AccountRecords.currency_value) > func.abs(BuildDecisionBookPayments.payment_amount)
|
||||||
|
# ).scalar()
|
||||||
|
# print(f"Old account records: {old_account_records:,.2f}")
|
||||||
|
# print(f"New account records: {new_account_records:,.2f}")
|
||||||
|
|
||||||
|
# build_to_iterate = Build.query.filter_by(id=build_id).first()
|
||||||
|
|
||||||
|
# iterate_decision_books = BuildDecisionBook.query.filter_by(build_id=build_to_iterate.id).all()
|
||||||
|
|
||||||
|
# for decision_book_to_iterate in iterate_decision_books:
|
||||||
|
# joined_query = joined_decision_book_sql_query(build_decision_book_id=decision_book_to_iterate.id)
|
||||||
|
# print(f"Build Decision Book ID: {decision_book_to_iterate.id}")
|
||||||
|
# results = session.execute(joined_query).fetchall()
|
||||||
|
|
||||||
|
# print(f"\nResults for build_decision_book_id={decision_book_to_iterate.id}:")
|
||||||
|
# print("-" * 80)
|
||||||
|
# print(f"{'Build ID':<10} {'Parts ID':<10} {'Part Code':<15} {'Part No':<10} {'Paid (odenen)':<15} {'Debt (borc)':<15}")
|
||||||
|
# print("-" * 80)
|
||||||
|
|
||||||
|
# for row in results:
|
||||||
|
# print(f"{row.build_decision_book_id:<10} {row.build_parts_id:<10} {row.part_code:<15} {row.part_no:<10} {row.odenen:<15.2f} {row.borc:<15.2f}")
|
||||||
|
|
||||||
|
# print("-" * 80)
|
||||||
|
# print(f"Total rows: {len(results)}")
|
||||||
|
# print(f"Query execution time: {perf_counter() - start_time:.4f} seconds")
|
||||||
|
|
||||||
|
# print(f"\nResults for build_decision_book_id={build_decision_book_id}:")
|
||||||
|
# print(f"Selected decision book starts at: {old_expiry_starts} - ends at: {old_expiry_ends}")
|
||||||
|
# print("-" * 80)
|
||||||
|
# print(f"{'Build ID':<10} {'Parts ID':<10} {'Part Code':<15} {'Part No':<10} {'Paid (odenen)':<15} {'Debt (borc)':<15}")
|
||||||
|
# print("-" * 80)
|
||||||
|
|
||||||
|
# for row in results:
|
||||||
|
# print(f"{row.build_decision_book_id:<10} {row.build_parts_id:<10} {row.part_code:<15} {row.part_no:<10} {row.odenen:<15.2f} {row.borc:<15.2f}")
|
||||||
|
|
||||||
|
# print("-" * 80)
|
||||||
|
# print(f"Total rows: {len(results)}")
|
||||||
|
# print(f"Query execution time: {perf_counter() - start_time:.4f} seconds")
|
||||||
|
|
@ -0,0 +1,492 @@
|
||||||
|
import arrow
|
||||||
|
import calendar
|
||||||
|
import time
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from Schemas import BuildDecisionBookPayments, AccountRecords, ApiEnumDropdown, Build, BuildDecisionBook, AccountDelayInterest
|
||||||
|
from time import perf_counter
|
||||||
|
from sqlalchemy import select, func, distinct, cast, Date, String, literal, desc, and_, or_, case
|
||||||
|
from Controllers.Postgres.engine import get_session_factory
|
||||||
|
from interest_calculate import hesapla_gecikme_faizi
|
||||||
|
# from ServicesApi.Schemas.account.account import AccountRecords, AccountDelayInterest
|
||||||
|
# from ServicesApi.Schemas.building.decision_book import BuildDecisionBookPayments
|
||||||
|
|
||||||
|
|
||||||
|
def find_last_day_of_month(date_value):
|
||||||
|
today = date_value.date()
|
||||||
|
_, last_day = calendar.monthrange(today.year, today.month)
|
||||||
|
return datetime(today.year, today.month, last_day, 23, 59, 59)
|
||||||
|
|
||||||
|
|
||||||
|
def find_first_day_of_month(date_value):
|
||||||
|
today = date_value.date()
|
||||||
|
return datetime(today.year, today.month, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def add_month_to_date(date_value):
|
||||||
|
month_to_process = date_value.month
|
||||||
|
year_to_process = date_value.year
|
||||||
|
if date_value.month == 12:
|
||||||
|
month_to_process = 1
|
||||||
|
year_to_process += 1
|
||||||
|
else:
|
||||||
|
month_to_process += 1
|
||||||
|
_, last_day = calendar.monthrange(year_to_process, month_to_process)
|
||||||
|
return datetime(year_to_process, month_to_process, 1), datetime(year_to_process, month_to_process, last_day)
|
||||||
|
|
||||||
|
|
||||||
|
class BuildDuesTypes:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.debit: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.add_debit: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.renovation: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.lawyer_expence: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.service_fee: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.information: ApiEnumDropdownShallowCopy = None
|
||||||
|
|
||||||
|
|
||||||
|
class ApiEnumDropdownShallowCopy:
|
||||||
|
|
||||||
|
id: int
|
||||||
|
uuid: str
|
||||||
|
enum_class: str
|
||||||
|
key: str
|
||||||
|
value: str
|
||||||
|
|
||||||
|
def __init__(self, id: int, uuid: str, enum_class: str, key: str, value: str):
|
||||||
|
self.id = id
|
||||||
|
self.uuid = uuid
|
||||||
|
self.enum_class = enum_class
|
||||||
|
self.key = key
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
|
||||||
|
def retrieve_remainder_balance_set_if_needed(account_record_id: int, session):
|
||||||
|
"""Update the remainder_balance of an account after spending money.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
account_record: The account record to update
|
||||||
|
amount_spent: The amount spent in this transaction
|
||||||
|
session: Database session
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if all money is spent, False otherwise
|
||||||
|
"""
|
||||||
|
AccountRecords.set_session(session)
|
||||||
|
|
||||||
|
account_record_remainder_balance = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter(
|
||||||
|
BuildDecisionBookPayments.account_records_id == account_record_id,
|
||||||
|
BuildDecisionBookPayments.account_is_debit == False, BuildDecisionBookPayments.active == True,
|
||||||
|
).scalar()
|
||||||
|
|
||||||
|
if not account_record_remainder_balance:
|
||||||
|
account_record_remainder_balance = 0
|
||||||
|
|
||||||
|
account_record = AccountRecords.query.filter_by(id=account_record_id).first()
|
||||||
|
account_record.remainder_balance = -1 * abs(account_record_remainder_balance) if account_record_remainder_balance != 0 else 0
|
||||||
|
account_record.save()
|
||||||
|
return account_record_remainder_balance
|
||||||
|
|
||||||
|
|
||||||
|
def retrieve_current_debt_to_pay_from_database(ref_id: int, session):
|
||||||
|
debt_to_pay = session.query(func.sum(BuildDecisionBookPayments.payment_amount)).filter(BuildDecisionBookPayments.ref_id == ref_id, BuildDecisionBookPayments.active == True).scalar()
|
||||||
|
return abs(debt_to_pay)
|
||||||
|
|
||||||
|
|
||||||
|
def check_current_debt_to_pay_from_database_is_closed(ref_id: int, session):
|
||||||
|
session.commit()
|
||||||
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
payment_row_book = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.ref_id == str(ref_id),
|
||||||
|
BuildDecisionBookPayments.account_is_debit == True, BuildDecisionBookPayments.active == True).first()
|
||||||
|
debt_paid = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter(
|
||||||
|
BuildDecisionBookPayments.ref_id == str(ref_id), BuildDecisionBookPayments.account_is_debit == False, BuildDecisionBookPayments.active == True
|
||||||
|
).scalar()
|
||||||
|
payment_row_book.debt_paid = abs(debt_paid) if debt_paid else 0 # Debt Reminder is how much money is needed to close record
|
||||||
|
payment_row_book.debt_to_pay = abs(payment_row_book.payment_amount) - abs(payment_row_book.debt_paid) # Debt To Pay is how much money is needed to close record
|
||||||
|
payment_row_book.is_closed = payment_row_book.debt_to_pay == 0
|
||||||
|
payment_row_book.save()
|
||||||
|
|
||||||
|
|
||||||
|
def close_payment_book(payment_row_book, account_record, value, session):
|
||||||
|
"""Create a credit entry in BuildDecisionBookPayments to close a debt.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
payment_row_book: The debit entry to be paid
|
||||||
|
account_record: The account record containing the funds
|
||||||
|
value: The amount to pay
|
||||||
|
session: Database session
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The newly created payment record
|
||||||
|
"""
|
||||||
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
account_record_remainder_balance = retrieve_remainder_balance_set_if_needed(account_record_id=account_record.id, session=session)
|
||||||
|
print(f'NOT Updated remainder balance: {account_record_remainder_balance} | Account record id: {account_record.id}')
|
||||||
|
|
||||||
|
# Create a new credit entry (payment)
|
||||||
|
new_row = BuildDecisionBookPayments.create(
|
||||||
|
ref_id=str(payment_row_book.uu_id),
|
||||||
|
payment_plan_time_periods=payment_row_book.payment_plan_time_periods,
|
||||||
|
period_time=payment_row_book.period_time,
|
||||||
|
currency=payment_row_book.currency,
|
||||||
|
account_records_id=account_record.id,
|
||||||
|
account_records_uu_id=str(account_record.uu_id),
|
||||||
|
build_parts_id=payment_row_book.build_parts_id,
|
||||||
|
build_parts_uu_id=str(payment_row_book.build_parts_uu_id),
|
||||||
|
payment_amount=abs(value), # Negative for credit entries
|
||||||
|
payment_types_id=payment_row_book.payment_types_id,
|
||||||
|
payment_types_uu_id=str(payment_row_book.payment_types_uu_id),
|
||||||
|
process_date_m=payment_row_book.process_date.month,
|
||||||
|
process_date_y=payment_row_book.process_date.year,
|
||||||
|
process_date=payment_row_book.process_date,
|
||||||
|
build_decision_book_item_id=payment_row_book.build_decision_book_item_id if payment_row_book.build_decision_book_item_id else None,
|
||||||
|
build_decision_book_item_uu_id=str(payment_row_book.build_decision_book_item_uu_id) if payment_row_book.build_decision_book_item_uu_id else None,
|
||||||
|
decision_book_project_id=payment_row_book.decision_book_project_id if payment_row_book.decision_book_project_id else None,
|
||||||
|
decision_book_project_uu_id=str(payment_row_book.decision_book_project_uu_id) if payment_row_book.decision_book_project_uu_id else None,
|
||||||
|
build_decision_book_id=payment_row_book.build_decision_book_id if payment_row_book.build_decision_book_id else None,
|
||||||
|
build_decision_book_uu_id=str(payment_row_book.build_decision_book_uu_id) if payment_row_book.build_decision_book_uu_id else None,
|
||||||
|
is_confirmed=True,
|
||||||
|
active=True,
|
||||||
|
account_is_debit=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save the new payment record
|
||||||
|
saved_row = new_row.save()
|
||||||
|
session.commit()
|
||||||
|
session.refresh(saved_row)
|
||||||
|
account_record_remainder_balance = retrieve_remainder_balance_set_if_needed(account_record_id=account_record.id, session=session)
|
||||||
|
print(f'Updated remainder balance: {account_record_remainder_balance} | Account record id: {account_record.id}')
|
||||||
|
check_current_debt_to_pay_from_database_is_closed(ref_id=new_row.ref_id, session=session)
|
||||||
|
return account_record_remainder_balance
|
||||||
|
|
||||||
|
|
||||||
|
def get_enums_from_database():
|
||||||
|
build_dues_types = BuildDuesTypes()
|
||||||
|
with ApiEnumDropdown.new_session() as session:
|
||||||
|
|
||||||
|
ApiEnumDropdown.set_session(session)
|
||||||
|
|
||||||
|
debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-D").first() # Debit
|
||||||
|
add_debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-A").first() # Add Debit
|
||||||
|
renovation_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-R").first() # Renovation
|
||||||
|
late_payment_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-L").first() # Lawyer expence
|
||||||
|
service_fee_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-S").first() # Service fee
|
||||||
|
information_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-I").first() # Information
|
||||||
|
# close_last_period_receipt_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-CL").first() # Close Last Period Receipt
|
||||||
|
# open_new_period_receipt_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-OP").first() # Open New Period Receipt
|
||||||
|
|
||||||
|
build_dues_types.debit = ApiEnumDropdownShallowCopy(
|
||||||
|
debit_enum_shallow.id, str(debit_enum_shallow.uu_id), debit_enum_shallow.enum_class, debit_enum_shallow.key, debit_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.add_debit = ApiEnumDropdownShallowCopy(
|
||||||
|
add_debit_enum_shallow.id, str(add_debit_enum_shallow.uu_id), add_debit_enum_shallow.enum_class, add_debit_enum_shallow.key, add_debit_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.renovation = ApiEnumDropdownShallowCopy(
|
||||||
|
renovation_enum_shallow.id, str(renovation_enum_shallow.uu_id), renovation_enum_shallow.enum_class, renovation_enum_shallow.key, renovation_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.lawyer_expence = ApiEnumDropdownShallowCopy(
|
||||||
|
late_payment_enum_shallow.id, str(late_payment_enum_shallow.uu_id), late_payment_enum_shallow.enum_class, late_payment_enum_shallow.key, late_payment_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.service_fee = ApiEnumDropdownShallowCopy(
|
||||||
|
service_fee_enum_shallow.id, str(service_fee_enum_shallow.uu_id), service_fee_enum_shallow.enum_class, service_fee_enum_shallow.key, service_fee_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.information = ApiEnumDropdownShallowCopy(
|
||||||
|
information_enum_shallow.id, str(information_enum_shallow.uu_id), information_enum_shallow.enum_class, information_enum_shallow.key, information_enum_shallow.value
|
||||||
|
)
|
||||||
|
# build_dues_types.close_last_period_receipt = ApiEnumDropdownShallowCopy(
|
||||||
|
# close_last_period_receipt_enum_shallow.id, str(close_last_period_receipt_enum_shallow.uu_id), close_last_period_receipt_enum_shallow.enum_class, close_last_period_receipt_enum_shallow.key, close_last_period_receipt_enum_shallow.value
|
||||||
|
# )
|
||||||
|
# build_dues_types.open_new_period_receipt = ApiEnumDropdownShallowCopy(
|
||||||
|
# open_new_period_receipt_enum_shallow.id, str(open_new_period_receipt_enum_shallow.uu_id), open_new_period_receipt_enum_shallow.enum_class, open_new_period_receipt_enum_shallow.key, open_new_period_receipt_enum_shallow.value
|
||||||
|
# )
|
||||||
|
return [
|
||||||
|
build_dues_types.debit,
|
||||||
|
build_dues_types.lawyer_expence,
|
||||||
|
build_dues_types.add_debit,
|
||||||
|
build_dues_types.renovation,
|
||||||
|
build_dues_types.service_fee,
|
||||||
|
build_dues_types.information,
|
||||||
|
# build_dues_types.close_last_period_receipt,
|
||||||
|
# build_dues_types.open_new_period_receipt
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def do_payments(session, build_id: int, work_date: datetime):
|
||||||
|
"""Process payments for the current month's unpaid debts.
|
||||||
|
This function retrieves account records with available funds and processes
|
||||||
|
payments for current month's unpaid debts in order of payment type priority.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Set session for all models
|
||||||
|
AccountRecords.set_session(session)
|
||||||
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
Build.set_session(session)
|
||||||
|
BuildDecisionBook.set_session(session)
|
||||||
|
|
||||||
|
today = datetime.now()
|
||||||
|
|
||||||
|
# Get payment types in priority order
|
||||||
|
payment_type_list = get_enums_from_database()
|
||||||
|
fund_finished = lambda money_spend, money_in_account: money_spend == money_in_account
|
||||||
|
payments_made, total_amount_paid, paid_count = 0, 0, 0
|
||||||
|
target_build = Build.query.filter(Build.id == build_id).first()
|
||||||
|
if not target_build:
|
||||||
|
raise ValueError(f"Build with id {build_id} not found")
|
||||||
|
|
||||||
|
decision_book = BuildDecisionBook.query.filter(
|
||||||
|
BuildDecisionBook.build_id == build_id, cast(BuildDecisionBook.expiry_starts, Date) <= work_date.date(),
|
||||||
|
cast(BuildDecisionBook.expiry_ends, Date) >= work_date.date(), BuildDecisionBook.decision_type == "RBM",
|
||||||
|
BuildDecisionBook.active == True
|
||||||
|
).first()
|
||||||
|
if not decision_book:
|
||||||
|
raise ValueError(f"Decision book not found for build with id {build_id}")
|
||||||
|
|
||||||
|
period_date_start = decision_book.expiry_starts
|
||||||
|
period_date_end = decision_book.expiry_ends
|
||||||
|
# period_id = decision_book.id
|
||||||
|
|
||||||
|
first_date_of_process_date = find_first_day_of_month(datetime(work_date.year, work_date.month, 1))
|
||||||
|
last_date_of_process_date = find_last_day_of_month(datetime(work_date.year, work_date.month, 1))
|
||||||
|
if work_date.month == today.month and work_date.year == today.year:
|
||||||
|
last_date_of_process_date = today
|
||||||
|
|
||||||
|
# Current month date filter
|
||||||
|
date_query_tuple = (
|
||||||
|
cast(BuildDecisionBookPayments.process_date, Date) >= first_date_of_process_date.date(),
|
||||||
|
cast(BuildDecisionBookPayments.process_date, Date) <= last_date_of_process_date.date()
|
||||||
|
)
|
||||||
|
date_query_account_tuple = (
|
||||||
|
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(),
|
||||||
|
cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date()
|
||||||
|
)
|
||||||
|
|
||||||
|
for payment_type in payment_type_list:
|
||||||
|
query_of_payments = BuildDecisionBookPayments.query.filter(*date_query_tuple)
|
||||||
|
query_of_payments = query_of_payments.filter(
|
||||||
|
BuildDecisionBookPayments.payment_types_id == payment_type.id, BuildDecisionBookPayments.account_is_debit == True,
|
||||||
|
BuildDecisionBookPayments.is_closed == False, BuildDecisionBookPayments.active == True,
|
||||||
|
).order_by(BuildDecisionBookPayments.process_date.asc(), BuildDecisionBookPayments.debt_to_pay.desc()).all()
|
||||||
|
# priority_uuids = [payment_type.uuid, *[pt.uuid for pt in payment_type_list if pt.uuid != payment_type.uuid]]
|
||||||
|
# case_order = case(*[(AccountRecords.payment_result_type_uu_id == uuid, index) for index, uuid in enumerate(priority_uuids)], else_=len(priority_uuids))
|
||||||
|
|
||||||
|
for payment_row in query_of_payments:
|
||||||
|
money_to_pay_rows = AccountRecords.query.filter(
|
||||||
|
AccountRecords.build_parts_id == payment_row.build_parts_id, AccountRecords.payment_result_type == payment_type.id, AccountRecords.active == True,
|
||||||
|
AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance), *date_query_account_tuple,
|
||||||
|
).order_by(AccountRecords.bank_date.asc(), AccountRecords.remainder_balance.desc()).all()
|
||||||
|
for money_to_pay_row in money_to_pay_rows:
|
||||||
|
# Get remainder balance from database regardless trust over AccountRecords row from first query
|
||||||
|
remainder_balance_from_database = retrieve_remainder_balance_set_if_needed(money_to_pay_row.id, session)
|
||||||
|
available_funds = abs(money_to_pay_row.currency_value) - abs(remainder_balance_from_database)
|
||||||
|
# BuildDecisionBookPayments must be refreshed to check is there still money to be paid for this row
|
||||||
|
debt_to_pay = retrieve_current_debt_to_pay_from_database(ref_id=payment_row.ref_id, session=session)
|
||||||
|
if debt_to_pay == 0:
|
||||||
|
break # For session caused errors double check the database FOR multi process actions
|
||||||
|
if abs(available_funds) > abs(debt_to_pay):
|
||||||
|
payments_made += 1
|
||||||
|
total_amount_paid += debt_to_pay
|
||||||
|
paid_count += 1
|
||||||
|
close_payment_book(payment_row, money_to_pay_row, abs(debt_to_pay), session)
|
||||||
|
break # More fund to spend so go to next BuildDecisionBookPayments
|
||||||
|
else:
|
||||||
|
payments_made += 1
|
||||||
|
total_amount_paid += available_funds
|
||||||
|
paid_count += 1
|
||||||
|
close_payment_book(payment_row, money_to_pay_row, abs(available_funds), session)
|
||||||
|
continue # Fund has finished so go to next AccountRecords
|
||||||
|
|
||||||
|
print('payments_made', payments_made)
|
||||||
|
print('total_amount_paid', total_amount_paid)
|
||||||
|
print('paid_count', paid_count)
|
||||||
|
session.close()
|
||||||
|
session_factory.remove() # Clean up the session from the registry
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_interest_from_date_paid_to_date_debt_to_pay(session, payment_value, payment, record):
|
||||||
|
"""Calculate interest from date paid to date debt to pay."""
|
||||||
|
AccountDelayInterest.set_session(session)
|
||||||
|
today = datetime.now()
|
||||||
|
calculated_interest = hesapla_gecikme_faizi(borc=payment_value, vade_tarihi=payment.process_date, islem_tarihi=today, faiz_turu="gecikme")
|
||||||
|
if not calculated_interest.toplam > 0:
|
||||||
|
return None
|
||||||
|
print('interest', dict(
|
||||||
|
debt_to_pay=payment_value, payment_process_date=str(payment.process_date.date()), record_bank_date=str(record.bank_date.date()),
|
||||||
|
available_money=abs(record.currency_value) - abs(record.remainder_balance), account_records_id=record.id, build_decision_book_payment_id=payment.id,
|
||||||
|
new_build_decision_book_payment_id=payment.build_decision_book_id, interest_rate=calculated_interest.gunluk_oran, delay_day=calculated_interest.gecikme_gunu,
|
||||||
|
daily_rate=calculated_interest.gunluk_oran, interest=calculated_interest.faiz, bsmv=calculated_interest.bsmv, kkdf=calculated_interest.kkdf,
|
||||||
|
total=calculated_interest.toplam,
|
||||||
|
))
|
||||||
|
# new_row_account_delay_interest = AccountDelayInterest.create(
|
||||||
|
# account_records_id=record.id, account_records_uu_id=str(record.uu_id), build_decision_book_payment_id=payment.id, build_decision_book_payment_uu_id=str(payment.uu_id),
|
||||||
|
# new_build_decision_book_payment_id=payment.build_decision_book_id, new_build_decision_book_payment_uu_id=str(payment.build_decision_book_uu_id),
|
||||||
|
# interest_turn="gecikme", interest_rate=calculated_interest.gunluk_oran, delay_day=calculated_interest.gecikme_gunu, daily_rate=calculated_interest.gunluk_oran,
|
||||||
|
# interest=calculated_interest.faiz, bsmv=calculated_interest.bsmv, kkdf=calculated_interest.kkdf, total=calculated_interest.toplam,
|
||||||
|
# )
|
||||||
|
# new_row_account_delay_interest.save()
|
||||||
|
# session.commit()
|
||||||
|
# session.refresh(new_row_account_delay_interest)
|
||||||
|
# return new_row_account_delay_interest
|
||||||
|
|
||||||
|
|
||||||
|
def do_payments_of_overdue_payments(session, build_id: int):
|
||||||
|
"""Process payments for the previous month's unpaid debts.
|
||||||
|
This function retrieves account records with available funds and processes
|
||||||
|
payments for previous month's unpaid debts in order of payment type priority.
|
||||||
|
"""
|
||||||
|
session_factory = get_session_factory()
|
||||||
|
session = session_factory()
|
||||||
|
|
||||||
|
# Set session for all models
|
||||||
|
AccountRecords.set_session(session)
|
||||||
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
Build.set_session(session)
|
||||||
|
BuildDecisionBook.set_session(session)
|
||||||
|
|
||||||
|
# Get payment types in priority order
|
||||||
|
payment_type_list = get_enums_from_database()
|
||||||
|
fund_finished = lambda money_spend, money_in_account: money_spend == money_in_account
|
||||||
|
payments_made, total_amount_paid, paid_count = 0, 0, 0
|
||||||
|
|
||||||
|
# first_date_of_process_date = find_first_day_of_month(datetime(period_date_start.year, period_date_start.month, 1))
|
||||||
|
# last_date_of_process_date = find_first_day_of_month(datetime(now.year, now.month - 1, 1))
|
||||||
|
# print('first_date_of_process_date', first_date_of_process_date)
|
||||||
|
# print('last_date_of_process_date', last_date_of_process_date)
|
||||||
|
|
||||||
|
# # Current month date filter
|
||||||
|
# date_query_tuple = (
|
||||||
|
# cast(BuildDecisionBookPayments.process_date, Date) >= first_date_of_process_date.date(), cast(BuildDecisionBookPayments.process_date, Date) <= last_date_of_process_date.date()
|
||||||
|
# )
|
||||||
|
|
||||||
|
priority_uuids = [pt.uuid for pt in payment_type_list]
|
||||||
|
case_order = case(*[(AccountRecords.payment_result_type_uu_id == str(uuid), index) for index, uuid in enumerate(priority_uuids)], else_=len(priority_uuids))
|
||||||
|
case_order_payment = case(*[(BuildDecisionBookPayments.payment_types_uu_id == str(uuid), index) for index, uuid in enumerate(priority_uuids)], else_=len(priority_uuids))
|
||||||
|
# for payment_type in payment_type_list:
|
||||||
|
query_of_payments = BuildDecisionBookPayments.query.filter(
|
||||||
|
BuildDecisionBookPayments.account_is_debit == True, BuildDecisionBookPayments.active == True,
|
||||||
|
cast(BuildDecisionBookPayments.process_date, Date) < find_first_day_of_month(today)
|
||||||
|
).order_by(
|
||||||
|
# Order by process_date in ascending order (oldest first)
|
||||||
|
cast(BuildDecisionBookPayments.process_date, Date).asc(), BuildDecisionBookPayments.payment_amount.desc(),
|
||||||
|
case_order_payment.asc(),
|
||||||
|
).all()
|
||||||
|
for payment_row in query_of_payments:
|
||||||
|
# Get the payment_row's process_date to find closest bank_date in AccountRecords
|
||||||
|
# First get all eligible records
|
||||||
|
money_to_pay_rows = AccountRecords.query.filter(
|
||||||
|
AccountRecords.build_parts_id == payment_row.build_parts_id,
|
||||||
|
# AccountRecords.payment_result_type == payment_type.id,
|
||||||
|
AccountRecords.active == True,
|
||||||
|
AccountRecords.currency_value > 0,
|
||||||
|
func.abs(AccountRecords.currency_value) > func.abs(AccountRecords.remainder_balance),
|
||||||
|
# Filter for bank_dates that are valid for this payment
|
||||||
|
# cast(AccountRecords.bank_date, Date) <= arrow.get(payment_row.process_date).to("Europe/Istanbul").date()
|
||||||
|
).order_by(
|
||||||
|
# Order by bank_date in ascending order (oldest first)
|
||||||
|
cast(AccountRecords.bank_date, Date).asc(),
|
||||||
|
case_order.asc(),
|
||||||
|
).all()
|
||||||
|
|
||||||
|
# Sort the results in Python by the absolute difference between dates
|
||||||
|
if money_to_pay_rows:
|
||||||
|
payment_date = payment_row.process_date.date() if hasattr(payment_row.process_date, 'date') else payment_row.process_date
|
||||||
|
money_to_pay_rows.sort(key=lambda x: abs((x.bank_date.date() if hasattr(x.bank_date, 'date') else x.bank_date) - payment_date))
|
||||||
|
for money_to_pay_row in money_to_pay_rows:
|
||||||
|
# Get remainder balance from database regardless trust over AccountRecords row from first query
|
||||||
|
remainder_balance_from_database = retrieve_remainder_balance_set_if_needed(money_to_pay_row.id, session)
|
||||||
|
available_funds = abs(money_to_pay_row.currency_value) - abs(remainder_balance_from_database)
|
||||||
|
# BuildDecisionBookPayments must be refreshed to check is there still money to be paid for this row
|
||||||
|
debt_to_pay = retrieve_current_debt_to_pay_from_database(ref_id=payment_row.ref_id, session=session)
|
||||||
|
if debt_to_pay == 0:
|
||||||
|
break # For session caused errors double check the database FOR multi process actions
|
||||||
|
if abs(available_funds) > abs(debt_to_pay):
|
||||||
|
print('More Money than debt to pay ------------------------------------------------------------------------------------------------------------------------')
|
||||||
|
payments_made += 1
|
||||||
|
total_amount_paid += debt_to_pay
|
||||||
|
paid_count += 1
|
||||||
|
close_payment_book(payment_row, money_to_pay_row, abs(debt_to_pay), session)
|
||||||
|
calculate_interest_from_date_paid_to_date_debt_to_pay(session=session, payment_value=abs(debt_to_pay), payment=payment_row, record=money_to_pay_row)
|
||||||
|
break # More fund to spend so go to next BuildDecisionBookPayments
|
||||||
|
else:
|
||||||
|
print('All Money is spent ---------------------------------------------------------------------------------------------------------------------------------')
|
||||||
|
payments_made += 1
|
||||||
|
total_amount_paid += available_funds
|
||||||
|
paid_count += 1
|
||||||
|
close_payment_book(payment_row, money_to_pay_row, abs(available_funds), session)
|
||||||
|
calculate_interest_from_date_paid_to_date_debt_to_pay(session=session, payment_value=abs(available_funds), payment=payment_row, record=money_to_pay_row)
|
||||||
|
continue # Fund has finished so go to next AccountRecords
|
||||||
|
print('payments_made', payments_made)
|
||||||
|
print('total_amount_paid', total_amount_paid)
|
||||||
|
print('paid_count', paid_count)
|
||||||
|
|
||||||
|
|
||||||
|
def do_regular_monthly_payers_payment_function(session, build_id: int, period_count: int = 1, skip_count: int = 0):
|
||||||
|
today = datetime.now()
|
||||||
|
Build.set_session(session)
|
||||||
|
BuildDecisionBook.set_session(session)
|
||||||
|
|
||||||
|
target_build = Build.query.filter(Build.id == build_id).first()
|
||||||
|
if not target_build:
|
||||||
|
raise ValueError(f"Build with id {build_id} not found")
|
||||||
|
decision_books = BuildDecisionBook.query.filter(
|
||||||
|
BuildDecisionBook.build_id == build_id, cast(BuildDecisionBook.expiry_starts, Date) <= today.date(), BuildDecisionBook.active == True,
|
||||||
|
).order_by(BuildDecisionBook.expiry_starts.desc()).limit(period_count + skip_count).offset(skip_count).all()
|
||||||
|
if not decision_books:
|
||||||
|
raise ValueError(f"Decision book not found for build with id {build_id}")
|
||||||
|
if skip_count > 0:
|
||||||
|
decision_books = decision_books[skip_count:]
|
||||||
|
first_decision_book = decision_books[-1]
|
||||||
|
work_date = find_first_day_of_month(first_decision_book.expiry_starts)
|
||||||
|
while work_date <= today:
|
||||||
|
do_payments(session=session, build_id=build_id, work_date=work_date)
|
||||||
|
work_date, _ = add_month_to_date(work_date)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
today, build_id, start_time = datetime.now(), 1, perf_counter()
|
||||||
|
|
||||||
|
session_factory = get_session_factory()
|
||||||
|
session = session_factory()
|
||||||
|
|
||||||
|
print("\n===== PROCESSING PAYMENTS =====\n")
|
||||||
|
print("Starting payment processing at:", str(today))
|
||||||
|
|
||||||
|
# Process payments for current month first
|
||||||
|
print("\n2. Processing current month payments...")
|
||||||
|
do_payments(session=session, build_id=build_id, work_date=today)
|
||||||
|
|
||||||
|
# Do regular monthly payers payments
|
||||||
|
print("\n1. Processing regular monthly payers payments...")
|
||||||
|
do_regular_monthly_payers_payment_function(session=session, build_id=build_id, period_count=4, skip_count=0)
|
||||||
|
|
||||||
|
# Commit and flush session to prevent session caused errors
|
||||||
|
session.commit()
|
||||||
|
session.flush()
|
||||||
|
|
||||||
|
# Process payments for previous months
|
||||||
|
print("\n2. Processing previous months payments...")
|
||||||
|
total_amount_of_available_funds = session.query(func.sum(AccountRecords.currency_value)).filter(
|
||||||
|
AccountRecords.currency_value > 0, func.abs(AccountRecords.currency_value) > func.abs(AccountRecords.remainder_balance),
|
||||||
|
AccountRecords.build_parts_id.isnot(None), AccountRecords.active == True
|
||||||
|
).scalar()
|
||||||
|
print(f"Total amount of available funds: {total_amount_of_available_funds:,.2f}")
|
||||||
|
total_amount_of_debt_to_pay = session.query(func.sum(BuildDecisionBookPayments.debt_to_pay)).filter(
|
||||||
|
BuildDecisionBookPayments.debt_to_pay > 0,
|
||||||
|
BuildDecisionBookPayments.account_is_debit == True,
|
||||||
|
BuildDecisionBookPayments.active == True
|
||||||
|
).scalar()
|
||||||
|
print(f"Total amount of debt to pay: {total_amount_of_debt_to_pay:,.2f}")
|
||||||
|
|
||||||
|
if total_amount_of_available_funds - total_amount_of_debt_to_pay > 0:
|
||||||
|
do_payments_of_overdue_payments(session=session, build_id=build_id)
|
||||||
|
print("\n===== PAYMENT PROCESSING COMPLETE =====\n")
|
||||||
|
|
||||||
|
# Analyze the payment situation after processing payments
|
||||||
|
print("\n===== ANALYZING PAYMENT SITUATION AFTER PROCESSING =====\n")
|
||||||
|
# analyze_payment_function()
|
||||||
|
|
||||||
|
end_time = perf_counter()
|
||||||
|
print(f"\n{end_time - start_time:.3f} : seconds")
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
import arrow
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
from typing import Literal
|
||||||
|
from datetime import date
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GecikmeFaizSonucu:
|
||||||
|
faiz_turu: str
|
||||||
|
gecikme_gunu: int
|
||||||
|
gunluk_oran: float
|
||||||
|
faiz: float
|
||||||
|
bsmv: float
|
||||||
|
kkdf: float
|
||||||
|
toplam: float
|
||||||
|
|
||||||
|
|
||||||
|
# Faiz türüne göre aylık faiz oranı (%)
|
||||||
|
GLOBAL_FAIZ_ORANLARI = {"akdi": 5.0, "gecikme": 5.3}
|
||||||
|
|
||||||
|
# Vergi oranları (%)
|
||||||
|
GLOBAL_BSMV_ORANI = 15.0
|
||||||
|
GLOBAL_KKDF_ORANI = 15.0
|
||||||
|
|
||||||
|
|
||||||
|
def hesapla_komisyon(vade_tarihi: date, islem_tarihi: date, tutar: float, oran: float) -> float:
|
||||||
|
gecikme = (islem_tarihi - vade_tarihi).days
|
||||||
|
if gecikme <= 0:
|
||||||
|
return 0.0
|
||||||
|
komisyon = tutar * oran * gecikme
|
||||||
|
return round(komisyon, 2)
|
||||||
|
|
||||||
|
|
||||||
|
def hesapla_gunluk_oran(
|
||||||
|
faiz_orani: float, tip: Literal["MIR", "YIR"], gun: int = 0
|
||||||
|
) -> float:
|
||||||
|
"""
|
||||||
|
orijinal_oran: Yıllık ya da aylık faiz oranı (örneğin %12 için 0.12)
|
||||||
|
tip: 'MIR' (Monthly Interest Rate) ya da 'YIR' (Yearly Interest Rate)
|
||||||
|
"""
|
||||||
|
faiz_orani = faiz_orani / 100
|
||||||
|
if tip.upper() == "MIR":
|
||||||
|
if gun < 1:
|
||||||
|
gun = 30
|
||||||
|
return faiz_orani / gun # varsayılan aylık 30 gün
|
||||||
|
elif tip.upper() == "YIR":
|
||||||
|
if gun < 1:
|
||||||
|
gun = 360
|
||||||
|
return faiz_orani / gun # varsayılan yıl 365 gün
|
||||||
|
else:
|
||||||
|
raise ValueError("Faiz tipi yalnızca 'MIR' ya da 'YIR' olabilir.")
|
||||||
|
|
||||||
|
|
||||||
|
def hesapla_gecikme_faizi(
|
||||||
|
borc: float, vade_tarihi: date, islem_tarihi: date | None = None, faiz_orani: float = None, faiz_turu: str = "akdi", bsmv_orani: float = None, kkdf_orani: float = None,
|
||||||
|
) -> GecikmeFaizSonucu:
|
||||||
|
|
||||||
|
if islem_tarihi is None:
|
||||||
|
islem_tarihi = date.today()
|
||||||
|
gecikme_gun = (arrow.get(islem_tarihi) - arrow.get(vade_tarihi)).days
|
||||||
|
if gecikme_gun <= 0:
|
||||||
|
return GecikmeFaizSonucu(faiz_turu, 0, 0, 0, 0, 0, 0)
|
||||||
|
|
||||||
|
# Varsayılan oranları içerden bağla
|
||||||
|
if faiz_orani is None:
|
||||||
|
faiz_orani = GLOBAL_FAIZ_ORANLARI.get(faiz_turu, 5.0)
|
||||||
|
if bsmv_orani is None:
|
||||||
|
bsmv_orani = GLOBAL_BSMV_ORANI
|
||||||
|
if kkdf_orani is None:
|
||||||
|
kkdf_orani = GLOBAL_KKDF_ORANI
|
||||||
|
|
||||||
|
faiz_tipi = "MIR" if faiz_turu == "akdi" else "YIR"
|
||||||
|
gunluk_oran = hesapla_gunluk_oran(faiz_orani, faiz_tipi)
|
||||||
|
|
||||||
|
faiz_tutari = Decimal(borc) * Decimal(gunluk_oran) * Decimal(gecikme_gun)
|
||||||
|
bsmv = Decimal(faiz_tutari) * Decimal(bsmv_orani / 100)
|
||||||
|
kkdf = Decimal(faiz_tutari) * Decimal(kkdf_orani / 100)
|
||||||
|
toplam_faiz = Decimal(faiz_tutari) + Decimal(bsmv) + Decimal(kkdf)
|
||||||
|
|
||||||
|
return GecikmeFaizSonucu(
|
||||||
|
faiz_turu=faiz_turu, gecikme_gunu=gecikme_gun, gunluk_oran=round(gunluk_oran, 6), faiz=round(faiz_tutari, 2), bsmv=round(bsmv, 2), kkdf=round(kkdf, 2), toplam=round(toplam_faiz, 4),
|
||||||
|
)
|
||||||
|
|
||||||
|
"""
|
||||||
|
2025 Güncel Oranlar (TCMB verilerine göre)
|
||||||
|
tip: 'MIR' (Monthly Interest Rate) ya da 'YIR' (Yearly Interest Rate)
|
||||||
|
Faiz Türü Aylık Oran Günlük Yaklaşık Açıklama
|
||||||
|
Akdi Faiz %5,00 %0,1667 Asgari üzerindeki borca uygulanır
|
||||||
|
Gecikme Faizi %5,30 %0,1767 Asgari tutarın ödenmeyen kısmına uygulanır
|
||||||
|
|
||||||
|
CREATE TABLE public.account_delay_interest (
|
||||||
|
|
||||||
|
account_records_id int4 NOT NULL,
|
||||||
|
build_decision_book_payment_id int4 NOT NULL,
|
||||||
|
build_decision_book_payment_refid varchar(100) NOT NULL,
|
||||||
|
interest_turn varchar(10) NOT NULL,
|
||||||
|
interest_rate numeric(10, 2) NOT NULL,
|
||||||
|
delay_day int2 NOT NULL,
|
||||||
|
daily_rate numeric(20, 6) NOT NULL,
|
||||||
|
interest numeric(20, 6) NOT NULL,
|
||||||
|
bsmv numeric(20, 6) NOT NULL,
|
||||||
|
kkdf numeric(20, 6) NOT NULL,
|
||||||
|
total numeric(20, 6) NOT NULL,
|
||||||
|
new_build_decision_book_payment_id int4 NULL,
|
||||||
|
approved_record bool false NOT NULL,
|
||||||
|
approving_person int4 NOT NULL,
|
||||||
|
approving_at timestamptz NULL,
|
||||||
|
|
||||||
|
id serial4 NOT NULL,
|
||||||
|
uu_id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
expiry_starts timestamptz DEFAULT now() NOT NULL,
|
||||||
|
expiry_ends timestamptz DEFAULT now() NOT NULL,
|
||||||
|
created_at timestamptz DEFAULT now() NOT NULL,
|
||||||
|
updated_at timestamptz DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT account_delay_interest_pkey PRIMARY KEY (id))
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX _account_delay_interest_ndx_01 ON public.account_delay_interest USING btree (account_records_id, build_decision_book_payment_id);
|
||||||
|
CREATE INDEX _account_delay_interest_ndx_02 ON public.account_delay_interest USING btree (build_decision_book_payment_id);
|
||||||
|
CREATE UNIQUE INDEX _account_delay_interest_ndx_03 ON public.account_delay_interest USING btree (build_decision_book_payment_refid);
|
||||||
|
CREATE UNIQUE INDEX _account_delay_interest_ndx_04 ON public.account_delay_interest USING btree (uu_id);
|
||||||
|
|
||||||
|
-- Column comments
|
||||||
|
COMMENT ON COLUMN public.account_delay_interest.build_decision_book_payment_refid IS 'ref id';
|
||||||
|
COMMENT ON COLUMN public.account_delay_interest.interest_turn IS 'faiz turu';
|
||||||
|
COMMENT ON COLUMN public.account_delay_interest.interest_rate IS 'uygulanan faiz';
|
||||||
|
COMMENT ON COLUMN public.account_delay_interest.delay_day IS 'gecikme_gunu';
|
||||||
|
COMMENT ON COLUMN public.account_delay_interest.daily_rate IS 'gunluk_oran';
|
||||||
|
COMMENT ON COLUMN public.account_delay_interest.interest IS 'faiz';
|
||||||
|
COMMENT ON COLUMN public.account_delay_interest.bsmv IS 'bsmv';
|
||||||
|
COMMENT ON COLUMN public.account_delay_interest.kkdf IS 'kkdf';
|
||||||
|
COMMENT ON COLUMN public.account_delay_interest.total IS 'toplam';
|
||||||
|
COMMENT ON COLUMN public.account_delay_interest.approved_record IS 'onaylandı';
|
||||||
|
COMMENT ON COLUMN public.account_delay_interest.approving_person IS 'onaylayan';
|
||||||
|
COMMENT ON COLUMN public.account_delay_interest.approving_at IS 'onay zamani';
|
||||||
|
"""
|
||||||
|
|
@ -1,326 +0,0 @@
|
||||||
import sys
|
|
||||||
import arrow
|
|
||||||
from decimal import Decimal
|
|
||||||
from Schemas import BuildDecisionBookPayments, AccountRecords, ApiEnumDropdown
|
|
||||||
|
|
||||||
# Counters for tracking conditions
|
|
||||||
counter_current_currency_not_positive = 0 # Track when current_currency_value <= 0
|
|
||||||
counter_net_amount_not_positive = 0 # Track when net_amount <= 0
|
|
||||||
counter_account_records_updated = 0 # Track number of account records updated
|
|
||||||
counter_payments_found = 0 # Track how many payments were found
|
|
||||||
counter_found_payment_skips = 0 # Track how many times we skip due to found_payment
|
|
||||||
counter_payment_exceptions = 0 # Track exceptions during payment processing
|
|
||||||
counter_missing_build_parts_id = 0 # Track accounts with missing build_parts_id
|
|
||||||
counter_null_payment_types = 0 # Track payments with null payment_types_id
|
|
||||||
|
|
||||||
|
|
||||||
def pay_the_registration(account_record, receive_enum, debit_enum, is_old_record: bool = False, session=None):
|
|
||||||
# If no session is provided, create a new one
|
|
||||||
if session is None:
|
|
||||||
with AccountRecords.new_session() as new_session:
|
|
||||||
AccountRecords.set_session(new_session)
|
|
||||||
BuildDecisionBookPayments.set_session(new_session)
|
|
||||||
return _process_payment(account_record, receive_enum, debit_enum, is_old_record, new_session)
|
|
||||||
else:
|
|
||||||
# Use the provided session
|
|
||||||
AccountRecords.set_session(session)
|
|
||||||
BuildDecisionBookPayments.set_session(session)
|
|
||||||
return _process_payment(account_record, receive_enum, debit_enum, is_old_record, session)
|
|
||||||
|
|
||||||
|
|
||||||
def _process_payment(account_record, receive_enum, debit_enum, is_old_record, session):
|
|
||||||
"""Internal function to process payments with a given session"""
|
|
||||||
current_currency_value = float(Decimal(account_record.currency_value)) - float(Decimal(account_record.remainder_balance))
|
|
||||||
if not current_currency_value > 0:
|
|
||||||
global counter_current_currency_not_positive
|
|
||||||
counter_current_currency_not_positive += 1
|
|
||||||
return current_currency_value
|
|
||||||
|
|
||||||
# Check if account_record has build_parts_id
|
|
||||||
if account_record.build_parts_id is None:
|
|
||||||
global counter_missing_build_parts_id
|
|
||||||
counter_missing_build_parts_id += 1
|
|
||||||
return current_currency_value
|
|
||||||
|
|
||||||
process_date = arrow.get(account_record.bank_date)
|
|
||||||
account_bank_date_year, account_bank_date_month = (process_date.date().year, process_date.date().month)
|
|
||||||
|
|
||||||
# First, try to find payments with null payment_types_id
|
|
||||||
payment_arguments_debit = [
|
|
||||||
BuildDecisionBookPayments.build_parts_id == account_record.build_parts_id,
|
|
||||||
BuildDecisionBookPayments.account_records_id == None,
|
|
||||||
]
|
|
||||||
|
|
||||||
# Add date filters if not processing old records
|
|
||||||
if not is_old_record:
|
|
||||||
payment_arguments_debit.extend([BuildDecisionBookPayments.process_date_y == int(account_bank_date_year), BuildDecisionBookPayments.process_date_m == int(account_bank_date_month)])
|
|
||||||
|
|
||||||
# First try with debit_enum.id
|
|
||||||
payments = BuildDecisionBookPayments.query.filter(*payment_arguments_debit, BuildDecisionBookPayments.payment_types_id == debit_enum.id).order_by(BuildDecisionBookPayments.process_date.asc()).all()
|
|
||||||
|
|
||||||
# If no payments found, try with null payment_types_id
|
|
||||||
if len(payments) == 0:
|
|
||||||
payments = BuildDecisionBookPayments.query.filter(*payment_arguments_debit, BuildDecisionBookPayments.payment_types_id == None).order_by(BuildDecisionBookPayments.process_date.asc()).all()
|
|
||||||
if len(payments) > 0:
|
|
||||||
global counter_null_payment_types
|
|
||||||
counter_null_payment_types += len(payments)
|
|
||||||
|
|
||||||
global counter_payments_found
|
|
||||||
counter_payments_found += len(payments)
|
|
||||||
|
|
||||||
# Debug: Print info about the first few payments found (if any)
|
|
||||||
if len(payments) > 0 and account_record.id % 100 == 0: # Only print for every 100th record to avoid too much output
|
|
||||||
print(f"DEBUG: Found {len(payments)} payments for account_record {account_record.id}")
|
|
||||||
if len(payments) > 0:
|
|
||||||
sample_payment = payments[0]
|
|
||||||
print(f" Sample payment: ID={getattr(sample_payment, 'id', 'N/A')}, amount={getattr(sample_payment, 'payment_amount', 'N/A')}")
|
|
||||||
|
|
||||||
if len(payments) == 0:
|
|
||||||
# No payments found for this account record
|
|
||||||
return current_currency_value
|
|
||||||
for payment in payments:
|
|
||||||
if not current_currency_value > 0:
|
|
||||||
return current_currency_value
|
|
||||||
payment_arguments_receive = [
|
|
||||||
BuildDecisionBookPayments.build_parts_id == account_record.build_parts_id,
|
|
||||||
BuildDecisionBookPayments.payment_plan_time_periods == payment.payment_plan_time_periods,
|
|
||||||
BuildDecisionBookPayments.payment_types_id == receive_enum.id,
|
|
||||||
BuildDecisionBookPayments.build_decision_book_item_id == payment.build_decision_book_item_id,
|
|
||||||
BuildDecisionBookPayments.decision_book_project_id == payment.decision_book_project_id,
|
|
||||||
BuildDecisionBookPayments.process_date == payment.process_date,
|
|
||||||
]
|
|
||||||
if not is_old_record:
|
|
||||||
payment_arguments_receive.extend([BuildDecisionBookPayments.process_date_y == int(account_bank_date_year), BuildDecisionBookPayments.process_date_m == int(account_bank_date_month)])
|
|
||||||
|
|
||||||
payment_received = BuildDecisionBookPayments.query.filter(*payment_arguments_receive).all()
|
|
||||||
sum_of_payment_received = sum([abs(payment.payment_amount) for payment in payment_received])
|
|
||||||
net_amount = float(abs(Decimal(payment.payment_amount))) - float(abs(Decimal(sum_of_payment_received)))
|
|
||||||
if not net_amount > 0:
|
|
||||||
global counter_net_amount_not_positive
|
|
||||||
counter_net_amount_not_positive += 1
|
|
||||||
continue
|
|
||||||
if float(abs(current_currency_value)) < float(abs(net_amount)):
|
|
||||||
net_amount = float(current_currency_value)
|
|
||||||
process_date = arrow.get(payment.process_date)
|
|
||||||
try:
|
|
||||||
found_payment = BuildDecisionBookPayments.query.filter_by(
|
|
||||||
build_parts_id=payment.build_parts_id, payment_plan_time_periods=payment.payment_plan_time_periods,
|
|
||||||
payment_types_id=receive_enum.id, build_decision_book_item_id=payment.build_decision_book_item_id,
|
|
||||||
decision_book_project_id=payment.decision_book_project_id, process_date=str(process_date),
|
|
||||||
).first()
|
|
||||||
if found_payment:
|
|
||||||
global counter_found_payment_skips
|
|
||||||
counter_found_payment_skips += 1
|
|
||||||
continue
|
|
||||||
created_book_payment = BuildDecisionBookPayments.create(
|
|
||||||
payment_plan_time_periods=payment.payment_plan_time_periods, payment_amount=float(abs(net_amount)),
|
|
||||||
payment_types_id=receive_enum.id, payment_types_uu_id=str(receive_enum.uu_id),
|
|
||||||
process_date=str(process_date), process_date_m=process_date.date().month, process_date_y=process_date.date().year,
|
|
||||||
period_time=f"{process_date.year}-{str(process_date.month).zfill(2)}", build_parts_id=payment.build_parts_id,
|
|
||||||
build_parts_uu_id=str(payment.build_parts_uu_id), account_records_id=account_record.id,
|
|
||||||
account_records_uu_id=str(account_record.uu_id), build_decision_book_item_id=payment.build_decision_book_item_id,
|
|
||||||
build_decision_book_item_uu_id=str(payment.build_decision_book_item_uu_id), decision_book_project_id=payment.decision_book_project_id,
|
|
||||||
decision_book_project_uu_id=str(payment.decision_book_project_uu_id),
|
|
||||||
)
|
|
||||||
created_book_payment.save()
|
|
||||||
created_payment_amount = float(Decimal(created_book_payment.payment_amount))
|
|
||||||
remainder_balance = float(Decimal(account_record.remainder_balance)) + float(abs(created_payment_amount))
|
|
||||||
account_record.update(remainder_balance=remainder_balance)
|
|
||||||
account_record.save()
|
|
||||||
|
|
||||||
global counter_account_records_updated
|
|
||||||
counter_account_records_updated += 1
|
|
||||||
if current_currency_value >= abs(net_amount):
|
|
||||||
current_currency_value -= abs(net_amount)
|
|
||||||
except Exception as e:
|
|
||||||
print("Exception of decision payment:", e)
|
|
||||||
global counter_payment_exceptions
|
|
||||||
counter_payment_exceptions += 1
|
|
||||||
return current_currency_value
|
|
||||||
|
|
||||||
|
|
||||||
def create_direct_payment(account_record, receive_enum, session):
|
|
||||||
"""
|
|
||||||
Create a direct payment record for an account record without relying on matching BuildDecisionBookPayments
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Calculate the amount to process
|
|
||||||
payment_amount = float(Decimal(account_record.currency_value)) - float(Decimal(account_record.remainder_balance))
|
|
||||||
if payment_amount <= 0:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Get process date information
|
|
||||||
process_date = arrow.get(account_record.bank_date)
|
|
||||||
process_date_y = process_date.date().year
|
|
||||||
process_date_m = process_date.date().month
|
|
||||||
period_time = f"{process_date_y}-{str(process_date_m).zfill(2)}"
|
|
||||||
|
|
||||||
# Check if a payment already exists for this account record
|
|
||||||
existing_payment = BuildDecisionBookPayments.query.filter_by(account_records_id=account_record.id, payment_types_id=receive_enum.id).first()
|
|
||||||
if existing_payment: # Skip if payment already exists
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Create a new payment record directly
|
|
||||||
created_book_payment = BuildDecisionBookPayments.create(
|
|
||||||
payment_plan_time_periods=1, # Default value
|
|
||||||
payment_types_id=receive_enum.id, payment_types_uu_id=str(receive_enum.uu_id), payment_amount=payment_amount, process_date=str(process_date), process_date_y=process_date_y,
|
|
||||||
process_date_m=process_date_m, period_time=period_time, account_records_id=account_record.id, account_records_uu_id=str(account_record.uu_id),
|
|
||||||
build_parts_id=account_record.build_parts_id, build_parts_uu_id=str(account_record.build_parts_uu_id) if hasattr(account_record, 'build_parts_uu_id') and account_record.build_parts_uu_id else None,
|
|
||||||
decision_book_project_id=getattr(account_record, 'decision_book_project_id', None),
|
|
||||||
decision_book_project_uu_id=str(account_record.decision_book_project_uu_id) if hasattr(account_record, 'decision_book_project_uu_id') and account_record.decision_book_project_uu_id else None,
|
|
||||||
)
|
|
||||||
created_book_payment.save()
|
|
||||||
|
|
||||||
# Update the account record
|
|
||||||
remainder_balance = float(Decimal(account_record.remainder_balance)) + float(abs(payment_amount))
|
|
||||||
account_record.update(remainder_balance=remainder_balance)
|
|
||||||
account_record.save()
|
|
||||||
|
|
||||||
global counter_account_records_updated
|
|
||||||
counter_account_records_updated += 1
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Exception in create_direct_payment for account {account_record.id}: {e}")
|
|
||||||
global counter_payment_exceptions
|
|
||||||
counter_payment_exceptions += 1
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def send_accounts_to_decision_payment():
|
|
||||||
with ApiEnumDropdown.new_session() as session:
|
|
||||||
# Set the session for all models that will be used
|
|
||||||
ApiEnumDropdown.set_session(session)
|
|
||||||
AccountRecords.set_session(session)
|
|
||||||
BuildDecisionBookPayments.set_session(session)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Get required enum values
|
|
||||||
receive_enum = ApiEnumDropdown.query.filter_by(enum_class="DebitTypes", key="DT-R").first()
|
|
||||||
debit_enum = ApiEnumDropdown.query.filter_by(enum_class="DebitTypes", key="DT-D").first()
|
|
||||||
if not receive_enum or not debit_enum:
|
|
||||||
print("Error: Could not find required enum values")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Check if there are any BuildDecisionBookPayments records at all
|
|
||||||
total_payments = BuildDecisionBookPayments.query.count()
|
|
||||||
print(f"\n--- DEBUG: Database Statistics ---")
|
|
||||||
print(f"Total BuildDecisionBookPayments records in database: {total_payments}")
|
|
||||||
|
|
||||||
# Check how many have payment_types_id = debit_enum.id
|
|
||||||
debit_payments = BuildDecisionBookPayments.query.filter_by(payment_types_id=debit_enum.id).count()
|
|
||||||
print(f"BuildDecisionBookPayments with payment_types_id={debit_enum.id} (DT-D): {debit_payments}")
|
|
||||||
|
|
||||||
# Check how many have account_records_id = None
|
|
||||||
null_account_payments = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.account_records_id == None).count()
|
|
||||||
print(f"BuildDecisionBookPayments with account_records_id=None: {null_account_payments}")
|
|
||||||
|
|
||||||
# Check a sample payment record
|
|
||||||
sample_payment = BuildDecisionBookPayments.query.first()
|
|
||||||
if sample_payment:
|
|
||||||
print("\n--- Sample BuildDecisionBookPayment ---")
|
|
||||||
print(f"ID: {getattr(sample_payment, 'id', 'N/A')}")
|
|
||||||
print(f"payment_types_id: {getattr(sample_payment, 'payment_types_id', 'N/A')}")
|
|
||||||
print(f"build_parts_id: {getattr(sample_payment, 'build_parts_id', 'N/A')}")
|
|
||||||
print(f"account_records_id: {getattr(sample_payment, 'account_records_id', 'N/A')}")
|
|
||||||
print(f"process_date_y: {getattr(sample_payment, 'process_date_y', 'N/A')}")
|
|
||||||
print(f"process_date_m: {getattr(sample_payment, 'process_date_m', 'N/A')}")
|
|
||||||
else:
|
|
||||||
print("No BuildDecisionBookPayment records found in the database!")
|
|
||||||
|
|
||||||
# Check a sample account record
|
|
||||||
sample_account = AccountRecords.query.filter(AccountRecords.remainder_balance < AccountRecords.currency_value, AccountRecords.receive_debit == receive_enum.id).first()
|
|
||||||
if sample_account:
|
|
||||||
print("\n--- Sample AccountRecord ---")
|
|
||||||
print(f"ID: {getattr(sample_account, 'id', 'N/A')}")
|
|
||||||
print(f"build_parts_id: {getattr(sample_account, 'build_parts_id', 'N/A')}")
|
|
||||||
print(f"bank_date: {getattr(sample_account, 'bank_date', 'N/A')}")
|
|
||||||
|
|
||||||
# Try to find payments for this specific account record
|
|
||||||
if sample_account.bank_date:
|
|
||||||
process_date = arrow.get(sample_account.bank_date)
|
|
||||||
account_bank_date_year, account_bank_date_month = (process_date.date().year, process_date.date().month)
|
|
||||||
|
|
||||||
print("\n--- Checking for payments for sample account ---")
|
|
||||||
print(f"Looking for payments with build_parts_id={sample_account.build_parts_id}, payment_types_id={debit_enum.id}, account_records_id=None")
|
|
||||||
|
|
||||||
# Try without date filters first
|
|
||||||
basic_payments = BuildDecisionBookPayments.query.filter(
|
|
||||||
BuildDecisionBookPayments.build_parts_id == sample_account.build_parts_id, BuildDecisionBookPayments.payment_types_id == debit_enum.id,
|
|
||||||
BuildDecisionBookPayments.account_records_id == None
|
|
||||||
).count()
|
|
||||||
print(f"Found {basic_payments} payments without date filters")
|
|
||||||
|
|
||||||
# Now try with date filters
|
|
||||||
dated_payments = BuildDecisionBookPayments.query.filter(
|
|
||||||
BuildDecisionBookPayments.build_parts_id == sample_account.build_parts_id, BuildDecisionBookPayments.payment_types_id == debit_enum.id,
|
|
||||||
BuildDecisionBookPayments.account_records_id == None, BuildDecisionBookPayments.process_date_y == int(account_bank_date_year),
|
|
||||||
BuildDecisionBookPayments.process_date_m == int(account_bank_date_month)
|
|
||||||
).count()
|
|
||||||
print(f"Found {dated_payments} payments with date filters (year={account_bank_date_year}, month={account_bank_date_month})")
|
|
||||||
else:
|
|
||||||
print("No matching AccountRecord found for debugging!")
|
|
||||||
|
|
||||||
# Query for account records that need payment processing
|
|
||||||
# Note: We removed the approved_record condition as it was too restrictive
|
|
||||||
account_records_list = AccountRecords.query.filter(AccountRecords.remainder_balance < AccountRecords.currency_value, AccountRecords.receive_debit == receive_enum.id
|
|
||||||
).order_by(AccountRecords.bank_date.desc()).all()
|
|
||||||
|
|
||||||
print(f"\nProcessing {len(account_records_list)} account records")
|
|
||||||
|
|
||||||
# Track how many records were processed with each method
|
|
||||||
traditional_method_count = 0
|
|
||||||
direct_method_count = 0
|
|
||||||
|
|
||||||
for account_record in account_records_list:
|
|
||||||
# Try the traditional method first
|
|
||||||
current_currency_value = pay_the_registration(account_record, receive_enum, debit_enum, False, session)
|
|
||||||
|
|
||||||
if current_currency_value > 0:
|
|
||||||
# If first pass found payments, try with old records too
|
|
||||||
pay_the_registration(account_record, receive_enum, debit_enum, True, session)
|
|
||||||
traditional_method_count += 1
|
|
||||||
else:
|
|
||||||
# If traditional method didn't work, try direct payment for all records
|
|
||||||
# This will handle records with missing build_parts_id
|
|
||||||
if create_direct_payment(account_record, receive_enum, session):
|
|
||||||
direct_method_count += 1
|
|
||||||
if direct_method_count % 10 == 0: # Only print every 10th record to avoid too much output
|
|
||||||
print(f"Direct payment created for account_record {account_record.id}")
|
|
||||||
|
|
||||||
# Refresh the account record to get updated values
|
|
||||||
session.refresh(account_record)
|
|
||||||
|
|
||||||
# Update status if the remainder balance equals the currency value
|
|
||||||
if abs(float(Decimal(account_record.remainder_balance))) == abs(float(Decimal(account_record.currency_value))):
|
|
||||||
account_record.update(status_id=97)
|
|
||||||
account_record.save()
|
|
||||||
|
|
||||||
print(f"\nProcessed with traditional method: {traditional_method_count} records")
|
|
||||||
print(f"Processed with direct payment method: {direct_method_count} records")
|
|
||||||
|
|
||||||
print("Payment processing completed successfully")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error in send_accounts_to_decision_payment: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
# Rollback the session in case of error
|
|
||||||
session.rollback()
|
|
||||||
return
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print("Payment Service is running...")
|
|
||||||
try:
|
|
||||||
send_accounts_to_decision_payment()
|
|
||||||
|
|
||||||
# Print counter statistics
|
|
||||||
print("\n--- Processing Statistics ---")
|
|
||||||
print(f"Records where current_currency_value <= 0: {counter_current_currency_not_positive}")
|
|
||||||
print(f"Records where net_amount <= 0: {counter_net_amount_not_positive}")
|
|
||||||
print(f"Account records updated: {counter_account_records_updated}")
|
|
||||||
print(f"Total payments found: {counter_payments_found}")
|
|
||||||
print(f"Skips due to found_payment: {counter_found_payment_skips}")
|
|
||||||
print(f"Payment exceptions: {counter_payment_exceptions}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
print("Payment Service is finished...")
|
|
||||||
|
|
@ -6,11 +6,13 @@ from decimal import Decimal
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from Schemas import BuildDecisionBookPayments, AccountRecords, ApiEnumDropdown, Build, BuildDecisionBook
|
from Schemas import BuildDecisionBookPayments, AccountRecords, ApiEnumDropdown, Build, BuildDecisionBook
|
||||||
|
# from ServicesApi.Schemas.building.decision_book import BuildDecisionBookPayments, BuildDecisionBook
|
||||||
|
# from ServicesApi.Schemas.building.build import Build
|
||||||
|
# from ServicesApi.Schemas.account.account import AccountRecords
|
||||||
|
# from ServicesApi.Schemas.others.enums import ApiEnumDropdown
|
||||||
from time import perf_counter
|
from time import perf_counter
|
||||||
from sqlalchemy import select, func, distinct, cast, Date, String, literal, desc, and_, or_
|
from sqlalchemy import select, func, distinct, cast, Date, String, literal, desc, and_, or_, case
|
||||||
from Controllers.Postgres.engine import get_session_factory
|
from Controllers.Postgres.engine import get_session_factory
|
||||||
#from ServicesApi.Schemas.account.account import AccountRecords
|
|
||||||
#from ServicesApi.Schemas.building.decision_book import BuildDecisionBookPayments
|
|
||||||
|
|
||||||
|
|
||||||
def find_last_day_of_month(date_value):
|
def find_last_day_of_month(date_value):
|
||||||
|
|
@ -23,6 +25,18 @@ def find_first_day_of_month(date_value):
|
||||||
return datetime(today.year, today.month, 1)
|
return datetime(today.year, today.month, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def add_month_to_date(date_value):
|
||||||
|
month_to_process = date_value.month
|
||||||
|
year_to_process = date_value.year
|
||||||
|
if date_value.month == 12:
|
||||||
|
month_to_process = 1
|
||||||
|
year_to_process += 1
|
||||||
|
else:
|
||||||
|
month_to_process += 1
|
||||||
|
_, last_day = calendar.monthrange(year_to_process, month_to_process)
|
||||||
|
return datetime(year_to_process, month_to_process, 1), datetime(year_to_process, month_to_process, last_day)
|
||||||
|
|
||||||
|
|
||||||
class BuildDuesTypes:
|
class BuildDuesTypes:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
@ -83,194 +97,44 @@ def get_enums_from_database():
|
||||||
return [build_dues_types.debit, build_dues_types.lawyer_expence, build_dues_types.add_debit, build_dues_types.renovation, build_dues_types.service_fee, build_dues_types.information]
|
return [build_dues_types.debit, build_dues_types.lawyer_expence, build_dues_types.add_debit, build_dues_types.renovation, build_dues_types.service_fee, build_dues_types.information]
|
||||||
|
|
||||||
|
|
||||||
def generate_total_paid_amount_for_spesific_build_part_id(build_parts_id: int, session):
|
def retrieve_remainder_balance_set_if_needed(account_record_id: int, session):
|
||||||
"""
|
"""Update the remainder_balance of an account after spending money.
|
||||||
Calculate the total amount paid for a specific build part ID.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
build_parts_id: The build part ID to calculate payments for
|
account_record: The account record to update
|
||||||
|
amount_spent: The amount spent in this transaction
|
||||||
session: Database session
|
session: Database session
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: The total amount paid (absolute value)
|
bool: True if all money is spent, False otherwise
|
||||||
"""
|
"""
|
||||||
payment_query = session.query(func.sum(BuildDecisionBookPayments.payment_amount)).filter(
|
|
||||||
BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
|
||||||
BuildDecisionBookPayments.account_is_debit == False,
|
|
||||||
cast(BuildDecisionBookPayments.process_date, Date) >= '2022-01-01'
|
|
||||||
).scalar()
|
|
||||||
return payment_query if payment_query is not None else 0
|
|
||||||
|
|
||||||
|
|
||||||
def generate_total_debt_amount_for_spesific_build_part_id(build_parts_id: int, session):
|
|
||||||
|
|
||||||
# Use SQLAlchemy's func.sum to calculate the total debts
|
|
||||||
# For total debt, we want to include ALL debts, both processed and unprocessed
|
|
||||||
result = session.query(
|
|
||||||
func.sum(BuildDecisionBookPayments.payment_amount)
|
|
||||||
).filter(
|
|
||||||
BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
|
||||||
BuildDecisionBookPayments.account_is_debit == True,
|
|
||||||
cast(BuildDecisionBookPayments.process_date, Date) >= '2022-01-01'
|
|
||||||
).scalar()
|
|
||||||
|
|
||||||
# Return 0 if no debts found, otherwise return the absolute value of the sum
|
|
||||||
return abs(result) if result is not None else 0
|
|
||||||
|
|
||||||
|
|
||||||
def generate_total_amount_that_user_has_in_account(account_record: AccountRecords, session):
|
|
||||||
# Get total amount that user has in account
|
|
||||||
result = session.query(
|
|
||||||
func.sum(AccountRecords.currency_value)
|
|
||||||
).filter(
|
|
||||||
AccountRecords.build_parts_id == account_record.build_parts_id,
|
|
||||||
AccountRecords.currency_value > 0,
|
|
||||||
cast(AccountRecords.bank_date, Date) >= '2022-01-01'
|
|
||||||
).scalar()
|
|
||||||
|
|
||||||
# Return 0 if no payments found, otherwise return the absolute value of the sum
|
|
||||||
return abs(result)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _print_debt_details(debt, session):
|
|
||||||
"""Helper function to print detailed information about an unpaid debt.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
debt: The BuildDecisionBookPayments object representing the debt
|
|
||||||
session: Database session
|
|
||||||
"""
|
|
||||||
# Get the sum of payments for this debt
|
|
||||||
payments_sum = session.query(
|
|
||||||
func.sum(BuildDecisionBookPayments.payment_amount)
|
|
||||||
).filter(
|
|
||||||
BuildDecisionBookPayments.ref_id == debt.ref_id,
|
|
||||||
BuildDecisionBookPayments.account_is_debit == False
|
|
||||||
).scalar() or 0
|
|
||||||
|
|
||||||
# Calculate remaining amount
|
|
||||||
debit_amount = abs(debt.payment_amount)
|
|
||||||
remaining = debit_amount - abs(payments_sum)
|
|
||||||
payment_percentage = (abs(payments_sum) / debit_amount) * 100 if debit_amount > 0 else 0
|
|
||||||
|
|
||||||
# Format the date for display
|
|
||||||
date_str = debt.process_date.strftime('%Y-%m-%d') if debt.process_date else 'Unknown date'
|
|
||||||
|
|
||||||
|
|
||||||
def analyze_payment_function():
|
|
||||||
session_factory = get_session_factory()
|
|
||||||
session = session_factory()
|
|
||||||
# Set session for all models
|
|
||||||
AccountRecords.set_session(session)
|
AccountRecords.set_session(session)
|
||||||
|
|
||||||
|
account_record_remainder_balance = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter(
|
||||||
|
BuildDecisionBookPayments.account_records_id == account_record_id,
|
||||||
|
BuildDecisionBookPayments.account_is_debit == False
|
||||||
|
).scalar()
|
||||||
|
|
||||||
|
if not account_record_remainder_balance:
|
||||||
|
account_record_remainder_balance = 0
|
||||||
|
|
||||||
|
account_record = AccountRecords.query.filter_by(id=account_record_id).first()
|
||||||
|
account_record.remainder_balance = -1 * abs(account_record_remainder_balance) if account_record_remainder_balance != 0 else 0
|
||||||
|
account_record.save()
|
||||||
|
return account_record_remainder_balance
|
||||||
|
|
||||||
|
|
||||||
|
def check_current_debt_to_pay_from_database_is_closed(ref_id: int, session):
|
||||||
|
session.commit()
|
||||||
BuildDecisionBookPayments.set_session(session)
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
payment_row_book = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.ref_id == str(ref_id), BuildDecisionBookPayments.account_is_debit == True).first()
|
||||||
order_pay = get_enums_from_database()
|
debt_paid = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter(
|
||||||
# Get distinct build_parts_id values from account records with positive currency_value
|
BuildDecisionBookPayments.ref_id == str(ref_id), BuildDecisionBookPayments.account_is_debit == False
|
||||||
# This avoids redundant processing of the same build_parts_id
|
).scalar()
|
||||||
distinct_build_parts = session.query(
|
payment_row_book.debt_paid = abs(debt_paid) if debt_paid else 0 # Debt Reminder is how much money is needed to close record
|
||||||
distinct(AccountRecords.build_parts_id)
|
payment_row_book.debt_to_pay = abs(payment_row_book.payment_amount) - abs(payment_row_book.debt_paid) # Debt To Pay is how much money is needed to close record
|
||||||
).filter(
|
payment_row_book.is_closed = payment_row_book.debt_to_pay == 0
|
||||||
AccountRecords.build_parts_id.isnot(None),
|
payment_row_book.save()
|
||||||
AccountRecords.currency_value > 0,
|
|
||||||
AccountRecords.bank_date >= '2022-01-01'
|
|
||||||
).order_by(AccountRecords.build_parts_id.desc()).all()
|
|
||||||
|
|
||||||
start_time = time.time()
|
|
||||||
|
|
||||||
for build_part_id_tuple in distinct_build_parts:
|
|
||||||
build_part_id = build_part_id_tuple[0] # Extract the ID from the tuple
|
|
||||||
process_date = datetime.now()
|
|
||||||
last_date_of_process_date = datetime(process_date.year, process_date.month, 1) - timedelta(days=1)
|
|
||||||
first_date_of_process_date = datetime(process_date.year, process_date.month, 1)
|
|
||||||
|
|
||||||
print(f"\n{'=' * 50}")
|
|
||||||
print(f"ACCOUNT ANALYSIS FOR BUILD PART ID: {build_part_id}")
|
|
||||||
print(f"{'=' * 50}")
|
|
||||||
|
|
||||||
# Calculate total paid amount for this build_part_id
|
|
||||||
total_amount_paid = generate_total_paid_amount_for_spesific_build_part_id(build_part_id, session)
|
|
||||||
|
|
||||||
# Calculate total debt amount for this build_part_id
|
|
||||||
total_debt_amount = generate_total_debt_amount_for_spesific_build_part_id(build_part_id, session)
|
|
||||||
|
|
||||||
# Get total amount in account for this build_part_id
|
|
||||||
account_record = AccountRecords()
|
|
||||||
account_record.build_parts_id = build_part_id
|
|
||||||
total_amount_in_account = generate_total_amount_that_user_has_in_account(account_record, session)
|
|
||||||
|
|
||||||
# Calculate remaining amount to be paid
|
|
||||||
amount_need_to_paid = total_debt_amount - total_amount_paid
|
|
||||||
total_amount_that_user_need_to_transfer = abs(amount_need_to_paid) - abs(total_amount_in_account)
|
|
||||||
|
|
||||||
# Print summary with clear descriptions
|
|
||||||
print(f"PAYMENT SUMMARY:")
|
|
||||||
print(f" • Total debt amount: {total_debt_amount:,.2f} TL")
|
|
||||||
print(f" • Amount already paid: {total_amount_paid:,.2f} TL")
|
|
||||||
print(f" • Remaining debt to be collected: {amount_need_to_paid:,.2f} TL")
|
|
||||||
print(f" • Current account balance: {total_amount_in_account:,.2f} TL")
|
|
||||||
|
|
||||||
if total_amount_that_user_need_to_transfer > 0:
|
|
||||||
print(f" • Additional funds needed: {total_amount_that_user_need_to_transfer:,.2f} TL")
|
|
||||||
elif amount_need_to_paid <= 0:
|
|
||||||
print(f" • Account is fully paid with no outstanding debt")
|
|
||||||
else:
|
|
||||||
print(f" • Sufficient funds available to close all debt")
|
|
||||||
# Show debt coverage percentage
|
|
||||||
if total_debt_amount > 0:
|
|
||||||
# Calculate current coverage (already paid)
|
|
||||||
current_coverage_percentage = (total_amount_paid / total_debt_amount) * 100
|
|
||||||
|
|
||||||
# Calculate potential coverage (including available funds)
|
|
||||||
potential_coverage = min(100, ((total_amount_paid + total_amount_in_account) / total_debt_amount) * 100)
|
|
||||||
|
|
||||||
# Display both percentages
|
|
||||||
print(f" • Current debt coverage: {current_coverage_percentage:.2f}%")
|
|
||||||
print(f" • Potential debt coverage with available funds: {potential_coverage:.2f}%")
|
|
||||||
|
|
||||||
# Analyze unpaid debts for each payment type
|
|
||||||
print("\nUNPAID DEBTS ANALYSIS BY PAYMENT TYPE:")
|
|
||||||
for payment_type in order_pay:
|
|
||||||
# Get unpaid debts for current month
|
|
||||||
date_query_current = (
|
|
||||||
BuildDecisionBookPayments.process_date >= first_date_of_process_date,
|
|
||||||
BuildDecisionBookPayments.process_date <= process_date
|
|
||||||
)
|
|
||||||
date_query_previous = (
|
|
||||||
BuildDecisionBookPayments.process_date < first_date_of_process_date,
|
|
||||||
)
|
|
||||||
|
|
||||||
current_unpaid_debts = get_unpaid_debts(build_parts_id=build_part_id, session=session, debit_type=payment_type, date_query=date_query_current)
|
|
||||||
|
|
||||||
# Get unpaid debts from previous months
|
|
||||||
previous_unpaid_debts = get_unpaid_debts(build_parts_id=build_part_id, session=session, debit_type=payment_type, date_query=date_query_previous)
|
|
||||||
|
|
||||||
# Calculate totals
|
|
||||||
current_total = sum(abs(debt[2]) for debt in current_unpaid_debts)
|
|
||||||
previous_total = sum(abs(debt[2]) for debt in previous_unpaid_debts)
|
|
||||||
grand_total = current_total + previous_total
|
|
||||||
|
|
||||||
# Print summary for this payment type
|
|
||||||
if current_unpaid_debts or previous_unpaid_debts:
|
|
||||||
print(f" • {payment_type.key}: Total unpaid: {grand_total:,.2f} TL")
|
|
||||||
|
|
||||||
# Current month details
|
|
||||||
if current_unpaid_debts:
|
|
||||||
print(f" - Current month: {len(current_unpaid_debts)} debts, {current_total:,.2f} TL")
|
|
||||||
# Show details of each unpaid debt if there aren't too many
|
|
||||||
# if len(current_unpaid_debts) <= 3:
|
|
||||||
# for debt in current_unpaid_debts:
|
|
||||||
# _print_debt_details(debt, session)
|
|
||||||
|
|
||||||
# Previous months details
|
|
||||||
if previous_unpaid_debts:
|
|
||||||
print(f" - Previous months: {len(previous_unpaid_debts)} debts, {previous_total:,.2f} TL")
|
|
||||||
# Show details of each unpaid debt if there aren't too many
|
|
||||||
# if len(previous_unpaid_debts) <= 3:
|
|
||||||
# for debt in previous_unpaid_debts:
|
|
||||||
# _print_debt_details(debt, session)
|
|
||||||
else:
|
|
||||||
print(f" • {payment_type.key}: All debts paid")
|
|
||||||
print(f"{'=' * 50}\n")
|
|
||||||
|
|
||||||
|
|
||||||
def close_payment_book(payment_row_book, account_record, value, session):
|
def close_payment_book(payment_row_book, account_record, value, session):
|
||||||
|
|
@ -286,7 +150,9 @@ def close_payment_book(payment_row_book, account_record, value, session):
|
||||||
The newly created payment record
|
The newly created payment record
|
||||||
"""
|
"""
|
||||||
BuildDecisionBookPayments.set_session(session)
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
account_record_remainder_balance = retrieve_remainder_balance_set_if_needed(account_record_id=account_record.id, session=session)
|
||||||
|
print(f'NOT Updated remainder balance: {account_record_remainder_balance} | Account record id: {account_record.id}')
|
||||||
|
|
||||||
# Create a new credit entry (payment)
|
# Create a new credit entry (payment)
|
||||||
new_row = BuildDecisionBookPayments.create(
|
new_row = BuildDecisionBookPayments.create(
|
||||||
ref_id=str(payment_row_book.uu_id),
|
ref_id=str(payment_row_book.uu_id),
|
||||||
|
|
@ -317,538 +183,126 @@ def close_payment_book(payment_row_book, account_record, value, session):
|
||||||
saved_row = new_row.save()
|
saved_row = new_row.save()
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(saved_row)
|
session.refresh(saved_row)
|
||||||
session.flush()
|
account_record_remainder_balance = retrieve_remainder_balance_set_if_needed(account_record_id=account_record.id, session=session)
|
||||||
return saved_row
|
print(f'Updated remainder balance: {account_record_remainder_balance} | Account record id: {account_record.id}')
|
||||||
|
check_current_debt_to_pay_from_database_is_closed(ref_id=new_row.ref_id, session=session)
|
||||||
|
return account_record_remainder_balance
|
||||||
|
|
||||||
|
|
||||||
def update_account_remainder_if_spent(account_record, ref_id: str, session):
|
def refresh_ids_to_iterate_account_records(session, limit, offset):
|
||||||
"""Update the remainder_balance of an account after spending money.
|
return session.query(AccountRecords.id).filter(
|
||||||
|
AccountRecords.active == True, func.abs(AccountRecords.currency_value) - func.abs(AccountRecords.remainder_balance) > 0,
|
||||||
|
AccountRecords.currency_value > 0,
|
||||||
|
).order_by(AccountRecords.bank_date.desc()).limit(limit).offset(offset).all()
|
||||||
|
|
||||||
|
|
||||||
|
def get_ids_to_iterate_account_records(session, limit, offset):
|
||||||
|
"""Get account records to process with pagination.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
account_record: The account record to update
|
|
||||||
amount_spent: The amount spent in this transaction
|
|
||||||
session: Database session
|
session: Database session
|
||||||
|
limit: Maximum number of records to return
|
||||||
|
offset: Number of records to skip
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if all money is spent, False otherwise
|
List of account record IDs
|
||||||
"""
|
"""
|
||||||
AccountRecords.set_session(session)
|
today = datetime.now()
|
||||||
BuildDecisionBookPayments.set_session(session)
|
return session.query(AccountRecords.id).filter(
|
||||||
session.commit()
|
AccountRecords.active == True, func.abs(AccountRecords.currency_value) - func.abs(AccountRecords.remainder_balance) > 0,
|
||||||
sum_of_paid = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter(
|
AccountRecords.currency_value > 0, AccountRecords.build_parts_id.isnot(None), cast(AccountRecords.bank_date, Date) < find_first_day_of_month(today).date()
|
||||||
BuildDecisionBookPayments.account_records_id == account_record.id,
|
).order_by(AccountRecords.bank_date.desc()).limit(limit).offset(offset).all()
|
||||||
BuildDecisionBookPayments.account_is_debit == False
|
|
||||||
).scalar()
|
|
||||||
if not sum_of_paid:
|
|
||||||
return False
|
|
||||||
debit_row = BuildDecisionBookPayments.query.filter_by(ref_id=ref_id).first()
|
|
||||||
account_record_to_update = AccountRecords.query.filter_by(id=account_record.id).first()
|
|
||||||
account_record_to_update.remainder_balance = (-1 * abs(sum_of_paid)) or 0
|
|
||||||
account_record_to_update.save()
|
|
||||||
session.commit()
|
|
||||||
session.refresh(account_record_to_update)
|
|
||||||
# Get the current remainder balance
|
|
||||||
if abs(sum_of_paid) == abs(account_record_to_update.currency_value):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def update_all_spent_accounts(session):
|
def get_read_payment_type(payment_result_type_uu_id, payment_type_list):
|
||||||
"""Update remainder_balance for all accounts with payments.
|
for payment_type in payment_type_list:
|
||||||
|
if payment_type.uuid == payment_result_type_uu_id:
|
||||||
This function finds account records in BuildDecisionBookPayments and updates
|
return payment_type.key
|
||||||
their remainder_balance based on the sum of payments made, regardless of whether
|
return None
|
||||||
all funds have been spent or not.
|
|
||||||
|
|
||||||
|
def same_month_actions(limit=10, offset=0):
|
||||||
|
"""Main function to process account records and find debits.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
session: Database session
|
limit: Maximum number of records to process
|
||||||
|
offset: Number of records to skip
|
||||||
"""
|
"""
|
||||||
with AccountRecords.new_session() as session:
|
today, build_id, start_time = datetime.now(), 1, perf_counter()
|
||||||
# Set sessions for models
|
|
||||||
|
session_factory = get_session_factory()
|
||||||
|
session = session_factory()
|
||||||
|
payment_type_list = get_enums_from_database()
|
||||||
|
|
||||||
|
print(f"Processing with limit={limit}, offset={offset}")
|
||||||
|
account_records = get_ids_to_iterate_account_records(session=session, limit=limit, offset=offset)
|
||||||
|
print(f"Found {len(account_records)} account records to process")
|
||||||
|
|
||||||
|
for account_record in account_records:
|
||||||
AccountRecords.set_session(session)
|
AccountRecords.set_session(session)
|
||||||
BuildDecisionBookPayments.set_session(session)
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
|
||||||
|
account_record_selected = AccountRecords.query.filter_by(id=account_record.id).first()
|
||||||
|
payment_result_type_uu_id = account_record_selected.payment_result_type_uu_id
|
||||||
|
|
||||||
|
# Extract month and year from the bank transaction date instead of using current date
|
||||||
|
bank_date = account_record_selected.bank_date
|
||||||
|
bank_month = bank_date.month
|
||||||
|
bank_year = bank_date.year
|
||||||
|
priority_uuids = [payment_result_type_uu_id, *[pt.uuid for pt in payment_type_list if pt.uuid != payment_result_type_uu_id]]
|
||||||
|
order_payment_by_type = case(*[(BuildDecisionBookPayments.payment_types_uu_id == uuid, index) for index, uuid in enumerate(priority_uuids)], else_=len(priority_uuids))
|
||||||
|
|
||||||
# Get distinct account_records_id values from BuildDecisionBookPayments
|
same_month_build_decision_book_debits = BuildDecisionBookPayments.query.filter(
|
||||||
distinct_account_ids = session.query(BuildDecisionBookPayments.account_records_id).filter(
|
BuildDecisionBookPayments.account_is_debit.is_(True),
|
||||||
BuildDecisionBookPayments.account_records_id.isnot(None),
|
BuildDecisionBookPayments.active.is_(True),
|
||||||
BuildDecisionBookPayments.account_is_debit == False # Credit entries (payments)
|
BuildDecisionBookPayments.build_parts_id == account_record_selected.build_parts_id,
|
||||||
).distinct().all()
|
func.extract('month', BuildDecisionBookPayments.process_date) == bank_month,
|
||||||
|
func.extract('year', BuildDecisionBookPayments.process_date) == bank_year,
|
||||||
updated_count = 0
|
BuildDecisionBookPayments.is_closed.is_(False),
|
||||||
|
# BuildDecisionBookPayments.payment_types_uu_id == payment_result_type_uu_id,
|
||||||
for account_id_tuple in distinct_account_ids:
|
).order_by(order_payment_by_type, BuildDecisionBookPayments.process_date.desc()).all()
|
||||||
account_id = account_id_tuple[0]
|
match_payment_type = get_read_payment_type(payment_result_type_uu_id, payment_type_list)
|
||||||
|
print('fund', dict(
|
||||||
# Get the account record
|
id=account_record_selected.id,
|
||||||
account = AccountRecords.query.filter_by(id=account_id).first()
|
build_parts_id=account_record_selected.build_parts_id,
|
||||||
if not account or not account.build_parts_id or account.currency_value <= 0:
|
payment_result_type_uu_id=match_payment_type,
|
||||||
continue
|
bank_date=arrow.get(account_record_selected.bank_date).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
currency_value=account_record_selected.currency_value,
|
||||||
# Calculate the sum of payments made using this account
|
remainder_balance=account_record_selected.remainder_balance,
|
||||||
# Note: payment_amount is negative for credit entries, so we need to use abs() to get the positive amount
|
length_matches=len(same_month_build_decision_book_debits),
|
||||||
payment_query = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter(
|
))
|
||||||
BuildDecisionBookPayments.account_records_id == account_id,
|
for same_month_build_decision_book_debit in same_month_build_decision_book_debits:
|
||||||
BuildDecisionBookPayments.account_is_debit == False # Credit entries (payments)
|
match_payment_type = get_read_payment_type(same_month_build_decision_book_debit.payment_types_uu_id, payment_type_list)
|
||||||
)
|
print('debt', dict(
|
||||||
|
id=same_month_build_decision_book_debit.id,
|
||||||
payment_sum = payment_query.scalar() or 0
|
payment_type=match_payment_type,
|
||||||
|
debt_date=arrow.get(same_month_build_decision_book_debit.process_date).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
# Update remainder_balance for ALL accounts, regardless of payment_sum value
|
payment_amount=same_month_build_decision_book_debit.payment_amount,
|
||||||
threshold = Decimal('0.01')
|
debt_paid=same_month_build_decision_book_debit.debt_paid,
|
||||||
fully_spent = abs(payment_sum) >= abs(account.currency_value) - threshold
|
debt_to_pay=same_month_build_decision_book_debit.debt_to_pay,
|
||||||
status = "All funds spent" if fully_spent else "Partial payment"
|
is_closed=same_month_build_decision_book_debit.is_closed,
|
||||||
|
))
|
||||||
# Store the positive value in remainder_balance
|
|
||||||
account.remainder_balance = -1 * abs(payment_sum)
|
|
||||||
account.save()
|
|
||||||
updated_count += 1
|
|
||||||
session.commit()
|
|
||||||
print(f"\nTotal accounts updated: {updated_count}")
|
|
||||||
|
|
||||||
|
|
||||||
# def find_amount_to_pay_by_ref_id(ref_id, session):
|
|
||||||
# """Calculate the remaining amount to pay for a specific debt reference ID.
|
|
||||||
|
|
||||||
# Args:
|
|
||||||
# ref_id: The reference ID of the debt (this is the uu_id of the debt record)
|
|
||||||
# session: Database session
|
|
||||||
|
|
||||||
# Returns:
|
|
||||||
# float: The remaining amount to pay
|
|
||||||
# """
|
|
||||||
# # Get the original debt amount - the debt is identified by its uu_id which is passed as ref_id
|
|
||||||
# debit = BuildDecisionBookPayments.query.filter(
|
|
||||||
# BuildDecisionBookPayments.uu_id == ref_id,
|
|
||||||
# BuildDecisionBookPayments.account_is_debit == True
|
|
||||||
# ).first()
|
|
||||||
# if not debit:
|
|
||||||
# return 0 # No debit found, nothing to pay
|
|
||||||
|
|
||||||
# debit_amount = abs(debit.payment_amount) # Ensure positive value for debit amount
|
|
||||||
|
|
||||||
# # Get the sum of payments already made for this debt
|
|
||||||
# # The ref_id in credit records points to the uu_id of the original debit
|
|
||||||
# # Note: payment_amount is negative for credit entries, so we use abs() to get positive values
|
|
||||||
# credit_amount = session.query(
|
|
||||||
# func.sum(func.abs(BuildDecisionBookPayments.payment_amount))
|
|
||||||
# ).filter(
|
|
||||||
# BuildDecisionBookPayments.ref_id == str(ref_id),
|
|
||||||
# BuildDecisionBookPayments.account_is_debit == False
|
|
||||||
# ).scalar() or 0
|
|
||||||
# # Calculate remaining amount to pay
|
|
||||||
# remaining = abs(debit_amount) - abs(credit_amount)
|
|
||||||
# # Ensure we don't return negative values
|
|
||||||
# if remaining < 0:
|
|
||||||
# return 0
|
|
||||||
|
|
||||||
# return remaining
|
|
||||||
|
|
||||||
def get_unpaid_debts_via_build_parts(build_parts_id: int, session, debit_type, date_query: tuple):
|
|
||||||
"""Find BuildDecisionBookPayments entries where the debt has NOT been fully paid for a specific debit type.
|
|
||||||
|
|
||||||
This function identifies payments where the sum of payments is less than the debit amount,
|
|
||||||
meaning the debt has not been fully closed.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
build_parts_id: The build part ID to check
|
|
||||||
session: Database session
|
|
||||||
debit_type: The specific debit type to check
|
|
||||||
date_query: Tuple of date filters to apply
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list: List of unpaid debt entries (full BuildDecisionBookPayments objects)
|
|
||||||
|
|
||||||
query:
|
|
||||||
SELECT
|
|
||||||
bpf.ref_id,
|
|
||||||
bpf.process_date,
|
|
||||||
ABS(COALESCE(SUM(bpf.payment_amount), 0)) AS total_payments
|
|
||||||
FROM public.build_decision_book_payments AS bpf
|
|
||||||
GROUP BY
|
|
||||||
bpf.ref_id,
|
|
||||||
bpf.process_date
|
|
||||||
HAVING ABS(COALESCE(SUM(bpf.payment_amount), 0)) > 0
|
|
||||||
order by bpf.process_date
|
|
||||||
"""
|
|
||||||
# Create a subquery for the payment sums without executing it separately
|
|
||||||
payment_sums_subquery = select(
|
|
||||||
BuildDecisionBookPayments.ref_id,
|
|
||||||
BuildDecisionBookPayments.process_date,
|
|
||||||
func.abs(func.coalesce(func.sum(BuildDecisionBookPayments.payment_amount), 0)).label("total_payments")
|
|
||||||
).filter(
|
|
||||||
BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
|
||||||
BuildDecisionBookPayments.payment_types_id == debit_type.id,
|
|
||||||
*date_query
|
|
||||||
).group_by(
|
|
||||||
BuildDecisionBookPayments.ref_id, BuildDecisionBookPayments.process_date
|
|
||||||
).having(
|
|
||||||
func.abs(func.coalesce(func.sum(BuildDecisionBookPayments.payment_amount), 0)) > 0
|
|
||||||
).order_by(BuildDecisionBookPayments.process_date.desc())
|
|
||||||
|
|
||||||
# Use the subquery directly in the main query
|
|
||||||
payment_sums = session.execute(payment_sums_subquery).all()
|
|
||||||
payment_sums_list = []
|
|
||||||
for item in payment_sums:
|
|
||||||
payment_sums_list.append({"ref_id": item[0], "process_date": item[1], "total_payments": item[2]})
|
|
||||||
return payment_sums
|
|
||||||
|
|
||||||
|
|
||||||
def get_unpaid_debts(session, debit_type, date_query: tuple):
|
|
||||||
"""Find BuildDecisionBookPayments entries where the debt has NOT been fully paid for a specific debit type.
|
|
||||||
|
|
||||||
This function identifies payments where the sum of payments is less than the debit amount,
|
|
||||||
meaning the debt has not been fully closed.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
build_parts_id: The build part ID to check
|
|
||||||
session: Database session
|
|
||||||
debit_type: The specific debit type to check
|
|
||||||
date_query: Tuple of date filters to apply
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list: List of unpaid debt entries (full BuildDecisionBookPayments objects)
|
|
||||||
|
|
||||||
query:
|
|
||||||
SELECT
|
|
||||||
bpf.ref_id,
|
|
||||||
bpf.process_date,
|
|
||||||
ABS(COALESCE(SUM(bpf.payment_amount), 0)) AS total_payments
|
|
||||||
FROM public.build_decision_book_payments AS bpf
|
|
||||||
GROUP BY
|
|
||||||
bpf.ref_id,
|
|
||||||
bpf.process_date
|
|
||||||
HAVING ABS(COALESCE(SUM(bpf.payment_amount), 0)) > 0
|
|
||||||
order by bpf.process_date
|
|
||||||
"""
|
|
||||||
# Create a subquery for the payment sums without executing it separately
|
|
||||||
do_payments_set = lambda ref, date, total: {"ref_id": ref, "process_date": date, "total_payments": total}
|
|
||||||
payment_sums_subquery = select(BuildDecisionBookPayments.ref_id, BuildDecisionBookPayments.process_date,
|
|
||||||
func.abs(func.coalesce(func.sum(BuildDecisionBookPayments.payment_amount), 0)).label("total_payments")
|
|
||||||
).filter(BuildDecisionBookPayments.payment_types_id == debit_type.id, *date_query
|
|
||||||
).group_by(BuildDecisionBookPayments.ref_id, BuildDecisionBookPayments.process_date
|
|
||||||
).having(func.abs(func.coalesce(func.sum(BuildDecisionBookPayments.payment_amount), 0)) > 0
|
|
||||||
).order_by(BuildDecisionBookPayments.process_date.asc())
|
|
||||||
|
|
||||||
payment_sums = session.execute(payment_sums_subquery).all()
|
|
||||||
return [do_payments_set(item[0], item[1], item[2]) for item in payment_sums]
|
|
||||||
|
|
||||||
|
|
||||||
def do_payments_of_this_month(build_id: int = 1):
|
|
||||||
"""Process payments for the current month's unpaid debts.
|
|
||||||
This function retrieves account records with available funds and processes
|
|
||||||
payments for current month's unpaid debts in order of payment type priority.
|
|
||||||
"""
|
|
||||||
session_factory = get_session_factory()
|
|
||||||
session = session_factory()
|
|
||||||
|
|
||||||
# Set session for all models
|
|
||||||
AccountRecords.set_session(session)
|
|
||||||
BuildDecisionBookPayments.set_session(session)
|
|
||||||
Build.set_session(session)
|
|
||||||
BuildDecisionBook.set_session(session)
|
|
||||||
|
|
||||||
# Get payment types in priority order
|
|
||||||
payment_type_list = get_enums_from_database()
|
|
||||||
fund_finished = lambda money_spend, money_in_account: money_spend == money_in_account
|
|
||||||
payments_made, total_amount_paid, paid_count = 0, 0, 0
|
|
||||||
target_build = Build.query.filter(Build.id == build_id).first()
|
|
||||||
if not target_build:
|
|
||||||
raise ValueError(f"Build with id {build_id} not found")
|
|
||||||
|
|
||||||
now = datetime.now()
|
|
||||||
decision_book = BuildDecisionBook.query.filter(
|
|
||||||
BuildDecisionBook.build_id == build_id, cast(BuildDecisionBook.expiry_starts, Date) <= now.date(),
|
|
||||||
cast(BuildDecisionBook.expiry_ends, Date) >= now.date(), BuildDecisionBook.decision_type == "RBM"
|
|
||||||
).first()
|
|
||||||
if not decision_book:
|
|
||||||
raise ValueError(f"Decision book not found for build with id {build_id}")
|
|
||||||
|
|
||||||
period_date_start = decision_book.expiry_starts
|
|
||||||
period_date_end = decision_book.expiry_ends
|
|
||||||
period_id = decision_book.id
|
|
||||||
|
|
||||||
first_date_of_process_date = find_first_day_of_month(now)
|
|
||||||
last_date_of_process_date = find_last_day_of_month(now)
|
|
||||||
|
|
||||||
# Current month date filter
|
|
||||||
date_query_tuple = (
|
|
||||||
cast(BuildDecisionBookPayments.process_date, Date) >= first_date_of_process_date.date(), cast(BuildDecisionBookPayments.process_date, Date) <= last_date_of_process_date.date()
|
|
||||||
)
|
|
||||||
|
|
||||||
update_all_spent_accounts(session)
|
|
||||||
|
|
||||||
for payment_type in payment_type_list:
|
|
||||||
unpaid_debts = get_unpaid_debts(session, payment_type, date_query_tuple)
|
|
||||||
for unpaid_debt in unpaid_debts:
|
|
||||||
amount_to_pay = unpaid_debt["total_payments"]
|
|
||||||
ref_id = unpaid_debt["ref_id"]
|
|
||||||
process_date = unpaid_debt["process_date"]
|
|
||||||
debit_row = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.uu_id == ref_id).first()
|
|
||||||
build_parts_id = debit_row.build_parts_id
|
|
||||||
money_to_pay_rows = AccountRecords.query.filter(
|
|
||||||
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
|
|
||||||
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(), cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date(),
|
|
||||||
).order_by(AccountRecords.bank_date.asc()).all()
|
|
||||||
for money_to_pay_row in money_to_pay_rows:
|
|
||||||
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
|
||||||
money_to_pay_row = AccountRecords.query.filter(AccountRecords.id == money_to_pay_row.id).first()
|
|
||||||
available_money = abs(money_to_pay_row.currency_value) - abs(money_to_pay_row.remainder_balance)
|
|
||||||
if available_money > amount_to_pay:
|
|
||||||
print('NOT All money is spent amount_to_pay:', amount_to_pay, 'debit_row payment_amount:', debit_row.payment_amount)
|
|
||||||
close_payment_book(debit_row, money_to_pay_row, amount_to_pay, session)
|
|
||||||
total_amount_paid += amount_to_pay
|
|
||||||
paid_count += 1
|
|
||||||
payments_made += 1
|
|
||||||
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
|
||||||
break
|
|
||||||
elif available_money <= amount_to_pay:
|
|
||||||
print('All money is spent amount_to_pay:', amount_to_pay, 'debit_row payment_amount:', debit_row.payment_amount)
|
|
||||||
close_payment_book(debit_row, money_to_pay_row, available_money, session)
|
|
||||||
total_amount_paid += available_money
|
|
||||||
paid_count += 1
|
|
||||||
payments_made += 1
|
|
||||||
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
print(f"Something else happened available_money: {available_money}, amount_to_pay: {amount_to_pay}")
|
|
||||||
|
|
||||||
update_all_spent_accounts(session)
|
|
||||||
|
|
||||||
print('payments_made', payments_made)
|
|
||||||
print('total_amount_paid', total_amount_paid)
|
|
||||||
print('paid_count', paid_count)
|
|
||||||
|
|
||||||
|
|
||||||
def do_payments_of_previos_months(build_id: int = 1):
|
|
||||||
"""Process payments for previous months' unpaid debts.
|
|
||||||
|
|
||||||
This function retrieves account records with available funds and processes
|
|
||||||
payments for previous months' unpaid debts in order of payment type priority.
|
|
||||||
"""
|
|
||||||
"""Process payments for the current month's unpaid debts.
|
|
||||||
This function retrieves account records with available funds and processes
|
|
||||||
payments for current month's unpaid debts in order of payment type priority.
|
|
||||||
"""
|
|
||||||
session_factory = get_session_factory()
|
|
||||||
session = session_factory()
|
|
||||||
|
|
||||||
# Set session for all models
|
|
||||||
AccountRecords.set_session(session)
|
|
||||||
BuildDecisionBookPayments.set_session(session)
|
|
||||||
Build.set_session(session)
|
|
||||||
BuildDecisionBook.set_session(session)
|
|
||||||
|
|
||||||
# Get payment types in priority order
|
|
||||||
payment_type_list = get_enums_from_database()
|
|
||||||
fund_finished = lambda money_spend, money_in_account: money_spend == money_in_account
|
|
||||||
payments_made, total_amount_paid, paid_count = 0, 0, 0
|
|
||||||
target_build = Build.query.filter(Build.id == build_id).first()
|
|
||||||
if not target_build:
|
|
||||||
raise ValueError(f"Build with id {build_id} not found")
|
|
||||||
|
|
||||||
now = datetime.now()
|
|
||||||
decision_book = BuildDecisionBook.query.filter(
|
|
||||||
BuildDecisionBook.build_id == build_id,
|
|
||||||
cast(BuildDecisionBook.expiry_starts, Date) <= now.date(),
|
|
||||||
cast(BuildDecisionBook.expiry_ends, Date) >= now.date(),
|
|
||||||
BuildDecisionBook.decision_type == "RBM"
|
|
||||||
).first()
|
|
||||||
if not decision_book:
|
|
||||||
raise ValueError(f"Decision book not found for build with id {build_id}")
|
|
||||||
early_date = datetime(now.year - 1, now.month, now.day)
|
|
||||||
early_decision_book = BuildDecisionBook.query.filter(
|
|
||||||
BuildDecisionBook.build_id == build_id,
|
|
||||||
cast(BuildDecisionBook.expiry_starts, Date) <= early_date.date(),
|
|
||||||
cast(BuildDecisionBook.expiry_ends, Date) >= early_date.date(),
|
|
||||||
BuildDecisionBook.decision_type == "RBM"
|
|
||||||
).first()
|
|
||||||
|
|
||||||
period_date_start = decision_book.expiry_starts
|
|
||||||
period_date_end = decision_book.expiry_ends
|
|
||||||
period_id = decision_book.id
|
|
||||||
early_period_date_start = early_decision_book.expiry_starts
|
|
||||||
early_period_date_end = early_decision_book.expiry_ends
|
|
||||||
early_period_id = early_decision_book.id
|
|
||||||
|
|
||||||
first_date_of_process_date = arrow.get(period_date_start).datetime
|
|
||||||
last_date_of_process_date = now
|
|
||||||
|
|
||||||
# Current month date filter
|
|
||||||
date_query_tuple = (
|
|
||||||
cast(BuildDecisionBookPayments.process_date, Date) >= first_date_of_process_date.date(),
|
|
||||||
cast(BuildDecisionBookPayments.process_date, Date) <= last_date_of_process_date.date()
|
|
||||||
)
|
|
||||||
|
|
||||||
update_all_spent_accounts(session)
|
|
||||||
|
|
||||||
for payment_type in payment_type_list:
|
|
||||||
unpaid_debts = get_unpaid_debts(session, payment_type, date_query_tuple)
|
|
||||||
print('length unpaid debts: ', len(unpaid_debts))
|
|
||||||
for unpaid_debt in unpaid_debts:
|
|
||||||
amount_to_pay = unpaid_debt["total_payments"]
|
|
||||||
ref_id = unpaid_debt["ref_id"]
|
|
||||||
process_date = unpaid_debt["process_date"]
|
|
||||||
debit_row = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.uu_id == ref_id).first()
|
|
||||||
build_parts_id = debit_row.build_parts_id
|
|
||||||
money_to_pay_rows = AccountRecords.query.filter(
|
|
||||||
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
|
|
||||||
cast(AccountRecords.bank_date, Date) >= find_first_day_of_month(process_date).date(), cast(AccountRecords.bank_date, Date) <= find_last_day_of_month(process_date).date(),
|
|
||||||
).order_by(AccountRecords.bank_date.asc()).all()
|
|
||||||
if not money_to_pay_rows:
|
|
||||||
money_to_pay_rows = AccountRecords.query.filter(
|
|
||||||
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
|
|
||||||
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(), cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date(),
|
|
||||||
).order_by(AccountRecords.bank_date.asc()).all()
|
|
||||||
for money_to_pay_row in money_to_pay_rows:
|
|
||||||
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
|
||||||
money_to_pay_row = AccountRecords.query.filter(AccountRecords.id == money_to_pay_row.id).first()
|
|
||||||
available_money = abs(money_to_pay_row.currency_value) - abs(money_to_pay_row.remainder_balance)
|
|
||||||
if available_money > amount_to_pay:
|
|
||||||
print('NOT All money is spent amount_to_pay:', amount_to_pay, 'debit_row payment_amount:', debit_row.payment_amount)
|
|
||||||
close_payment_book(debit_row, money_to_pay_row, amount_to_pay, session)
|
|
||||||
total_amount_paid += amount_to_pay
|
|
||||||
paid_count += 1
|
|
||||||
payments_made += 1
|
|
||||||
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
|
||||||
break
|
|
||||||
elif available_money <= amount_to_pay:
|
|
||||||
print('All money is spent amount_to_pay:', available_money, 'debit_row payment_amount:', debit_row.payment_amount)
|
|
||||||
close_payment_book(debit_row, money_to_pay_row, available_money, session)
|
|
||||||
total_amount_paid += available_money
|
|
||||||
paid_count += 1
|
|
||||||
payments_made += 1
|
|
||||||
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
print(f"Something else happened available_money: {available_money}, amount_to_pay: {amount_to_pay}")
|
|
||||||
|
|
||||||
print('This years decision book payments')
|
|
||||||
print('payments_made', payments_made)
|
|
||||||
print('total_amount_paid', total_amount_paid)
|
|
||||||
print('paid_count', paid_count)
|
|
||||||
payments_made, total_amount_paid, paid_count = 0, 0, 0
|
|
||||||
update_all_spent_accounts(session)
|
|
||||||
first_date_of_process_date = arrow.get(early_period_date_start).datetime
|
|
||||||
last_date_of_process_date = arrow.get(early_period_date_end).datetime
|
|
||||||
|
|
||||||
# Early month date filter
|
|
||||||
date_query_tuple = (
|
|
||||||
cast(BuildDecisionBookPayments.process_date, Date) >= first_date_of_process_date.date(), cast(BuildDecisionBookPayments.process_date, Date) <= last_date_of_process_date.date()
|
|
||||||
)
|
|
||||||
|
|
||||||
for payment_type in payment_type_list:
|
|
||||||
unpaid_debts = get_unpaid_debts(session, payment_type, date_query_tuple)
|
|
||||||
print('length unpaid debts: ', len(unpaid_debts))
|
|
||||||
for unpaid_debt in unpaid_debts:
|
|
||||||
amount_to_pay = unpaid_debt["total_payments"]
|
|
||||||
ref_id = unpaid_debt["ref_id"]
|
|
||||||
process_date = unpaid_debt["process_date"]
|
|
||||||
debit_row = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.uu_id == ref_id).first()
|
|
||||||
build_parts_id = debit_row.build_parts_id
|
|
||||||
first_date_of_process_date = find_first_day_of_month(process_date)
|
|
||||||
last_date_of_process_date = find_last_day_of_month(process_date)
|
|
||||||
money_to_pay_rows = AccountRecords.query.filter(
|
|
||||||
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
|
|
||||||
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(), cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date(),
|
|
||||||
).order_by(AccountRecords.bank_date.asc()).all()
|
|
||||||
if not money_to_pay_rows:
|
|
||||||
money_to_pay_rows = AccountRecords.query.filter(
|
|
||||||
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
|
|
||||||
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(), cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date(),
|
|
||||||
).order_by(AccountRecords.bank_date.asc()).all()
|
|
||||||
for money_to_pay_row in money_to_pay_rows:
|
|
||||||
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
|
||||||
money_to_pay_row = AccountRecords.query.filter(AccountRecords.id == money_to_pay_row.id).first()
|
|
||||||
available_money = abs(money_to_pay_row.currency_value) - abs(money_to_pay_row.remainder_balance)
|
|
||||||
if available_money > amount_to_pay:
|
|
||||||
print('NOT All money is spent amount_to_pay:', amount_to_pay, 'debit_row payment_amount:', debit_row.payment_amount)
|
|
||||||
close_payment_book(debit_row, money_to_pay_row, amount_to_pay, session)
|
|
||||||
total_amount_paid += amount_to_pay
|
|
||||||
paid_count += 1
|
|
||||||
payments_made += 1
|
|
||||||
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
|
||||||
break
|
|
||||||
elif available_money <= amount_to_pay:
|
|
||||||
print('All money is spent amount_to_pay:', available_money, 'debit_row payment_amount:', debit_row.payment_amount)
|
|
||||||
close_payment_book(debit_row, money_to_pay_row, available_money, session)
|
|
||||||
total_amount_paid += available_money
|
|
||||||
paid_count += 1
|
|
||||||
payments_made += 1
|
|
||||||
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
print(f"Something else happened available_money: {available_money}, amount_to_pay: {amount_to_pay}")
|
|
||||||
|
|
||||||
update_all_spent_accounts(session)
|
|
||||||
print('Early years decision book payments')
|
|
||||||
print('payments_made', payments_made)
|
|
||||||
print('total_amount_paid', total_amount_paid)
|
|
||||||
print('paid_count', paid_count)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
start_time = perf_counter()
|
import sys
|
||||||
|
|
||||||
print("\n===== PROCESSING PAYMENTS =====\n")
|
# Default values
|
||||||
print("Starting payment processing at:", datetime.now())
|
limit = 10
|
||||||
|
offset = 0
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
try:
|
||||||
|
limit = int(sys.argv[1])
|
||||||
|
except ValueError:
|
||||||
|
print(f"Error: Invalid limit value '{sys.argv[1]}'. Using default limit={limit}")
|
||||||
|
|
||||||
|
if len(sys.argv) > 2:
|
||||||
|
try:
|
||||||
|
offset = int(sys.argv[2])
|
||||||
|
except ValueError:
|
||||||
|
print(f"Error: Invalid offset value '{sys.argv[2]}'. Using default offset={offset}")
|
||||||
|
|
||||||
|
same_month_actions(limit=limit, offset=offset)
|
||||||
|
|
||||||
# Process payments for current month first
|
|
||||||
print("\n1. Processing current month payments...")
|
|
||||||
do_payments_of_this_month()
|
|
||||||
|
|
||||||
# Process payments for previous months
|
|
||||||
print("\n2. Processing previous months payments...")
|
|
||||||
do_payments_of_previos_months()
|
|
||||||
|
|
||||||
print("\n===== PAYMENT PROCESSING COMPLETE =====\n")
|
|
||||||
print("Payment processing completed at:", datetime.now())
|
|
||||||
|
|
||||||
# Analyze the payment situation after processing payments
|
|
||||||
print("\n===== ANALYZING PAYMENT SITUATION AFTER PROCESSING =====\n")
|
|
||||||
# analyze_payment_function()
|
|
||||||
|
|
||||||
end_time = perf_counter()
|
|
||||||
print(f"\n{end_time - start_time:.3f} : seconds")
|
|
||||||
|
|
||||||
|
|
||||||
# # Create a subquery to get the sum of payments for each debit's uu_id
|
|
||||||
# # For credit entries, ref_id points to the original debit's uu_id
|
|
||||||
# payment_sums = session.query(
|
|
||||||
# BuildDecisionBookPayments.ref_id.label('original_debt_id'),
|
|
||||||
# func.sum(func.abs(BuildDecisionBookPayments.payment_amount)).label('payment_sum')
|
|
||||||
# ).filter(
|
|
||||||
# BuildDecisionBookPayments.account_is_debit == False # Credit entries only
|
|
||||||
# ).group_by(BuildDecisionBookPayments.ref_id).subquery()
|
|
||||||
|
|
||||||
# # Main query to find debits with their payment sums
|
|
||||||
# query = session.query(BuildDecisionBookPayments)
|
|
||||||
|
|
||||||
# # Join with payment sums - cast uu_id to string to match ref_id type
|
|
||||||
# query = query.outerjoin(
|
|
||||||
# payment_sums,
|
|
||||||
# func.cast(BuildDecisionBookPayments.uu_id, String) == payment_sums.c.original_debt_id
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Filter for debits of the specified build part and payment type
|
|
||||||
# query = query.filter(
|
|
||||||
# BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
|
||||||
# BuildDecisionBookPayments.payment_types_id == debit_type.id,
|
|
||||||
# BuildDecisionBookPayments.account_is_debit == True, # Debit entries only
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Apply date filters if provided
|
|
||||||
# if date_query:
|
|
||||||
# for date_filter in date_query:
|
|
||||||
# query = query.filter(date_filter)
|
|
||||||
|
|
||||||
# # Filter for debits that are not fully paid
|
|
||||||
# # (payment_sum < debit_amount or payment_sum is NULL)
|
|
||||||
# query = query.filter(
|
|
||||||
# or_(
|
|
||||||
# payment_sums.c.payment_sum.is_(None),
|
|
||||||
# func.coalesce(payment_sums.c.payment_sum, 0) < func.abs(BuildDecisionBookPayments.payment_amount)
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Execute the query and return the results
|
|
||||||
# results = query.order_by(BuildDecisionBookPayments.process_date).all()
|
|
||||||
|
|
@ -0,0 +1,417 @@
|
||||||
|
import arrow
|
||||||
|
import calendar
|
||||||
|
|
||||||
|
from time import perf_counter
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from Schemas import BuildDecisionBookPayments, AccountRecords, AccountDelayInterest, ApiEnumDropdown, Build, BuildDecisionBook
|
||||||
|
from sqlalchemy import select, func, distinct, cast, Date, String, literal, desc, and_, or_, case
|
||||||
|
from Controllers.Postgres.engine import get_session_factory
|
||||||
|
|
||||||
|
|
||||||
|
def find_last_day_of_month(date_value):
|
||||||
|
today = date_value.date()
|
||||||
|
_, last_day = calendar.monthrange(today.year, today.month)
|
||||||
|
return datetime(today.year, today.month, last_day, 23, 59, 59)
|
||||||
|
|
||||||
|
|
||||||
|
def find_first_day_of_month(date_value):
|
||||||
|
today = date_value.date()
|
||||||
|
return datetime(today.year, today.month, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def add_month_to_date(date_value):
|
||||||
|
month_to_process = date_value.month
|
||||||
|
year_to_process = date_value.year
|
||||||
|
if date_value.month == 12:
|
||||||
|
month_to_process = 1
|
||||||
|
year_to_process += 1
|
||||||
|
else:
|
||||||
|
month_to_process += 1
|
||||||
|
_, last_day = calendar.monthrange(year_to_process, month_to_process)
|
||||||
|
return datetime(year_to_process, month_to_process, 1), datetime(year_to_process, month_to_process, last_day)
|
||||||
|
|
||||||
|
|
||||||
|
class BuildDuesTypes:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.debit: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.add_debit: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.renovation: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.lawyer_expence: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.service_fee: ApiEnumDropdownShallowCopy = None
|
||||||
|
self.information: ApiEnumDropdownShallowCopy = None
|
||||||
|
|
||||||
|
|
||||||
|
class ApiEnumDropdownShallowCopy:
|
||||||
|
id: int
|
||||||
|
uuid: str
|
||||||
|
enum_class: str
|
||||||
|
key: str
|
||||||
|
value: str
|
||||||
|
|
||||||
|
def __init__(self, id: int, uuid: str, enum_class: str, key: str, value: str):
|
||||||
|
self.id = id
|
||||||
|
self.uuid = uuid
|
||||||
|
self.enum_class = enum_class
|
||||||
|
self.key = key
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
|
||||||
|
def close_payment_book(payment_row_book, account_record, value, session, is_commission_applicable: bool = False):
|
||||||
|
"""Create a credit entry in BuildDecisionBookPayments to close a debt.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
payment_row_book: The debit entry to be paid
|
||||||
|
account_record: The account record containing the funds
|
||||||
|
value: The amount to pay
|
||||||
|
session: Database session
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The newly created payment record
|
||||||
|
"""
|
||||||
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
# account_record_remainder_balance = retrieve_remainder_balance_set_if_needed(account_record_id=account_record.id, session=session)
|
||||||
|
# print(f'NOT Updated remainder balance: {account_record_remainder_balance} | Account record id: {account_record.id}')
|
||||||
|
|
||||||
|
# Create a new credit entry (payment)
|
||||||
|
|
||||||
|
new_row = BuildDecisionBookPayments.create(
|
||||||
|
ref_id=str(payment_row_book.uu_id),
|
||||||
|
payment_plan_time_periods=payment_row_book.payment_plan_time_periods,
|
||||||
|
period_time=payment_row_book.period_time,
|
||||||
|
currency=payment_row_book.currency,
|
||||||
|
account_records_id=account_record.id,
|
||||||
|
account_records_uu_id=str(account_record.uu_id),
|
||||||
|
build_parts_id=payment_row_book.build_parts_id,
|
||||||
|
build_parts_uu_id=str(payment_row_book.build_parts_uu_id),
|
||||||
|
payment_amount=abs(value), # Negative for credit entries
|
||||||
|
payment_types_id=payment_row_book.payment_types_id,
|
||||||
|
payment_types_uu_id=str(payment_row_book.payment_types_uu_id),
|
||||||
|
process_date_m=payment_row_book.process_date.month,
|
||||||
|
process_date_y=payment_row_book.process_date.year,
|
||||||
|
process_date=payment_row_book.process_date,
|
||||||
|
build_decision_book_item_id=payment_row_book.build_decision_book_item_id if payment_row_book.build_decision_book_item_id else None,
|
||||||
|
build_decision_book_item_uu_id=str(payment_row_book.build_decision_book_item_uu_id) if payment_row_book.build_decision_book_item_uu_id else None,
|
||||||
|
decision_book_project_id=payment_row_book.decision_book_project_id if payment_row_book.decision_book_project_id else None,
|
||||||
|
decision_book_project_uu_id=str(payment_row_book.decision_book_project_uu_id) if payment_row_book.decision_book_project_uu_id else None,
|
||||||
|
build_decision_book_id=payment_row_book.build_decision_book_id if payment_row_book.build_decision_book_id else None,
|
||||||
|
build_decision_book_uu_id=str(payment_row_book.build_decision_book_uu_id) if payment_row_book.build_decision_book_uu_id else None,
|
||||||
|
is_confirmed=True,
|
||||||
|
account_is_debit=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save the new payment record
|
||||||
|
saved_row = new_row.save()
|
||||||
|
session.commit()
|
||||||
|
session.refresh(saved_row)
|
||||||
|
|
||||||
|
if is_commission_applicable:
|
||||||
|
AccountDelayInterest.set_session(session)
|
||||||
|
new_row_interest = AccountDelayInterest.create(
|
||||||
|
account_record_bank_date=account_record.bank_date,
|
||||||
|
book_payment_process_date=payment_row_book.process_date,
|
||||||
|
debt=abs(value),
|
||||||
|
account_records_id=account_record.id,
|
||||||
|
account_records_uu_id=str(account_record.uu_id),
|
||||||
|
build_decision_book_payment_id=saved_row.id,
|
||||||
|
build_decision_book_payment_uu_id=str(saved_row.uu_id),
|
||||||
|
)
|
||||||
|
new_row_interest.save()
|
||||||
|
session.commit()
|
||||||
|
session.refresh(new_row_interest)
|
||||||
|
print(f'Commission applicable: amount : {value} bank date: {account_record.bank_date} process date: {payment_row_book.process_date}')
|
||||||
|
# account_record_remainder_balance = retrieve_remainder_balance_set_if_needed(account_record_id=account_record.id, session=session)
|
||||||
|
# print(f'Updated remainder balance: {account_record_remainder_balance} | Account record id: {account_record.id}')
|
||||||
|
# check_current_debt_to_pay_from_database_is_closed(ref_id=new_row.ref_id, session=session)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_enums_from_database():
|
||||||
|
build_dues_types = BuildDuesTypes()
|
||||||
|
with ApiEnumDropdown.new_session() as session:
|
||||||
|
|
||||||
|
ApiEnumDropdown.set_session(session)
|
||||||
|
|
||||||
|
debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-D").first() # Debit
|
||||||
|
add_debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-A").first() # Add Debit
|
||||||
|
renovation_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-R").first() # Renovation
|
||||||
|
late_payment_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-L").first() # Lawyer expence
|
||||||
|
service_fee_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-S").first() # Service fee
|
||||||
|
information_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-I").first() # Information
|
||||||
|
|
||||||
|
build_dues_types.debit = ApiEnumDropdownShallowCopy(
|
||||||
|
debit_enum_shallow.id, str(debit_enum_shallow.uu_id), debit_enum_shallow.enum_class, debit_enum_shallow.key, debit_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.add_debit = ApiEnumDropdownShallowCopy(
|
||||||
|
add_debit_enum_shallow.id, str(add_debit_enum_shallow.uu_id), add_debit_enum_shallow.enum_class, add_debit_enum_shallow.key, add_debit_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.renovation = ApiEnumDropdownShallowCopy(
|
||||||
|
renovation_enum_shallow.id, str(renovation_enum_shallow.uu_id), renovation_enum_shallow.enum_class, renovation_enum_shallow.key, renovation_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.lawyer_expence = ApiEnumDropdownShallowCopy(
|
||||||
|
late_payment_enum_shallow.id, str(late_payment_enum_shallow.uu_id), late_payment_enum_shallow.enum_class, late_payment_enum_shallow.key, late_payment_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.service_fee = ApiEnumDropdownShallowCopy(
|
||||||
|
service_fee_enum_shallow.id, str(service_fee_enum_shallow.uu_id), service_fee_enum_shallow.enum_class, service_fee_enum_shallow.key, service_fee_enum_shallow.value
|
||||||
|
)
|
||||||
|
build_dues_types.information = ApiEnumDropdownShallowCopy(
|
||||||
|
information_enum_shallow.id, str(information_enum_shallow.uu_id), information_enum_shallow.enum_class, information_enum_shallow.key, information_enum_shallow.value
|
||||||
|
)
|
||||||
|
return [build_dues_types.debit, build_dues_types.lawyer_expence, build_dues_types.add_debit, build_dues_types.renovation, build_dues_types.service_fee, build_dues_types.information]
|
||||||
|
|
||||||
|
|
||||||
|
def get_read_payment_type(payment_result_type_uu_id):
|
||||||
|
payment_type_list = get_enums_from_database()
|
||||||
|
for payment_type in payment_type_list:
|
||||||
|
if payment_type.uuid == payment_result_type_uu_id:
|
||||||
|
return payment_type.key
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def pay_book_payment_by_account_records_id(account_records_id: int, session, payment_type_list: list):
|
||||||
|
timezone, today = 'Europe/Istanbul', datetime.now()
|
||||||
|
AccountRecords.set_session(session)
|
||||||
|
account_record_selected = AccountRecords.query.filter_by(id=account_records_id).first()
|
||||||
|
if not account_record_selected:
|
||||||
|
print(f"Account record not found with id: {account_records_id}")
|
||||||
|
return
|
||||||
|
# raise ValueError(f"Account record not found with id: {account_records_id}")
|
||||||
|
if not account_record_selected.currency_value + account_record_selected.remainder_balance > 0:
|
||||||
|
print(f"Account record {account_record_selected.id} has no money to pay {account_record_selected.currency_value + account_record_selected.remainder_balance}")
|
||||||
|
return
|
||||||
|
# raise ValueError(f"Account record {account_record_selected.id} has no money to pay {account_record_selected.currency_value + account_record_selected.remainder_balance}")
|
||||||
|
|
||||||
|
BuildDecisionBook.set_session(session)
|
||||||
|
selected_decision_book = BuildDecisionBook.query.filter_by(id=account_record_selected.build_decision_book_id).first()
|
||||||
|
if not selected_decision_book:
|
||||||
|
print(f"Build decision book not found with id: {account_record_selected.build_decision_book_id}")
|
||||||
|
return
|
||||||
|
# raise ValueError(f"Build decision book not found with id: {account_record_selected.build_decision_book_id}")
|
||||||
|
|
||||||
|
BuildDecisionBookPayments.set_session(session)
|
||||||
|
payment_result_type_uu_id = account_record_selected.payment_result_type_uu_id or payment_type_list[0]
|
||||||
|
priority_uuids = [payment_result_type_uu_id, *[pt.uuid for pt in payment_type_list if pt.uuid != payment_result_type_uu_id]]
|
||||||
|
order_payment_by_type = case(*[(BuildDecisionBookPayments.payment_types_uu_id == uuid, index) for index, uuid in enumerate(priority_uuids)], else_=len(priority_uuids))
|
||||||
|
|
||||||
|
payment_value_func = lambda account_record_selected: account_record_selected.currency_value + account_record_selected.remainder_balance
|
||||||
|
period_start_date = arrow.get(selected_decision_book.expiry_starts).to(timezone).datetime
|
||||||
|
period_end_date = arrow.get(selected_decision_book.expiry_ends).to(timezone).datetime
|
||||||
|
bank_date_from = arrow.get(account_record_selected.bank_date).to(timezone).datetime
|
||||||
|
month_start_date = find_first_day_of_month(bank_date_from)
|
||||||
|
payment_type_list = [payment_result_type_uu_id, *[pt.uuid for pt in payment_type_list if pt.uuid != payment_result_type_uu_id]]
|
||||||
|
|
||||||
|
# Do payments of period_start_date to bank_date_from [This months payment]
|
||||||
|
print('Do payments of period_start_date to bank_date_from [This months payment]')
|
||||||
|
for payment_type in payment_type_list:
|
||||||
|
# No matter what paymnet_uu_id check for debt and money amount to clarify debt close !
|
||||||
|
selected_payments = BuildDecisionBookPayments.query.filter(
|
||||||
|
BuildDecisionBookPayments.account_is_debit.is_(True),
|
||||||
|
BuildDecisionBookPayments.active.is_(True),
|
||||||
|
BuildDecisionBookPayments.build_parts_id == account_record_selected.build_parts_id,
|
||||||
|
BuildDecisionBookPayments.payment_types_uu_id == payment_type,
|
||||||
|
BuildDecisionBookPayments.is_closed.is_(False),
|
||||||
|
cast(BuildDecisionBookPayments.process_date, Date) >= month_start_date,
|
||||||
|
cast(BuildDecisionBookPayments.process_date, Date) <= bank_date_from,
|
||||||
|
).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||||
|
print(
|
||||||
|
'dates period_start_date:', month_start_date.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
'period_end_date:', bank_date_from.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
"payment_type", get_read_payment_type(payment_type),
|
||||||
|
)
|
||||||
|
print('fund', dict(
|
||||||
|
id=account_record_selected.id, build_parts_id=account_record_selected.build_parts_id, money_available=payment_value_func(account_record_selected),
|
||||||
|
bank_date=arrow.get(bank_date_from).format('YYYY-MM-DD HH:mm:ss'), currency_value=account_record_selected.currency_value,
|
||||||
|
remainder_balance=account_record_selected.remainder_balance, length_matches=len(selected_payments),
|
||||||
|
))
|
||||||
|
money_available = payment_value_func(account_record_selected)
|
||||||
|
if not money_available > 0:
|
||||||
|
print(f"Account record {account_record_selected.id} has no money to pay {money_available}")
|
||||||
|
break
|
||||||
|
# raise ValueError(f"Account record {account_record_selected.id} has no money to pay {money_available}")
|
||||||
|
|
||||||
|
for selected_payment in selected_payments:
|
||||||
|
money_available = payment_value_func(account_record_selected)
|
||||||
|
if selected_payment.is_closed:
|
||||||
|
continue
|
||||||
|
print('debt', dict(
|
||||||
|
id=selected_payment.id, payment_type=selected_payment.payment_types_id,
|
||||||
|
debt_date=arrow.get(selected_payment.process_date).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
payment_amount=selected_payment.payment_amount, debt_paid=selected_payment.debt_paid,
|
||||||
|
debt_to_pay=selected_payment.debt_to_pay, is_closed=selected_payment.is_closed,
|
||||||
|
))
|
||||||
|
if money_available > (-1 * selected_payment.debt_to_pay):
|
||||||
|
close_payment_book(
|
||||||
|
payment_row_book=selected_payment,
|
||||||
|
account_record=account_record_selected,
|
||||||
|
value=(-1 * selected_payment.debt_to_pay),
|
||||||
|
session=session,
|
||||||
|
is_commission_applicable=False
|
||||||
|
)
|
||||||
|
account_record_selected = AccountRecords.query.filter_by(id=account_records_id).first()
|
||||||
|
money_available = payment_value_func(account_record_selected)
|
||||||
|
print('updated fund', dict(
|
||||||
|
id=account_record_selected.id, build_parts_id=account_record_selected.build_parts_id, money_available=money_available,
|
||||||
|
bank_date=arrow.get(bank_date_from).format('YYYY-MM-DD HH:mm:ss'), currency_value=account_record_selected.currency_value,
|
||||||
|
remainder_balance=account_record_selected.remainder_balance, length_matches=len(selected_payments),
|
||||||
|
))
|
||||||
|
print(f"There is still money to pay for this account record {account_record_selected.id} fund to continue : {money_available}")
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
close_payment_book(
|
||||||
|
payment_row_book=selected_payment,
|
||||||
|
account_record=account_record_selected,
|
||||||
|
value=money_available,
|
||||||
|
session=session,
|
||||||
|
is_commission_applicable=False
|
||||||
|
)
|
||||||
|
account_record_selected = AccountRecords.query.filter_by(id=account_records_id).first()
|
||||||
|
money_available = payment_value_func(account_record_selected)
|
||||||
|
print('updated fund', dict(
|
||||||
|
id=account_record_selected.id, build_parts_id=account_record_selected.build_parts_id, money_available=money_available,
|
||||||
|
bank_date=arrow.get(bank_date_from).format('YYYY-MM-DD HH:mm:ss'), currency_value=account_record_selected.currency_value,
|
||||||
|
remainder_balance=account_record_selected.remainder_balance, length_matches=len(selected_payments),
|
||||||
|
))
|
||||||
|
print(f"All payments for account record {account_record_selected.id} have been paid.")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Do payments of period_start_date to bank_date_from [Build Decision Book Payments of period]
|
||||||
|
print('Do payments of period_start_date to bank_date_from [Build Decision Book Payments of period]')
|
||||||
|
|
||||||
|
# for payment_type in payment_type_list:
|
||||||
|
money_available = payment_value_func(account_record_selected)
|
||||||
|
if not money_available > 0:
|
||||||
|
print(f"Account record {account_record_selected.id} has no money to pay {money_available}")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"No debt is found for account record {account_record_selected.id} for this month. Fund is still available: {money_available}")
|
||||||
|
selected_payments = BuildDecisionBookPayments.query.filter(
|
||||||
|
BuildDecisionBookPayments.account_is_debit.is_(True),
|
||||||
|
BuildDecisionBookPayments.active.is_(True),
|
||||||
|
BuildDecisionBookPayments.is_closed.is_(False),
|
||||||
|
BuildDecisionBookPayments.build_parts_id == account_record_selected.build_parts_id,
|
||||||
|
# BuildDecisionBookPayments.payment_types_uu_id == payment_type,
|
||||||
|
cast(BuildDecisionBookPayments.process_date, Date) >= period_start_date,
|
||||||
|
cast(BuildDecisionBookPayments.process_date, Date) <= bank_date_from,
|
||||||
|
).order_by(BuildDecisionBookPayments.process_date.desc(), order_payment_by_type).all()
|
||||||
|
print(
|
||||||
|
'dates period_start_date:', period_start_date.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
'period_end_date:', bank_date_from.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
"payment_type", get_read_payment_type(payment_type),
|
||||||
|
)
|
||||||
|
for selected_payment in selected_payments:
|
||||||
|
money_available = payment_value_func(account_record_selected)
|
||||||
|
if selected_payment.is_closed:
|
||||||
|
continue
|
||||||
|
print('debt', dict(
|
||||||
|
id=selected_payment.id, payment_type=selected_payment.payment_types_id,
|
||||||
|
debt_date=arrow.get(selected_payment.process_date).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
payment_amount=selected_payment.payment_amount, debt_paid=selected_payment.debt_paid,
|
||||||
|
debt_to_pay=selected_payment.debt_to_pay, is_closed=selected_payment.is_closed,
|
||||||
|
))
|
||||||
|
if money_available > (-1 * selected_payment.debt_to_pay):
|
||||||
|
close_payment_book(
|
||||||
|
payment_row_book=selected_payment, account_record=account_record_selected, value=(-1 * selected_payment.debt_to_pay), session=session, is_commission_applicable=True
|
||||||
|
)
|
||||||
|
account_record_selected = AccountRecords.query.filter_by(id=account_records_id).first()
|
||||||
|
money_available = payment_value_func(account_record_selected)
|
||||||
|
print('updated fund', dict(
|
||||||
|
id=account_record_selected.id, build_parts_id=account_record_selected.build_parts_id, money_available=money_available,
|
||||||
|
bank_date=arrow.get(bank_date_from).format('YYYY-MM-DD HH:mm:ss'), currency_value=account_record_selected.currency_value,
|
||||||
|
remainder_balance=account_record_selected.remainder_balance, length_matches=len(selected_payments),
|
||||||
|
))
|
||||||
|
print(f"There is still money to pay for this account record {account_record_selected.id} fund to continue : {money_available}")
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
close_payment_book(
|
||||||
|
payment_row_book=selected_payment, account_record=account_record_selected, value=money_available, session=session, is_commission_applicable=True
|
||||||
|
)
|
||||||
|
account_record_selected = AccountRecords.query.filter_by(id=account_records_id).first()
|
||||||
|
money_available = payment_value_func(account_record_selected)
|
||||||
|
print('updated fund', dict(
|
||||||
|
id=account_record_selected.id, build_parts_id=account_record_selected.build_parts_id, money_available=money_available,
|
||||||
|
bank_date=arrow.get(bank_date_from).format('YYYY-MM-DD HH:mm:ss'), currency_value=account_record_selected.currency_value,
|
||||||
|
remainder_balance=account_record_selected.remainder_balance, length_matches=len(selected_payments),
|
||||||
|
))
|
||||||
|
print(f"All payments for account record {account_record_selected.id} have been paid.")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Do all payments of period_start_date to current_date [All payments]
|
||||||
|
print('Do all payments of period_start_date to current_date [All payments]')
|
||||||
|
# for payment_type in payment_type_list:
|
||||||
|
money_available = payment_value_func(account_record_selected)
|
||||||
|
if not money_available > 0:
|
||||||
|
print(f"Account record {account_record_selected.id} has no money to pay {money_available}")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"No debt is found for account record {account_record_selected.id} for this month. Fund is still available: {money_available}")
|
||||||
|
selected_payments = BuildDecisionBookPayments.query.filter(
|
||||||
|
BuildDecisionBookPayments.account_is_debit.is_(True),
|
||||||
|
BuildDecisionBookPayments.active.is_(True),
|
||||||
|
BuildDecisionBookPayments.is_closed.is_(False),
|
||||||
|
BuildDecisionBookPayments.build_parts_id == account_record_selected.build_parts_id,
|
||||||
|
# BuildDecisionBookPayments.payment_types_uu_id == payment_type,
|
||||||
|
cast(BuildDecisionBookPayments.process_date, Date) >= period_start_date,
|
||||||
|
cast(BuildDecisionBookPayments.process_date, Date) <= today.date(),
|
||||||
|
).order_by(BuildDecisionBookPayments.process_date.desc(), order_payment_by_type).all()
|
||||||
|
print(
|
||||||
|
'dates period_start_date:', period_start_date.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
'period_end_date:', today.date().strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
"payment_type", get_read_payment_type(payment_type),
|
||||||
|
)
|
||||||
|
for selected_payment in selected_payments:
|
||||||
|
money_available = payment_value_func(account_record_selected)
|
||||||
|
if selected_payment.is_closed:
|
||||||
|
continue
|
||||||
|
print('debt', dict(
|
||||||
|
id=selected_payment.id, payment_type=selected_payment.payment_types_id,
|
||||||
|
debt_date=arrow.get(selected_payment.process_date).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
payment_amount=selected_payment.payment_amount, debt_paid=selected_payment.debt_paid,
|
||||||
|
debt_to_pay=selected_payment.debt_to_pay, is_closed=selected_payment.is_closed,
|
||||||
|
))
|
||||||
|
if money_available > (-1 * selected_payment.debt_to_pay):
|
||||||
|
close_payment_book(
|
||||||
|
payment_row_book=selected_payment, account_record=account_record_selected, value=(-1 * selected_payment.debt_to_pay), session=session, is_commission_applicable=True
|
||||||
|
)
|
||||||
|
account_record_selected = AccountRecords.query.filter_by(id=account_records_id).first()
|
||||||
|
money_available = payment_value_func(account_record_selected)
|
||||||
|
print('updated fund', dict(
|
||||||
|
id=account_record_selected.id, build_parts_id=account_record_selected.build_parts_id, money_available=money_available,
|
||||||
|
bank_date=arrow.get(bank_date_from).format('YYYY-MM-DD HH:mm:ss'), currency_value=account_record_selected.currency_value,
|
||||||
|
remainder_balance=account_record_selected.remainder_balance, length_matches=len(selected_payments),
|
||||||
|
))
|
||||||
|
print(f"There is still money to pay for this account record {account_record_selected.id} fund to continue : {money_available}")
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
close_payment_book(
|
||||||
|
payment_row_book=selected_payment, account_record=account_record_selected, value=money_available, session=session, is_commission_applicable=True
|
||||||
|
)
|
||||||
|
account_record_selected = AccountRecords.query.filter_by(id=account_records_id).first()
|
||||||
|
money_available = payment_value_func(account_record_selected)
|
||||||
|
print('updated fund', dict(
|
||||||
|
id=account_record_selected.id, build_parts_id=account_record_selected.build_parts_id, money_available=money_available,
|
||||||
|
bank_date=arrow.get(bank_date_from).format('YYYY-MM-DD HH:mm:ss'), currency_value=account_record_selected.currency_value,
|
||||||
|
remainder_balance=account_record_selected.remainder_balance, length_matches=len(selected_payments),
|
||||||
|
))
|
||||||
|
print(f"All payments for account record {account_record_selected.id} have been paid.")
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
id_given = 1
|
||||||
|
session_factory = get_session_factory()
|
||||||
|
session = session_factory()
|
||||||
|
payment_type_list = get_enums_from_database()
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
try:
|
||||||
|
id_given = int(sys.argv[1])
|
||||||
|
except ValueError:
|
||||||
|
print(f"Error: Invalid limit value '{sys.argv[1]}'. Using default limit={limit}")
|
||||||
|
|
||||||
|
result = pay_book_payment_by_account_records_id(account_records_id=id_given, session=session, payment_type_list=payment_type_list)
|
||||||
|
|
@ -339,17 +339,17 @@ services:
|
||||||
# ports:
|
# ports:
|
||||||
# - "8005:8005"
|
# - "8005:8005"
|
||||||
|
|
||||||
# initializer_service:
|
initializer_service:
|
||||||
# container_name: initializer_service
|
container_name: initializer_service
|
||||||
# build:
|
build:
|
||||||
# context: .
|
context: .
|
||||||
# dockerfile: ServicesApi/Builds/Initial/Dockerfile
|
dockerfile: ServicesApi/Builds/Initial/Dockerfile
|
||||||
# environment:
|
environment:
|
||||||
# - SET_ALEMBIC=1
|
- SET_ALEMBIC=1
|
||||||
# networks:
|
networks:
|
||||||
# - wag-services
|
- wag-services
|
||||||
# env_file:
|
env_file:
|
||||||
# - api_env.env
|
- api_env.env
|
||||||
# mem_limit: 512m
|
# mem_limit: 512m
|
||||||
# cpus: 0.5
|
# cpus: 0.5
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Navigate to the project root directory
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
echo "Building and starting the finder_payment_service container..."
|
||||||
|
docker compose up --build -d finder_payment_service
|
||||||
|
|
||||||
|
echo "Waiting for container to be ready..."
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
echo "Running the Python script inside the container..."
|
||||||
|
docker exec finder_payment_service python /draft/readme.py
|
||||||
|
|
||||||
|
echo "Done! Check the output above."
|
||||||
Loading…
Reference in New Issue