"""Earnings Transfer Marshmallow schema.""" import re from decimal import Decimal from abacus_common_logic.marshalling.custom_fields import ma from marshmallow import ValidationError, validates_schema from royalties.constants.constants import ( DEFAULT_PAGE_LIMIT, DEFAULT_PAGE_OFFSET, EARNINGS_TRANSFER_INPUT, EARNINGS_TRANSFER_RATE_TYPES, EARNINGS_TRANSFER_SORT_OPTIONS, EARNINGS_TRANSFER_TYPES, PAYMENT_SCHEDULES, SORT_ORDER_OPTIONS, ) from royalties.constants.error import ( ERROR_INVALID_CRITERIA_DUPLICATE_PAYMENT_ENTITIES, ERROR_INVALID_CRITERIA_DUPLICATE_PAYMENT_SCHEDULES, ) class EarningsTransferBaseSchema(ma.Schema): """Earnings Transfer Base schema.""" from_contract_id = ma.NonNegativeInteger(required=True) to_contract_id = ma.NonNegativeInteger(required=True) transfer_type = ma.Enum(options=EARNINGS_TRANSFER_TYPES, required=True) rate_type = ma.Enum(options=EARNINGS_TRANSFER_RATE_TYPES) transfer_amount = ma.Decimal(as_string=True, required=True) input = ma.Enum(options=EARNINGS_TRANSFER_INPUT) negative = ma.Boolean(required=True) active = ma.Boolean(required=True) use_static_balance = ma.Boolean(required=True) comment = ma.NonemptyString(allow_none=True) class EarningsTransferDetailSchema(EarningsTransferBaseSchema): """Earnings Transfer Detail schema.""" earnings_transfer_id = ma.NonNegativeInteger(required=True) created_at = ma.FormattedDate(required=True) class EarningsTransferFilterSchema(ma.Schema): """Earnings Transfer List filter schema.""" limit = ma.NonNegativeInteger(missing=DEFAULT_PAGE_LIMIT) offset = ma.NonNegativeInteger(missing=DEFAULT_PAGE_OFFSET) sort_by = ma.Enum( options=EARNINGS_TRANSFER_SORT_OPTIONS, load_default=EARNINGS_TRANSFER_SORT_OPTIONS.EARNINGS_TRANSFER_ID, ) sort_order = ma.Enum( options=SORT_ORDER_OPTIONS, load_default=SORT_ORDER_OPTIONS.ASC ) reference_payment_entities = ma.NonemptyString( allow_none=True, ) payment_schedules = ma.NonemptyString(allow_none=True) @validates_schema def validate_fields(self, data, **kwargs): """Validate the payment entities.""" reference_payment_entities = data.get('reference_payment_entities') payment_schedules = data.get('payment_schedules') if reference_payment_entities and not reference_payment_entities.strip(): raise ValidationError( 'At least one payment entity is required.', 'reference_payment_entities' ) if payment_schedules and not payment_schedules.strip(): raise ValidationError( 'At least one payment schedule is required.', 'payment_schedules' ) if reference_payment_entities: if not re.match(r'^\d+(,\d+)*$', reference_payment_entities): raise ValidationError( 'Must be a comma-separated list of digits.', 'reference_payment_entities', ) reference_payment_entities = reference_payment_entities.split(',') if len(set(reference_payment_entities)) != len(reference_payment_entities): raise ValidationError( ERROR_INVALID_CRITERIA_DUPLICATE_PAYMENT_ENTITIES, 'reference_payment_entities', ) if payment_schedules: payment_schedules = payment_schedules.split(',') invalid_payment_schedules = [ payment_schedule for payment_schedule in payment_schedules if payment_schedule not in PAYMENT_SCHEDULES ] if invalid_payment_schedules: raise ValidationError( f'Must be one of: {", ".join(PAYMENT_SCHEDULES)}', 'payment_schedules', ) if payment_schedules and len(set(payment_schedules)) != len( payment_schedules ): raise ValidationError( ERROR_INVALID_CRITERIA_DUPLICATE_PAYMENT_SCHEDULES, 'payment_schedules', ) def validate_amount_and_input( rate_type, amount, input_type ) -> dict[str, list[str]] | None: """Validate amount and input fields.""" if rate_type == EARNINGS_TRANSFER_RATE_TYPES.PERCENT: if amount is not None and not (Decimal('0') <= amount <= Decimal('100')): return { 'transfer_amount': [ f'Transfer amount must be between 0 and 100 when rate type is {EARNINGS_TRANSFER_RATE_TYPES.PERCENT}.' ] } elif rate_type == EARNINGS_TRANSFER_RATE_TYPES.FLAT_RATE: if amount is not None and amount <= 0: return { 'transfer_amount': [ f'Transfer amount must be a positive value when rate type is {EARNINGS_TRANSFER_RATE_TYPES.FLAT_RATE}.' ] } if input_type and input_type != EARNINGS_TRANSFER_INPUT.CLOSING_BALANCE: return { 'input': [ 'Only closing_balance transfer source is allowed for flat_rate.' ] } class EarningsTransferPostSchema(ma.Schema): """Earnings Transfer POST schema.""" from_contract_id = ma.NonNegativeInteger(required=True) to_contract_id = ma.NonNegativeInteger(required=True) transfer_type = ma.Enum(options=EARNINGS_TRANSFER_TYPES, required=True) rate_type = ma.Enum(options=EARNINGS_TRANSFER_RATE_TYPES) transfer_amount = ma.Decimal(as_string=True, required=True) transfer_source = ma.Enum( options=EARNINGS_TRANSFER_INPUT, data_key='input', attribute='transfer_source', ) negative = ma.Boolean(required=True) active = ma.Boolean(required=False) use_static_balance = ma.Boolean(required=False) comment = ma.NonemptyString(allow_none=True) @validates_schema def validate_amount_by_rate_type(self, data, **kwargs): """Validate amount field.""" rate_type = data.get('rate_type') amount = data.get('transfer_amount') input_type = data.get('transfer_source') error = validate_amount_and_input(rate_type, amount, input_type) if error: raise ValidationError(error) @validates_schema def validate_contract(self, data, **kwargs): """Validate from/to contract fields.""" from_contract_id = data.get('from_contract_id') to_contract_id = data.get('to_contract_id') if from_contract_id and to_contract_id and from_contract_id == to_contract_id: raise ValidationError( 'Source and destination contracts must be different.', field_name='to_contract_id', ) class EarningsTransferPutSchema(ma.Schema): """Earnings Transfer PUT schema.""" earnings_transfer_id = ma.NonNegativeInteger(required=True) from_contract_id = ma.NonNegativeInteger(required=False) to_contract_id = ma.NonNegativeInteger(required=False) rate_type = ma.Enum(options=EARNINGS_TRANSFER_RATE_TYPES) transfer_type = ma.Enum(options=EARNINGS_TRANSFER_TYPES, required=False) input_type = ma.Enum( options=EARNINGS_TRANSFER_INPUT, data_key='input', attribute='input', required=False, ) transfer_amount = ma.Decimal(as_string=True, required=False) negative = ma.Boolean(required=False) active = ma.Boolean(required=False) use_static_balance = ma.Boolean(required=False) comment = ma.NonemptyString(allow_none=True) @validates_schema def validate_amount_by_rate_type(self, data, **kwargs): """Validate amount field.""" rate_type = data.get('rate_type') amount = data.get('transfer_amount') input_type = data.get('input') error = validate_amount_and_input(rate_type, amount, input_type) if error: raise ValidationError(error) @validates_schema def validate_contract(self, data, **kwargs): """Validate from/to contract fields.""" from_contract_id = data.get('from_contract_id') to_contract_id = data.get('to_contract_id') if from_contract_id and to_contract_id and from_contract_id == to_contract_id: raise ValidationError( 'Source and destination contracts must be different.', field_name='to_contract_id', )