"""Shared validation and aggregation helpers for procurement serializers.""" import re from datetime import date from rest_framework import serializers from procurement.constants import ProcurementItemExecutionStatus from procurement.models import ProcurementRequest, ProcurementSettings PROBLEM_ITEM_STATUSES = { ProcurementItemExecutionStatus.REJECTED, ProcurementItemExecutionStatus.COMPLETED_WITH_ISSUE, ProcurementItemExecutionStatus.EDITED, ProcurementItemExecutionStatus.DEFECTIVE, } DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$") UNIT_DISPLAY_LABELS = { "шт": "штуки", "компл": "комплекты", "упак": "упаковки", "короб": "короба", "пачка": "пачки", "кг": "килограммы", "г": "граммы", "л": "литры", "мл": "миллилитры", "м": "метры", "кв. м": "кв. метры", "куб. м": "куб. метры", "рулон": "рулоны", "пара": "пары", "услуга": "услуги", "час": "часы", } def _normalize_unit(value): return re.sub(r"\s+", " ", str(value or "").strip()).lower() def _unit_display_label(value): unit = _normalize_unit(value) if not unit: return "единиц" return UNIT_DISPLAY_LABELS.get(unit, unit) def _validate_expected_delivery_dates(value): if value in (None, ""): return [] if not isinstance(value, list): raise serializers.ValidationError("Ожидаемые даты должны быть списком.") result = [] for raw in value: item = str(raw or "").strip() if not item: continue if not DATE_RE.fullmatch(item): raise serializers.ValidationError( "Каждая ожидаемая дата должна быть в формате YYYY-MM-DD." ) try: parsed = date.fromisoformat(item) except ValueError as exc: raise serializers.ValidationError( "Каждая ожидаемая дата должна быть корректной календарной датой." ) from exc result.append(parsed.isoformat()) return result def _expected_delivery_date_values(item): dates = [] for raw in item.expected_delivery_dates or []: if not isinstance(raw, str) or not DATE_RE.fullmatch(raw): continue try: dates.append(date.fromisoformat(raw)) except ValueError: continue return dates def _validate_processing_department_available( processing_department, request_type=ProcurementRequest.RequestType.STANDARD, ): if not processing_department: return settings = ProcurementSettings.get_solo() if request_type == ProcurementRequest.RequestType.SUBSCRIPTION: available_departments = ( settings.subscription_available_processing_departments.all() ) error_message = "Выберите отдел-исполнитель из допустимых для подписок." else: available_departments = settings.available_processing_departments.all() error_message = "Выберите отдел-исполнитель из доступных для закупок." if ( available_departments.exists() and not available_departments.filter(pk=processing_department.pk).exists() ): raise serializers.ValidationError({"processing_department": error_message}) def _request_items(obj): return list(obj.items.all()) def _effective_ordered_quantity(item): return item.effective_ordered_quantity def _effective_received_quantity(item): return item.effective_received_quantity