You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
194 lines
6.4 KiB
194 lines
6.4 KiB
import random
|
|
from django.conf import settings
|
|
from django.db import models
|
|
from django.utils import timezone
|
|
from django.utils.text import slugify
|
|
from django.utils.translation import gettext_lazy as _
|
|
from apps.profiles.models import Institution
|
|
|
|
|
|
class CampaignCategory(models.TextChoices):
|
|
MOSQUE_CONSTRUCTION = 'mosque_construction', _('Mosque & Center Construction / Renovation')
|
|
HUMANITARIAN_RELIEF = 'humanitarian_relief', _('Humanitarian & Disaster Relief')
|
|
PILGRIM_SUPPORT = 'pilgrim_support', _('Imam Reza (AS) Pilgrim Services')
|
|
CULTURAL_PUBLICATIONS = 'cultural_publications', _('Quran & Islamic Publications Translation')
|
|
EDUCATIONAL_SCHOLARSHIPS = 'educational_scholarships', _('Seminary & Academic Scholarships')
|
|
GENERAL = 'general', _('General Waqf & Endowment')
|
|
|
|
|
|
class CampaignPriority(models.TextChoices):
|
|
URGENT = 'urgent', _('Urgent Appeal')
|
|
HIGH = 'high', _('High Priority')
|
|
MEDIUM = 'medium', _('Medium Priority')
|
|
|
|
|
|
class PaymentStatus(models.TextChoices):
|
|
COMPLETED = 'completed', _('Completed')
|
|
PENDING = 'pending', _('Pending')
|
|
FAILED = 'failed', _('Failed')
|
|
|
|
|
|
class DonationCampaign(models.Model):
|
|
title = models.CharField(max_length=255, verbose_name=_('Campaign Title'))
|
|
slug = models.SlugField(
|
|
max_length=280,
|
|
unique=True,
|
|
blank=True,
|
|
null=True,
|
|
allow_unicode=True,
|
|
verbose_name=_('Slug / URL Identifier')
|
|
)
|
|
description = models.TextField(verbose_name=_('Campaign Description & Objectives'))
|
|
target_amount = models.DecimalField(
|
|
max_digits=14,
|
|
decimal_places=2,
|
|
verbose_name=_('Target Amount')
|
|
)
|
|
raised_amount = models.DecimalField(
|
|
max_digits=14,
|
|
decimal_places=2,
|
|
default=0.00,
|
|
verbose_name=_('Accumulated Raised Amount')
|
|
)
|
|
currency = models.CharField(
|
|
max_length=10,
|
|
default='USD',
|
|
verbose_name=_('Currency (e.g. USD, EUR, IRR, AED)')
|
|
)
|
|
category = models.CharField(
|
|
max_length=50,
|
|
choices=CampaignCategory.choices,
|
|
default=CampaignCategory.HUMANITARIAN_RELIEF,
|
|
verbose_name=_('Category')
|
|
)
|
|
priority = models.CharField(
|
|
max_length=20,
|
|
choices=CampaignPriority.choices,
|
|
default=CampaignPriority.MEDIUM,
|
|
verbose_name=_('Priority')
|
|
)
|
|
cover_image = models.ImageField(
|
|
upload_to='donations/covers/%Y/%m/',
|
|
blank=True,
|
|
null=True,
|
|
verbose_name=_('Campaign Image')
|
|
)
|
|
institution = models.ForeignKey(
|
|
Institution,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='donation_campaigns',
|
|
verbose_name=_('Beneficiary Institution / Mosque')
|
|
)
|
|
organization_name = models.CharField(
|
|
max_length=255,
|
|
blank=True,
|
|
null=True,
|
|
verbose_name=_('Managing Foundation Name')
|
|
)
|
|
deadline = models.DateField(
|
|
null=True,
|
|
blank=True,
|
|
verbose_name=_('Campaign Deadline')
|
|
)
|
|
is_active = models.BooleanField(default=True, verbose_name=_('Is Active'))
|
|
is_featured = models.BooleanField(default=False, verbose_name=_('Is Featured'))
|
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created At'))
|
|
updated_at = models.DateTimeField(auto_now=True, verbose_name=_('Updated At'))
|
|
|
|
class Meta:
|
|
ordering = ('-is_featured', '-priority', '-created_at')
|
|
verbose_name = _('Donation Campaign')
|
|
verbose_name_plural = _('Donation Campaigns')
|
|
|
|
def __str__(self):
|
|
return f"{self.title} ({self.raised_amount}/{self.target_amount} {self.currency})"
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.slug:
|
|
base_slug = slugify(self.title, allow_unicode=True) or f"campaign-{random.randint(1000, 9999)}"
|
|
slug = base_slug
|
|
counter = 1
|
|
while DonationCampaign.objects.filter(slug=slug).exclude(pk=self.pk).exists():
|
|
slug = f"{base_slug}-{counter}"
|
|
counter += 1
|
|
self.slug = slug
|
|
super().save(*args, **kwargs)
|
|
|
|
@property
|
|
def progress_percentage(self) -> int:
|
|
if self.target_amount and self.target_amount > 0:
|
|
pct = int((self.raised_amount / self.target_amount) * 100)
|
|
return min(100, pct)
|
|
return 0
|
|
|
|
@property
|
|
def donors_count(self) -> int:
|
|
return self.transactions.filter(payment_status=PaymentStatus.COMPLETED).count()
|
|
|
|
|
|
class DonationTransaction(models.Model):
|
|
transaction_reference = models.CharField(
|
|
max_length=100,
|
|
unique=True,
|
|
blank=True,
|
|
verbose_name=_('Transaction Reference'),
|
|
help_text=_('e.g. DON-RAZAVI-2026-XYZ123')
|
|
)
|
|
campaign = models.ForeignKey(
|
|
DonationCampaign,
|
|
on_delete=models.CASCADE,
|
|
related_name='transactions',
|
|
verbose_name=_('Campaign')
|
|
)
|
|
donor_user = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='donations',
|
|
verbose_name=_('Donor Account')
|
|
)
|
|
donor_name = models.CharField(
|
|
max_length=255,
|
|
default='Anonymous Donor',
|
|
verbose_name=_('Donor Display Name')
|
|
)
|
|
donor_email = models.EmailField(
|
|
blank=True,
|
|
null=True,
|
|
verbose_name=_('Donor Email')
|
|
)
|
|
amount = models.DecimalField(
|
|
max_digits=12,
|
|
decimal_places=2,
|
|
verbose_name=_('Donation Amount')
|
|
)
|
|
currency = models.CharField(
|
|
max_length=10,
|
|
default='USD',
|
|
verbose_name=_('Currency')
|
|
)
|
|
payment_status = models.CharField(
|
|
max_length=20,
|
|
choices=PaymentStatus.choices,
|
|
default=PaymentStatus.COMPLETED,
|
|
verbose_name=_('Payment Status')
|
|
)
|
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Donated At'))
|
|
|
|
class Meta:
|
|
ordering = ('-created_at',)
|
|
verbose_name = _('Donation Transaction')
|
|
verbose_name_plural = _('Donation Transactions')
|
|
|
|
def __str__(self):
|
|
return f"{self.transaction_reference} - {self.amount} {self.currency} by {self.donor_name}"
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.transaction_reference:
|
|
year = timezone.now().year
|
|
rand_code = ''.join(random.choices('ABCDEFGHJKLMNPQRSTUVWXYZ23456789', k=6))
|
|
self.transaction_reference = f"DON-RAZAVI-{year}-{rand_code}"
|
|
super().save(*args, **kwargs)
|