Commit 6140d228 authored by Administrator's avatar Administrator

Update 1 files via Son of Anton

parent 275ded0f
...@@ -8,63 +8,216 @@ use App\Core\App; ...@@ -8,63 +8,216 @@ use App\Core\App;
final class MemberNumberGenerator final class MemberNumberGenerator
{ {
/** /**
* Generate the next available membership number. * Assign the next membership number to a member.
* Sequential, gap-free, atomic to prevent duplicates. * Called ONLY after membership value payment is confirmed.
* Only called after payment confirmation. * Number is based on payment priority — first to pay gets next number.
*/ */
public static function next(): string public static function assign(int $memberId): ?string
{ {
$db = App::getInstance()->db(); $db = App::getInstance()->db();
// Use a transaction with locking to prevent concurrent duplicates // Check member exists and doesn't already have a number
$db->beginTransaction(); $member = $db->selectOne(
try { "SELECT id, membership_number FROM members WHERE id = ? AND is_archived = 0",
// Get the current maximum membership number [$memberId]
$row = $db->selectOne(
"SELECT MAX(CAST(membership_number AS UNSIGNED)) as max_num FROM members WHERE membership_number IS NOT NULL AND membership_number REGEXP '^[0-9]+$' FOR UPDATE"
); );
if (!$member) return null;
if (!empty($member['membership_number'])) return $member['membership_number'];
// Get next available membership number
$nextNumber = self::getNextMembershipNumber();
if ($nextNumber === null) return null;
$startNumber = 10012; // Default starting number (1001/2 per spec — interpreted as 10012) $numberStr = (string) $nextNumber;
$db->update(
'members',
['membership_number' => $numberStr, 'updated_at' => date('Y-m-d H:i:s')],
'`id` = ?',
[$memberId]
);
// Check system config for starting number return $numberStr;
$configRow = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = 'membership_start_number'");
if ($configRow && $configRow['config_value']) {
$startNumber = (int) $configRow['config_value'];
} }
$maxNum = $row['max_num'] ? (int) $row['max_num'] : ($startNumber - 1); /**
$nextNum = max($maxNum + 1, $startNumber); * Get next available membership number.
* Membership numbers are separate from form numbers.
* Format: sequential integers starting from system config.
*/
public static function getNextMembershipNumber(): ?int
{
$db = App::getInstance()->db();
$db->commit(); // Check existing membership numbers
$last = $db->selectOne(
"SELECT MAX(CAST(membership_number AS UNSIGNED)) as last_num
FROM members
WHERE membership_number IS NOT NULL
AND membership_number REGEXP '^[0-9]+$'"
);
$lastNum = (int) ($last['last_num'] ?? 0);
return (string) $nextNum; if ($lastNum > 0) {
} catch (\Throwable $e) { return $lastNum + 1;
$db->rollBack();
throw $e;
} }
// Check system config for starting membership number
$config = $db->selectOne(
"SELECT config_value FROM system_config WHERE config_key = ?",
['membership.number_start']
);
if ($config && is_numeric($config['config_value']) && (int) $config['config_value'] > 0) {
return (int) $config['config_value'];
}
// Default starting number per spec: 1001/2 format = 1001
return 1001;
} }
/** /**
* Assign a membership number to a member. * Set the starting membership number (separate from form number).
* Called only after payment is confirmed (Phase 11).
*/ */
public static function assign(int $memberId): string public static function setMembershipNumberStart(int $number): void
{
$db = App::getInstance()->db();
$existing = $db->selectOne(
"SELECT id FROM system_config WHERE config_key = ?",
['membership.number_start']
);
if ($existing) {
$db->update('system_config', [
'config_value' => (string) $number,
'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [(int) $existing['id']]);
} else {
$db->insert('system_config', [
'config_key' => 'membership.number_start',
'config_value' => (string) $number,
'config_type' => 'integer',
'group_name' => 'membership',
'description_ar' => 'رقم بداية العضويات (رقم العضوية وليس الاستمارة)',
'description_en' => 'Starting Membership Number',
'is_editable' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
}
// ── Form Number Methods (unchanged) ──
public static function next(): ?int
{ {
$db = App::getInstance()->db(); $db = App::getInstance()->db();
$last = $db->selectOne(
"SELECT MAX(CAST(form_number AS UNSIGNED)) as last_num
FROM members
WHERE form_number IS NOT NULL
AND form_number REGEXP '^[0-9]+$'"
);
$lastNum = (int) ($last['last_num'] ?? 0);
// Check member doesn't already have a number if ($lastNum > 0) {
$member = $db->selectOne("SELECT membership_number FROM members WHERE id = ?", [$memberId]); return $lastNum + 1;
if ($member && $member['membership_number']) {
return $member['membership_number'];
} }
$number = self::next(); $config = $db->selectOne(
"SELECT config_value FROM system_config WHERE config_key = ?",
['membership.form_start_number']
);
if ($config && is_numeric($config['config_value']) && (int) $config['config_value'] > 0) {
return (int) $config['config_value'];
}
return null;
}
$db->update('members', [ public static function setStartNumber(int $number): void
'membership_number' => $number, {
$db = App::getInstance()->db();
$existing = $db->selectOne(
"SELECT id FROM system_config WHERE config_key = ?",
['membership.form_start_number']
);
if ($existing) {
$db->update('system_config', [
'config_value' => (string) $number,
'updated_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [$memberId]); ], '`id` = ?', [(int) $existing['id']]);
} else {
$db->insert('system_config', [
'config_key' => 'membership.form_start_number',
'config_value' => (string) $number,
'config_type' => 'integer',
'group_name' => 'membership',
'description_ar' => 'رقم بداية الاستمارات',
'description_en' => 'Starting Form Number',
'is_editable' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
}
public static function isConfigured(): bool
{
return self::next() !== null;
}
public static function getFormFee(): string
{
try {
$db = App::getInstance()->db();
$fee = $db->selectOne(
"SELECT base_amount FROM service_catalog WHERE service_code = 'FORM_NEW_MEMBERSHIP' AND is_active = 1 LIMIT 1"
);
if ($fee && $fee['base_amount']) {
return $fee['base_amount'];
}
} catch (\Throwable $e) {}
return '505.00';
}
/**
* Get the membership value based on branch and qualification.
* Per spec: High qual = 150,000, Medium = 225,000, No qual = 300,000
*/
public static function getMembershipValue(int $branchId, ?int $qualificationId): string
{
if (!$qualificationId) return '300000.00'; // No qualification default
try {
$db = App::getInstance()->db();
$pricing = $db->selectOne(
"SELECT price FROM pricing_configs
WHERE branch_id = ? AND qualification_id = ? AND membership_type = 'working'
AND is_active = 1 AND effective_from <= CURDATE()
AND (effective_to IS NULL OR effective_to >= CURDATE())
ORDER BY effective_from DESC LIMIT 1",
[$branchId, $qualificationId]
);
if ($pricing) return $pricing['price'];
} catch (\Throwable $e) {}
// Fallback based on qualification code
try {
$db = App::getInstance()->db();
$qual = $db->selectOne("SELECT code FROM qualifications WHERE id = ?", [$qualificationId]);
if ($qual) {
return match ($qual['code'] ?? '') {
'high' => '150000.00',
'medium' => '225000.00',
'none' => '300000.00',
default => '150000.00',
};
}
} catch (\Throwable $e) {}
return $number; return '150000.00';
} }
} }
\ No newline at end of file
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