updated payment service

This commit is contained in:
Berkay 2025-07-06 17:04:29 +03:00
parent 88afa6b329
commit 9edc6cb6a0
17 changed files with 2871 additions and 1177 deletions

View File

@ -17,6 +17,7 @@ if __name__ == "__main__":
with get_db() as db_session: with get_db() as db_session:
if set_alembic: if set_alembic:
generate_alembic(session=db_session) generate_alembic(session=db_session)
exit()
try: try:
create_one_address(db_session=db_session) create_one_address(db_session=db_session)
except Exception as e: except Exception as e:

View File

@ -115,6 +115,26 @@ def init_api_enums_build_types(db_session):
"type_code": "BDT-I", "type_code": "BDT-I",
"type_name": "Information", "type_name": "Information",
}, },
{
"enum_class": "BuildDuesTypes",
"type_code": "BDT-CL",
"type_name": "Close Last Period Receipt",
},
{
"enum_class": "BuildDuesTypes",
"type_code": "BDT-OP",
"type_name": "Open New Period Receipt",
},
{
"enum_class": "BuildDuesTypes",
"type_code": "BDT-CT",
"type_name": "Commission Type",
},
{
"enum_class": "BuildDuesTypes",
"type_code": "BDT-FPR",
"type_name": "Fixation Payment Receipt",
},
{ {
"enum_class": "AccountingReceiptTypes", "enum_class": "AccountingReceiptTypes",
"type_code": "ART-A", "type_code": "ART-A",

View File

@ -6,6 +6,7 @@ from Schemas.account.account import (
AccountDetail, AccountDetail,
AccountRecordExchanges, AccountRecordExchanges,
AccountRecords, AccountRecords,
AccountDelayInterest,
) )
from Schemas.account.iban import ( from Schemas.account.iban import (
BuildIbans, BuildIbans,
@ -124,6 +125,7 @@ __all__ = [
"AccountDetail", "AccountDetail",
"AccountRecordExchanges", "AccountRecordExchanges",
"AccountRecords", "AccountRecords",
"AccountDelayInterest",
"BuildIbans", "BuildIbans",
"BuildIbanDescription", "BuildIbanDescription",
"RelationshipEmployee2PostCode", "RelationshipEmployee2PostCode",

View File

@ -315,6 +315,45 @@ class AccountRecordExchanges(CrudCollection):
) )
class AccountDelayInterest(CrudCollection):
__tablename__ = "account_delay_interest"
__exclude__fields__ = []
interest_turn: Mapped[str] = mapped_column(String(10), nullable=False, server_default="akdi")
interest_rate: Mapped[float] = mapped_column(Numeric(10, 2), nullable=False, server_default="0")
delay_day: Mapped[int] = mapped_column(Integer, nullable=False, comment="Delay in days", server_default="0")
daily_rate: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, server_default="0")
interest: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, server_default="0")
bsmv: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, server_default="0")
kkdf: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, server_default="0")
total: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, server_default="0")
account_record_bank_date: Mapped[TIMESTAMP] = mapped_column(TIMESTAMP(timezone=True), nullable=True)
book_payment_process_date: Mapped[TIMESTAMP] = mapped_column(TIMESTAMP(timezone=True), nullable=True)
debt: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False)
approving_at: Mapped[TIMESTAMP] = mapped_column(TIMESTAMP(timezone=True), nullable=True)
approved_record: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default="0")
approving_person_id: Mapped[int] = mapped_column(Integer, nullable=True)
approving_person_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Approving Person UU ID")
bank_resp_company_id: Mapped[int] = mapped_column(Integer, nullable=True)
bank_resp_company_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Bank Response Company UU ID")
account_records_id: Mapped[int] = mapped_column(ForeignKey("account_records.id"), nullable=False)
account_records_uu_id: Mapped[str] = mapped_column(String(100), nullable=False)
build_decision_book_payment_id: Mapped[int] = mapped_column(ForeignKey("build_decision_book_payments.id"), nullable=False)
build_decision_book_payment_uu_id: Mapped[str] = mapped_column(String(100), nullable=True)
new_build_decision_book_payment_id: Mapped[int] = mapped_column(Integer, nullable=True)
new_build_decision_book_payment_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="New Build Decision Book Payment UU ID")
__table_args__ = (
Index("_account_delay_interest_ndx_00", account_records_id, build_decision_book_payment_id),
{"comment": "Account Delay Interest Information"},
)
class AccountRecords(CrudCollection): class AccountRecords(CrudCollection):
""" """
build_decision_book_id = kaydın sorumlu olduğu karar defteri build_decision_book_id = kaydın sorumlu olduğu karar defteri
@ -324,48 +363,20 @@ class AccountRecords(CrudCollection):
__tablename__ = "account_records" __tablename__ = "account_records"
__exclude__fields__ = [] __exclude__fields__ = []
__enum_list__ = [ __enum_list__ = [("receive_debit", "DebitTypes", "D"),("budget_type", "BudgetType", "B")]
("receive_debit", "DebitTypes", "D"),
("budget_type", "BudgetType", "B"),
]
iban: Mapped[str] = mapped_column( iban: Mapped[str] = mapped_column(String(64), nullable=False, comment="IBAN Number of Bank")
String(64), nullable=False, comment="IBAN Number of Bank" bank_date: Mapped[TIMESTAMP] = mapped_column(TIMESTAMP(timezone=True), nullable=False, comment="Bank Transaction Date")
) currency_value: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, comment="Currency Value")
bank_date: Mapped[TIMESTAMP] = mapped_column( bank_balance: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, comment="Bank Balance")
TIMESTAMP(timezone=True), nullable=False, comment="Bank Transaction Date" currency: Mapped[str] = mapped_column(String(5), nullable=False, comment="Unit of Currency")
) additional_balance: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, comment="Additional Balance")
channel_branch: Mapped[str] = mapped_column(String(120), nullable=False, comment="Branch Bank")
currency_value: Mapped[float] = mapped_column( process_name: Mapped[str] = mapped_column(String, nullable=False, comment="Bank Process Type Name")
Numeric(20, 6), nullable=False, comment="Currency Value" process_type: Mapped[str] = mapped_column(String, nullable=False, comment="Bank Process Type")
) process_comment: Mapped[str] = mapped_column(String, nullable=False, comment="Transaction Record Comment")
bank_balance: Mapped[float] = mapped_column( process_garbage: Mapped[str] = mapped_column(String, nullable=True, comment="Transaction Record Garbage")
Numeric(20, 6), nullable=False, comment="Bank Balance" bank_reference_code: Mapped[str] = mapped_column(String, nullable=False, comment="Bank Reference Code")
)
currency: Mapped[str] = mapped_column(
String(5), nullable=False, comment="Unit of Currency"
)
additional_balance: Mapped[float] = mapped_column(
Numeric(20, 6), nullable=False, comment="Additional Balance"
)
channel_branch: Mapped[str] = mapped_column(
String(120), nullable=False, comment="Branch Bank"
)
process_name: Mapped[str] = mapped_column(
String, nullable=False, comment="Bank Process Type Name"
)
process_type: Mapped[str] = mapped_column(
String, nullable=False, comment="Bank Process Type"
)
process_comment: Mapped[str] = mapped_column(
String, nullable=False, comment="Transaction Record Comment"
)
process_garbage: Mapped[str] = mapped_column(
String, nullable=True, comment="Transaction Record Garbage"
)
bank_reference_code: Mapped[str] = mapped_column(
String, nullable=False, comment="Bank Reference Code"
)
add_comment_note: Mapped[str] = mapped_column(String, server_default="") add_comment_note: Mapped[str] = mapped_column(String, server_default="")
is_receipt_mail_send: Mapped[bool] = mapped_column(Boolean, server_default="0") is_receipt_mail_send: Mapped[bool] = mapped_column(Boolean, server_default="0")
@ -378,100 +389,45 @@ class AccountRecords(CrudCollection):
bank_date_w: Mapped[int] = mapped_column(SmallInteger) bank_date_w: Mapped[int] = mapped_column(SmallInteger)
bank_date_d: Mapped[int] = mapped_column(SmallInteger) bank_date_d: Mapped[int] = mapped_column(SmallInteger)
approving_accounting_record: Mapped[bool] = mapped_column( approving_accounting_record: Mapped[bool] = mapped_column(Boolean, server_default="0")
Boolean, server_default="0" accounting_receipt_date: Mapped[TIMESTAMP] = mapped_column(TIMESTAMP(timezone=True), server_default="1900-01-01 00:00:00")
)
accounting_receipt_date: Mapped[TIMESTAMP] = mapped_column(
TIMESTAMP(timezone=True), server_default="1900-01-01 00:00:00"
)
accounting_receipt_number: Mapped[int] = mapped_column(Integer, server_default="0") accounting_receipt_number: Mapped[int] = mapped_column(Integer, server_default="0")
status_id: Mapped[int] = mapped_column(SmallInteger, server_default="0") status_id: Mapped[int] = mapped_column(SmallInteger, server_default="0")
approved_record: Mapped[bool] = mapped_column(Boolean, server_default="0") approved_record: Mapped[bool] = mapped_column(Boolean, server_default="0")
import_file_name: Mapped[str] = mapped_column( import_file_name: Mapped[str] = mapped_column(String, nullable=True, comment="XLS Key")
String, nullable=True, comment="XLS Key" receive_debit: Mapped[int] = mapped_column(ForeignKey("api_enum_dropdown.id"), nullable=True)
) receive_debit_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Debit UU ID")
budget_type: Mapped[int] = mapped_column(ForeignKey("api_enum_dropdown.id"), nullable=True)
receive_debit: Mapped[int] = mapped_column( budget_type_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Budget Type UU ID")
ForeignKey("api_enum_dropdown.id"), nullable=True
)
receive_debit_uu_id: Mapped[str] = mapped_column(
String, nullable=True, comment="Debit UU ID"
)
budget_type: Mapped[int] = mapped_column(
ForeignKey("api_enum_dropdown.id"), nullable=True
)
budget_type_uu_id: Mapped[str] = mapped_column(
String, nullable=True, comment="Budget Type UU ID"
)
company_id: Mapped[int] = mapped_column(ForeignKey("companies.id"), nullable=True) company_id: Mapped[int] = mapped_column(ForeignKey("companies.id"), nullable=True)
company_uu_id: Mapped[str] = mapped_column( company_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Company UU ID")
String, nullable=True, comment="Company UU ID" send_company_id: Mapped[int] = mapped_column(ForeignKey("companies.id"), nullable=True)
) send_company_uu_id = mapped_column(String, nullable=True, comment="Send Company UU ID")
send_company_id: Mapped[int] = mapped_column(
ForeignKey("companies.id"), nullable=True
)
send_company_uu_id = mapped_column(
String, nullable=True, comment="Send Company UU ID"
)
send_person_id: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=True) send_person_id: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=True)
send_person_uu_id: Mapped[str] = mapped_column( send_person_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Send Person UU ID")
String, nullable=True, comment="Send Person UU ID" approving_accounting_person: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=True)
) approving_accounting_person_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Approving Accounting Person UU ID")
approving_accounting_person: Mapped[int] = mapped_column( living_space_id: Mapped[int] = mapped_column(ForeignKey("build_living_space.id"), nullable=True)
ForeignKey("people.id"), nullable=True living_space_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Living Space UU ID")
)
approving_accounting_person_uu_id: Mapped[str] = mapped_column(
String, nullable=True, comment="Approving Accounting Person UU ID"
)
living_space_id: Mapped[int] = mapped_column(
ForeignKey("build_living_space.id"), nullable=True
)
living_space_uu_id: Mapped[str] = mapped_column(
String, nullable=True, comment="Living Space UU ID"
)
customer_id: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=True) customer_id: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=True)
customer_uu_id = mapped_column(String, nullable=True, comment="Customer UU ID") customer_uu_id = mapped_column(String, nullable=True, comment="Customer UU ID")
build_id: Mapped[int] = mapped_column(ForeignKey("build.id"), nullable=True) build_id: Mapped[int] = mapped_column(ForeignKey("build.id"), nullable=True)
build_uu_id: Mapped[str] = mapped_column( build_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Build UU ID")
String, nullable=True, comment="Build UU ID" build_parts_id: Mapped[int] = mapped_column(ForeignKey("build_parts.id"), nullable=True)
) build_parts_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Build Parts UU ID")
build_parts_id: Mapped[int] = mapped_column( build_decision_book_id: Mapped[int] = mapped_column(ForeignKey("build_decision_book.id"), nullable=True)
ForeignKey("build_parts.id"), nullable=True build_decision_book_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Build Decision Book UU ID")
) payment_result_type: Mapped[int] = mapped_column(ForeignKey("api_enum_dropdown.id"), nullable=True)
build_parts_uu_id: Mapped[str] = mapped_column( payment_result_type_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Payment Result Type UU ID")
String, nullable=True, comment="Build Parts UU ID" is_commission_applied: Mapped[bool] = mapped_column(Boolean, server_default="0")
)
build_decision_book_id: Mapped[int] = mapped_column(
ForeignKey("build_decision_book.id"), nullable=True
)
build_decision_book_uu_id: Mapped[str] = mapped_column(
String, nullable=True, comment="Build Decision Book UU ID"
)
# payment_result_type = Mapped[int] = mapped_column(
# ForeignKey("api_enum_dropdown.id"), nullable=True
# )
# payment_result_type_uu_id: Mapped[str] = mapped_column(
# String, nullable=True, comment="Payment Result Type UU ID"
# )
__table_args__ = ( __table_args__ = (
Index("_budget_records_ndx_00", is_receipt_mail_send, bank_date), Index("_budget_records_ndx_00", is_receipt_mail_send, bank_date),
Index( Index("_budget_records_ndx_01", iban, bank_date, bank_reference_code, bank_balance, unique=True),
"_budget_records_ndx_01",
iban,
bank_date,
bank_reference_code,
bank_balance,
unique=True,
),
Index("_budget_records_ndx_02", status_id, bank_date), Index("_budget_records_ndx_02", status_id, bank_date),
{ {"comment": "Bank Records that are related to building and financial transactions"},
"comment": "Bank Records that are related to building and financial transactions"
},
) )
# def payment_budget_record_close(self): # def payment_budget_record_close(self):

