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

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)