"""Schema for payment search serialization.""" from datetime import datetime from abacus_common_logic.marshalling.custom_fields import ma from marshmallow.decorators import pre_dump, validates_schema from marshmallow.exceptions import ValidationError from payment.constants.constants import PAYMENT_SEARCH_STATUSES, PAYMENT_SEARCH_TYPES from payment.constants.error import ERROR_PAYMENTS_SEARCH_REQUIRED_FILTERS class PaymentSearchFilterKeySchema(ma.Schema): """Filter key schema for payment search.""" account = ma.String(required=False) contract = ma.String(required=False) payment_types = ma.List( ma.Enum(options=PAYMENT_SEARCH_TYPES), required=False, ) payment_statuses = ma.List( ma.Enum(options=PAYMENT_SEARCH_STATUSES), required=False, ) payment_release_date_from = ma.FormattedDate(required=False) payment_release_date_to = ma.FormattedDate(required=False) payment_created_date_from = ma.FormattedDate(required=False) payment_created_date_to = ma.FormattedDate(required=False) @validates_schema def validate_at_least_one_filter(self, data, **kwargs): """Validate that at least one filter is provided.""" if not any(data.values()): raise ValidationError(ERROR_PAYMENTS_SEARCH_REQUIRED_FILTERS) @validates_schema def validate_date_ranges(self, data, **kwargs): """Validate that date_from is not later than date_to.""" for prefix in ('payment_release_date', 'payment_created_date'): date_from = data.get(f'{prefix}_from') date_to = data.get(f'{prefix}_to') if date_from and date_to and date_from > date_to: raise ValidationError( f'{prefix}_from must not be later than {prefix}_to.', field_name=f'{prefix}_from', ) class PaymentSearchFilterParamsPostSchema(ma.Schema): """Post request params for payment search filtering.""" filters = ma.Nested(PaymentSearchFilterKeySchema, required=True) class PaymentSearchItemSchema(ma.Schema): """Schema for a single payment search result item.""" id = ma.String(data_key='_id', attribute='_id') account_id = ma.Integer(required=True) account_name = ma.String(required=True) contract_id = ma.Integer(required=True) contract_name = ma.String(required=True) currency = ma.String(required=True) error_message = ma.String(allow_none=True) payment_created_date = ma.FormattedDateTime(allow_none=True) payment_id = ma.Integer(required=True) payment_name = ma.String(required=True) payment_release_date = ma.FormattedDateTime(allow_none=True) payment_status = ma.String(required=True) payment_type = ma.String(required=True) post_tax_amount = ma.Decimal(as_string=True) pre_tax_amount = ma.Decimal(as_string=True) statement_period_id = ma.Integer(required=True) statement_period_name = ma.String(required=True) sent_statement_period_id = ma.Integer(allow_none=True) sent_statement_period_name = ma.String(allow_none=True) @pre_dump def normalize_fields(self, data, **kwargs): """ Serialize empty string in a consistent way and dates to apply formatting. Cortex returns all empty fields as empty strings. We have to normalize expected empty fields, to avoid serialization errors when converting into consistent values, e.g. Integer, Decimal. The same for dates, to apply our standard formatting. """ for field in ( 'error_message', 'sent_statement_period_id', 'sent_statement_period_name', ): if data.get(field) == '': data[field] = None for field in ('payment_created_date', 'payment_release_date'): value = data.get(field) if value: data[field] = datetime.fromisoformat(value) else: data[field] = None return data class PaymentSearchListSchema(ma.Schema): """Schema for payment search list response.""" items = ma.Nested(PaymentSearchItemSchema, many=True) total_count = ma.NonNegativeInteger(required=True)