import '../../models/clinic_discount.dart'; import '../../models/job.dart'; import '../../models/prosthetic_product.dart'; class PricingBreakdown { const PricingBreakdown({ required this.billableUnits, required this.unitPrice, required this.baseAmount, required this.discountAmount, required this.finalAmount, required this.appliedDiscounts, }); final int billableUnits; final double unitPrice; final double baseAmount; final double discountAmount; final double finalAmount; final List appliedDiscounts; } class PricingService { PricingService._(); static final instance = PricingService._(); int billableUnitsForType(ProstheticType type, int memberCount) { final safeCount = memberCount <= 0 ? 1 : memberCount; return switch (type) { ProstheticType.hareketli || ProstheticType.tamProtez || ProstheticType.parsiyel => 1, _ => safeCount, }; } int billableUnitsForTeeth(ProstheticType type, Iterable teeth) { final selected = teeth.toSet(); if (selected.isEmpty) return billableUnitsForType(type, 0); if (type.family == ProstheticFamily.hareketli) return 1; final upper = { for (int i = 11; i <= 18; i++) i, for (int i = 21; i <= 28; i++) i, }; final lower = { for (int i = 31; i <= 38; i++) i, for (int i = 41; i <= 48; i++) i, }; var units = 0; final remaining = Set.from(selected); if (upper.every(selected.contains)) { units += 1; remaining.removeAll(upper); } if (lower.every(selected.contains)) { units += 1; remaining.removeAll(lower); } return units + remaining.length; } String unitLabelForType(ProstheticType type) { return switch (type) { ProstheticType.hareketli || ProstheticType.tamProtez || ProstheticType.parsiyel => 'vaka', _ => 'diş', }; } PricingBreakdown calculate({ required ProstheticProduct product, required ProstheticType prostheticType, required int memberCount, required String clinicTenantId, required List discounts, Iterable? teeth, }) { final billableUnits = teeth == null ? billableUnitsForType(prostheticType, memberCount) : billableUnitsForTeeth(prostheticType, teeth); final unitPrice = product.unitPrice ?? 0; final baseAmount = unitPrice * billableUnits; final applicable = discounts.where((discount) { if (!discount.isActive) return false; if (!(discount.appliesToAll || discount.clinicTenantId == clinicTenantId)) { return false; } if (!(discount.appliesToAllTypes || discount.prostheticType == prostheticType.value)) { return false; } if (discount.minQuantity > 0 && billableUnits < discount.minQuantity) { return false; } return true; }).toList(); double running = baseAmount; for (final discount in applicable) { running = discount.discountType == DiscountType.percentage ? running * (1 - discount.discountValue / 100) : running - discount.discountValue; } final finalAmount = running.clamp(0, double.infinity).toDouble(); return PricingBreakdown( billableUnits: billableUnits, unitPrice: unitPrice, baseAmount: baseAmount, discountAmount: (baseAmount - finalAmount).clamp(0, double.infinity).toDouble(), finalAmount: finalAmount, appliedDiscounts: applicable, ); } }