diff --git a/src/Discount/Service/DiscountEngine.php b/src/Discount/Service/DiscountEngine.php new file mode 100644 index 00000000..dc36af2f --- /dev/null +++ b/src/Discount/Service/DiscountEngine.php @@ -0,0 +1,150 @@ + + * مرتب بر priority نزولی (از repository). فقط قوانینِ برقرار با discount_rials > 0. + */ + public function evaluate(PatientSession $session): array + { + $record = $session->getRecord(); + $rules = $this->ruleRepo->findActiveForOwner($record->getEntityType(), $record->getEntityId()); + + $final = $session->getFinalPriceRials(); + $remaining = max(0, $final - $session->getPaidTotalRials()); + $now = time(); + + $out = []; + foreach ($rules as $rule) { + if (!$this->withinValidity($rule, $now)) { + continue; + } + $base = $this->applicableBase($rule, $session, $final, $now); + if ($base === null) { + continue; + } + $rials = min($this->computeRials($rule, $base), $remaining); + if ($rials <= 0) { + continue; + } + $out[] = [ + 'rule_uuid' => $rule->getUuid(), + 'rule_name' => $rule->getName(), + 'type' => $rule->getType(), + 'discount_type' => $rule->getDiscountType(), + 'value' => $rule->getValue(), + 'discount_rials' => $rials, + 'combinable' => $rule->isCombinable(), + 'priority' => $rule->getPriority(), + ]; + } + return $out; + } + + private function withinValidity(DiscountRule $rule, int $now): bool + { + if ($rule->getValidFrom() !== null && $now < $rule->getValidFrom()) { + return false; + } + if ($rule->getValidTo() !== null && $now > $rule->getValidTo()) { + return false; + } + return true; + } + + /** + * مبنای محاسبه‌ی تخفیف اگر قانون برقرار باشد؛ null یعنی قانون اعمال نمی‌شود. + */ + private function applicableBase(DiscountRule $rule, PatientSession $session, int $final, int $now): ?int + { + $record = $session->getRecord(); + + return match ($rule->getType()) { + DiscountRule::TYPE_PATIENT_TAG => $this->hasTag($session, $rule->getTargetTagId()) ? $final : null, + + DiscountRule::TYPE_INVOICE_AMOUNT => ($rule->getMinAmountRials() !== null && $final >= $rule->getMinAmountRials()) ? $final : null, + + DiscountRule::TYPE_SPECIFIC_PATIENT => ($rule->getTargetRecordId() !== null && $record->getId() === $rule->getTargetRecordId()) ? $final : null, + + DiscountRule::TYPE_OCCASION => $this->occasionMatches($rule, $session, $now) ? $final : null, + + DiscountRule::TYPE_SERVICE => $this->serviceBase($session, $rule->getTargetServiceItemId()), + + DiscountRule::TYPE_VISIT_COUNT => ($rule->getMinVisitCount() !== null + && $this->sessionRepo->countByRecord($record) >= $rule->getMinVisitCount()) ? $final : null, + + default => null, + }; + } + + private function hasTag(PatientSession $session, ?int $tagId): bool + { + if ($tagId === null) { + return false; + } + foreach ($session->getRecord()->getTags() as $tag) { + if ($tag->getId() === $tagId) { + return true; + } + } + return false; + } + + /** مبنای تخفیف سرویس = جمع خطوطِ همان سرویس؛ null اگر سرویس در پرونده نباشد. */ + private function serviceBase(PatientSession $session, ?int $serviceItemId): ?int + { + if ($serviceItemId === null) { + return null; + } + $sum = 0; + foreach ($session->getServices() as $line) { + /** @var SessionService $line */ + if ($line->getServiceItem()->getId() === $serviceItemId) { + $sum += $line->getLineTotalRials(); + } + } + return $sum > 0 ? $sum : null; + } + + private function occasionMatches(DiscountRule $rule, PatientSession $session, int $now): bool + { + if ($rule->getOccasionKind() !== DiscountRule::OCCASION_BIRTHDAY) { + // مناسبت مبتنی بر بازه‌ی تاریخی؛ withinValidity قبلاً چک شده. + return true; + } + $profile = $this->profileRepo->findByUser($session->getRecord()->getUser()); + $dob = $profile?->getDateOfBirth(); + if ($dob === null) { + return false; + } + return date('m-d', $dob) === date('m-d', $now); + } + + private function computeRials(DiscountRule $rule, int $base): int + { + if ($rule->getDiscountType() === DiscountRule::DISCOUNT_PERCENT) { + return (int) round($base * min(100, $rule->getValue()) / 100); + } + return min($rule->getValue(), $base); + } +}