diff --git a/README.md b/README.md index 910d225..7fd4b17 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,9 @@ ## Backend Docs: [Backend](./docs/api.md) + +## Bank Services Docs: + +Todo: +Text from bank iban dan build_id bulup | build_id den managment_room[living_space_id] bulup idisine +account records default id haline getirelecek diff --git a/ServicesApi/Builds/Building/events/builds/supers_events.py b/ServicesApi/Builds/Building/events/builds/supers_events.py index d5bb436..ae9fce0 100644 --- a/ServicesApi/Builds/Building/events/builds/supers_events.py +++ b/ServicesApi/Builds/Building/events/builds/supers_events.py @@ -79,11 +79,11 @@ def super_build_create_callable(data, headers: CommonHeaders): Build.set_session(db_session) Addresses.set_session(db_session) - BuildTypes.set_session(db_session) + ApiEnumDropdown.set_session(db_session) BuildSites.set_session(db_session) address_id = Addresses.query.filter(Addresses.uu_id == data.address_uu_id).first() - build_types_id = BuildTypes.query.filter(BuildTypes.uu_id == data.build_types_uu_id).first() + build_types_id = ApiEnumDropdown.query.filter(ApiEnumDropdown.uu_id == data.build_types_uu_id).first() sites_id = BuildSites.query.filter(BuildSites.uu_id == data.site_uu_id).first() build = Build.create(**data.model_dump(), address_id=getattr(address_id, "id", None), build_types_id=getattr(build_types_id, "id", None), site_id=getattr(sites_id, "id", None)) build.save() @@ -104,11 +104,11 @@ def super_build_update_callable(uu_id: str, data, headers: CommonHeaders): with Build.new_session() as db_session: Build.set_session(db_session) Addresses.set_session(db_session) - BuildTypes.set_session(db_session) + ApiEnumDropdown.set_session(db_session) BuildSites.set_session(db_session) address_id = Addresses.query.filter(Addresses.uu_id == data.address_uu_id).first() - build_types_id = BuildTypes.query.filter(BuildTypes.uu_id == data.build_types_uu_id).first() + build_types_id = ApiEnumDropdown.query.filter(ApiEnumDropdown.uu_id == data.build_types_uu_id).first() sites_id = BuildSites.query.filter(BuildSites.uu_id == data.site_uu_id).first() build = Build.query.filter(Build.uu_id == uu_id).first() diff --git a/ServicesApi/Builds/Initial/init_enums.py b/ServicesApi/Builds/Initial/init_enums.py index cb7ba59..3e4ab07 100644 --- a/ServicesApi/Builds/Initial/init_enums.py +++ b/ServicesApi/Builds/Initial/init_enums.py @@ -1,17 +1,17 @@ from pydantic import BaseModel -from Schemas import BuildTypes, ApiEnumDropdown +from Schemas import ApiEnumDropdown -class InsertBuildTypes(BaseModel): - function_code: str - type_code: str - lang: str - type_name: str +# class InsertBuildTypes(BaseModel): +# function_code: str +# type_code: str +# lang: str +# type_name: str def init_api_enums_build_types(db_session): - BuildTypes.set_session(db_session) + # BuildTypes.set_session(db_session) ApiEnumDropdown.set_session(db_session) insert_types = [ @@ -71,24 +71,59 @@ def init_api_enums_build_types(db_session): }, ] - for insert_type in insert_types: - build_types = InsertBuildTypes( - function_code="EVYOS", - lang=insert_type["lang"], - type_code=str(insert_type["type_code"]).upper(), - type_name=insert_type["type_name"], - ) - created_build_type = BuildTypes.query.filter_by( - function_code=build_types.function_code, - type_code=build_types.type_code, - ).first() - if not created_build_type: - created_build_type = BuildTypes.find_or_create( - **build_types.model_dump(), is_confirmed=True, db=db_session - ) - created_build_type.save() + # for insert_type in insert_types: + # build_types = dict(enum_class="BuildTypes", key=insert_type["type_code"], value=insert_type["type_name"]) + # created_build_type = ApiEnumDropdown.query.filter_by(enum_class="BuildTypes", **build_types).first() + # if not created_build_type: + # created_build_type = ApiEnumDropdown.find_or_create(**build_types, is_confirmed=True, db=db_session) + # created_build_type.save() insert_enums = [ + { + "enum_class": "BuildTypes", + "type_code": "APT_KZN", + "type_name": "Apartman Kazan Dairesi", + }, + { + "enum_class": "BuildTypes", + "type_code": "APT_GRJ", + "type_name": "Apartman Garaj", + }, + { + "enum_class": "BuildTypes", + "type_code": "APT_DP", + "type_name": "Apartman Depo", + }, + { + "enum_class": "BuildTypes", + "type_code": "DAIRE", + "type_name": "Apartman Dairesi", + }, + { + "enum_class": "BuildTypes", + "type_code": "APT", + "type_name": "Apartman Binası", + }, + { + "enum_class": "BuildTypes", + "type_code": "APT_YNT", + "type_name": "Apartman Yönetimi", + }, + { + "enum_class": "BuildTypes", + "type_code": "APT_PRK", + "type_name": "Apartman Açık Park Alanı", + }, + { + "enum_class": "BuildTypes", + "type_code": "APT_YSL", + "type_name": "Apartman Yeşil Alan", + }, + { + "enum_class": "BuildTypes", + "type_code": "APT_YOL", + "type_name": "Apartman Ara Yol", + }, {"enum_class": "BuildDuesTypes", "type_code": "BDT-D", "type_name": "Debit"}, { "enum_class": "BuildDuesTypes", @@ -274,6 +309,7 @@ def init_api_enums_build_types(db_session): created_api_enum = ApiEnumDropdown.query.filter_by( enum_class=insert_enum["enum_class"], key=str(insert_enum["type_code"]).upper(), + lang="tr", ).first() if not created_api_enum: created_api_enum = ApiEnumDropdown.create( diff --git a/ServicesApi/Builds/Initial/init_occ_defaults.py b/ServicesApi/Builds/Initial/init_occ_defaults.py index 8b6c784..d8cb88f 100644 --- a/ServicesApi/Builds/Initial/init_occ_defaults.py +++ b/ServicesApi/Builds/Initial/init_occ_defaults.py @@ -8,7 +8,7 @@ from Schemas import ( People, Build, BuildParts, - BuildTypes, + # BuildTypes, ApiEnumDropdown, Companies, OccupantTypes, @@ -23,7 +23,7 @@ def create_occupant_defaults(db_session): People.set_session(db_session) Build.set_session(db_session) BuildParts.set_session(db_session) - BuildTypes.set_session(db_session) + # BuildTypes.set_session(db_session) ApiEnumDropdown.set_session(db_session) Companies.set_session(db_session) OccupantTypes.set_session(db_session) @@ -34,7 +34,7 @@ def create_occupant_defaults(db_session): company_id, company_uu_id = company_management.id, str(company_management.uu_id) active_row = dict(is_confirmed=True, active=True, deleted=False, is_notification_send=True) - build_type = BuildTypes.query.filter_by(type_code = "APT").first() + build_type = ApiEnumDropdown.query.filter_by(enum_class = "BuildTypes", key = "APT").first() address = Addresses.query.filter_by(letter_address = "Example Address").first() created_build = Build.query.filter_by(build_name = "Build Example").first() @@ -52,8 +52,8 @@ def create_occupant_defaults(db_session): ) created_list.append(created_build) - build_type_created = BuildTypes.query.filter_by(type_code = "APT").first() - build_type_flat = BuildTypes.query.filter_by(type_code = "DAIRE").first() + build_type_created = ApiEnumDropdown.query.filter_by(enum_class = "BuildTypes", key = "APT").first() + build_type_flat = ApiEnumDropdown.query.filter_by(enum_class = "BuildTypes", key = "DAIRE").first() enum_dropdown = ApiEnumDropdown.query.filter_by(key = "NE", enum_class = "Directions").first() occupant_type_prs = OccupantTypes.query.filter_by(occupant_code = "MT-PRS").first() diff --git a/ServicesApi/Controllers/Postgres/config.py b/ServicesApi/Controllers/Postgres/config.py index 53fbec6..736d505 100644 --- a/ServicesApi/Controllers/Postgres/config.py +++ b/ServicesApi/Controllers/Postgres/config.py @@ -4,6 +4,19 @@ from pydantic_settings import BaseSettings, SettingsConfigDict class Configs(BaseSettings): """ Postgresql configuration settings. + POSTGRES_USER=postgres + POSTGRES_PASSWORD=password + POSTGRES_DB=postgres + POSTGRES_HOST=10.10.2.14 + POSTGRES_PORT=5432 + POSTGRES_ENGINE=postgresql+psycopg2 + POSTGRES_POOL_PRE_PING=True + POSTGRES_POOL_SIZE=20 + POSTGRES_MAX_OVERFLOW=10 + POSTGRES_POOL_RECYCLE=600 + POSTGRES_POOL_TIMEOUT=30 + POSTGRES_ECHO=True + # "postgresql+psycopg2://postgres:password@10.10.2.14:5432/postgres" """ DB: str = "" diff --git a/ServicesApi/Schemas/__init__.py b/ServicesApi/Schemas/__init__.py index 8d6d6d7..8eacc2f 100644 --- a/ServicesApi/Schemas/__init__.py +++ b/ServicesApi/Schemas/__init__.py @@ -26,7 +26,7 @@ from Schemas.address.address import ( AddressStreet, ) from Schemas.building.build import ( - BuildTypes, + # BuildTypes, Part2Employee, RelationshipEmployee2Build, Build, @@ -139,7 +139,7 @@ __all__ = [ "AddressLocality", "AddressNeighborhood", "AddressStreet", - "BuildTypes", + # "BuildTypes", "Part2Employee", "RelationshipEmployee2Build", "Build", diff --git a/ServicesApi/Schemas/account/account.py b/ServicesApi/Schemas/account/account.py index 0552d25..1428c78 100644 --- a/ServicesApi/Schemas/account/account.py +++ b/ServicesApi/Schemas/account/account.py @@ -315,6 +315,7 @@ class AccountRecordExchanges(CrudCollection): ) + class AccountDelayInterest(CrudCollection): __tablename__ = "account_delay_interest" @@ -354,6 +355,44 @@ class AccountDelayInterest(CrudCollection): ) +class AccountRecordsModelTrain(CrudCollection): + + __tablename__ = "account_records_model_train" + __exclude__fields__ = [] + + start_index: Mapped[int] = mapped_column(Integer, nullable=False) + end_index: Mapped[int] = mapped_column(Integer, nullable=False) + search_text: Mapped[str] = mapped_column(String, nullable=False) + + category_id: Mapped[int] = mapped_column(ForeignKey("api_enum_dropdown.id"), nullable=False) + category_uu_id: Mapped[str] = mapped_column(String(100), nullable=False) + 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) + is_model_trained: Mapped[bool] = mapped_column(Boolean, server_default="0") + trained_at: Mapped[TIMESTAMP] = mapped_column(TIMESTAMP(timezone=True), nullable=True) + + +class AccountRecordsPredict(CrudCollection): + """ + prediction_model = tahmin eden model ismi + prediction_result = tahmin edilen sonuc + treshold = tahmin edilen sonucun yüzdesi + is_first_prediction = account record da kullanılmak tahmin mi? + """ + __tablename__ = "account_records_predict" + __exclude__fields__ = [] + + 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) + prediction_model: Mapped[str] = mapped_column(String(10), nullable=False) + prediction_result: Mapped[int] = mapped_column(Integer, nullable=False) + prediction_field: Mapped[str] = mapped_column(String(10), nullable=False, server_default="") + treshold: Mapped[float] = mapped_column(Numeric(18, 6), nullable=True) + is_first_prediction: Mapped[bool] = mapped_column(Boolean, server_default="0") + is_approved: Mapped[bool] = mapped_column(Boolean, server_default="0") + approved_at: Mapped[TIMESTAMP] = mapped_column(TIMESTAMP(timezone=True), nullable=True) + + class AccountRecords(CrudCollection): """ build_decision_book_id = kaydın sorumlu olduğu karar defteri @@ -368,6 +407,7 @@ class AccountRecords(CrudCollection): iban: Mapped[str] = mapped_column(String(64), nullable=False, comment="IBAN Number of Bank") bank_date: Mapped[TIMESTAMP] = mapped_column(TIMESTAMP(timezone=True), nullable=False, comment="Bank Transaction Date") currency_value: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, comment="Currency Value") + remainder_balance: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0") bank_balance: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, comment="Bank Balance") currency: Mapped[str] = mapped_column(String(5), nullable=False, comment="Unit of Currency") additional_balance: Mapped[float] = mapped_column(Numeric(20, 6), nullable=False, comment="Additional Balance") @@ -377,12 +417,13 @@ class AccountRecords(CrudCollection): process_comment: Mapped[str] = mapped_column(String, nullable=False, comment="Transaction Record Comment") process_garbage: Mapped[str] = mapped_column(String, nullable=True, comment="Transaction Record Garbage") bank_reference_code: Mapped[str] = mapped_column(String, nullable=False, comment="Bank Reference Code") - add_comment_note: Mapped[str] = mapped_column(String, server_default="") is_receipt_mail_send: Mapped[bool] = mapped_column(Boolean, server_default="0") + approved_record: Mapped[bool] = mapped_column(Boolean, server_default="0") + found_from = mapped_column(String, server_default="") similarity: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0") - remainder_balance: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0") + import_file_name: Mapped[str] = mapped_column(String, nullable=True, comment="XLS Key") bank_date_y: Mapped[int] = mapped_column(Integer) bank_date_m: Mapped[int] = mapped_column(SmallInteger) @@ -394,8 +435,6 @@ class AccountRecords(CrudCollection): accounting_receipt_number: Mapped[int] = mapped_column(Integer, server_default="0") status_id: Mapped[int] = mapped_column(SmallInteger, server_default="0") - approved_record: Mapped[bool] = mapped_column(Boolean, server_default="0") - import_file_name: Mapped[str] = mapped_column(String, nullable=True, comment="XLS Key") receive_debit: Mapped[int] = mapped_column(ForeignKey("api_enum_dropdown.id"), nullable=True) receive_debit_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Debit UU ID") budget_type: Mapped[int] = mapped_column(ForeignKey("api_enum_dropdown.id"), nullable=True) @@ -422,7 +461,7 @@ class AccountRecords(CrudCollection): payment_result_type: Mapped[int] = mapped_column(ForeignKey("api_enum_dropdown.id"), nullable=True) payment_result_type_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Payment Result Type UU ID") is_commission_applied: Mapped[bool] = mapped_column(Boolean, server_default="0") - + __table_args__ = ( Index("_budget_records_ndx_00", is_receipt_mail_send, bank_date), Index("_budget_records_ndx_01", iban, bank_date, bank_reference_code, bank_balance, unique=True), diff --git a/ServicesApi/Schemas/building/build.py b/ServicesApi/Schemas/building/build.py index 5ae81c2..2988be0 100644 --- a/ServicesApi/Schemas/building/build.py +++ b/ServicesApi/Schemas/building/build.py @@ -148,7 +148,7 @@ class Build(CrudCollection): site_uu_id: Mapped[str] = mapped_column(String, comment="Site UUID", nullable=True) address_id: Mapped[int] = mapped_column(ForeignKey("addresses.id"), nullable=False) address_uu_id: Mapped[str] = mapped_column(String, comment="Address UUID", nullable=False) - build_types_id: Mapped[int] = mapped_column(ForeignKey("build_types.id"), nullable=False, comment="Building Type") + build_types_id: Mapped[int] = mapped_column(ForeignKey("api_enum_dropdown.id"), nullable=False, comment="Building Type") build_types_uu_id: Mapped[str] = mapped_column(String, comment="Building Type UUID") parts: Mapped[List["BuildParts"]] = relationship("BuildParts", back_populates="buildings", foreign_keys="BuildParts.build_id") @@ -238,8 +238,8 @@ class Build(CrudCollection): building_types = None for part in self.parts: building_types = {} - build_type = BuildTypes.query.filter( - BuildTypes.id == part.build_part_type_id + build_type = ApiEnumDropdown.query.filter( + ApiEnumDropdown.id == part.part_type_id ).first() if not build_type: raise HTTPException( @@ -316,7 +316,7 @@ class BuildParts(CrudCollection): String, nullable=True, comment="Part Direction UUID" ) part_type_id: Mapped[int] = mapped_column( - ForeignKey("build_types.id"), nullable=False, comment="Building Part Type" + ForeignKey("api_enum_dropdown.id"), nullable=False, comment="Building Part Type" ) part_type_uu_id: Mapped[str] = mapped_column( String, nullable=False, comment="Building Part Type UUID" @@ -332,9 +332,9 @@ class BuildParts(CrudCollection): ) def part_name(self, db): - BuildTypes.set_session(db) - if build_type := BuildTypes.query.filter( - BuildTypes.id == self.part_type_id + ApiEnumDropdown.set_session(db) + if build_type := ApiEnumDropdown.query.filter( + ApiEnumDropdown.id == self.part_type_id ).first(): return f"{str(build_type.type_name).upper()} : {str(self.part_no).upper()}" return f"Undefined:{str(build_type.type_name).upper()}" @@ -488,7 +488,7 @@ class BuildArea(CrudCollection): build_id: Mapped[int] = mapped_column(ForeignKey("build.id")) build_uu_id: Mapped[str] = mapped_column(String, comment="Building UUID") part_type_id: Mapped[int] = mapped_column( - ForeignKey("build_types.id"), nullable=True, comment="Building Part Type" + ForeignKey("api_enum_dropdown.id"), nullable=True, comment="Building Part Type" ) part_type_uu_id: Mapped[str] = mapped_column( String, nullable=True, comment="Building Part Type UUID" diff --git a/ServicesApi/Schemas/others/enums.py b/ServicesApi/Schemas/others/enums.py index 1f43f6d..6542a22 100644 --- a/ServicesApi/Schemas/others/enums.py +++ b/ServicesApi/Schemas/others/enums.py @@ -17,6 +17,7 @@ class ApiEnumDropdown(CrudCollection): uu_id: Mapped[str] = mapped_column( UUID, server_default=text("gen_random_uuid()"), index=True, unique=True ) + lang: Mapped[str] = mapped_column(String, nullable=False, server_default="tr", comment="Language") enum_class: Mapped[str] = mapped_column( String, nullable=False, comment="Enum Constant Name" ) diff --git a/ServicesBank/Finder/Payment/exact_runner.py b/ServicesBank/Finder/Payment/exact_runner.py new file mode 100644 index 0000000..7bdb6ae --- /dev/null +++ b/ServicesBank/Finder/Payment/exact_runner.py @@ -0,0 +1,445 @@ +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,text, Integer, Float, func, distinct, cast, Date, String, literal, desc, and_, or_, case +from sqlalchemy.sql.expression import literal +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 + +class ExactMatch: + def __init__(self, + account_record_id, build_decision_book_payment_id, living, diff, day_df, process_comment, + bank_date, process_date, fund, debt, payment_correct): + self.account_record_id = account_record_id + self.build_decision_book_payment_id = build_decision_book_payment_id + self.living = living + self.diff = diff + self.day_df = day_df + self.process_comment = process_comment + self.bank_date = str(bank_date) + self.process_date = str(process_date) + self.fund = fund + self.debt = debt + self.payment_correct = payment_correct + + def to_dict(self): + return { + 'account_record_id': self.account_record_id, + 'build_decision_book_payment_id': self.build_decision_book_payment_id, + # 'process_comment': self.process_comment, + 'living': self.living, + 'diff': self.diff, + 'day_df': self.day_df, + 'bank_date': self.bank_date, + 'process_date': self.process_date, + 'fund': self.fund, + 'debt': self.debt, + 'payment_correct': self.payment_correct + } + +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), + replication_id=99, # Triggers for trigger_account_delay_interest calculation [active trigger] + bank_resp_company_id=1, # Evyos interest rates will be applied when id is given + bank_resp_company_uu_id="CMP", + ) + 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 + + +# Debt pay debt that is paid in same month early bird +def pay_debt_that_is_paid_in_same_month_early_bird(session, payment_type_list, date_from): + # Create aliases for tables + ar = AccountRecords.__table__.alias('ar') + bdp = BuildDecisionBookPayments.__table__.alias('bdp') + + # Calculate day difference expression + day_diff = (func.cast(func.to_char(ar.c.bank_date, 'YYYYMMDD'), Integer) - + func.cast(func.to_char(bdp.c.process_date, 'YYYYMMDD'), Integer)).label('day_df') + + # Calculate absolute difference between fund and payment + amount_diff = func.abs((ar.c.currency_value + ar.c.remainder_balance) - func.abs(bdp.c.debt_to_pay)).label('diff') + + # Calculate fund amount + fund_amount = (ar.c.currency_value + ar.c.remainder_balance).label('fund') + + # Payment correctness check + payment_correct = (ar.c.payment_result_type == bdp.c.payment_types_id).label('payment_correct') + # func.to_char(ar.c.bank_date, 'YYYY-MM') == func.to_char(bdp.c.process_date, 'YYYY-MM'), + + # Build the query using SQLAlchemy + query = session.query( + ar.c.id.label('account_record_id'), + bdp.c.id.label('build_decision_book_payment_id'), + ar.c.living_space_id.label('living'), + amount_diff, + day_diff, + ar.c.process_comment, + func.cast(ar.c.bank_date, Date).label('bank_date'), + func.cast(bdp.c.process_date, Date).label('process_date'), + fund_amount, + bdp.c.debt_to_pay.label('debt'), + payment_correct + ).join( + bdp, + and_( + ar.c.build_parts_id == bdp.c.build_parts_id, + func.to_char(ar.c.bank_date, 'YYYY-MM') == func.to_char(bdp.c.process_date, 'YYYY-MM'), + ar.c.payment_result_type == bdp.c.payment_types_id, + (ar.c.currency_value + ar.c.remainder_balance) == func.abs(bdp.c.debt_to_pay), + ) + ).filter( + ar.c.currency_value > 0, + bdp.c.build_decision_book_id == ar.c.build_decision_book_id, + bdp.c.is_closed.is_(False), + bdp.c.account_is_debit.is_(True), + func.cast(ar.c.bank_date, Date) <= datetime.now(), + func.cast(bdp.c.process_date, Date) <= datetime.now(), + func.cast(ar.c.bank_date, Date) > date_from, + func.cast(bdp.c.process_date, Date) > date_from + ).order_by(ar.c.living_space_id.asc(), day_diff.asc(), amount_diff.asc(), ar.c.bank_date.desc(), bdp.c.process_date.desc()) + return query.all() + + +# Debt pay debt that pay date in between thirty one day +def pay_debt_that_pay_date_in_between_thirty_one_day(session, payment_type_list, date_from): + # Create aliases for tables + ar = AccountRecords.__table__.alias('ar') + bdp = BuildDecisionBookPayments.__table__.alias('bdp') + + # Calculate day difference expression + day_diff = (func.cast(func.to_char(ar.c.bank_date, 'YYYYMMDD'), Integer) - + func.cast(func.to_char(bdp.c.process_date, 'YYYYMMDD'), Integer)).label('day_df') + + # Calculate absolute difference between fund and payment + amount_diff = func.abs((ar.c.currency_value + ar.c.remainder_balance) - + func.abs(bdp.c.debt_to_pay)).label('diff') + + # Calculate fund amount + fund_amount = (ar.c.currency_value + ar.c.remainder_balance).label('fund') + + # Payment correctness check + payment_correct = (ar.c.payment_result_type == bdp.c.payment_types_id).label('payment_correct') + # func.to_char(ar.c.bank_date, 'YYYY-MM') == func.to_char(bdp.c.process_date, 'YYYY-MM'), + + # Build the query using SQLAlchemy + query = session.query( + ar.c.id.label('account_record_id'), + bdp.c.id.label('build_decision_book_payment_id'), + ar.c.living_space_id.label('living'), + amount_diff, + day_diff, + ar.c.process_comment, + func.cast(ar.c.bank_date, Date).label('bank_date'), + func.cast(bdp.c.process_date, Date).label('process_date'), + fund_amount, + bdp.c.debt_to_pay.label('debt'), + payment_correct + ).join( + bdp, + and_( + ar.c.build_parts_id == bdp.c.build_parts_id, + ar.c.payment_result_type == bdp.c.payment_types_id, + (ar.c.currency_value + ar.c.remainder_balance) == func.abs(bdp.c.debt_to_pay), + func.abs(func.cast(ar.c.bank_date, Date) - func.cast(bdp.c.process_date, Date)) < 32, + ) + ).filter( + bdp.c.is_closed.is_(False), + ar.c.currency_value > 0, + bdp.c.account_is_debit.is_(True), + bdp.c.build_decision_book_id == ar.c.build_decision_book_id, + func.cast(ar.c.bank_date, Date) < datetime.now(), + func.cast(bdp.c.process_date, Date) < datetime.now(), + func.cast(ar.c.bank_date, Date) > date_from, + func.cast(bdp.c.process_date, Date) > date_from + ).order_by(ar.c.living_space_id.asc(), day_diff.asc(), amount_diff.asc(), ar.c.bank_date.desc(), bdp.c.process_date.desc()) + return query.all() + + +# Debt pay debt that has sent to do many rows +def pay_debt_that_has_sent_to_do_many_rows(session, payment_type_list, date_from): + # Create aliases for tables + ar = AccountRecords.__table__.alias('ar') + bdp = BuildDecisionBookPayments.__table__.alias('bdp') + + # Calculate day difference expression + day_diff = (func.cast(func.to_char(ar.c.bank_date, 'YYYYMMDD'), Integer) - + func.cast(func.to_char(bdp.c.process_date, 'YYYYMMDD'), Integer)).label('day_df') + + # Calculate absolute difference between fund and payment + amount_diff = func.abs((ar.c.currency_value + ar.c.remainder_balance) - + func.abs(bdp.c.debt_to_pay)).label('diff') + + # Calculate fund amount + fund_amount = (ar.c.currency_value + ar.c.remainder_balance).label('fund') + + # Payment correctness check + payment_correct = (ar.c.payment_result_type == bdp.c.payment_types_id).label('payment_correct') + # func.to_char(ar.c.bank_date, 'YYYY-MM') == func.to_char(bdp.c.process_date, 'YYYY-MM'), + + # Build the query using SQLAlchemy + query = session.query( + ar.c.id.label('account_record_id'), + bdp.c.id.label('build_decision_book_payment_id'), + ar.c.living_space_id.label('living'), + amount_diff, + day_diff, + ar.c.process_comment, + func.cast(ar.c.bank_date, Date).label('bank_date'), + func.cast(bdp.c.process_date, Date).label('process_date'), + fund_amount, + bdp.c.debt_to_pay.label('debt'), + payment_correct + ).join( + bdp, + and_( + ar.c.build_parts_id == bdp.c.build_parts_id, + ar.c.build_decision_book_id == bdp.c.build_decision_book_id, + func.abs(func.cast(ar.c.bank_date, Date) - func.cast(bdp.c.process_date, Date)) < 32, + ar.c.payment_result_type == bdp.c.payment_types_id, + (ar.c.currency_value + ar.c.remainder_balance) >= func.abs(bdp.c.debt_to_pay), + bdp.c.is_closed.is_(False), + ) + ).filter( + ar.c.currency_value > 0, + ar.c.build_decision_book_id == bdp.c.build_decision_book_id, + bdp.c.account_is_debit.is_(True), + func.cast(ar.c.bank_date, Date) < datetime.now(), + func.cast(bdp.c.process_date, Date) < datetime.now(), + func.cast(ar.c.bank_date, Date) > date_from, + func.cast(bdp.c.process_date, Date) > date_from + ).order_by( + ar.c.living_space_id.asc(), + day_diff.asc(), + amount_diff.asc(), + ar.c.bank_date.desc(), + bdp.c.process_date.desc() + ) + return query.all() + + +if __name__ == "__main__": + # import sys + + # Default values + # id_given = 1 + count_row = 0 + session_factory = get_session_factory() + session = session_factory() + payment_type_list = get_enums_from_database() + date_from = '2022-01-01' + early_bird_results = pay_debt_that_is_paid_in_same_month_early_bird(session=session, payment_type_list=payment_type_list, date_from=date_from) + for early_bird_result in early_bird_results: + AccountRecords.set_session(session) + BuildDecisionBookPayments.set_session(session) + print(ExactMatch(*early_bird_result).to_dict()) + account_record = AccountRecords.query.filter_by(id=early_bird_result.account_record_id).first() + book_payment = BuildDecisionBookPayments.query.filter_by(id=early_bird_result.build_decision_book_payment_id).first() + fund = account_record.currency_value + account_record.remainder_balance + if fund < abs(book_payment.debt_to_pay): + print("Fund is less than debt to pay", "-" * 500) + continue + if book_payment.is_closed: + print("Payment is closed", "-" * 500) + continue + close_payment_book(account_record=account_record, payment_row_book=book_payment, session=session, value=book_payment.debt_to_pay, is_commission_applicable=False) + count_row += 1 + + late_bird_results = pay_debt_that_pay_date_in_between_thirty_one_day(session=session, payment_type_list=payment_type_list, date_from=date_from) + for late_bird_result in late_bird_results: + AccountRecords.set_session(session) + BuildDecisionBookPayments.set_session(session) + print(ExactMatch(*late_bird_result).to_dict()) + account_record = AccountRecords.query.filter_by(id=late_bird_result.account_record_id).first() + book_payment = BuildDecisionBookPayments.query.filter_by(id=late_bird_result.build_decision_book_payment_id).first() + fund = account_record.currency_value + account_record.remainder_balance + if fund < abs(book_payment.debt_to_pay): + print("Fund is less than debt to pay", "-" * 500) + continue + if book_payment.is_closed: + print("Payment is closed", "-" * 500) + continue + close_payment_book(account_record=account_record, payment_row_book=book_payment, session=session, value=book_payment.debt_to_pay, is_commission_applicable=False) + count_row += 1 + + thirty_three_days = pay_debt_that_has_sent_to_do_many_rows(session=session, payment_type_list=payment_type_list, date_from=date_from) + for thirty_three_day in thirty_three_days: + AccountRecords.set_session(session) + BuildDecisionBookPayments.set_session(session) + print(ExactMatch(*thirty_three_day).to_dict()) + account_record = AccountRecords.query.filter_by(id=thirty_three_day.account_record_id).first() + book_payment = BuildDecisionBookPayments.query.filter_by(id=thirty_three_day.build_decision_book_payment_id).first() + fund = account_record.currency_value + account_record.remainder_balance + if fund < abs(book_payment.debt_to_pay): + print("Fund is less than debt to pay", "-" * 500) + continue + if book_payment.is_closed: + print("Payment is closed", "-" * 500) + continue + close_payment_book(account_record=account_record, payment_row_book=book_payment, session=session, value=book_payment.debt_to_pay, is_commission_applicable=False) + count_row += 1 + session.close() + print(f"Processed {count_row} rows") + # 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}") + \ No newline at end of file diff --git a/ServicesBank/Finder/Payment/run_app.sh b/ServicesBank/Finder/Payment/run_app.sh index 83dcf19..dfb5ac8 100644 --- a/ServicesBank/Finder/Payment/run_app.sh +++ b/ServicesBank/Finder/Payment/run_app.sh @@ -23,4 +23,4 @@ export PYTHONUNBUFFERED export PYTHONDONTWRITEBYTECODE # env >> /var/log/cron.log -/usr/local/bin/python /runner.py +/usr/local/bin/python /exact_runner.py diff --git a/ServicesBank/Finder/Payment/runner_new.py b/ServicesBank/Finder/Payment/runner_new.py index 2d3c681..f44c8d6 100644 --- a/ServicesBank/Finder/Payment/runner_new.py +++ b/ServicesBank/Finder/Payment/runner_new.py @@ -106,7 +106,6 @@ def close_payment_book(payment_row_book, account_record, value, session, is_comm saved_row = new_row.save() session.commit() session.refresh(saved_row) - if is_commission_applicable: AccountDelayInterest.set_session(session) new_row_interest = AccountDelayInterest.create( @@ -117,6 +116,9 @@ def close_payment_book(payment_row_book, account_record, value, session, is_comm 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), + replication_id=99, # Triggers for trigger_account_delay_interest calculation [active trigger] + bank_resp_company_id=1, # Evyos interest rates will be applied when id is given + bank_resp_company_uu_id="CMP", ) new_row_interest.save() session.commit() @@ -191,7 +193,7 @@ def pay_book_payment_by_account_records_id(account_records_id: int, session, pay # 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] + payment_result_type_uu_id = account_record_selected.payment_result_type_uu_id or payment_type_list[0].uuid 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)) @@ -360,6 +362,7 @@ def pay_book_payment_by_account_records_id(account_records_id: int, session, pay '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: @@ -399,19 +402,24 @@ def pay_book_payment_by_account_records_id(account_records_id: int, session, pay if __name__ == "__main__": - import sys + # import sys # Default values - id_given = 1 + # id_given = 1 session_factory = get_session_factory() session = session_factory() payment_type_list = get_enums_from_database() # Parse command line arguments - if len(sys.argv) > 1: - try: - id_given = int(sys.argv[1]) - except ValueError: - print(f"Error: Invalid limit value '{sys.argv[1]}'. Using default limit={limit}") - - result = pay_book_payment_by_account_records_id(account_records_id=id_given, session=session, payment_type_list=payment_type_list) \ No newline at end of file + # 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}") + account_records_all = 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()).all() + for account_record_id in account_records_all: + print('account_record_id', int(account_record_id[0])) + result = pay_book_payment_by_account_records_id(account_records_id=int(account_record_id[0]), session=session, payment_type_list=payment_type_list) + session.close()