From 2691751e701bee813690bd45b34f1688604dfe71 Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Fri, 17 Jul 2026 12:04:56 +0330 Subject: [PATCH] feat(discount): apply rule-based discounts on the session payment step PaymentStep now loads /session/{uuid}/discount-suggestions and lets the operator apply a suggested rule (PATCH discount_rule_uuid) or remove it, alongside the existing manual discount. Adds an amount summary (before / discount + source rule label / final payable). Verified end-to-end. Co-Authored-By: Claude Fable 5 --- .../admin/components/SessionServiceCard.tsx | 1 + .../admin/components/session/PaymentStep.tsx | 62 +++++++++++++++++-- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/assets/admin/components/SessionServiceCard.tsx b/assets/admin/components/SessionServiceCard.tsx index 41eccf8e..5a7b2edd 100644 --- a/assets/admin/components/SessionServiceCard.tsx +++ b/assets/admin/components/SessionServiceCard.tsx @@ -27,6 +27,7 @@ export interface SessionCardData { discount_type?: 'percent' | 'fixed' | null; discount_value?: number; discount_rials?: number; + applied_discount_rule_label?: string | null; paid_total_rials?: number; paid_at?: number | null; payments?: SessionPaymentEntry[]; diff --git a/assets/admin/components/session/PaymentStep.tsx b/assets/admin/components/session/PaymentStep.tsx index 253aefdc..86091113 100644 --- a/assets/admin/components/session/PaymentStep.tsx +++ b/assets/admin/components/session/PaymentStep.tsx @@ -1,9 +1,11 @@ import { useState } from 'react'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { ChevronDownIcon } from '@heroicons/react/24/outline'; import { toast } from 'sonner'; import { api } from '../../lib/api'; +import type { ApiResponse } from '../../lib/api'; import { formatRial } from '../../lib/utils'; +import type { DiscountSuggestion } from '../../types'; import type { SessionCardData } from '../SessionServiceCard'; import SearchableSelect from '../ui/SearchableSelect'; import PersianDateInput from '../ui/PersianDateInput'; @@ -54,6 +56,7 @@ export default function PaymentStep({ recordUuid, session, walletBalance, onCont const invalidate = () => { qc.invalidateQueries({ queryKey: ['patient-sessions', recordUuid] }); qc.invalidateQueries({ queryKey: ['patient-wallet', recordUuid] }); + qc.invalidateQueries({ queryKey: ['discount-suggestions', sessionUuid] }); }; const discountMut = useMutation({ @@ -68,13 +71,22 @@ export default function PaymentStep({ recordUuid, session, walletBalance, onCont onError: (e: Error) => toast.error(e.message), }); + const suggestionsQ = useQuery({ + queryKey: ['discount-suggestions', sessionUuid], + queryFn: () => api.get>(`/api/v1/session/${sessionUuid}/discount-suggestions`), + }); + const suggestions: DiscountSuggestion[] = (suggestionsQ.data?.data as any)?.data ?? (suggestionsQ.data?.data as any) ?? []; + const applyDiscount = () => { if (!discountType || !discountValue) return; discountMut.mutate({ discount_type: discountType, discount_value: Number(discountValue) }); }; + const applyRule = (ruleUuid: string) => { + discountMut.mutate({ discount_rule_uuid: ruleUuid }); + }; const removeDiscount = () => { setDiscountType(''); setDiscountValue(''); - discountMut.mutate({ discount_type: null }); + discountMut.mutate({ discount_rule_uuid: null }); }; const submitPayment = (method: string) => { if (!amount) return; @@ -85,6 +97,8 @@ export default function PaymentStep({ recordUuid, session, walletBalance, onCont const discountRials = session.discount_rials ?? 0; const debt = session.patient_debt_rials ?? 0; const payments = session.payments ?? []; + const appliedRuleLabel = session.applied_discount_rule_label ?? null; + const payable = Math.max(0, finalPrice - discountRials); return ( <> @@ -95,6 +109,31 @@ export default function PaymentStep({ recordUuid, session, walletBalance, onCont {formatRial(finalPrice)} + {/* تخفیف‌های پیشنهادی (قوانین تخفیف) */} + {suggestions.length > 0 && ( +
+ تخفیف‌های قابل اعمال: +
+ {suggestions.map((s) => ( +
+ + {s.rule_name} — {formatRial(s.discount_rials)} + {' '}({s.discount_type === 'percent' ? `${s.value}٪` : 'مبلغ ثابت'}) + + +
+ ))} +
+
+ )} + {/* تخفیف — port of tauri DiscountInput (نوع + مقدار + ثبت) */} تخفیف:
@@ -134,10 +173,21 @@ export default function PaymentStep({ recordUuid, session, walletBalance, onCont
-

- مبلغ تخفیف:{' '} - {formatRial(discountRials)} -

+ {/* خلاصه‌ی مبلغ: قبل از تخفیف / تخفیف / نهایی / منبع */} +
+
+ مبلغ قبل از تخفیف: + {formatRial(finalPrice)} +
+
+ مبلغ تخفیف{appliedRuleLabel ? ` (${appliedRuleLabel})` : ''}: + {formatRial(discountRials)} +
+
+ مبلغ نهایی قابل پرداخت: + {formatRial(payable)} +
+
{/* تاریخ پرداخت */} تاریخ پرداخت