124 lines
3.5 KiB
Dart
124 lines
3.5 KiB
Dart
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<ClinicDiscount> 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<int> 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<int>.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<ClinicDiscount> discounts,
|
|
Iterable<int>? 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,
|
|
);
|
|
}
|
|
}
|