Coverage for website/reimbursements/models.py: 89.13%
38 statements
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
1from django.core.exceptions import ValidationError
2from django.core.validators import (
3 FileExtensionValidator,
4 MinLengthValidator,
5)
6from django.db import models
7from django.utils import timezone
9from payments.models import PaymentAmountField
10from utils.media.services import get_upload_to_function
11from utils.validators import RangeValueValidator
14def validate_file_size(file):
15 max_size_mb = 10
16 if file.size > max_size_mb * 1000 * 1000:
17 raise ValidationError(f"File size must be less than {max_size_mb} MB")
20class Reimbursement(models.Model):
21 class Verdict(models.TextChoices):
22 APPROVED = "approved", "Approved"
23 DENIED = "denied", "Denied"
25 owner = models.ForeignKey(
26 "members.Member",
27 related_name="reimbursements",
28 on_delete=models.PROTECT,
29 )
31 amount = PaymentAmountField(
32 max_digits=5,
33 decimal_places=2,
34 help_text="How much did you pay (in euros)?",
35 allow_zero=True, # To make sure no double error msg is shown
36 validators=[RangeValueValidator(lower=0, lower_inclusive=False)],
37 )
39 date_incurred = models.DateField(
40 help_text="When was this payment made?",
41 )
43 description = models.TextField(
44 max_length=512,
45 help_text="Why did you make this payment?",
46 validators=[MinLengthValidator(10)],
47 )
49 # FileField and not ImageField because companies often send invoices as pdf
50 receipt = models.FileField(
51 upload_to=get_upload_to_function("reimbursements/receipts"),
52 validators=[
53 FileExtensionValidator(
54 allowed_extensions=["pdf", "jpg", "jpeg", "png"],
55 message="Only pdf, jpg, jpeg and png files are allowed.",
56 ),
57 validate_file_size,
58 ],
59 )
61 created = models.DateTimeField(auto_now_add=True)
63 verdict = models.CharField(
64 max_length=40,
65 choices=Verdict.choices,
66 null=True,
67 blank=True,
68 )
70 verdict_clarification = models.TextField(
71 help_text="Why did you choose this verdict?",
72 null=True,
73 blank=True,
74 )
76 evaluated_at = models.DateTimeField(null=True, editable=False)
78 evaluated_by = models.ForeignKey(
79 "auth.User",
80 related_name="reimbursements_approved",
81 on_delete=models.SET_NULL,
82 editable=False,
83 null=True,
84 )
86 class Meta:
87 ordering = ["created"]
89 def clean(self):
90 super().clean()
92 errors = {}
94 if (
95 self.date_incurred is not None
96 and self.date_incurred > timezone.now().date()
97 ):
98 errors["date_incurred"] = "The date incurred cannot be in the future."
100 if self.verdict == self.Verdict.DENIED and not self.verdict_clarification:
101 errors["verdict_clarification"] = (
102 "You must provide a reason for the denial."
103 )
105 if errors:
106 raise ValidationError(errors)
108 def __str__(self):
109 return f"Reimbursement #{self.id}"