fix(admin): send visit_price_rials from appointment create modal & drawer

NewAppointmentModal and NewAppointmentDrawer never read the require-visit-
price setting nor sent visit_price_rials, so booking failed with 422
"هزینه ویزیت الزامی است" when the flag was on. Add a visit-price field
(prefilled from the free-visit price), require it when the flag is set, and
include visit_price_rials in the POST payload, matching AppointmentCreatePage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-17 11:02:23 +03:30
co-authored by Claude Fable 5
parent 15f1c8d1eb
commit 36d7fe0303
2 changed files with 61 additions and 4 deletions
@@ -9,7 +9,7 @@ import PersianDateInput from './ui/PersianDateInput';
import PriceInput from './ui/PriceInput';
import SearchableSelect from './ui/SearchableSelect';
import { WalletChargeLink } from './AppointmentActions';
import { tehranWallClockToUnix, tomanToRial, toEnglishDigits, sanitizeMobileInput } from '../lib/utils';
import { tehranWallClockToUnix, tomanToRial, rialToToman, toEnglishDigits, sanitizeMobileInput } from '../lib/utils';
interface Option { uuid: string; name?: string; full_name?: string }
interface PatientRow { uuid: string; user_name?: string; user_mobile?: string; user_national_code?: string }
@@ -108,6 +108,19 @@ export default function NewAppointmentDrawer({ doctorUuid, defaultDate, queryKey
const [status, setStatus] = useState('pending');
const [note, setNote] = useState('');
// ── هزینه ویزیت — الزامی بودن از تنظیمات «الزامی کردن هزینه ویزیت» (فیلد UI تومان،
// API ریالی). بدون این مقدار وقتی فلگ فعال است backend خطای ۴۲۲ می‌دهد.
const pricingQ = useQuery<ApiResponse<{ free_visit_price_rials: number; require_visit_price: boolean }>>({
queryKey: ['insurance-pricing'], queryFn: () => api.get('/api/v1/insurance-pricing'),
});
const freeVisit = (pricingQ.data as any)?.data?.free_visit_price_rials ?? 0;
const requireVisit = (pricingQ.data as any)?.data?.require_visit_price ?? false;
const [visitPriceToman, setVisitPriceToman] = useState(0);
const [visitPriceTouched, setVisitPriceTouched] = useState(false);
useEffect(() => {
if (!visitPriceTouched && freeVisit > 0) setVisitPriceToman(rialToToman(freeVisit));
}, [freeVisit, visitPriceTouched]);
const effectiveName = pickedPatient?.user_name || name.trim();
const effectiveMobile = pickedPatient?.user_mobile || mobile.trim();
const effectiveNationalCode = (pickedPatient?.user_national_code || nationalCode).replace(/\D/g, '');
@@ -116,8 +129,9 @@ export default function NewAppointmentDrawer({ doctorUuid, defaultDate, queryKey
: serviceMode
? (serviceUuids.length > 0 && !!pickedSlot)
: (!!start && !!end);
const visitPriceValid = !requireVisit || visitPriceToman > 0;
const valid = !!doctorUuid && !!date && effectiveName.length >= 2 && effectiveMobile.length >= 10
&& effectiveNationalCode.length === 10 && timingValid;
&& effectiveNationalCode.length === 10 && timingValid && visitPriceValid;
const create = useMutation({
mutationFn: async () => {
@@ -136,6 +150,7 @@ export default function NewAppointmentDrawer({ doctorUuid, defaultDate, queryKey
...(serviceMode ? { service_item_uuids: serviceUuids } : itemUuid ? { service_item_uuid: itemUuid } : {}),
...(staffUuid ? { staff_uuid: staffUuid } : {}),
...(depositRequired ? { deposit_required: true, deposit_amount_rials: tomanToRial(depositToman) } : {}),
...(visitPriceToman > 0 ? { visit_price_rials: tomanToRial(visitPriceToman) } : {}),
...(note.trim() ? { note: note.trim() } : {}),
};
const res: any = await api.post('/api/v1/my/appointment', payload);
@@ -340,6 +355,17 @@ export default function NewAppointmentDrawer({ doctorUuid, defaultDate, queryKey
</>
)}
<div style={{ fontSize: 13.5, fontWeight: 700, margin: '6px 0 10px' }}>هزینه ویزیت:</div>
<label style={label}>
هزینه ویزیت (تومان){requireVisit && <span style={{ color: 'var(--danger)' }}> *</span>}
</label>
<div style={{ margin: '6px 0 4px' }}>
<PriceInput value={visitPriceToman} onChange={(v) => { setVisitPriceToman(v); setVisitPriceTouched(true); }} />
</div>
{requireVisit && visitPriceToman <= 0 && (
<div style={{ fontSize: 12, color: 'var(--danger)', marginBottom: 12 }}>هزینه ویزیت الزامی است</div>
)}
<label style={label}>انتخاب وضعیت</label>
<div style={{ margin: '6px 0 12px' }}>
<SearchableSelect