View File

@ -11,6 +11,7 @@ from Schemas.base_imports import (
mapped_column, mapped_column,
Mapped, Mapped,
) )
from sqlalchemy import text
class BuildIbans(CrudCollection): class BuildIbans(CrudCollection):
@ -44,6 +45,30 @@ class BuildIbans(CrudCollection):
{"comment": "IBANs related to money transactions due to building objects"}, {"comment": "IBANs related to money transactions due to building objects"},
) )
class CompanyDelayInterest(CrudCollection):
__tablename__ = "company_delay_interest"
company_id: Mapped[int] = mapped_column(Integer, ForeignKey("companies.id"), nullable=True)
company_uu_id: Mapped[str] = mapped_column(String, nullable=True)
build_id: Mapped[int] = mapped_column(Integer, ForeignKey("build.id"), nullable=True)
build_uu_id: Mapped[str] = mapped_column(String, nullable=True)
daily_interest_type: Mapped[str] = mapped_column(String(24), nullable=True)
daily_interest_rate: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, server_default=text("0"))
bsmv_rate: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, server_default=text("0"))
kkdf_rate: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, server_default=text("0"))
start_date: Mapped[TIMESTAMP] = mapped_column(TIMESTAMP(timezone=True), nullable=False)
stop_date: Mapped[TIMESTAMP] = mapped_column(TIMESTAMP(timezone=True), nullable=False, server_default=text("'2900-01-01 03:00:00+03'::timestamptz"))
__table_args__ = (
Index("_company_delay_interest_ndx_01", "company_id", "build_id", "start_date", unique=True),
Index("ix_company_delay_interest_build_uu_id", "company_id", "build_id"),
{"comment": "Company Delay Interest Information"},
)
class BuildIbanDescription(CrudCollection): class BuildIbanDescription(CrudCollection):
""" """

View File

