from django.db import models from django.core.validators import MinValueValidator from decimal import Decimal from django.contrib.auth.models import User from django.utils import timezone class UserProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile') class Meta: verbose_name = "User Profile" verbose_name_plural = "User Profiles" def __str__(self): return f"{self.user.username}'s profile" class Project(models.Model): name = models.CharField(max_length=200) description = models.TextField(blank=True) supervisors = models.ManyToManyField(User, related_name='assigned_projects', blank=True) created_at = models.DateTimeField(auto_now_add=True) is_active = models.BooleanField(default=True) class Meta: verbose_name = "Project" verbose_name_plural = "Projects" def __str__(self): return self.name class Worker(models.Model): name = models.CharField(max_length=200) id_no = models.CharField(max_length=50, unique=True, verbose_name="ID Number") phone_no = models.CharField(max_length=20, verbose_name="Phone Number") monthly_salary = models.DecimalField(max_digits=10, decimal_places=2, validators=[MinValueValidator(Decimal('0.00'))]) # New fields photo = models.ImageField(upload_to='workers/photos/', blank=True, null=True) id_photo = models.ImageField(upload_to='workers/ids/', blank=True, null=True, verbose_name="ID Document") date_of_employment = models.DateField(default=timezone.now) notes = models.TextField(blank=True) created_at = models.DateTimeField(auto_now_add=True) is_active = models.BooleanField(default=True) class Meta: verbose_name = "Worker" verbose_name_plural = "Workers" @property def day_rate(self): return self.monthly_salary / Decimal('20.0') @property def projects_worked_on_count(self): """Returns the number of distinct projects this worker has worked on.""" return self.work_logs.values('project').distinct().count() def __str__(self): return self.name class Team(models.Model): name = models.CharField(max_length=200) workers = models.ManyToManyField(Worker, related_name='teams') supervisor = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='managed_teams') created_at = models.DateTimeField(auto_now_add=True) is_active = models.BooleanField(default=True) class Meta: verbose_name = "Team" verbose_name_plural = "Teams" def __str__(self): return self.name class WorkLog(models.Model): OT_CHOICES = [ (Decimal('0'), 'None'), (Decimal('0.25'), '1/4 Day'), (Decimal('0.5'), '1/2 Day'), (Decimal('0.75'), '3/4 Day'), (Decimal('1.0'), 'Full Day'), ] date = models.DateField() project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='logs') team = models.ForeignKey(Team, on_delete=models.SET_NULL, null=True, blank=True, related_name='work_logs') workers = models.ManyToManyField(Worker, related_name='work_logs') supervisor = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) notes = models.TextField(blank=True) overtime = models.DecimalField(max_digits=3, decimal_places=2, default=0, choices=OT_CHOICES) overtime_paid_to = models.ManyToManyField(Worker, blank=True, related_name='overtime_paid_logs') class Meta: verbose_name = "Work Log / Attendance" verbose_name_plural = "Work Logs / Attendance" def __str__(self): return f"{self.date} - {self.project.name}" class PayrollRecord(models.Model): worker = models.ForeignKey(Worker, on_delete=models.CASCADE, related_name='payroll_records') date = models.DateField(default=timezone.now) amount = models.DecimalField(max_digits=10, decimal_places=2) work_logs = models.ManyToManyField(WorkLog, related_name='paid_in') created_at = models.DateTimeField(auto_now_add=True) class Meta: verbose_name = "Payroll Record" verbose_name_plural = "Payroll Records" def __str__(self): return f"Payment to {self.worker.name} on {self.date}" class Loan(models.Model): worker = models.ForeignKey(Worker, on_delete=models.CASCADE, related_name='loans') amount = models.DecimalField(max_digits=10, decimal_places=2, help_text="Principal amount borrowed") balance = models.DecimalField(max_digits=10, decimal_places=2, default=0, help_text="Remaining amount to be repaid") date = models.DateField(default=timezone.now) reason = models.TextField(blank=True) is_active = models.BooleanField(default=True) class Meta: verbose_name = "Loan" verbose_name_plural = "Loans" def save(self, *args, **kwargs): if not self.pk: # On creation self.balance = self.amount super().save(*args, **kwargs) def __str__(self): return f"Loan for {self.worker.name} - R{self.amount}" class PayrollAdjustment(models.Model): ADJUSTMENT_TYPES = [ ('BONUS', 'Bonus'), ('OVERTIME', 'Overtime'), ('DEDUCTION', 'Deduction'), ('LOAN_REPAYMENT', 'Loan Repayment'), ('LOAN', 'New Loan'), ] worker = models.ForeignKey(Worker, on_delete=models.CASCADE, related_name='adjustments') payroll_record = models.ForeignKey(PayrollRecord, on_delete=models.SET_NULL, null=True, blank=True, related_name='adjustments') loan = models.ForeignKey(Loan, on_delete=models.SET_NULL, null=True, blank=True, related_name='repayments') amount = models.DecimalField(max_digits=10, decimal_places=2, help_text="Positive adds to pay, negative subtracts (except for Loan Repayment which is auto-handled)") date = models.DateField(default=timezone.now) description = models.CharField(max_length=255) type = models.CharField(max_length=20, choices=ADJUSTMENT_TYPES, default='DEDUCTION') class Meta: verbose_name = "Payroll Adjustment" verbose_name_plural = "Payroll Adjustments" def __str__(self): return f"{self.get_type_display()} - {self.amount} for {self.worker.name}" class ExpenseReceipt(models.Model): VAT_CHOICES = [ ('INCLUDED', 'VAT Included'), ('EXCLUDED', 'VAT Excluded'), ('NONE', 'No VAT'), ] PAYMENT_METHODS = [ ('CASH', 'Cash'), ('CARD', 'Card'), ('EFT', 'EFT'), ('OTHER', 'Other'), ] user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='generated_receipts') date = models.DateField(default=timezone.now) vendor = models.CharField(max_length=200) description = models.TextField(blank=True) payment_method = models.CharField(max_length=10, choices=PAYMENT_METHODS, default='CASH') vat_type = models.CharField(max_length=10, choices=VAT_CHOICES, default='NONE') # Financials (Stored for record keeping) subtotal = models.DecimalField(max_digits=12, decimal_places=2, default=0) vat_amount = models.DecimalField(max_digits=12, decimal_places=2, default=0) total_amount = models.DecimalField(max_digits=12, decimal_places=2, default=0) created_at = models.DateTimeField(auto_now_add=True) class Meta: verbose_name = "Expense Receipt" verbose_name_plural = "Expense Receipts" def __str__(self): return f"Receipt from {self.vendor} - {self.date}" class ExpenseLineItem(models.Model): receipt = models.ForeignKey(ExpenseReceipt, on_delete=models.CASCADE, related_name='items') product = models.CharField(max_length=255, verbose_name="Product/Item") amount = models.DecimalField(max_digits=10, decimal_places=2) class Meta: verbose_name = "Expense Line Item" verbose_name_plural = "Expense Line Items" def __str__(self): return f"{self.product} - {self.amount}"