Commit 9a8823b4 authored by Fares's avatar Fares

feat(pricing): enhance special discounts with conditions, bonuses, and date ranges

Expanded the special discounts system to support:
- Discount types: percentage, fixed amount, or free subscription
- Date range (effective_from/effective_to) for time-bounded offers
- Conditions: none (direct), full_payment, or min_payment threshold
- Bonus: free subscription years granted when discount activates
- Applies-to targeting: membership_fee, subscription, or all

Added SpecialDiscountService for evaluating conditional discounts and
applying free subscription bonuses on member.activated event.

Updated form UI with dynamic visibility and validation.

Migration: Phase_98_003 adds new columns to special_discounts table.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent e0e2fdd8
......@@ -418,20 +418,50 @@ final class BillingService
// ── 2b. Special Discount ──
if (!empty($member['special_discount_id'])) {
$discountRow = $db->selectOne(
"SELECT sd.name_ar, sd.discount_percentage FROM special_discounts sd WHERE sd.id = ?",
"SELECT * FROM special_discounts sd WHERE sd.id = ? AND sd.is_active = 1",
[(int) $member['special_discount_id']]
);
if ($discountRow) {
$discountAmount = $member['discount_amount'] ?? bcdiv(bcmul($membershipValue, $discountRow['discount_percentage'], 4), '100', 2);
// Discount is a negative line item
$items[] = [
'type' => 'special_discount',
'label' => 'خصم خاص: ' . $discountRow['name_ar'] . ' (' . $discountRow['discount_percentage'] . '%)',
'amount' => '-' . $discountAmount,
'paid' => false,
'included' => false,
'category' => 'discount',
];
$today = date('Y-m-d');
$inRange = (empty($discountRow['effective_from']) || $today >= $discountRow['effective_from'])
&& (empty($discountRow['effective_to']) || $today <= $discountRow['effective_to']);
if ($inRange) {
$discType = $discountRow['discount_type'] ?? 'percentage';
if ($discType === 'percentage') {
$discountAmount = $member['discount_amount'] ?? bcdiv(bcmul($membershipValue, $discountRow['discount_percentage'], 4), '100', 2);
$discountLabel = 'خصم خاص: ' . $discountRow['name_ar'] . ' (' . $discountRow['discount_percentage'] . '%)';
} elseif ($discType === 'fixed_amount') {
$discountAmount = $discountRow['fixed_amount'] ?? '0.00';
$discountLabel = 'خصم خاص: ' . $discountRow['name_ar'] . ' (' . money($discountAmount) . ')';
} else {
$discountAmount = '0.00';
$discountLabel = 'خصم خاص: ' . $discountRow['name_ar'];
}
if (bccomp($discountAmount, '0', 2) > 0) {
$items[] = [
'type' => 'special_discount',
'label' => $discountLabel,
'amount' => '-' . $discountAmount,
'paid' => false,
'included' => false,
'category' => 'discount',
];
}
$bonusYears = (int) ($discountRow['bonus_free_subscription_years'] ?? 0);
if ($bonusYears > 0) {
$items[] = [
'type' => 'bonus_info',
'label' => 'مكافأة: ' . $bonusYears . ' سنة اشتراك مجاني',
'amount' => '0.00',
'paid' => false,
'included' => true,
'included_note' => 'تُمنح عند إتمام السداد',
'category' => 'bonus',
];
}
}
}
}
......
......@@ -36,31 +36,20 @@ class SpecialDiscountController extends Controller
public function store(Request $request): Response
{
$nameAr = trim((string) $request->post('name_ar', ''));
$nameEn = trim((string) $request->post('name_en', '')) ?: null;
$percentage = trim((string) $request->post('discount_percentage', '0'));
$requiresDocument = (int) $request->post('requires_document', 0);
$description = trim((string) $request->post('description', '')) ?: null;
$data = $this->extractFormData($request);
if ($nameAr === '') {
if ($data['name_ar'] === '') {
return $this->redirect('/pricing/special-discounts/create')->withError('اسم الخصم مطلوب');
}
if (bccomp($percentage, '0', 2) <= 0 || bccomp($percentage, '100', 2) > 0) {
return $this->redirect('/pricing/special-discounts/create')->withError('نسبة الخصم يجب أن تكون بين 0.01% و 100%');
}
$employee = App::getInstance()->currentEmployee();
$validationError = $this->validateDiscountData($data);
if ($validationError) {
return $this->redirect('/pricing/special-discounts/create')->withError($validationError);
}
SpecialDiscount::create([
'name_ar' => $nameAr,
'name_en' => $nameEn,
'discount_percentage' => $percentage,
'requires_document' => $requiresDocument,
'description' => $description,
'is_active' => 1,
]);
SpecialDiscount::create($this->buildCreateArray($data));
return $this->redirect('/pricing/special-discounts')->withSuccess('تم إضافة الخصم الخاص بنجاح');
return $this->redirect('/pricing/special-discounts')->withSuccess('تم إضافة الخصم بنجاح');
}
public function edit(Request $request, string $id): Response
......@@ -84,31 +73,25 @@ class SpecialDiscountController extends Controller
return $this->redirect('/pricing/special-discounts')->withError('الخصم غير موجود');
}
$nameAr = trim((string) $request->post('name_ar', ''));
$nameEn = trim((string) $request->post('name_en', '')) ?: null;
$percentage = trim((string) $request->post('discount_percentage', '0'));
$requiresDocument = (int) $request->post('requires_document', 0);
$description = trim((string) $request->post('description', '')) ?: null;
$isActive = (int) $request->post('is_active', 1);
$data = $this->extractFormData($request);
$data['is_active'] = (int) $request->post('is_active', 1);
if ($nameAr === '') {
if ($data['name_ar'] === '') {
return $this->redirect("/pricing/special-discounts/{$id}/edit")->withError('اسم الخصم مطلوب');
}
if (bccomp($percentage, '0', 2) <= 0 || bccomp($percentage, '100', 2) > 0) {
return $this->redirect("/pricing/special-discounts/{$id}/edit")->withError('نسبة الخصم يجب أن تكون بين 0.01% و 100%');
$validationError = $this->validateDiscountData($data);
if ($validationError) {
return $this->redirect("/pricing/special-discounts/{$id}/edit")->withError($validationError);
}
$db->update('special_discounts', [
'name_ar' => $nameAr,
'name_en' => $nameEn,
'discount_percentage' => $percentage,
'requires_document' => $requiresDocument,
'description' => $description,
'is_active' => $isActive,
'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [(int) $id]);
$updateArray = $this->buildCreateArray($data);
$updateArray['is_active'] = $data['is_active'];
$updateArray['updated_at'] = date('Y-m-d H:i:s');
return $this->redirect('/pricing/special-discounts')->withSuccess('تم تحديث الخصم الخاص');
$db->update('special_discounts', $updateArray, '`id` = ?', [(int) $id]);
return $this->redirect('/pricing/special-discounts')->withSuccess('تم تحديث الخصم');
}
public function toggleActive(Request $request, string $id): Response
......@@ -127,4 +110,86 @@ class SpecialDiscountController extends Controller
return $this->redirect('/pricing/special-discounts')->withSuccess($newActive ? 'تم تفعيل الخصم' : 'تم تعطيل الخصم');
}
private function extractFormData(Request $request): array
{
return [
'name_ar' => trim((string) $request->post('name_ar', '')),
'name_en' => trim((string) $request->post('name_en', '')) ?: null,
'discount_type' => $request->post('discount_type', 'percentage'),
'discount_percentage' => trim((string) $request->post('discount_percentage', '0')),
'fixed_amount' => trim((string) $request->post('fixed_amount', '')) ?: null,
'applies_to' => $request->post('applies_to', 'membership_fee'),
'effective_from' => trim((string) $request->post('effective_from', '')) ?: null,
'effective_to' => trim((string) $request->post('effective_to', '')) ?: null,
'condition_type' => $request->post('condition_type', 'none'),
'condition_min_amount' => trim((string) $request->post('condition_min_amount', '')) ?: null,
'bonus_free_subscription_years'=> (int) $request->post('bonus_free_subscription_years', 0),
'requires_document' => (int) $request->post('requires_document', 0),
'description' => trim((string) $request->post('description', '')) ?: null,
];
}
private function validateDiscountData(array $data): ?string
{
$type = $data['discount_type'];
if ($type === 'percentage') {
$pct = $data['discount_percentage'];
if (bccomp($pct, '0', 2) <= 0 || bccomp($pct, '100', 2) > 0) {
return 'نسبة الخصم يجب أن تكون بين 0.01% و 100%';
}
} elseif ($type === 'fixed_amount') {
if (!$data['fixed_amount'] || bccomp($data['fixed_amount'], '0', 2) <= 0) {
return 'مبلغ الخصم الثابت مطلوب ويجب أن يكون أكبر من صفر';
}
}
if ($data['condition_type'] === 'min_payment') {
if (!$data['condition_min_amount'] || bccomp($data['condition_min_amount'], '0', 2) <= 0) {
return 'الحد الأدنى للسداد مطلوب عند اختيار شرط "سداد حد أدنى"';
}
}
$validTypes = ['percentage', 'fixed_amount', 'free_subscription'];
if (!in_array($type, $validTypes, true)) {
return 'نوع الخصم غير صالح';
}
$validAppliesTo = ['membership_fee', 'subscription', 'all'];
if (!in_array($data['applies_to'], $validAppliesTo, true)) {
return 'مجال التطبيق غير صالح';
}
$validConditions = ['none', 'full_payment', 'min_payment'];
if (!in_array($data['condition_type'], $validConditions, true)) {
return 'شرط التطبيق غير صالح';
}
if ($data['effective_from'] && $data['effective_to'] && $data['effective_from'] > $data['effective_to']) {
return 'تاريخ البداية يجب أن يكون قبل تاريخ الانتهاء';
}
return null;
}
private function buildCreateArray(array $data): array
{
return [
'name_ar' => $data['name_ar'],
'name_en' => $data['name_en'],
'discount_type' => $data['discount_type'],
'discount_percentage' => $data['discount_type'] === 'percentage' ? $data['discount_percentage'] : '0.00',
'fixed_amount' => $data['discount_type'] === 'fixed_amount' ? $data['fixed_amount'] : null,
'applies_to' => $data['applies_to'],
'effective_from' => $data['effective_from'],
'effective_to' => $data['effective_to'],
'condition_type' => $data['condition_type'],
'condition_min_amount' => $data['condition_type'] === 'min_payment' ? $data['condition_min_amount'] : null,
'bonus_free_subscription_years' => $data['bonus_free_subscription_years'],
'requires_document' => $data['requires_document'],
'description' => $data['description'],
'is_active' => 1,
];
}
}
......@@ -12,11 +12,23 @@ class SpecialDiscount extends Model
protected static bool $softDelete = false;
protected static bool $timestamps = true;
protected static array $fillable = [
'name_ar', 'name_en', 'discount_percentage', 'requires_document',
'description', 'is_active',
'name_ar', 'name_en', 'discount_percentage', 'discount_type',
'applies_to', 'effective_from', 'effective_to', 'fixed_amount',
'condition_type', 'condition_min_amount', 'bonus_free_subscription_years',
'requires_document', 'description', 'is_active',
];
public static function allActive(): array
{
$db = App::getInstance()->db();
$today = date('Y-m-d');
return $db->select(
"SELECT * FROM `special_discounts` WHERE `is_active` = 1 AND (effective_from IS NULL OR effective_from <= ?) AND (effective_to IS NULL OR effective_to >= ?) ORDER BY `name_ar`",
[$today, $today]
);
}
public static function allActiveUnfiltered(): array
{
$db = App::getInstance()->db();
return $db->select(
......
<?php
declare(strict_types=1);
namespace App\Modules\Pricing\Services;
use App\Core\App;
use App\Core\Logger;
final class SpecialDiscountService
{
/**
* Evaluate which discounts apply to a member based on their payment context.
* Called during billing to calculate applicable discount amount.
*
* @return array{discount_amount: string, discount_label: string, bonus_free_years: int, discount_id: int|null}
*/
public static function evaluateForMember(int $memberId, string $membershipValue, string $totalPaid = '0.00'): array
{
$db = App::getInstance()->db();
$today = date('Y-m-d');
$member = $db->selectOne("SELECT * FROM members WHERE id = ?", [$memberId]);
if (!$member) {
return self::emptyResult();
}
// If the member has a specific discount assigned, use that
if (!empty($member['special_discount_id'])) {
$discount = $db->selectOne(
"SELECT * FROM special_discounts WHERE id = ? AND is_active = 1",
[(int) $member['special_discount_id']]
);
if ($discount) {
if (self::isWithinDateRange($discount, $today)) {
return self::calculateDiscount($discount, $membershipValue, $totalPaid);
}
}
}
// Check for auto-applicable conditional discounts (full_payment, min_payment)
$conditionalDiscounts = $db->select(
"SELECT * FROM special_discounts WHERE is_active = 1 AND condition_type != 'none' AND (effective_from IS NULL OR effective_from <= ?) AND (effective_to IS NULL OR effective_to >= ?) ORDER BY discount_percentage DESC",
[$today, $today]
);
foreach ($conditionalDiscounts as $discount) {
if (self::conditionMet($discount, $membershipValue, $totalPaid)) {
return self::calculateDiscount($discount, $membershipValue, $totalPaid);
}
}
return self::emptyResult();
}
/**
* Check if a member qualifies for bonus free subscription years.
* Called after a membership_fee payment is completed.
*/
public static function getBonusFreeYears(int $memberId): int
{
$db = App::getInstance()->db();
$today = date('Y-m-d');
$member = $db->selectOne("SELECT special_discount_id, membership_value FROM members WHERE id = ?", [$memberId]);
if (!$member || empty($member['special_discount_id'])) {
// Check conditional discounts
$membershipValue = $member['membership_value'] ?? '0.00';
$totalPaid = self::getTotalMembershipPaid($db, $memberId);
$conditionalDiscounts = $db->select(
"SELECT * FROM special_discounts WHERE is_active = 1 AND condition_type != 'none' AND bonus_free_subscription_years > 0 AND (effective_from IS NULL OR effective_from <= ?) AND (effective_to IS NULL OR effective_to >= ?)",
[$today, $today]
);
foreach ($conditionalDiscounts as $d) {
if (self::conditionMet($d, $membershipValue, $totalPaid)) {
return (int) $d['bonus_free_subscription_years'];
}
}
return 0;
}
$discount = $db->selectOne(
"SELECT * FROM special_discounts WHERE id = ? AND is_active = 1 AND bonus_free_subscription_years > 0",
[(int) $member['special_discount_id']]
);
if (!$discount || !self::isWithinDateRange($discount, $today)) {
return 0;
}
return (int) $discount['bonus_free_subscription_years'];
}
/**
* Apply free subscription bonus: mark N years of subscriptions as paid (discount = 100%).
*/
public static function applyFreeSubscriptionBonus(int $memberId, int $freeYears): void
{
if ($freeYears <= 0) return;
$db = App::getInstance()->db();
$currentFy = financial_year();
$pendingSubs = $db->select(
"SELECT id, financial_year, total_amount FROM subscriptions WHERE member_id = ? AND status = 'pending' ORDER BY financial_year ASC LIMIT ?",
[$memberId, $freeYears * 10]
);
$yearsMarked = 0;
$lastFy = '';
foreach ($pendingSubs as $sub) {
if ($sub['financial_year'] !== $lastFy) {
$yearsMarked++;
$lastFy = $sub['financial_year'];
if ($yearsMarked > $freeYears) break;
}
$db->update('subscriptions', [
'status' => 'paid',
'discount_amount' => $sub['total_amount'],
'total_amount' => '0.00',
'paid_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
'receipt_number' => 'FREE-BONUS',
], '`id` = ?', [(int) $sub['id']]);
}
if ($yearsMarked > 0) {
Logger::info("SpecialDiscountService: applied {$yearsMarked} free subscription year(s) for member #{$memberId}");
}
}
private static function conditionMet(array $discount, string $membershipValue, string $totalPaid): bool
{
$conditionType = $discount['condition_type'] ?? 'none';
if ($conditionType === 'full_payment') {
return bccomp($membershipValue, '0.01', 2) >= 0 && bccomp($totalPaid, $membershipValue, 2) >= 0;
}
if ($conditionType === 'min_payment') {
$minAmount = $discount['condition_min_amount'] ?? '0.00';
return bccomp($totalPaid, $minAmount, 2) >= 0;
}
return false;
}
private static function calculateDiscount(array $discount, string $membershipValue, string $totalPaid): array
{
$type = $discount['discount_type'] ?? 'percentage';
$discountAmount = '0.00';
if ($type === 'percentage') {
$pct = $discount['discount_percentage'] ?? '0.00';
$discountAmount = bcdiv(bcmul($membershipValue, $pct, 4), '100', 2);
} elseif ($type === 'fixed_amount') {
$discountAmount = $discount['fixed_amount'] ?? '0.00';
} elseif ($type === 'free_subscription') {
$discountAmount = '0.00';
}
return [
'discount_amount' => $discountAmount,
'discount_label' => $discount['name_ar'] ?? 'خصم خاص',
'discount_id' => (int) $discount['id'],
'discount_type' => $type,
'percentage' => $discount['discount_percentage'] ?? '0.00',
'bonus_free_years'=> (int) ($discount['bonus_free_subscription_years'] ?? 0),
'applies_to' => $discount['applies_to'] ?? 'membership_fee',
];
}
private static function isWithinDateRange(array $discount, string $today): bool
{
$from = $discount['effective_from'] ?? null;
$to = $discount['effective_to'] ?? null;
if ($from && $today < $from) return false;
if ($to && $today > $to) return false;
return true;
}
private static function getTotalMembershipPaid(\App\Core\Database $db, int $memberId): string
{
$row = $db->selectOne(
"SELECT COALESCE(SUM(amount), 0) as total FROM payments WHERE member_id = ? AND payment_type IN ('membership_fee', 'down_payment') AND is_voided = 0",
[$memberId]
);
return $row['total'] ?? '0.00';
}
private static function emptyResult(): array
{
return [
'discount_amount' => '0.00',
'discount_label' => '',
'discount_id' => null,
'discount_type' => null,
'percentage' => '0.00',
'bonus_free_years'=> 0,
'applies_to' => 'membership_fee',
];
}
}
......@@ -40,10 +40,13 @@
<tr>
<th>#</th>
<th>اسم الخصم</th>
<th>النسبة</th>
<th>يحتاج إثبات</th>
<th>النوع</th>
<th>القيمة</th>
<th>يُطبَّق على</th>
<th>الشرط</th>
<th>المكافأة</th>
<th>الفترة</th>
<th>الحالة</th>
<th>الوصف</th>
<th></th>
</tr>
</thead>
......@@ -52,16 +55,62 @@
<tr>
<td><?= (int) $row['id'] ?></td>
<td style="font-weight:600;"><?= e($row['name_ar']) ?></td>
<td style="font-weight:700;color:#0D7377;"><?= e($row['discount_percentage']) ?>%</td>
<td>
<?php if ($row['requires_document']): ?>
<span style="display:inline-block;padding:3px 10px;border-radius:10px;font-size:12px;font-weight:600;background:#FFF7ED;color:#D97706;">
<i data-lucide="file-check" style="width:12px;height:12px;vertical-align:middle;margin-left:3px;"></i> مطلوب
</span>
<td style="font-size:12px;">
<?= match($row['discount_type'] ?? 'percentage') {
'percentage' => 'نسبة مئوية',
'fixed_amount' => 'مبلغ ثابت',
'free_subscription' => 'اشتراك مجاني',
default => $row['discount_type'] ?? '—'
} ?>
</td>
<td style="font-weight:700;color:#0D7377;">
<?php if (($row['discount_type'] ?? 'percentage') === 'percentage'): ?>
<?= e($row['discount_percentage']) ?>%
<?php elseif (($row['discount_type'] ?? '') === 'fixed_amount'): ?>
<?= money($row['fixed_amount'] ?? '0') ?>
<?php else: ?>
<?php endif; ?>
</td>
<td style="font-size:12px;">
<?= match($row['applies_to'] ?? 'membership_fee') {
'membership_fee' => 'العضوية',
'subscription' => 'الاشتراك',
'all' => 'الكل',
default => '—'
} ?>
</td>
<td style="font-size:12px;">
<?= match($row['condition_type'] ?? 'none') {
'none' => '<span style="color:#9CA3AF;">بدون شرط</span>',
'full_payment' => 'سداد كامل',
'min_payment' => 'حد أدنى ' . money($row['condition_min_amount'] ?? '0'),
default => '—'
} ?>
</td>
<td style="font-size:12px;">
<?php $bonus = (int) ($row['bonus_free_subscription_years'] ?? 0); ?>
<?php if ($bonus > 0): ?>
<span style="color:#059669;font-weight:600;"><?= $bonus ?> سنة مجانية</span>
<?php else: ?>
<span style="color:#9CA3AF;font-size:12px;">غير مطلوب</span>
<span style="color:#9CA3AF;"></span>
<?php endif; ?>
</td>
<td style="font-size:11px;color:#6B7280;">
<?php
$from = $row['effective_from'] ?? null;
$to = $row['effective_to'] ?? null;
if ($from && $to) {
echo e($from) . '<br>' . e($to);
} elseif ($from) {
echo 'من ' . e($from);
} elseif ($to) {
echo 'حتى ' . e($to);
} else {
echo '<span style="color:#9CA3AF;">دائم</span>';
}
?>
</td>
<td>
<?php if ($row['is_active']): ?>
<span style="display:inline-block;padding:3px 10px;border-radius:10px;font-size:12px;font-weight:600;background:#ECFDF5;color:#059669;">فعال</span>
......@@ -69,7 +118,6 @@
<span style="display:inline-block;padding:3px 10px;border-radius:10px;font-size:12px;font-weight:600;background:#F3F4F6;color:#6B7280;">معطل</span>
<?php endif; ?>
</td>
<td style="font-size:12px;color:#6B7280;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"><?= e($row['description'] ?? '—') ?></td>
<td style="white-space:nowrap;">
<a href="/pricing/special-discounts/<?= (int) $row['id'] ?>/edit" class="btn btn-sm btn-outline" style="font-size:12px;padding:4px 10px;">
<i data-lucide="edit" style="width:12px;height:12px;vertical-align:middle;"></i> تعديل
......@@ -101,7 +149,7 @@
<div style="padding:60px 20px;text-align:center;">
<div style="margin-bottom:15px;"><i data-lucide="percent" style="width:48px;height:48px;color:#D1D5DB;"></i></div>
<h3 style="color:#6B7280;margin:0 0 8px;">لا توجد خصومات خاصة</h3>
<p style="color:#9CA3AF;font-size:14px;margin:0 0 15px;">أضف أنواع الخصومات الخاصة (مثل: عضو شباب ورياضة) لتطبيقها على الأعضاء.</p>
<p style="color:#9CA3AF;font-size:14px;margin:0 0 15px;">أضف أنواع الخصومات الخاصة لتطبيقها على الأعضاء تلقائياً أو يدوياً.</p>
<a href="/pricing/special-discounts/create" class="btn btn-primary">إضافة خصم جديد</a>
</div>
<?php endif; ?>
......
......@@ -3,6 +3,7 @@ declare(strict_types=1);
use App\Core\Registries\MenuRegistry;
use App\Core\Registries\PermissionRegistry;
use App\Core\EventBus;
MenuRegistry::register('pricing', [
'label_ar' => 'التسعير',
......@@ -25,3 +26,18 @@ PermissionRegistry::register('pricing', [
'pricing.special_discounts.create' => ['ar' => 'إنشاء خصم خاص', 'en' => 'Create Special Discount'],
'pricing.special_discounts.edit' => ['ar' => 'تعديل الخصومات الخاصة','en' => 'Edit Special Discounts'],
]);
// When a member is activated, check if their discount includes free subscription bonus
EventBus::listen('member.activated', function (array $data) {
try {
$memberId = (int) ($data['member_id'] ?? 0);
if ($memberId <= 0) return;
$freeYears = \App\Modules\Pricing\Services\SpecialDiscountService::getBonusFreeYears($memberId);
if ($freeYears > 0) {
\App\Modules\Pricing\Services\SpecialDiscountService::applyFreeSubscriptionBonus($memberId, $freeYears);
}
} catch (\Throwable $e) {
\App\Core\Logger::error("pricing: member.activated bonus check failed: " . $e->getMessage(), ['data' => $data]);
}
}, 50);
<?php
declare(strict_types=1);
use App\Core\Database;
return function (Database $db): void {
$cols = [
'discount_type' => "ALTER TABLE special_discounts ADD COLUMN discount_type ENUM('percentage','fixed_amount','free_subscription') NOT NULL DEFAULT 'percentage' AFTER discount_percentage",
'applies_to' => "ALTER TABLE special_discounts ADD COLUMN applies_to ENUM('membership_fee','subscription','all') NOT NULL DEFAULT 'membership_fee' AFTER discount_type",
'effective_from' => "ALTER TABLE special_discounts ADD COLUMN effective_from DATE NULL DEFAULT NULL AFTER applies_to",
'effective_to' => "ALTER TABLE special_discounts ADD COLUMN effective_to DATE NULL DEFAULT NULL AFTER effective_from",
'fixed_amount' => "ALTER TABLE special_discounts ADD COLUMN fixed_amount DECIMAL(15,2) NULL DEFAULT NULL AFTER effective_to",
'condition_type' => "ALTER TABLE special_discounts ADD COLUMN condition_type ENUM('none','min_payment','full_payment') NOT NULL DEFAULT 'none' AFTER fixed_amount",
'condition_min_amount' => "ALTER TABLE special_discounts ADD COLUMN condition_min_amount DECIMAL(15,2) NULL DEFAULT NULL AFTER condition_type",
'bonus_free_subscription_years' => "ALTER TABLE special_discounts ADD COLUMN bonus_free_subscription_years INT UNSIGNED NOT NULL DEFAULT 0 AFTER condition_min_amount",
];
foreach ($cols as $col => $sql) {
$exists = $db->selectOne(
"SELECT 1 FROM information_schema.COLUMNS WHERE table_schema = DATABASE() AND table_name = 'special_discounts' AND column_name = ?",
[$col]
);
if (!$exists) {
$db->raw($sql);
}
}
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment