diff --git a/src/views/app/AppOwnerView.vue b/src/views/app/AppOwnerView.vue
index d66dbe7..9cca5b0 100644
--- a/src/views/app/AppOwnerView.vue
+++ b/src/views/app/AppOwnerView.vue
@@ -178,6 +178,56 @@
+
+
+
+
+
+
+
+ اگر کد خالی باشد، بهصورت خودکار با فرمت NEW + درصد ساخته میشود (مثلاً NEW10).
+
+
+
ذخیره تنظیمات نوبتدهی
@@ -587,8 +637,18 @@ const bookingForm = ref({
slotStepMinutes: '30',
autoApproveBookings: false,
tipsEnabled: false,
+ promoDiscountEnabled: false,
+ promoDiscountTitle: 'اولین رزرو شما',
+ promoDiscountPercent: '10',
+ promoDiscountCode: '',
+ promoDiscountMode: 'first_booking',
});
+const promoDiscountModeOptions = [
+ { value: 'first_booking', label: 'فقط اولین رزرو مشتری' },
+ { value: 'always', label: 'همه رزروها (تا وقتی فعال است)' },
+];
+
const bookingExpanded = ref(false);
const bookingMessage = ref('');
const bookingError = ref('');
@@ -637,7 +697,10 @@ const servicesSummary = computed(() => {
const bookingSummary = computed(() => {
const auto = bookingForm.value.autoApproveBookings ? 'تأیید خودکار' : 'تأیید دستی';
const tips = bookingForm.value.tipsEnabled ? 'حسن انجام کار فعال' : 'بدون حسن انجام کار';
- return `${bookingForm.value.concurrentCapacity} همزمان · گام ${bookingForm.value.slotStepMinutes} دقیقه · ${auto} · ${tips}`;
+ const discount = bookingForm.value.promoDiscountEnabled
+ ? `${bookingForm.value.promoDiscountTitle || 'تخفیف'} ${bookingForm.value.promoDiscountPercent}٪`
+ : 'بدون تخفیف';
+ return `${bookingForm.value.concurrentCapacity} همزمان · گام ${bookingForm.value.slotStepMinutes} دقیقه · ${auto} · ${tips} · ${discount}`;
});
function emptyClassPriceForm() {
@@ -738,6 +801,14 @@ onMounted(async () => {
slotStepMinutes: String(business.value.slotStepMinutes ?? 30),
autoApproveBookings: business.value.autoApproveBookings === true,
tipsEnabled: business.value.tipsEnabled === true,
+ promoDiscountEnabled: business.value.promoDiscountEnabled === true,
+ promoDiscountTitle: business.value.promoDiscountTitle?.trim() || 'اولین رزرو شما',
+ promoDiscountPercent:
+ business.value.promoDiscountPercent != null
+ ? String(business.value.promoDiscountPercent)
+ : '10',
+ promoDiscountCode: business.value.promoDiscountCode ?? '',
+ promoDiscountMode: business.value.promoDiscountMode === 'always' ? 'always' : 'first_booking',
};
await loadServicesAndTemplates();
}
@@ -851,6 +922,33 @@ async function onSaveBookingSettings() {
bookingMessage.value = '';
return;
}
+
+ let promoDiscountPercent: number | null = null;
+ let promoDiscountCode: string | null = null;
+ let promoDiscountTitle: string | null = null;
+ let promoDiscountMode: 'first_booking' | 'always' = 'first_booking';
+ if (bookingForm.value.promoDiscountEnabled) {
+ promoDiscountTitle = bookingForm.value.promoDiscountTitle.trim();
+ if (!promoDiscountTitle) {
+ bookingError.value = 'عنوان تخفیف را وارد کنید.';
+ bookingMessage.value = '';
+ return;
+ }
+ promoDiscountPercent = Number.parseInt(bookingForm.value.promoDiscountPercent, 10);
+ if (
+ !Number.isFinite(promoDiscountPercent) ||
+ promoDiscountPercent < 1 ||
+ promoDiscountPercent > 100
+ ) {
+ bookingError.value = 'درصد تخفیف باید عددی بین ۱ تا ۱۰۰ باشد.';
+ bookingMessage.value = '';
+ return;
+ }
+ promoDiscountCode = bookingForm.value.promoDiscountCode.trim().toUpperCase() || null;
+ promoDiscountMode =
+ bookingForm.value.promoDiscountMode === 'always' ? 'always' : 'first_booking';
+ }
+
savingBooking.value = true;
bookingError.value = '';
bookingMessage.value = '';
@@ -862,6 +960,11 @@ async function onSaveBookingSettings() {
slotStepMinutes,
autoApproveBookings: bookingForm.value.autoApproveBookings,
tipsEnabled: bookingForm.value.tipsEnabled,
+ promoDiscountEnabled: bookingForm.value.promoDiscountEnabled,
+ promoDiscountTitle,
+ promoDiscountPercent,
+ promoDiscountCode,
+ promoDiscountMode,
});
tenant.syncSingleBusiness(business.value);
bookingMessage.value = 'تنظیمات نوبتدهی ذخیره شد.';
diff --git a/src/views/app/BookingPaymentView.vue b/src/views/app/BookingPaymentView.vue
index a0401a2..fc360fb 100644
--- a/src/views/app/BookingPaymentView.vue
+++ b/src/views/app/BookingPaymentView.vue
@@ -58,12 +58,25 @@
+
+ {{ promoDiscountLabel }}
+
-
{{ booking.businessService?.serviceTemplate?.title ?? 'خدمت' }}
(رایگان)
+
+ ({{ booking.promoDiscountPercent.toLocaleString('fa-IR') }}٪
+ {{ booking.promoDiscountTitle || 'تخفیف' }})
+
@@ -127,6 +140,13 @@ const canPay = computed(() => {
return details.value.bookings.some((booking) => booking.status === 'awaiting_payment');
});
+const promoDiscountLabel = computed(() => {
+ const hit = details.value?.bookings.find((booking) => booking.promoDiscountApplied);
+ if (!hit?.promoDiscountPercent) return '';
+ const title = hit.promoDiscountTitle?.trim() || 'تخفیف';
+ return `${title} — ${hit.promoDiscountPercent.toLocaleString('fa-IR')}٪ اعمال شد.`;
+});
+
async function load() {
const token = String(route.query.token ?? '').trim();
if (!token) {
diff --git a/tsconfig.app.json b/tsconfig.app.json
index 90b993f..b1e458e 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -15,7 +15,6 @@
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"jsx": "preserve",
- "baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}