Commit 509d0559 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix(cron): add cron daemon to Docker + fix subscription auto-generation

1. Install cron package in Dockerfile and start cron daemon in entrypoint
   (hourly: php cli.php cron). This was missing — cron jobs never ran.
2. SubscriptionGeneratorJob now delegates to SubscriptionGenerator service
   which handles exemptions, year-specific rates/discounts, per-person dedup.
3. Financial year 2026/2027 subscriptions will generate on next cron run
   (July 1-7 window still active).
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 20850756
...@@ -14,6 +14,7 @@ RUN apt-get update && apt-get install -y \ ...@@ -14,6 +14,7 @@ RUN apt-get update && apt-get install -y \
dos2unix \ dos2unix \
git \ git \
wget \ wget \
cron \
xvfb \ xvfb \
libxrender1 \ libxrender1 \
libfontconfig1 \ libfontconfig1 \
......
...@@ -3,10 +3,10 @@ declare(strict_types=1); ...@@ -3,10 +3,10 @@ declare(strict_types=1);
namespace CronJobs; namespace CronJobs;
use App\Core\App;
use App\Core\Database; use App\Core\Database;
use App\Core\Logger; use App\Core\Logger;
use App\Modules\ServiceCatalog\Models\ServicePrice; use App\Modules\Subscriptions\Services\SubscriptionGenerator;
use App\Modules\Rules\Services\RuleEngine;
class SubscriptionGeneratorJob class SubscriptionGeneratorJob
{ {
...@@ -15,7 +15,7 @@ class SubscriptionGeneratorJob ...@@ -15,7 +15,7 @@ class SubscriptionGeneratorJob
public function shouldRun(): bool public function shouldRun(): bool
{ {
// Run in July only // Run in July days 1-7 (financial year starts July 1st)
return (int) date('n') === 7 && (int) date('j') <= 7; return (int) date('n') === 7 && (int) date('j') <= 7;
} }
...@@ -23,100 +23,29 @@ class SubscriptionGeneratorJob ...@@ -23,100 +23,29 @@ class SubscriptionGeneratorJob
{ {
$year = (int) date('Y'); $year = (int) date('Y');
$financialYear = $year . '/' . ($year + 1); $financialYear = $year . '/' . ($year + 1);
$processed = 0;
// Check if already generated // Check if already generated (any rows for this FY means done)
$existing = $this->db->selectOne("SELECT COUNT(*) as c FROM subscriptions WHERE financial_year = ?", [$financialYear]); $existing = $this->db->selectOne(
"SELECT COUNT(*) as c FROM subscriptions WHERE financial_year = ?",
[$financialYear]
);
if ((int) ($existing['c'] ?? 0) > 0) { if ((int) ($existing['c'] ?? 0) > 0) {
return ['processed' => 0]; // Already generated return ['processed' => 0];
} }
// Get all active members // Boot App singleton so services can access db()
$members = $this->db->select("SELECT id, full_name_ar, membership_type FROM members WHERE status = 'active' AND is_archived = 0"); $app = App::getInstance();
$devFeeData = RuleEngine::get('DEVELOPMENT_FEE'); if (!$app->db()) {
$devFee = $devFeeData['amount'] ?? '35.00'; $app->setDb($this->db);
$memberRate = ServicePrice::getPrice('SVC_ANNUAL_MEMBER', '492.00');
$spouseRate = ServicePrice::getPrice('SVC_ANNUAL_SPOUSE', '492.00');
$childRate = ServicePrice::getPrice('SVC_ANNUAL_CHILD', '222.00');
$tempRate = ServicePrice::getPrice('SVC_ANNUAL_TEMP', '222.00');
$ts = date('Y-m-d H:i:s');
foreach ($members as $m) {
// Generate for member
$this->db->insert('subscriptions', [
'member_id' => (int) $m['id'],
'financial_year' => $financialYear,
'person_type' => 'member',
'person_id' => (int) $m['id'],
'person_name' => $m['full_name_ar'],
'base_amount' => $memberRate,
'development_fee'=> $devFee,
'total_amount' => bcadd($memberRate, $devFee, 2),
'status' => 'pending',
'created_at' => $ts,
'updated_at' => $ts,
]);
$processed++;
// Generate for spouses (NO dev fee for dependents)
$spouses = $this->db->select("SELECT id, full_name_ar FROM spouses WHERE member_id = ? AND is_archived = 0 AND status = 'active'", [(int) $m['id']]);
foreach ($spouses as $sp) {
$this->db->insert('subscriptions', [
'member_id' => (int) $m['id'],
'financial_year' => $financialYear,
'person_type' => 'spouse',
'person_id' => (int) $sp['id'],
'person_name' => $sp['full_name_ar'],
'base_amount' => $spouseRate,
'development_fee'=> '0.00',
'total_amount' => $spouseRate,
'status' => 'pending',
'created_at' => $ts,
'updated_at' => $ts,
]);
$processed++;
} }
// Generate for children (NO dev fee for dependents) // Delegate to the full-featured SubscriptionGenerator service
$children = $this->db->select("SELECT id, full_name_ar FROM children WHERE member_id = ? AND is_archived = 0 AND status = 'active'", [(int) $m['id']]); // which handles: exemptions, year-specific rates, discounts, per-person dedup
foreach ($children as $ch) { $result = SubscriptionGenerator::generateForYear($financialYear);
$this->db->insert('subscriptions', [
'member_id' => (int) $m['id'],
'financial_year' => $financialYear,
'person_type' => 'child',
'person_id' => (int) $ch['id'],
'person_name' => $ch['full_name_ar'],
'base_amount' => $childRate,
'development_fee'=> '0.00',
'total_amount' => $childRate,
'status' => 'pending',
'created_at' => $ts,
'updated_at' => $ts,
]);
$processed++;
}
// Generate for temporary members (NO dev fee for dependents) $processed = ($result['created'] ?? 0) + ($result['skipped'] ?? 0);
$temps = $this->db->select("SELECT id, full_name_ar FROM temporary_members WHERE member_id = ? AND is_archived = 0 AND status = 'active'", [(int) $m['id']]); Logger::info("Subscription generation (cron) complete for {$financialYear}: created={$result['created']}, skipped={$result['skipped']}");
foreach ($temps as $tmp) {
$this->db->insert('subscriptions', [
'member_id' => (int) $m['id'],
'financial_year' => $financialYear,
'person_type' => 'temporary',
'person_id' => (int) $tmp['id'],
'person_name' => $tmp['full_name_ar'],
'base_amount' => $tempRate,
'development_fee'=> '0.00',
'total_amount' => $tempRate,
'status' => 'pending',
'created_at' => $ts,
'updated_at' => $ts,
]);
$processed++;
}
}
Logger::info("Subscription generation complete for {$financialYear}: {$processed} records"); return ['processed' => $result['created'] ?? 0];
return ['processed' => $processed];
} }
} }
...@@ -37,6 +37,13 @@ php /var/www/html/docker/boot.php ...@@ -37,6 +37,13 @@ php /var/www/html/docker/boot.php
chown -R www-data:www-data /var/www/html/storage 2>/dev/null || true chown -R www-data:www-data /var/www/html/storage 2>/dev/null || true
chmod -R 775 /var/www/html/storage 2>/dev/null || true chmod -R 775 /var/www/html/storage 2>/dev/null || true
# ── Setup Cron (runs every hour) ──
echo "0 * * * * cd /var/www/html && php cli.php cron >> /var/www/html/storage/logs/cron.log 2>&1" > /etc/cron.d/cluberp-cron
chmod 0644 /etc/cron.d/cluberp-cron
crontab /etc/cron.d/cluberp-cron
service cron start
echo "Cron daemon started (hourly: php cli.php cron)"
echo "=========================================" echo "========================================="
echo "Starting Apache..." echo "Starting Apache..."
echo "=========================================" echo "========================================="
......
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