@ -570,13 +570,14 @@ class BuildDecisionBookPayments(CrudCollection):
build_parts_id: Mapped[int] = mapped_column(ForeignKey("build_parts.id"), nullable=False) build_parts_id: Mapped[int] = mapped_column(ForeignKey("build_parts.id"), nullable=False)
build_parts_uu_id: Mapped[str] = mapped_column(String, nullable=False, comment="Build Part UUID") build_parts_uu_id: Mapped[str] = mapped_column(String, nullable=False, comment="Build Part UUID")
decision_book_project_id: Mapped[int] = mapped_column( decision_book_project_id: Mapped[int] = mapped_column(
ForeignKey("build_decision_book_projects.id"), ForeignKey("build_decision_book_projects.id"), nullable=True, comment="Decision Book Project ID",
nullable=True,
comment="Decision Book Project ID",
) )
decision_book_project_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Decision Book Project UUID") decision_book_project_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Decision Book Project UUID")
account_records_id: Mapped[int] = mapped_column(ForeignKey("account_records.id"), nullable=True) account_records_id: Mapped[int] = mapped_column(ForeignKey("account_records.id"), nullable=True)
account_records_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Account Record UU ID") account_records_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Account Record UU ID")
is_closed: Mapped[bool] = mapped_column(Boolean, server_default="0", comment="Is Decision Book Payment Closed")
debt_to_pay: Mapped[float] = mapped_column(Numeric(16, 2), server_default="0", comment="Debt To Pay")
debt_paid: Mapped[float] = mapped_column(Numeric(16, 2), server_default="0", comment="Debt Paid")
# budget_records_id: Mapped[int] = mapped_column(ForeignKey("account_records.id"), nullable=True) # budget_records_id: Mapped[int] = mapped_column(ForeignKey("account_records.id"), nullable=True)
# budget_records_uu_id: Mapped[str] = mapped_column( # budget_records_uu_id: Mapped[str] = mapped_column(

View File

@ -7,38 +7,85 @@ end_time = perf_counter()
elapsed = end_time - start_time elapsed = end_time - start_time
print(f'{elapsed:.3f} : seconds') print(f'{elapsed:.3f} : seconds')
print('shallow_copy_list', len(shallow_copy_list)) print('shallow_copy_list', len(shallow_copy_list))
texts = [
"D 1 MÜBERRA BALTACI ŞUBAT 2016 O 4245N6000892 MÜBERRA BALTAC",
"BERAT VARNALI-GÜNEŞ APT NO-6 ŞUBAT 2016 ÖDEMESİ",
"BERAT VARNALI-GÜNEŞ APT NO-6 NİSAN 2016 ÖDEMESİ",
"GÖNÜL ARISOY- AİDAT*GÖNÜL ARISOY*H2406115923882",
"GÜLSER MAY - 8 NOLU DAIRENIN MAYIS 2016 ONARIM BEDELI",
"İREM YÜKSEKOL 12 NOLU DAİRE ARALIK AİDAT VE TAMİRAT PARASI",
"İREM YÜKSEKOL 12 NOLU DAİRE EKİM AİDAT VE TAMİRAT PARASI",
"GÜÇLÜ FATİH ERGÜN FATİH ERGÜN GÜÇLÜ D7 AİDAT",
"KASIM ARALIK 2015 OCAK 2016 TADILAT YUSUF EDEPLI DAIRE 10",
"muberra baltacı daıre 1 yakıt bedelı*MÜBERRA BALTACI*H2406175186422",
"YEŞİM ŞİMŞEK 5 NOLU DAİRE HAMDİYE AKAGÜNDÜZ",
"* HESAP İŞLETİM MASRAFI İADE (BTÇG) * 4245/ 76099902*MUH.HESA",
"4245 0500749 numarali hesap kapama",
"İREM YÜKSEKOL 12 NOLU DAİRE OCAK AİDAT VE TAMİRAT PARASI",
"GÜÇLÜ FATİH ERGÜN FATİH ERGÜN GÜÇLÜ D7 AİDAT",
"MUSTAFA EDEPLİ NO:11 MART AİDATI +20 TL ESKİ BORÇ *ALİ İHSAN EDEPLİ *4245X10Ç42",
"muberra baltaci daire 1 yakit bedeli*MUBERRA BALTACI*H2502245307227",
"OSMAN KILINÇ*0111*OSMAN KILINÇ - AİDAT - SUBAT*2238807",
"GÖNÜL ARISOY GÖNÜL ARISOY TARAFINDAN AKTA",
"AIDAT BEDELI*MEHMET KARATAY*H2504537864455",
"ELİFCAN DEMİRTAŞ*0062*CEP-EFTEMRİ-DAİRE 8 . AİDAT ÖDEMESİ*0130484",
"BALTACI MÜBERRA MÜBERRA BALTACI D:1YAKIT BED",
"SEZEN KONUR GÜNEŞ APARTMAN AİDATI 12 NUMARA",
"MEHMET KARATAY - HESABA AKTARILAN OCAK-SUBAT 2017 AIDAT",
"Osman Kilinc*0111*osman kilinc - 2024 - ekim*8614131*FAST",
"GÖNÜL ARISOY GÖNÜL ARISOY TARAFINDAN AKTA",
"GÖNÜL ARISOY GÖNÜL ARISOY TARAFINDAN AKTA",
"BALTACI MÜBERRA MÜBERRA BALTACI D:1YAKIT BED",
"SONGÜL VAR-2NOLU DAİRE KASIM 2015 ÖDEME",
"GÖNÜL ARISOY (9 NUMARA AİDAT)*GÖNÜL ARISOY*H2211022448099",
"BALTACI MÜBERRA MÜBERRA BALTACI D:1YAKIT BED",
"GÜÇLÜ FATİH ERGÜN FATİH ERGÜN GÜÇLÜ D7 AİDAT",
"2 nolu daire - TEMMUZ Ç4 TADİLAT*SONGÜL VAR*H2408373383590",
"GÜÇLÜ FATİH ERGÜN FATİH ERGÜN GÜÇLÜ D7 AİDAT",
"ELİFCAN DEMİRTAŞ*0062*ARALIK AYI AİDAT DAİRE 8*443Ç00*FAST",
"GÜÇLÜ FATİH ERGÜN FATİH ERGÜN GÜÇLÜ D7 AİDAT",
"ERİNÇ KARATAŞ 9 NOLU DAİRE AĞUSTOS AYI 15 GÜNLÜK AİDATI",
"HASAN CİHAN ŞENKÜÇÜK*0046*AİDAT*1690685*FAST",
"DAMLA GÖRMEZOĞLU*0099*6 nolu daire Kemal bey dava Ekim Kasım*2242091694*FAST",
"Müberra Baltacı Daire 1 Yakıt bedli*MÜBERRA BALTACI*H2107153811822",
"Osman Kılınç*0111*osman kılınç - aidat - Ç1- nisan*Ş12506*FAST",
"mübarra baltacı 1 nolu daire yakıt farkı şubat ayı dahil*MÜBERRA BALTACI*H210Ç5745006",
"ELİFCAN DEMİRTAŞ*0062*CEP-EFTEMRİ-DAİRE 8 EYLUL Ç0 AİDAT*0520317"
]
""" """
""" """
1. Stage (Incoming Money) 1. Stage (Incoming Money)
# BuildDecisionBookPayments are reverse records of AccountRecords
# BuildDecisionBookPayments are reverse records of AccountRecords
AccountRecords.approved_record == True AccountRecords.approved_record == True
AccountRecords.living_space_id is not None AccountRecords.living_space_id is not None
# AccountRecords.receive_debit = Credit Receiver (Incoming money from client) / Debit Sender (Debt to be paid by system)
1.1 # AccountRecords.receive_debit = Credit Receiver (Incoming money from client) / Debit Sender (Debt to be paid by system)
AccountRecords.currency_value > 0 Received Money Transaction +
AccountRecords.currency_value > AccountRecords.remainder_balance () You have extra money in system account 1.1
Money consumed => AccountRecords.currency_value != abs(AccountRecords.remainder_balance) singluar iban AccountRecords.currency_value > 0 Received Money Transaction +
Some payment done but money not yet all money is consumed => AccountRecords.currency_value + AccountRecords.remainder_balance != 0 AccountRecords.currency_value > AccountRecords.remainder_balance () You have extra money in system account
Money consumed => AccountRecords.currency_value != abs(AccountRecords.remainder_balance) singluar iban
Some payment done but money not yet all money is consumed => AccountRecords.currency_value + AccountRecords.remainder_balance != 0
AccountRecords.currency_value = AccountRecords.remainder_balance (There is no money that individual has in system) AccountRecords.currency_value = AccountRecords.remainder_balance (There is no money that individual has in system)
AccountRecords.bank_date (Date money arrived) AccountRecords.bank_date (Date money arrived)
AccountRecords.process_type (Type of bank transaction) AccountRecords.process_type (Type of bank transaction)
1.2 1.2
AccountRecords.currency_value < 0 Sent Money Transaction - AccountRecords.currency_value < 0 Sent Money Transaction -
2. Stage (Payment Match Process) 2. Stage (Payment Match Process)
Parse : BuildDecisionBookPayments.process_date (Year / Month / Day / Time) Parse : BuildDecisionBookPayments.process_date (Year / Month / Day / Time)
BuildDecisionBookPayments.account_records_id == None ( Payment is not assigned to any account record) BuildDecisionBookPayments.account_records_id == None ( Payment is not assigned to any account record)
BuildDecisionBookPayments.payment_types_id == debit_enum.id (Payment type is debit) BuildDecisionBookPayments.payment_types_id == debit_enum.id (Payment type is debit)
2.1 Check current month has any payment to due Payment Month == Money Arrived Month 2.1 Check current month has any payment to due Payment Month == Money Arrived Month
2.2 Check previous months has any payment to due Payment Month < Money Arrived Month 2.2 Check previous months has any payment to due Payment Month < Money Arrived Month
3. Stage (Payment Assignment Process) 3. Stage (Payment Assignment Process)
Do payment set left money to account record as AccountRecords.remainder_balance Do payment set left money to account record as AccountRecords.remainder_balance

View File

@ -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")

View File

@ -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()

View File

@ -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")

View File

@ -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")

View File

@ -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 ı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';
"""

View File

@ -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...")

View File

@ -6,11 +6,13 @@ from decimal import Decimal
from datetime import datetime, timedelta from datetime import datetime, timedelta
from Schemas import BuildDecisionBookPayments, AccountRecords, ApiEnumDropdown, Build, BuildDecisionBook from Schemas import BuildDecisionBookPayments, AccountRecords, ApiEnumDropdown, Build, BuildDecisionBook
# from ServicesApi.Schemas.building.decision_book import BuildDecisionBookPayments, BuildDecisionBook
# from ServicesApi.Schemas.building.build import Build
# from ServicesApi.Schemas.account.account import AccountRecords
# from ServicesApi.Schemas.others.enums import ApiEnumDropdown
from time import perf_counter from time import perf_counter
from sqlalchemy import select, func, distinct, cast, Date, String, literal, desc, and_, or_ from sqlalchemy import select, func, distinct, cast, Date, String, literal, desc, and_, or_, case
from Controllers.Postgres.engine import get_session_factory from Controllers.Postgres.engine import get_session_factory
#from ServicesApi.Schemas.account.account import AccountRecords
#from ServicesApi.Schemas.building.decision_book import BuildDecisionBookPayments
def find_last_day_of_month(date_value): def find_last_day_of_month(date_value):
@ -23,6 +25,18 @@ def find_first_day_of_month(date_value):
return datetime(today.year, today.month, 1) return datetime(today.year, today.month, 1)
def add_month_to_date(date_value):
month_to_process = date_value.month
year_to_process = date_value.year
if date_value.month == 12:
month_to_process = 1
year_to_process += 1
else:
month_to_process += 1
_, last_day = calendar.monthrange(year_to_process, month_to_process)
return datetime(year_to_process, month_to_process, 1), datetime(year_to_process, month_to_process, last_day)
class BuildDuesTypes: class BuildDuesTypes:
def __init__(self): def __init__(self):
@ -83,194 +97,44 @@ def get_enums_from_database():
return [build_dues_types.debit, build_dues_types.lawyer_expence, build_dues_types.add_debit, build_dues_types.renovation, build_dues_types.service_fee, build_dues_types.information] return [build_dues_types.debit, build_dues_types.lawyer_expence, build_dues_types.add_debit, build_dues_types.renovation, build_dues_types.service_fee, build_dues_types.information]
def generate_total_paid_amount_for_spesific_build_part_id(build_parts_id: int, session): def retrieve_remainder_balance_set_if_needed(account_record_id: int, session):
""" """Update the remainder_balance of an account after spending money.
Calculate the total amount paid for a specific build part ID.
Args: Args:
build_parts_id: The build part ID to calculate payments for account_record: The account record to update
amount_spent: The amount spent in this transaction
session: Database session session: Database session
Returns: Returns:
float: The total amount paid (absolute value) bool: True if all money is spent, False otherwise
""" """
payment_query = session.query(func.sum(BuildDecisionBookPayments.payment_amount)).filter(
BuildDecisionBookPayments.build_parts_id == build_parts_id,
BuildDecisionBookPayments.account_is_debit == False,
cast(BuildDecisionBookPayments.process_date, Date) >= '2022-01-01'
).scalar()
return payment_query if payment_query is not None else 0
def generate_total_debt_amount_for_spesific_build_part_id(build_parts_id: int, session):
# Use SQLAlchemy's func.sum to calculate the total debts
# For total debt, we want to include ALL debts, both processed and unprocessed
result = session.query(
func.sum(BuildDecisionBookPayments.payment_amount)
).filter(
BuildDecisionBookPayments.build_parts_id == build_parts_id,
BuildDecisionBookPayments.account_is_debit == True,
cast(BuildDecisionBookPayments.process_date, Date) >= '2022-01-01'
).scalar()
# Return 0 if no debts found, otherwise return the absolute value of the sum
return abs(result) if result is not None else 0
def generate_total_amount_that_user_has_in_account(account_record: AccountRecords, session):
# Get total amount that user has in account
result = session.query(
func.sum(AccountRecords.currency_value)
).filter(
AccountRecords.build_parts_id == account_record.build_parts_id,
AccountRecords.currency_value > 0,
cast(AccountRecords.bank_date, Date) >= '2022-01-01'
).scalar()
# Return 0 if no payments found, otherwise return the absolute value of the sum
return abs(result)
def _print_debt_details(debt, session):
"""Helper function to print detailed information about an unpaid debt.
Args:
debt: The BuildDecisionBookPayments object representing the debt
session: Database session
"""
# Get the sum of payments for this debt
payments_sum = session.query(
func.sum(BuildDecisionBookPayments.payment_amount)
).filter(
BuildDecisionBookPayments.ref_id == debt.ref_id,
BuildDecisionBookPayments.account_is_debit == False
).scalar() or 0
# Calculate remaining amount
debit_amount = abs(debt.payment_amount)
remaining = debit_amount - abs(payments_sum)
payment_percentage = (abs(payments_sum) / debit_amount) * 100 if debit_amount > 0 else 0
# Format the date for display
date_str = debt.process_date.strftime('%Y-%m-%d') if debt.process_date else 'Unknown date'
def analyze_payment_function():
session_factory = get_session_factory()
session = session_factory()
# Set session for all models
AccountRecords.set_session(session) AccountRecords.set_session(session)
account_record_remainder_balance = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter(
BuildDecisionBookPayments.account_records_id == account_record_id,
BuildDecisionBookPayments.account_is_debit == False
).scalar()
if not account_record_remainder_balance:
account_record_remainder_balance = 0
account_record = AccountRecords.query.filter_by(id=account_record_id).first()
account_record.remainder_balance = -1 * abs(account_record_remainder_balance) if account_record_remainder_balance != 0 else 0
account_record.save()
return account_record_remainder_balance
def check_current_debt_to_pay_from_database_is_closed(ref_id: int, session):
session.commit()
BuildDecisionBookPayments.set_session(session) BuildDecisionBookPayments.set_session(session)
payment_row_book = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.ref_id == str(ref_id), BuildDecisionBookPayments.account_is_debit == True).first()
order_pay = get_enums_from_database() debt_paid = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter(
# Get distinct build_parts_id values from account records with positive currency_value BuildDecisionBookPayments.ref_id == str(ref_id), BuildDecisionBookPayments.account_is_debit == False
# This avoids redundant processing of the same build_parts_id ).scalar()
distinct_build_parts = session.query( payment_row_book.debt_paid = abs(debt_paid) if debt_paid else 0 # Debt Reminder is how much money is needed to close record
distinct(AccountRecords.build_parts_id) payment_row_book.debt_to_pay = abs(payment_row_book.payment_amount) - abs(payment_row_book.debt_paid) # Debt To Pay is how much money is needed to close record
).filter( payment_row_book.is_closed = payment_row_book.debt_to_pay == 0
AccountRecords.build_parts_id.isnot(None), payment_row_book.save()
AccountRecords.currency_value > 0,
AccountRecords.bank_date >= '2022-01-01'
).order_by(AccountRecords.build_parts_id.desc()).all()
start_time = time.time()
for build_part_id_tuple in distinct_build_parts:
build_part_id = build_part_id_tuple[0] # Extract the ID from the tuple
process_date = datetime.now()
last_date_of_process_date = datetime(process_date.year, process_date.month, 1) - timedelta(days=1)
first_date_of_process_date = datetime(process_date.year, process_date.month, 1)
print(f"\n{'=' * 50}")
print(f"ACCOUNT ANALYSIS FOR BUILD PART ID: {build_part_id}")
print(f"{'=' * 50}")
# Calculate total paid amount for this build_part_id
total_amount_paid = generate_total_paid_amount_for_spesific_build_part_id(build_part_id, session)
# Calculate total debt amount for this build_part_id
total_debt_amount = generate_total_debt_amount_for_spesific_build_part_id(build_part_id, session)
# Get total amount in account for this build_part_id
account_record = AccountRecords()
account_record.build_parts_id = build_part_id
total_amount_in_account = generate_total_amount_that_user_has_in_account(account_record, session)
# Calculate remaining amount to be paid
amount_need_to_paid = total_debt_amount - total_amount_paid
total_amount_that_user_need_to_transfer = abs(amount_need_to_paid) - abs(total_amount_in_account)
# Print summary with clear descriptions
print(f"PAYMENT SUMMARY:")
print(f" • Total debt amount: {total_debt_amount:,.2f} TL")
print(f" • Amount already paid: {total_amount_paid:,.2f} TL")
print(f" • Remaining debt to be collected: {amount_need_to_paid:,.2f} TL")
print(f" • Current account balance: {total_amount_in_account:,.2f} TL")
if total_amount_that_user_need_to_transfer > 0:
print(f" • Additional funds needed: {total_amount_that_user_need_to_transfer:,.2f} TL")
elif amount_need_to_paid <= 0:
print(f" • Account is fully paid with no outstanding debt")
else:
print(f" • Sufficient funds available to close all debt")
# Show debt coverage percentage
if total_debt_amount > 0:
# Calculate current coverage (already paid)
current_coverage_percentage = (total_amount_paid / total_debt_amount) * 100
# Calculate potential coverage (including available funds)
potential_coverage = min(100, ((total_amount_paid + total_amount_in_account) / total_debt_amount) * 100)
# Display both percentages
print(f" • Current debt coverage: {current_coverage_percentage:.2f}%")
print(f" • Potential debt coverage with available funds: {potential_coverage:.2f}%")
# Analyze unpaid debts for each payment type
print("\nUNPAID DEBTS ANALYSIS BY PAYMENT TYPE:")
for payment_type in order_pay:
# Get unpaid debts for current month
date_query_current = (
BuildDecisionBookPayments.process_date >= first_date_of_process_date,
BuildDecisionBookPayments.process_date <= process_date
)
date_query_previous = (
BuildDecisionBookPayments.process_date < first_date_of_process_date,
)
current_unpaid_debts = get_unpaid_debts(build_parts_id=build_part_id, session=session, debit_type=payment_type, date_query=date_query_current)
# Get unpaid debts from previous months
previous_unpaid_debts = get_unpaid_debts(build_parts_id=build_part_id, session=session, debit_type=payment_type, date_query=date_query_previous)
# Calculate totals
current_total = sum(abs(debt[2]) for debt in current_unpaid_debts)
previous_total = sum(abs(debt[2]) for debt in previous_unpaid_debts)
grand_total = current_total + previous_total
# Print summary for this payment type
if current_unpaid_debts or previous_unpaid_debts:
print(f"{payment_type.key}: Total unpaid: {grand_total:,.2f} TL")
# Current month details
if current_unpaid_debts:
print(f" - Current month: {len(current_unpaid_debts)} debts, {current_total:,.2f} TL")
# Show details of each unpaid debt if there aren't too many
# if len(current_unpaid_debts) <= 3:
# for debt in current_unpaid_debts:
# _print_debt_details(debt, session)
# Previous months details
if previous_unpaid_debts:
print(f" - Previous months: {len(previous_unpaid_debts)} debts, {previous_total:,.2f} TL")
# Show details of each unpaid debt if there aren't too many
# if len(previous_unpaid_debts) <= 3:
# for debt in previous_unpaid_debts:
# _print_debt_details(debt, session)
else:
print(f"{payment_type.key}: All debts paid")
print(f"{'=' * 50}\n")
def close_payment_book(payment_row_book, account_record, value, session): def close_payment_book(payment_row_book, account_record, value, session):
@ -286,7 +150,9 @@ def close_payment_book(payment_row_book, account_record, value, session):
The newly created payment record The newly created payment record
""" """
BuildDecisionBookPayments.set_session(session) BuildDecisionBookPayments.set_session(session)
account_record_remainder_balance = retrieve_remainder_balance_set_if_needed(account_record_id=account_record.id, session=session)
print(f'NOT Updated remainder balance: {account_record_remainder_balance} | Account record id: {account_record.id}')
# Create a new credit entry (payment) # Create a new credit entry (payment)
new_row = BuildDecisionBookPayments.create( new_row = BuildDecisionBookPayments.create(
ref_id=str(payment_row_book.uu_id), ref_id=str(payment_row_book.uu_id),
@ -317,538 +183,126 @@ def close_payment_book(payment_row_book, account_record, value, session):
saved_row = new_row.save() saved_row = new_row.save()
session.commit() session.commit()
session.refresh(saved_row) session.refresh(saved_row)
session.flush() account_record_remainder_balance = retrieve_remainder_balance_set_if_needed(account_record_id=account_record.id, session=session)
return saved_row print(f'Updated remainder balance: {account_record_remainder_balance} | Account record id: {account_record.id}')
check_current_debt_to_pay_from_database_is_closed(ref_id=new_row.ref_id, session=session)
return account_record_remainder_balance
def update_account_remainder_if_spent(account_record, ref_id: str, session): def refresh_ids_to_iterate_account_records(session, limit, offset):
"""Update the remainder_balance of an account after spending money. return session.query(AccountRecords.id).filter(
AccountRecords.active == True, func.abs(AccountRecords.currency_value) - func.abs(AccountRecords.remainder_balance) > 0,
AccountRecords.currency_value > 0,
).order_by(AccountRecords.bank_date.desc()).limit(limit).offset(offset).all()
def get_ids_to_iterate_account_records(session, limit, offset):
"""Get account records to process with pagination.
Args: Args:
account_record: The account record to update
amount_spent: The amount spent in this transaction
session: Database session session: Database session
limit: Maximum number of records to return
offset: Number of records to skip
Returns: Returns:
bool: True if all money is spent, False otherwise List of account record IDs
""" """
AccountRecords.set_session(session) today = datetime.now()
BuildDecisionBookPayments.set_session(session) return session.query(AccountRecords.id).filter(
session.commit() AccountRecords.active == True, func.abs(AccountRecords.currency_value) - func.abs(AccountRecords.remainder_balance) > 0,
sum_of_paid = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter( AccountRecords.currency_value > 0, AccountRecords.build_parts_id.isnot(None), cast(AccountRecords.bank_date, Date) < find_first_day_of_month(today).date()
BuildDecisionBookPayments.account_records_id == account_record.id, ).order_by(AccountRecords.bank_date.desc()).limit(limit).offset(offset).all()
BuildDecisionBookPayments.account_is_debit == False
).scalar()
if not sum_of_paid:
return False
debit_row = BuildDecisionBookPayments.query.filter_by(ref_id=ref_id).first()
account_record_to_update = AccountRecords.query.filter_by(id=account_record.id).first()
account_record_to_update.remainder_balance = (-1 * abs(sum_of_paid)) or 0
account_record_to_update.save()
session.commit()
session.refresh(account_record_to_update)
# Get the current remainder balance
if abs(sum_of_paid) == abs(account_record_to_update.currency_value):
return True
return False
def update_all_spent_accounts(session): def get_read_payment_type(payment_result_type_uu_id, payment_type_list):
"""Update remainder_balance for all accounts with payments. for payment_type in payment_type_list:
if payment_type.uuid == payment_result_type_uu_id:
This function finds account records in BuildDecisionBookPayments and updates return payment_type.key
their remainder_balance based on the sum of payments made, regardless of whether return None
all funds have been spent or not.
def same_month_actions(limit=10, offset=0):
"""Main function to process account records and find debits.
Args: Args:
session: Database session limit: Maximum number of records to process
offset: Number of records to skip
""" """
with AccountRecords.new_session() as session: today, build_id, start_time = datetime.now(), 1, perf_counter()
# Set sessions for models
session_factory = get_session_factory()
session = session_factory()
payment_type_list = get_enums_from_database()
print(f"Processing with limit={limit}, offset={offset}")
account_records = get_ids_to_iterate_account_records(session=session, limit=limit, offset=offset)
print(f"Found {len(account_records)} account records to process")
for account_record in account_records:
AccountRecords.set_session(session) AccountRecords.set_session(session)
BuildDecisionBookPayments.set_session(session) BuildDecisionBookPayments.set_session(session)
account_record_selected = AccountRecords.query.filter_by(id=account_record.id).first()
payment_result_type_uu_id = account_record_selected.payment_result_type_uu_id
# Extract month and year from the bank transaction date instead of using current date
bank_date = account_record_selected.bank_date
bank_month = bank_date.month
bank_year = bank_date.year
priority_uuids = [payment_result_type_uu_id, *[pt.uuid for pt in payment_type_list if pt.uuid != payment_result_type_uu_id]]
order_payment_by_type = case(*[(BuildDecisionBookPayments.payment_types_uu_id == uuid, index) for index, uuid in enumerate(priority_uuids)], else_=len(priority_uuids))
# Get distinct account_records_id values from BuildDecisionBookPayments same_month_build_decision_book_debits = BuildDecisionBookPayments.query.filter(
distinct_account_ids = session.query(BuildDecisionBookPayments.account_records_id).filter( BuildDecisionBookPayments.account_is_debit.is_(True),
BuildDecisionBookPayments.account_records_id.isnot(None), BuildDecisionBookPayments.active.is_(True),
BuildDecisionBookPayments.account_is_debit == False # Credit entries (payments) BuildDecisionBookPayments.build_parts_id == account_record_selected.build_parts_id,
).distinct().all() func.extract('month', BuildDecisionBookPayments.process_date) == bank_month,
func.extract('year', BuildDecisionBookPayments.process_date) == bank_year,
updated_count = 0 BuildDecisionBookPayments.is_closed.is_(False),
# BuildDecisionBookPayments.payment_types_uu_id == payment_result_type_uu_id,
for account_id_tuple in distinct_account_ids: ).order_by(order_payment_by_type, BuildDecisionBookPayments.process_date.desc()).all()
account_id = account_id_tuple[0] match_payment_type = get_read_payment_type(payment_result_type_uu_id, payment_type_list)
print('fund', dict(
# Get the account record id=account_record_selected.id,
account = AccountRecords.query.filter_by(id=account_id).first() build_parts_id=account_record_selected.build_parts_id,
if not account or not account.build_parts_id or account.currency_value <= 0: payment_result_type_uu_id=match_payment_type,
continue bank_date=arrow.get(account_record_selected.bank_date).format('YYYY-MM-DD HH:mm:ss'),
currency_value=account_record_selected.currency_value,
# Calculate the sum of payments made using this account remainder_balance=account_record_selected.remainder_balance,
# Note: payment_amount is negative for credit entries, so we need to use abs() to get the positive amount length_matches=len(same_month_build_decision_book_debits),
payment_query = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter( ))
BuildDecisionBookPayments.account_records_id == account_id, for same_month_build_decision_book_debit in same_month_build_decision_book_debits:
BuildDecisionBookPayments.account_is_debit == False # Credit entries (payments) match_payment_type = get_read_payment_type(same_month_build_decision_book_debit.payment_types_uu_id, payment_type_list)
) print('debt', dict(
id=same_month_build_decision_book_debit.id,
payment_sum = payment_query.scalar() or 0 payment_type=match_payment_type,
debt_date=arrow.get(same_month_build_decision_book_debit.process_date).format('YYYY-MM-DD HH:mm:ss'),
# Update remainder_balance for ALL accounts, regardless of payment_sum value payment_amount=same_month_build_decision_book_debit.payment_amount,
threshold = Decimal('0.01') debt_paid=same_month_build_decision_book_debit.debt_paid,
fully_spent = abs(payment_sum) >= abs(account.currency_value) - threshold debt_to_pay=same_month_build_decision_book_debit.debt_to_pay,
status = "All funds spent" if fully_spent else "Partial payment" is_closed=same_month_build_decision_book_debit.is_closed,
))
# Store the positive value in remainder_balance
account.remainder_balance = -1 * abs(payment_sum)
account.save()
updated_count += 1
session.commit()
print(f"\nTotal accounts updated: {updated_count}")
# def find_amount_to_pay_by_ref_id(ref_id, session):
# """Calculate the remaining amount to pay for a specific debt reference ID.
# Args:
# ref_id: The reference ID of the debt (this is the uu_id of the debt record)
# session: Database session
# Returns:
# float: The remaining amount to pay
# """
# # Get the original debt amount - the debt is identified by its uu_id which is passed as ref_id
# debit = BuildDecisionBookPayments.query.filter(
# BuildDecisionBookPayments.uu_id == ref_id,
# BuildDecisionBookPayments.account_is_debit == True
# ).first()
# if not debit:
# return 0 # No debit found, nothing to pay
# debit_amount = abs(debit.payment_amount) # Ensure positive value for debit amount
# # Get the sum of payments already made for this debt
# # The ref_id in credit records points to the uu_id of the original debit
# # Note: payment_amount is negative for credit entries, so we use abs() to get positive values
# credit_amount = session.query(
# func.sum(func.abs(BuildDecisionBookPayments.payment_amount))
# ).filter(
# BuildDecisionBookPayments.ref_id == str(ref_id),
# BuildDecisionBookPayments.account_is_debit == False
# ).scalar() or 0
# # Calculate remaining amount to pay
# remaining = abs(debit_amount) - abs(credit_amount)
# # Ensure we don't return negative values
# if remaining < 0:
# return 0
# return remaining
def get_unpaid_debts_via_build_parts(build_parts_id: int, session, debit_type, date_query: tuple):
"""Find BuildDecisionBookPayments entries where the debt has NOT been fully paid for a specific debit type.
This function identifies payments where the sum of payments is less than the debit amount,
meaning the debt has not been fully closed.
Args:
build_parts_id: The build part ID to check
session: Database session
debit_type: The specific debit type to check
date_query: Tuple of date filters to apply
Returns:
list: List of unpaid debt entries (full BuildDecisionBookPayments objects)
query:
SELECT
bpf.ref_id,
bpf.process_date,
ABS(COALESCE(SUM(bpf.payment_amount), 0)) AS total_payments
FROM public.build_decision_book_payments AS bpf
GROUP BY
bpf.ref_id,
bpf.process_date
HAVING ABS(COALESCE(SUM(bpf.payment_amount), 0)) > 0
order by bpf.process_date
"""
# Create a subquery for the payment sums without executing it separately
payment_sums_subquery = select(
BuildDecisionBookPayments.ref_id,
BuildDecisionBookPayments.process_date,
func.abs(func.coalesce(func.sum(BuildDecisionBookPayments.payment_amount), 0)).label("total_payments")
).filter(
BuildDecisionBookPayments.build_parts_id == build_parts_id,
BuildDecisionBookPayments.payment_types_id == debit_type.id,
*date_query
).group_by(
BuildDecisionBookPayments.ref_id, BuildDecisionBookPayments.process_date
).having(
func.abs(func.coalesce(func.sum(BuildDecisionBookPayments.payment_amount), 0)) > 0
).order_by(BuildDecisionBookPayments.process_date.desc())
# Use the subquery directly in the main query
payment_sums = session.execute(payment_sums_subquery).all()
payment_sums_list = []
for item in payment_sums:
payment_sums_list.append({"ref_id": item[0], "process_date": item[1], "total_payments": item[2]})
return payment_sums
def get_unpaid_debts(session, debit_type, date_query: tuple):
"""Find BuildDecisionBookPayments entries where the debt has NOT been fully paid for a specific debit type.
This function identifies payments where the sum of payments is less than the debit amount,
meaning the debt has not been fully closed.
Args:
build_parts_id: The build part ID to check
session: Database session
debit_type: The specific debit type to check
date_query: Tuple of date filters to apply
Returns:
list: List of unpaid debt entries (full BuildDecisionBookPayments objects)
query:
SELECT
bpf.ref_id,
bpf.process_date,
ABS(COALESCE(SUM(bpf.payment_amount), 0)) AS total_payments
FROM public.build_decision_book_payments AS bpf
GROUP BY
bpf.ref_id,
bpf.process_date
HAVING ABS(COALESCE(SUM(bpf.payment_amount), 0)) > 0
order by bpf.process_date
"""
# Create a subquery for the payment sums without executing it separately
do_payments_set = lambda ref, date, total: {"ref_id": ref, "process_date": date, "total_payments": total}
payment_sums_subquery = select(BuildDecisionBookPayments.ref_id, BuildDecisionBookPayments.process_date,
func.abs(func.coalesce(func.sum(BuildDecisionBookPayments.payment_amount), 0)).label("total_payments")
).filter(BuildDecisionBookPayments.payment_types_id == debit_type.id, *date_query
).group_by(BuildDecisionBookPayments.ref_id, BuildDecisionBookPayments.process_date
).having(func.abs(func.coalesce(func.sum(BuildDecisionBookPayments.payment_amount), 0)) > 0
).order_by(BuildDecisionBookPayments.process_date.asc())
payment_sums = session.execute(payment_sums_subquery).all()
return [do_payments_set(item[0], item[1], item[2]) for item in payment_sums]
def do_payments_of_this_month(build_id: int = 1):
"""Process payments for the current month's unpaid debts.
This function retrieves account records with available funds and processes
payments for current month's unpaid debts in order of payment type priority.
"""
session_factory = get_session_factory()
session = session_factory()
# Set session for all models
AccountRecords.set_session(session)
BuildDecisionBookPayments.set_session(session)
Build.set_session(session)
BuildDecisionBook.set_session(session)
# Get payment types in priority order
payment_type_list = get_enums_from_database()
fund_finished = lambda money_spend, money_in_account: money_spend == money_in_account
payments_made, total_amount_paid, paid_count = 0, 0, 0
target_build = Build.query.filter(Build.id == build_id).first()
if not target_build:
raise ValueError(f"Build with id {build_id} not found")
now = datetime.now()
decision_book = BuildDecisionBook.query.filter(
BuildDecisionBook.build_id == build_id, cast(BuildDecisionBook.expiry_starts, Date) <= now.date(),
cast(BuildDecisionBook.expiry_ends, Date) >= now.date(), BuildDecisionBook.decision_type == "RBM"
).first()
if not decision_book:
raise ValueError(f"Decision book not found for build with id {build_id}")
period_date_start = decision_book.expiry_starts
period_date_end = decision_book.expiry_ends
period_id = decision_book.id
first_date_of_process_date = find_first_day_of_month(now)
last_date_of_process_date = find_last_day_of_month(now)
# Current month date filter
date_query_tuple = (
cast(BuildDecisionBookPayments.process_date, Date) >= first_date_of_process_date.date(), cast(BuildDecisionBookPayments.process_date, Date) <= last_date_of_process_date.date()
)
update_all_spent_accounts(session)
for payment_type in payment_type_list:
unpaid_debts = get_unpaid_debts(session, payment_type, date_query_tuple)
for unpaid_debt in unpaid_debts:
amount_to_pay = unpaid_debt["total_payments"]
ref_id = unpaid_debt["ref_id"]
process_date = unpaid_debt["process_date"]
debit_row = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.uu_id == ref_id).first()
build_parts_id = debit_row.build_parts_id
money_to_pay_rows = AccountRecords.query.filter(
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(), cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date(),
).order_by(AccountRecords.bank_date.asc()).all()
for money_to_pay_row in money_to_pay_rows:
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
money_to_pay_row = AccountRecords.query.filter(AccountRecords.id == money_to_pay_row.id).first()
available_money = abs(money_to_pay_row.currency_value) - abs(money_to_pay_row.remainder_balance)
if available_money > amount_to_pay:
print('NOT All money is spent amount_to_pay:', amount_to_pay, 'debit_row payment_amount:', debit_row.payment_amount)
close_payment_book(debit_row, money_to_pay_row, amount_to_pay, session)
total_amount_paid += amount_to_pay
paid_count += 1
payments_made += 1
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
break
elif available_money <= amount_to_pay:
print('All money is spent amount_to_pay:', amount_to_pay, 'debit_row payment_amount:', debit_row.payment_amount)
close_payment_book(debit_row, money_to_pay_row, available_money, session)
total_amount_paid += available_money
paid_count += 1
payments_made += 1
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
continue
else:
print(f"Something else happened available_money: {available_money}, amount_to_pay: {amount_to_pay}")
update_all_spent_accounts(session)
print('payments_made', payments_made)
print('total_amount_paid', total_amount_paid)
print('paid_count', paid_count)
def do_payments_of_previos_months(build_id: int = 1):
"""Process payments for previous months' unpaid debts.
This function retrieves account records with available funds and processes
payments for previous months' unpaid debts in order of payment type priority.
"""
"""Process payments for the current month's unpaid debts.
This function retrieves account records with available funds and processes
payments for current month's unpaid debts in order of payment type priority.
"""
session_factory = get_session_factory()
session = session_factory()
# Set session for all models
AccountRecords.set_session(session)
BuildDecisionBookPayments.set_session(session)
Build.set_session(session)
BuildDecisionBook.set_session(session)
# Get payment types in priority order
payment_type_list = get_enums_from_database()
fund_finished = lambda money_spend, money_in_account: money_spend == money_in_account
payments_made, total_amount_paid, paid_count = 0, 0, 0
target_build = Build.query.filter(Build.id == build_id).first()
if not target_build:
raise ValueError(f"Build with id {build_id} not found")
now = datetime.now()
decision_book = BuildDecisionBook.query.filter(
BuildDecisionBook.build_id == build_id,
cast(BuildDecisionBook.expiry_starts, Date) <= now.date(),
cast(BuildDecisionBook.expiry_ends, Date) >= now.date(),
BuildDecisionBook.decision_type == "RBM"
).first()
if not decision_book:
raise ValueError(f"Decision book not found for build with id {build_id}")
early_date = datetime(now.year - 1, now.month, now.day)
early_decision_book = BuildDecisionBook.query.filter(
BuildDecisionBook.build_id == build_id,
cast(BuildDecisionBook.expiry_starts, Date) <= early_date.date(),
cast(BuildDecisionBook.expiry_ends, Date) >= early_date.date(),
BuildDecisionBook.decision_type == "RBM"
).first()
period_date_start = decision_book.expiry_starts
period_date_end = decision_book.expiry_ends
period_id = decision_book.id
early_period_date_start = early_decision_book.expiry_starts
early_period_date_end = early_decision_book.expiry_ends
early_period_id = early_decision_book.id
first_date_of_process_date = arrow.get(period_date_start).datetime
last_date_of_process_date = now
# Current month date filter
date_query_tuple = (
cast(BuildDecisionBookPayments.process_date, Date) >= first_date_of_process_date.date(),
cast(BuildDecisionBookPayments.process_date, Date) <= last_date_of_process_date.date()
)
update_all_spent_accounts(session)
for payment_type in payment_type_list:
unpaid_debts = get_unpaid_debts(session, payment_type, date_query_tuple)
print('length unpaid debts: ', len(unpaid_debts))
for unpaid_debt in unpaid_debts:
amount_to_pay = unpaid_debt["total_payments"]
ref_id = unpaid_debt["ref_id"]
process_date = unpaid_debt["process_date"]
debit_row = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.uu_id == ref_id).first()
build_parts_id = debit_row.build_parts_id
money_to_pay_rows = AccountRecords.query.filter(
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
cast(AccountRecords.bank_date, Date) >= find_first_day_of_month(process_date).date(), cast(AccountRecords.bank_date, Date) <= find_last_day_of_month(process_date).date(),
).order_by(AccountRecords.bank_date.asc()).all()
if not money_to_pay_rows:
money_to_pay_rows = AccountRecords.query.filter(
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(), cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date(),
).order_by(AccountRecords.bank_date.asc()).all()
for money_to_pay_row in money_to_pay_rows:
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
money_to_pay_row = AccountRecords.query.filter(AccountRecords.id == money_to_pay_row.id).first()
available_money = abs(money_to_pay_row.currency_value) - abs(money_to_pay_row.remainder_balance)
if available_money > amount_to_pay:
print('NOT All money is spent amount_to_pay:', amount_to_pay, 'debit_row payment_amount:', debit_row.payment_amount)
close_payment_book(debit_row, money_to_pay_row, amount_to_pay, session)
total_amount_paid += amount_to_pay
paid_count += 1
payments_made += 1
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
break
elif available_money <= amount_to_pay:
print('All money is spent amount_to_pay:', available_money, 'debit_row payment_amount:', debit_row.payment_amount)
close_payment_book(debit_row, money_to_pay_row, available_money, session)
total_amount_paid += available_money
paid_count += 1
payments_made += 1
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
continue
else:
print(f"Something else happened available_money: {available_money}, amount_to_pay: {amount_to_pay}")
print('This years decision book payments')
print('payments_made', payments_made)
print('total_amount_paid', total_amount_paid)
print('paid_count', paid_count)
payments_made, total_amount_paid, paid_count = 0, 0, 0
update_all_spent_accounts(session)
first_date_of_process_date = arrow.get(early_period_date_start).datetime
last_date_of_process_date = arrow.get(early_period_date_end).datetime
# Early month date filter
date_query_tuple = (
cast(BuildDecisionBookPayments.process_date, Date) >= first_date_of_process_date.date(), cast(BuildDecisionBookPayments.process_date, Date) <= last_date_of_process_date.date()
)
for payment_type in payment_type_list:
unpaid_debts = get_unpaid_debts(session, payment_type, date_query_tuple)
print('length unpaid debts: ', len(unpaid_debts))
for unpaid_debt in unpaid_debts:
amount_to_pay = unpaid_debt["total_payments"]
ref_id = unpaid_debt["ref_id"]
process_date = unpaid_debt["process_date"]
debit_row = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.uu_id == ref_id).first()
build_parts_id = debit_row.build_parts_id
first_date_of_process_date = find_first_day_of_month(process_date)
last_date_of_process_date = find_last_day_of_month(process_date)
money_to_pay_rows = AccountRecords.query.filter(
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(), cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date(),
).order_by(AccountRecords.bank_date.asc()).all()
if not money_to_pay_rows:
money_to_pay_rows = AccountRecords.query.filter(
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(), cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date(),
).order_by(AccountRecords.bank_date.asc()).all()
for money_to_pay_row in money_to_pay_rows:
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
money_to_pay_row = AccountRecords.query.filter(AccountRecords.id == money_to_pay_row.id).first()
available_money = abs(money_to_pay_row.currency_value) - abs(money_to_pay_row.remainder_balance)
if available_money > amount_to_pay:
print('NOT All money is spent amount_to_pay:', amount_to_pay, 'debit_row payment_amount:', debit_row.payment_amount)
close_payment_book(debit_row, money_to_pay_row, amount_to_pay, session)
total_amount_paid += amount_to_pay
paid_count += 1
payments_made += 1
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
break
elif available_money <= amount_to_pay:
print('All money is spent amount_to_pay:', available_money, 'debit_row payment_amount:', debit_row.payment_amount)
close_payment_book(debit_row, money_to_pay_row, available_money, session)
total_amount_paid += available_money
paid_count += 1
payments_made += 1
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
continue
else:
print(f"Something else happened available_money: {available_money}, amount_to_pay: {amount_to_pay}")
update_all_spent_accounts(session)
print('Early years decision book payments')
print('payments_made', payments_made)
print('total_amount_paid', total_amount_paid)
print('paid_count', paid_count)
if __name__ == "__main__": if __name__ == "__main__":
start_time = perf_counter() import sys
print("\n===== PROCESSING PAYMENTS =====\n") # Default values
print("Starting payment processing at:", datetime.now()) limit = 10
offset = 0
# Parse command line arguments
if len(sys.argv) > 1:
try:
limit = int(sys.argv[1])
except ValueError:
print(f"Error: Invalid limit value '{sys.argv[1]}'. Using default limit={limit}")
if len(sys.argv) > 2:
try:
offset = int(sys.argv[2])
except ValueError:
print(f"Error: Invalid offset value '{sys.argv[2]}'. Using default offset={offset}")
same_month_actions(limit=limit, offset=offset)
# Process payments for current month first
print("\n1. Processing current month payments...")
do_payments_of_this_month()
# Process payments for previous months
print("\n2. Processing previous months payments...")
do_payments_of_previos_months()
print("\n===== PAYMENT PROCESSING COMPLETE =====\n")
print("Payment processing completed at:", datetime.now())
# Analyze the payment situation after processing payments
print("\n===== ANALYZING PAYMENT SITUATION AFTER PROCESSING =====\n")
# analyze_payment_function()
end_time = perf_counter()
print(f"\n{end_time - start_time:.3f} : seconds")
# # Create a subquery to get the sum of payments for each debit's uu_id
# # For credit entries, ref_id points to the original debit's uu_id
# payment_sums = session.query(
# BuildDecisionBookPayments.ref_id.label('original_debt_id'),
# func.sum(func.abs(BuildDecisionBookPayments.payment_amount)).label('payment_sum')
# ).filter(
# BuildDecisionBookPayments.account_is_debit == False # Credit entries only
# ).group_by(BuildDecisionBookPayments.ref_id).subquery()
# # Main query to find debits with their payment sums
# query = session.query(BuildDecisionBookPayments)
# # Join with payment sums - cast uu_id to string to match ref_id type
# query = query.outerjoin(
# payment_sums,
# func.cast(BuildDecisionBookPayments.uu_id, String) == payment_sums.c.original_debt_id
# )
# # Filter for debits of the specified build part and payment type
# query = query.filter(
# BuildDecisionBookPayments.build_parts_id == build_parts_id,
# BuildDecisionBookPayments.payment_types_id == debit_type.id,
# BuildDecisionBookPayments.account_is_debit == True, # Debit entries only
# )
# # Apply date filters if provided
# if date_query:
# for date_filter in date_query:
# query = query.filter(date_filter)
# # Filter for debits that are not fully paid
# # (payment_sum < debit_amount or payment_sum is NULL)
# query = query.filter(
# or_(
# payment_sums.c.payment_sum.is_(None),
# func.coalesce(payment_sums.c.payment_sum, 0) < func.abs(BuildDecisionBookPayments.payment_amount)
# )
# )
# # Execute the query and return the results
# results = query.order_by(BuildDecisionBookPayments.process_date).all()

View File

@ -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)

View File

@ -339,17 +339,17 @@ services:
# ports: # ports:
# - "8005:8005" # - "8005:8005"
# initializer_service: initializer_service:
# container_name: initializer_service container_name: initializer_service
# build: build:
# context: . context: .
# dockerfile: ServicesApi/Builds/Initial/Dockerfile dockerfile: ServicesApi/Builds/Initial/Dockerfile
# environment: environment:
# - SET_ALEMBIC=1 - SET_ALEMBIC=1
# networks: networks:
# - wag-services - wag-services
# env_file: env_file:
# - api_env.env - api_env.env
# mem_limit: 512m # mem_limit: 512m
# cpus: 0.5 # cpus: 0.5

15
run_finder_payment.sh Executable file
View File

@ -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."