from dataclasses import dataclass from datetime import datetime from decimal import Decimal import typing from typing import Dict, List, Optional from pydantic import BaseModel, ConfigDict, Field, TypeAdapter from src.constants import PaymentAllocationLedgerStatuses, PaymentAllocationStatuses class Event(BaseModel): model_config = ConfigDict(populate_by_name=True) event_name: str = Field(alias='eventName') statement_period_id: int = Field(alias='statementPeriodId') abacus_event_id: int = Field(alias='abacusEventId') target_type: str = Field(default='payment_group_payment', alias='targetType') target_id: int = Field(alias='targetId') class PaymentGroup(BaseModel): payment_group_id: int group_name: str is_reusable: bool group_criteria: typing.Optional[dict[str, typing.Any]] class PaymentGroupPayment(BaseModel): payment_group_id: int payment_name: str class AccountPayableContract(BaseModel): contract_id: int currency_code: str current_balance: Decimal class Account(BaseModel): account_id: int country_of_tax_residence: Optional[str] = None currency_code: Optional[str] = None payment_minimum: Optional[Decimal] = None payoneer_program_id: Optional[int] = 0 class GetAccountsResponse(BaseModel): items: List[Account] total_count: int class AccountPaymentHold(BaseModel): payment_hold_id: int account_id: int is_on_hold: bool start_date: str reason: str class ContractCloseBalance(BaseModel): worksheet_account_contract_closing_balance_id: int contract_id: int account_id: int amount: Decimal reference_payment_entity_id: int currency_code: str class PaginatedContractCloseBalances(BaseModel): items: List[ContractCloseBalance] total_count: int class PaymentEntity(BaseModel): reference_payment_entity_id: int payment_entity_name: str class AccountPaymentDetails(BaseModel): account_id: typing.Optional[int] = None current_statement_period_id: typing.Optional[int] = None balance_after_tax: typing.Optional[Decimal] = Decimal(0.0) class AccountPaymentDetailsResponse(BaseModel): items: List[AccountPaymentDetails] total_count: int class PaymentMethodMinimum(BaseModel): currency_code: str check_amount: Decimal wire_transfer_amount: Decimal western_union_amount: typing.Optional[Decimal] class PayableBalanceAfterTax(BaseModel): model_config = ConfigDict(populate_by_name=True) worksheet_account_contract_closing_balance_id: int contract_id: int account_id: int payable_amount_pre_tax: Decimal tax_withholding_amount: typing.Optional[Decimal] vat_amount: typing.Optional[Decimal] payable_amount_post_tax: Decimal currency_code: str country_of_tax_residence: str country_of_tax_policy: str class GetPayableBalanceAfterTaxResponse(BaseModel): items: List[PayableBalanceAfterTax] total_count: int class AggregatedBalancesAfterTax(BaseModel): model_config = ConfigDict(populate_by_name=True) payable_amount_pre_tax: Decimal = Field(default=Decimal(0)) tax_withholding_amount: typing.Optional[Decimal] = Field(default=None) vat_amount: typing.Optional[Decimal] = Field(default=None) payable_amount_post_tax: Decimal = Field(default=Decimal(0)) class LambdaResponse(BaseModel): model_config = ConfigDict(populate_by_name=True) status_code: int = Field(alias='statusCode') status_description: str = Field(alias='statusDescription') headers: Dict[str, str] = Field({'Content-Type': 'application/json'}) class PaymentAccount(BaseModel): model_config = ConfigDict(populate_by_name=True) contracts_payable: List[AccountPayableContract] currency_code: str current_balance: Decimal last_payment: Decimal tax_withholding: typing.Optional[Decimal] vat_amount: typing.Optional[Decimal] balance_after_tax: Decimal account_id: int payoneer_program_id: int last_statement_period_id: typing.Optional[int] class PaymentAccountInstance(PaymentAccount): payment_group_payment_account_id: int class AbacusState(BaseModel): action_name: str abacus_state_id: int action_status: str parent_table_id: int parent_table_name: str class AccountPaymentTerm(BaseModel): """Model for AccountPaymentTerm data.""" account_payment_term_id: int account_id: int currency_code: str payment_minimum: Optional[Decimal] class AccountPaymentTermDataloaderItem(BaseModel): """Model for dataloader response items.""" data: typing.Optional[AccountPaymentTerm] = None @classmethod def list_validate( cls, data: typing.List[typing.Mapping[str, typing.Any]] ) -> typing.List[typing.Self]: """Validate the list of items.""" return TypeAdapter( typing.List[cls] # type: ignore ).validate_python(data) class AccountPayee(BaseModel): """Model for AccountPayee data.""" account_payee_id: int account_id: int payoneer_program_id: int | None class AccountPayeeDataloaderItem(BaseModel): """Model for dataloader response items.""" data: typing.Optional[AccountPayee] = None @classmethod def list_validate( cls, data: typing.List[typing.Mapping[str, typing.Any]] ) -> typing.List[typing.Self]: """Validate the list of items.""" return TypeAdapter( typing.List[cls] # type: ignore ).validate_python(data) class PayableDetails(BaseModel): """Model for WorksheetAccountContractPayableDetails data.""" worksheet_account_contract_payable_details_id: int worksheet_account_contract_payable_after_tax_id: Optional[int] = None contract_id: int account_id: int statement_period_id: int target_table: str target_id: int reference_target_table: Optional[str] = None reference_target_id: Optional[int] = None payable_detail_type_id: int amount_payable: Decimal currency: str notes: Optional[str] = None class PayableDetailsResponse(BaseModel): """Model for response items.""" items: List[PayableDetails] total_count: int class PaymentAllocationFlowthrough(BaseModel): """Model for payment allocation flowthrough data.""" payment_allocation_id: int contract_id: int payee_type: str payee_id: int statement_period_id: int payment_allocation_type: str amount_to_payment: Decimal payment_status: PaymentAllocationStatuses payment_status_modified: Optional[datetime] = None amount_to_ledger: Decimal ledger_status: PaymentAllocationLedgerStatuses ledger_status_modified: Optional[datetime] = None currency_code: str description: Optional[str] = None created_at: datetime created_by: str last_modified: datetime last_modified_by: str class PaymentAllocationFlowthroughResponse(BaseModel): """Model for payment allocation flowthrough response items.""" items: List[PaymentAllocationFlowthrough] total_count: int class PaymentAllocationFlowthroughUpdate(BaseModel): """Model for updating payment allocation flowthrough data.""" payment_allocation_id: int payment_status: Optional[PaymentAllocationStatuses] = None ledger_status: Optional[PaymentAllocationLedgerStatuses] = None @dataclass(slots=True) class AggregatedBalance: """Model for aggregated payable amounts.""" payable_amount_pre_tax: Decimal tax_withholding_amount: Optional[Decimal] vat_amount: Optional[Decimal] payable_amount_post_tax: Decimal @dataclass(slots=True) class PaymentBatchItem: """Model for payment batch item.""" account_id: int payable_worksheets: List[PayableBalanceAfterTax] last_payment: AccountPaymentDetails | None payment_term: AccountPaymentTerm account_payee: AccountPayee @property def aggregated_balance(self) -> AggregatedBalance: pre_tax = Decimal(0) post_tax = Decimal(0) tax_total: Decimal | None = None vat_total: Decimal | None = None for entry in self.payable_worksheets: pre_tax += entry.payable_amount_pre_tax post_tax += entry.payable_amount_post_tax if entry.tax_withholding_amount is not None: tax_total = (tax_total or Decimal(0)) + entry.tax_withholding_amount if entry.vat_amount is not None: vat_total = (vat_total or Decimal(0)) + entry.vat_amount return AggregatedBalance( payable_amount_pre_tax=pre_tax, tax_withholding_amount=tax_total, vat_amount=vat_total, payable_amount_post_tax=post_tax, ) @dataclass(slots=True) class PaymentBatch: """Model for payment batch.""" abacus_event: Event payment_minimums: dict[str, PaymentMethodMinimum] items: List[PaymentBatchItem]