From 9edc6cb6a039d44640ef69f9169cc0c8d01ca9f4 Mon Sep 17 00:00:00 2001 From: Berkay Date: Sun, 6 Jul 2025 17:04:29 +0300 Subject: [PATCH] updated payment service --- ServicesApi/Builds/Initial/app.py | 1 + ServicesApi/Builds/Initial/init_enums.py | 20 + ServicesApi/Schemas/__init__.py | 2 + ServicesApi/Schemas/account/account.py | 198 ++-- ServicesApi/Schemas/account/iban.py | 25 + ServicesApi/Schemas/building/decision_book.py | 7 +- ServicesBank/Finder/Payment/README.md | 83 +- .../Finder/Payment/draft/old_runner_second.py | 385 ++++++++ .../draft/old_running_code_with_case_added.py | 869 ++++++++++++++++++ ServicesBank/Finder/Payment/draft/readme.py | 199 ++++ ServicesBank/Finder/Payment/ex_runner.py | 492 ++++++++++ .../Finder/Payment/interest_calculate.py | 137 +++ ServicesBank/Finder/Payment/old_runner.py | 326 ------- ServicesBank/Finder/Payment/runner.py | 850 +++-------------- ServicesBank/Finder/Payment/runner_new.py | 417 +++++++++ docker-compose.yml | 22 +- run_finder_payment.sh | 15 + 17 files changed, 2871 insertions(+), 1177 deletions(-) create mode 100644 ServicesBank/Finder/Payment/draft/old_runner_second.py create mode 100644 ServicesBank/Finder/Payment/draft/old_running_code_with_case_added.py create mode 100644 ServicesBank/Finder/Payment/draft/readme.py create mode 100644 ServicesBank/Finder/Payment/ex_runner.py create mode 100644 ServicesBank/Finder/Payment/interest_calculate.py create mode 100644 ServicesBank/Finder/Payment/runner_new.py create mode 100755 run_finder_payment.sh diff --git a/ServicesApi/Builds/Initial/app.py b/ServicesApi/Builds/Initial/app.py index a30dbf7..2cfb167 100644 --- a/ServicesApi/Builds/Initial/app.py +++ b/ServicesApi/Builds/Initial/app.py @@ -17,6 +17,7 @@ if __name__ == "__main__": with get_db() as db_session: if set_alembic: generate_alembic(session=db_session) + exit() try: create_one_address(db_session=db_session) except Exception as e: diff --git a/ServicesApi/Builds/Initial/init_enums.py b/ServicesApi/Builds/Initial/init_enums.py index 1841195..cb7ba59 100644 --- a/ServicesApi/Builds/Initial/init_enums.py +++ b/ServicesApi/Builds/Initial/init_enums.py @@ -115,6 +115,26 @@ def init_api_enums_build_types(db_session): "type_code": "BDT-I", "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", "type_code": "ART-A", diff --git a/ServicesApi/Schemas/__init__.py b/ServicesApi/Schemas/__init__.py index a69a36c..8d6d6d7 100644 --- a/ServicesApi/Schemas/__init__.py +++ b/ServicesApi/Schemas/__init__.py @@ -6,6 +6,7 @@ from Schemas.account.account import ( AccountDetail, AccountRecordExchanges, AccountRecords, + AccountDelayInterest, ) from Schemas.account.iban import ( BuildIbans, @@ -124,6 +125,7 @@ __all__ = [ "AccountDetail", "AccountRecordExchanges", "AccountRecords", + "AccountDelayInterest", "BuildIbans", "BuildIbanDescription", "RelationshipEmployee2PostCode", diff --git a/ServicesApi/Schemas/account/account.py b/ServicesApi/Schemas/account/account.py index b8671a0..0552d25 100644 --- a/ServicesApi/Schemas/account/account.py +++ b/ServicesApi/Schemas/account/account.py @@ -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): """ build_decision_book_id = kaydın sorumlu olduğu karar defteri @@ -324,48 +363,20 @@ class AccountRecords(CrudCollection): __tablename__ = "account_records" __exclude__fields__ = [] - __enum_list__ = [ - ("receive_debit", "DebitTypes", "D"), - ("budget_type", "BudgetType", "B"), - ] + __enum_list__ = [("receive_debit", "DebitTypes", "D"),("budget_type", "BudgetType", "B")] - iban: Mapped[str] = mapped_column( - 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_balance: Mapped[float] = mapped_column( - Numeric(20, 6), nullable=False, comment="Bank Balance" - ) - 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" - ) + iban: Mapped[str] = mapped_column(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_balance: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, comment="Bank Balance") + 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="") 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_d: Mapped[int] = mapped_column(SmallInteger) - approving_accounting_record: Mapped[bool] = mapped_column( - Boolean, server_default="0" - ) - accounting_receipt_date: Mapped[TIMESTAMP] = mapped_column( - TIMESTAMP(timezone=True), server_default="1900-01-01 00:00:00" - ) + approving_accounting_record: Mapped[bool] = mapped_column(Boolean, server_default="0") + 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") status_id: Mapped[int] = mapped_column(SmallInteger, server_default="0") approved_record: Mapped[bool] = mapped_column(Boolean, server_default="0") - import_file_name: Mapped[str] = mapped_column( - 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 - ) - budget_type_uu_id: Mapped[str] = mapped_column( - String, nullable=True, comment="Budget Type UU ID" - ) + import_file_name: Mapped[str] = mapped_column(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) + 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_uu_id: Mapped[str] = mapped_column( - 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" - ) - + company_uu_id: Mapped[str] = mapped_column(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_person_id: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=True) - send_person_uu_id: Mapped[str] = mapped_column( - 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" - ) - - 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" - ) + send_person_uu_id: Mapped[str] = mapped_column(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") + 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_uu_id = mapped_column(String, nullable=True, comment="Customer UU ID") build_id: Mapped[int] = mapped_column(ForeignKey("build.id"), nullable=True) - build_uu_id: Mapped[str] = mapped_column( - 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_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" - # ) + build_uu_id: Mapped[str] = mapped_column(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_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") + is_commission_applied: Mapped[bool] = mapped_column(Boolean, server_default="0") __table_args__ = ( Index("_budget_records_ndx_00", is_receipt_mail_send, bank_date), - Index( - "_budget_records_ndx_01", - iban, - bank_date, - bank_reference_code, - bank_balance, - unique=True, - ), + Index("_budget_records_ndx_01", iban, bank_date, bank_reference_code, bank_balance, unique=True), 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): diff --git a/ServicesApi/Schemas/account/iban.py b/ServicesApi/Schemas/account/iban.py index 09b8fdf..bb7abc2 100644 --- a/ServicesApi/Schemas/account/iban.py +++ b/ServicesApi/Schemas/account/iban.py @@ -11,6 +11,7 @@ from Schemas.base_imports import ( mapped_column, Mapped, ) +from sqlalchemy import text class BuildIbans(CrudCollection): @@ -44,6 +45,30 @@ class BuildIbans(CrudCollection): {"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): """ diff --git a/ServicesApi/Schemas/building/decision_book.py b/ServicesApi/Schemas/building/decision_book.py index f65f78b..0596835 100644 --- a/ServicesApi/Schemas/building/decision_book.py +++ b/ServicesApi/Schemas/building/decision_book.py @@ -570,13 +570,14 @@ class BuildDecisionBookPayments(CrudCollection): 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") decision_book_project_id: Mapped[int] = mapped_column( - ForeignKey("build_decision_book_projects.id"), - nullable=True, - comment="Decision Book Project ID", + ForeignKey("build_decision_book_projects.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") 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") + 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_uu_id: Mapped[str] = mapped_column( diff --git a/ServicesBank/Finder/Payment/README.md b/ServicesBank/Finder/Payment/README.md index 87b4266..3c4cbc4 100644 --- a/ServicesBank/Finder/Payment/README.md +++ b/ServicesBank/Finder/Payment/README.md @@ -7,38 +7,85 @@ end_time = perf_counter() elapsed = end_time - start_time print(f'{elapsed:.3f} : seconds') 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) -# BuildDecisionBookPayments are reverse records of AccountRecords + +# BuildDecisionBookPayments are reverse records of AccountRecords + AccountRecords.approved_record == True 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.currency_value > 0 Received Money Transaction + - 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.receive_debit = Credit Receiver (Incoming money from client) / Debit Sender (Debt to be paid by system) + +1.1 +AccountRecords.currency_value > 0 Received Money Transaction + +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.bank_date (Date money arrived) AccountRecords.process_type (Type of bank transaction) -1.2 - AccountRecords.currency_value < 0 Sent Money Transaction - - +1.2 +AccountRecords.currency_value < 0 Sent Money Transaction - 2. Stage (Payment Match Process) -Parse : BuildDecisionBookPayments.process_date (Year / Month / Day / Time) -BuildDecisionBookPayments.account_records_id == None ( Payment is not assigned to any account record) -BuildDecisionBookPayments.payment_types_id == debit_enum.id (Payment type is debit) + Parse : BuildDecisionBookPayments.process_date (Year / Month / Day / Time) + BuildDecisionBookPayments.account_records_id == None ( Payment is not assigned to any account record) + 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.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) -Do payment set left money to account record as AccountRecords.remainder_balance - - + Do payment set left money to account record as AccountRecords.remainder_balance diff --git a/ServicesBank/Finder/Payment/draft/old_runner_second.py b/ServicesBank/Finder/Payment/draft/old_runner_second.py new file mode 100644 index 0000000..446ff61 --- /dev/null +++ b/ServicesBank/Finder/Payment/draft/old_runner_second.py @@ -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") diff --git a/ServicesBank/Finder/Payment/draft/old_running_code_with_case_added.py b/ServicesBank/Finder/Payment/draft/old_running_code_with_case_added.py new file mode 100644 index 0000000..b60759c --- /dev/null +++ b/ServicesBank/Finder/Payment/draft/old_running_code_with_case_added.py @@ -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() \ No newline at end of file diff --git a/ServicesBank/Finder/Payment/draft/readme.py b/ServicesBank/Finder/Payment/draft/readme.py new file mode 100644 index 0000000..a262812 --- /dev/null +++ b/ServicesBank/Finder/Payment/draft/readme.py @@ -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") \ No newline at end of file diff --git a/ServicesBank/Finder/Payment/ex_runner.py b/ServicesBank/Finder/Payment/ex_runner.py new file mode 100644 index 0000000..f442a49 --- /dev/null +++ b/ServicesBank/Finder/Payment/ex_runner.py @@ -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") diff --git a/ServicesBank/Finder/Payment/interest_calculate.py b/ServicesBank/Finder/Payment/interest_calculate.py new file mode 100644 index 0000000..8753e19 --- /dev/null +++ b/ServicesBank/Finder/Payment/interest_calculate.py @@ -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'; +""" diff --git a/ServicesBank/Finder/Payment/old_runner.py b/ServicesBank/Finder/Payment/old_runner.py index 8599ff8..e69de29 100644 --- a/ServicesBank/Finder/Payment/old_runner.py +++ b/ServicesBank/Finder/Payment/old_runner.py @@ -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...") diff --git a/ServicesBank/Finder/Payment/runner.py b/ServicesBank/Finder/Payment/runner.py index f9f2d7f..5454edf 100644 --- a/ServicesBank/Finder/Payment/runner.py +++ b/ServicesBank/Finder/Payment/runner.py @@ -6,11 +6,13 @@ from decimal import Decimal from datetime import datetime, timedelta 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 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 ServicesApi.Schemas.account.account import AccountRecords -#from ServicesApi.Schemas.building.decision_book import BuildDecisionBookPayments 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) +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): @@ -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] -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. +def retrieve_remainder_balance_set_if_needed(account_record_id: int, session): + """Update the remainder_balance of an account after spending money. 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 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) + + 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) - - 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") + 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): @@ -286,7 +150,9 @@ def close_payment_book(payment_row_book, account_record, value, session): 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), @@ -317,538 +183,126 @@ def close_payment_book(payment_row_book, account_record, value, session): saved_row = new_row.save() session.commit() session.refresh(saved_row) - session.flush() - return 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 update_account_remainder_if_spent(account_record, ref_id: str, session): - """Update the remainder_balance of an account after spending money. +def refresh_ids_to_iterate_account_records(session, limit, offset): + 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: - account_record: The account record to update - amount_spent: The amount spent in this transaction session: Database session + limit: Maximum number of records to return + offset: Number of records to skip Returns: - bool: True if all money is spent, False otherwise + List of account record IDs """ - AccountRecords.set_session(session) - BuildDecisionBookPayments.set_session(session) - session.commit() - sum_of_paid = 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 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 + today = datetime.now() + return session.query(AccountRecords.id).filter( + AccountRecords.active == True, func.abs(AccountRecords.currency_value) - func.abs(AccountRecords.remainder_balance) > 0, + AccountRecords.currency_value > 0, AccountRecords.build_parts_id.isnot(None), cast(AccountRecords.bank_date, Date) < find_first_day_of_month(today).date() + ).order_by(AccountRecords.bank_date.desc()).limit(limit).offset(offset).all() -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. +def get_read_payment_type(payment_result_type_uu_id, payment_type_list): + for payment_type in payment_type_list: + if payment_type.uuid == payment_result_type_uu_id: + return payment_type.key + return None + + +def same_month_actions(limit=10, offset=0): + """Main function to process account records and find debits. Args: - session: Database session + limit: Maximum number of records to process + offset: Number of records to skip """ - with AccountRecords.new_session() as session: - # Set sessions for models + today, build_id, start_time = datetime.now(), 1, perf_counter() + + 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) 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 - 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 - 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) + same_month_build_decision_book_debits = BuildDecisionBookPayments.query.filter( + BuildDecisionBookPayments.account_is_debit.is_(True), + BuildDecisionBookPayments.active.is_(True), + BuildDecisionBookPayments.build_parts_id == account_record_selected.build_parts_id, + func.extract('month', BuildDecisionBookPayments.process_date) == bank_month, + func.extract('year', BuildDecisionBookPayments.process_date) == bank_year, + BuildDecisionBookPayments.is_closed.is_(False), + # BuildDecisionBookPayments.payment_types_uu_id == payment_result_type_uu_id, + ).order_by(order_payment_by_type, BuildDecisionBookPayments.process_date.desc()).all() + match_payment_type = get_read_payment_type(payment_result_type_uu_id, payment_type_list) + print('fund', dict( + id=account_record_selected.id, + build_parts_id=account_record_selected.build_parts_id, + payment_result_type_uu_id=match_payment_type, + bank_date=arrow.get(account_record_selected.bank_date).format('YYYY-MM-DD HH:mm:ss'), + currency_value=account_record_selected.currency_value, + remainder_balance=account_record_selected.remainder_balance, + length_matches=len(same_month_build_decision_book_debits), + )) + for same_month_build_decision_book_debit in same_month_build_decision_book_debits: + 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_type=match_payment_type, + debt_date=arrow.get(same_month_build_decision_book_debit.process_date).format('YYYY-MM-DD HH:mm:ss'), + payment_amount=same_month_build_decision_book_debit.payment_amount, + debt_paid=same_month_build_decision_book_debit.debt_paid, + debt_to_pay=same_month_build_decision_book_debit.debt_to_pay, + is_closed=same_month_build_decision_book_debit.is_closed, + )) if __name__ == "__main__": - start_time = perf_counter() + import sys - print("\n===== PROCESSING PAYMENTS =====\n") - print("Starting payment processing at:", datetime.now()) + # Default values + 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() \ No newline at end of file diff --git a/ServicesBank/Finder/Payment/runner_new.py b/ServicesBank/Finder/Payment/runner_new.py new file mode 100644 index 0000000..2d3c681 --- /dev/null +++ b/ServicesBank/Finder/Payment/runner_new.py @@ -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) \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index dd60895..3b09f93 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -339,17 +339,17 @@ services: # ports: # - "8005:8005" - # initializer_service: - # container_name: initializer_service - # build: - # context: . - # dockerfile: ServicesApi/Builds/Initial/Dockerfile - # environment: - # - SET_ALEMBIC=1 - # networks: - # - wag-services - # env_file: - # - api_env.env + initializer_service: + container_name: initializer_service + build: + context: . + dockerfile: ServicesApi/Builds/Initial/Dockerfile + environment: + - SET_ALEMBIC=1 + networks: + - wag-services + env_file: + - api_env.env # mem_limit: 512m # cpus: 0.5 diff --git a/run_finder_payment.sh b/run_finder_payment.sh new file mode 100755 index 0000000..3d24f98 --- /dev/null +++ b/run_finder_payment.sh @@ -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."