Files
claims-system/claims/models.py
2025-11-09 21:49:44 +01:00

152 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import uuid
from django.conf import settings
from django.core.files.storage import default_storage
from django.db import models
from django.utils.translation import gettext_lazy as _
from .validators import validate_receipt_file
class Project(models.Model):
name = models.CharField(max_length=255)
code = models.CharField(max_length=50, blank=True)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["name"]
def __str__(self):
if self.code:
return f"{self.code} {self.name}"
return self.name
class Claim(models.Model):
class Status(models.TextChoices):
PENDING = "pending", _("Pending")
APPROVED = "approved", _("Approved")
REJECTED = "rejected", _("Rejected")
class Currency(models.TextChoices):
SEK = "SEK", _("Swedish krona (SEK)")
EUR = "EUR", _("Euro (EUR)")
USD = "USD", _("US dollar (USD)")
GBP = "GBP", _("British pound (GBP)")
submitted_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="claims_submitted",
)
full_name = models.CharField(max_length=255)
email = models.EmailField()
amount = models.DecimalField(max_digits=10, decimal_places=2)
currency = models.CharField(
max_length=3,
choices=Currency.choices,
default=Currency.SEK,
)
description = models.TextField(help_text=_("Describe what the reimbursement is for"))
account_number = models.CharField(max_length=50)
receipt = models.FileField(
upload_to="receipts/",
blank=True,
null=True,
validators=[validate_receipt_file],
)
project = models.ForeignKey(
Project,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="claims",
)
status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)
decision_note = models.TextField(blank=True)
paid_at = models.DateTimeField(null=True, blank=True)
paid_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="claims_marked_paid",
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["-created_at"]
def __str__(self):
project = f" [{self.project}]" if self.project else ""
return f"{self.full_name} {self.amount} {self.currency}{project} ({self.get_status_display()})"
@property
def is_paid(self):
return self.paid_at is not None
def add_log(self, *, action, performed_by=None, from_status=None, to_status=None, note=""):
return ClaimLog.objects.create(
claim=self,
action=action,
from_status=from_status,
to_status=to_status or self.status,
note=note or "",
performed_by=performed_by,
)
def save(self, *args, **kwargs):
if self.receipt and not kwargs.pop("skip_receipt_rename", False):
original_name = self.receipt.name or ""
ext = original_name.rsplit(".", 1)[-1] if "." in original_name else "dat"
new_name = self._generate_unique_receipt_name(ext)
self.receipt.name = new_name
super().save(*args, **kwargs)
def _generate_unique_receipt_name(self, ext):
ext = ext.lower()
for _ in range(10):
candidate = f"receipts/{uuid.uuid4().hex}.{ext}"
if not default_storage.exists(candidate):
return candidate
return f"receipts/{uuid.uuid4().hex}.{ext}"
class ClaimLog(models.Model):
class Action(models.TextChoices):
CREATED = "created", _("Submitted")
STATUS_CHANGED = "status_changed", _("Status changed")
MARKED_PAID = "marked_paid", _("Marked as paid")
PROJECT_CHANGED = "project_changed", _("Project changed")
DETAILS_EDITED = "details_edited", _("Details edited")
claim = models.ForeignKey(Claim, related_name="logs", on_delete=models.CASCADE)
action = models.CharField(max_length=32, choices=Action.choices)
from_status = models.CharField(
max_length=20,
choices=Claim.Status.choices,
null=True,
blank=True,
)
to_status = models.CharField(max_length=20, choices=Claim.Status.choices)
note = models.TextField(blank=True)
performed_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="claim_logs",
)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created_at"]
def __str__(self):
return f"{self.get_action_display()} ({self.created_at:%Y-%m-%d %H:%M})"