feat: add visit price requirement feature
- Introduced a new boolean flag `require_visit_price` in the `EntityInsurancePricing` to enforce visit price for appointments. - Updated the appointment creation endpoints to validate `visit_price_rials` based on the new flag. - Added `visit_price_rials` field to the `Appointment` entity to store the visit price. - Enhanced the `PatientService` to validate visit price during session creation. - Updated API documentation to reflect changes in appointment and insurance pricing. - Implemented a new service `VisitPriceRequirementResolver` to determine if a visit price is required for a doctor based on their pricing settings. - Added migrations to update the database schema for the new fields.
This commit is contained in:
@@ -13,7 +13,7 @@ import SearchableSelect from '../components/ui/SearchableSelect';
|
||||
import { WalletChargeLink } from '../components/AppointmentActions';
|
||||
import { useDoctorBookingServices } from '../hooks/useDoctorBookingServices';
|
||||
import ServiceSlotPicker from '../components/appointments/ServiceSlotPicker';
|
||||
import { tehranWallClockToUnix } from '../lib/utils';
|
||||
import { tehranWallClockToUnix, rialToToman, tomanToRial } from '../lib/utils';
|
||||
|
||||
/**
|
||||
* افزودن نوبت — صفحهٔ کامل (بازسازی `CreateTurn.jsx` طرح tauri). از همان endpointهای
|
||||
@@ -104,6 +104,21 @@ export default function AppointmentCreatePage() {
|
||||
const [status, setStatus] = useState('pending');
|
||||
const [note, setNote] = useState('');
|
||||
|
||||
// ── هزینه ویزیت — الزامی بودن از تنظیمات «الزامی کردن هزینه ویزیت» (کاربر بدون
|
||||
// پروفایل doctor/clinic از این endpoint 403 میگیرد → فلگ false و فیلد اختیاری میماند)
|
||||
const pricingQ = useQuery<{ data: { 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;
|
||||
// فیلد UI تومان است؛ API ریالی (visit_price_rials).
|
||||
const [visitPriceToman, setVisitPriceToman] = useState(0);
|
||||
const [visitPriceTouched, setVisitPriceTouched] = useState(false);
|
||||
useEffect(() => {
|
||||
if (!visitPriceTouched && freeVisit > 0) setVisitPriceToman(rialToToman(freeVisit));
|
||||
}, [freeVisit, visitPriceTouched]);
|
||||
|
||||
const effectiveName = picked?.user_name || name.trim();
|
||||
const effectiveMobile = picked?.user_mobile || mobile.trim();
|
||||
const effectiveNationalCode = (picked?.user_national_code || nationalCode).replace(/\D/g, '');
|
||||
@@ -111,7 +126,7 @@ export default function AppointmentCreatePage() {
|
||||
? (servicePick.serviceUuids.length > 0 && !!servicePick.slot)
|
||||
: (!!start && !!end);
|
||||
const valid = !!doctorUuid && !!date && effectiveName.length >= 2 && effectiveMobile.length >= 10
|
||||
&& effectiveNationalCode.length === 10 && timingValid;
|
||||
&& effectiveNationalCode.length === 10 && timingValid && (!requireVisit || visitPriceToman > 0);
|
||||
|
||||
const create = useMutation({
|
||||
mutationFn: async () => {
|
||||
@@ -131,6 +146,7 @@ export default function AppointmentCreatePage() {
|
||||
}),
|
||||
...(staffUuid ? { staff_uuid: staffUuid } : {}),
|
||||
...(depositRequired ? { deposit_required: true, deposit_amount_rials: depositRials } : {}),
|
||||
...(visitPriceToman > 0 ? { visit_price_rials: tomanToRial(visitPriceToman) } : {}),
|
||||
...(note.trim() ? { note: note.trim() } : {}),
|
||||
};
|
||||
const res: any = await api.post(createEndpoint, payload);
|
||||
@@ -458,6 +474,20 @@ export default function AppointmentCreatePage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* هزینه ویزیت */}
|
||||
<div style={sectionTitle}>هزینه ویزیت</div>
|
||||
<div style={{ width: 300, maxWidth: '100%', marginBottom: 12 }}>
|
||||
<label style={label}>
|
||||
هزینه ویزیت (تومان){requireVisit && <span style={{ color: 'var(--danger)' }}> *</span>}
|
||||
</label>
|
||||
<div className="field" style={{ marginTop: 6, height: 44 }}>
|
||||
<PriceInput value={visitPriceToman} onChange={(v) => { setVisitPriceToman(v); setVisitPriceTouched(true); }} />
|
||||
</div>
|
||||
{requireVisit && visitPriceToman <= 0 && (
|
||||
<span style={{ fontSize: 12, color: 'var(--danger)', display: 'block', marginTop: 4 }}>هزینه ویزیت الزامی است</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{ maxWidth: 500 }}>
|
||||
<label style={label}>انتخاب وضعیت</label>
|
||||
<div style={{ margin: '6px 0 12px' }}>
|
||||
|
||||
Reference in New Issue
Block a user