The Mess Management module handles mess menu planning, student mess registration, meal tracking, billing, feedback, rebates, and all mess-related administrative functions.
from django.db import models
from django.contrib.auth.models import User
from applications.globals.models import ExtraInfo
from applications.academic_information.models import Student
from applications.programme_curriculum.models import Batch
# ==================== MESS CONFIGURATION ====================
class Mess(models.Model):
"""Mess facilities"""
MESS_TYPE = [
('VEG', 'Vegetarian'),
('NON_VEG', 'Non-Vegetarian'),
('BOTH', 'Both'),
]
name = models.CharField(max_length=100)
code = models.CharField(max_length=20, unique=True)
location = models.CharField(max_length=200, blank=True)
mess_type = models.CharField(max_length=20, choices=MESS_TYPE, default='BOTH')
# Capacity
seating_capacity = models.IntegerField(default=500)
# Timings
breakfast_start = models.TimeField(null=True, blank=True)
breakfast_end = models.TimeField(null=True, blank=True)
lunch_start = models.TimeField(null=True, blank=True)
lunch_end = models.TimeField(null=True, blank=True)
dinner_start = models.TimeField(null=True, blank=True)
dinner_end = models.TimeField(null=True, blank=True)
# Contact
contact_number = models.CharField(max_length=15, blank=True)
# Management
mess_incharge = models.ForeignKey(ExtraInfo, on_delete=models.SET_NULL, null=True, blank=True, related_name='mess_incharge_of')
mess_warden = models.ForeignKey(ExtraInfo, on_delete=models.SET_NULL, null=True, blank=True, related_name='mess_warden_of')
# Contractor
contractor_name = models.CharField(max_length=200, blank=True)
contractor_contact = models.CharField(max_length=15, blank=True)
contract_start = models.DateField(null=True, blank=True)
contract_end = models.DateField(null=True, blank=True)
is_active = models.BooleanField(default=True)
# ==================== MENU MANAGEMENT ====================
class MenuItem(models.Model):
"""Menu items"""
CATEGORY = [
('BREAKFAST', 'Breakfast'),
('LUNCH', 'Lunch'),
('DINNER', 'Dinner'),
('SNACKS', 'Snacks'),
('BEVERAGES', 'Beverages'),
]
TYPE = [
('VEG', 'Vegetarian'),
('NON_VEG', 'Non-Vegetarian'),
]
name = models.CharField(max_length=100)
category = models.CharField(max_length=20, choices=CATEGORY)
item_type = models.CharField(max_length=20, choices=TYPE, default='VEG')
description = models.TextField(blank=True)
# Nutritional info
calories = models.IntegerField(null=True, blank=True)
protein = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
is_active = models.BooleanField(default=True)
class MessMenu(models.Model):
"""Weekly menu schedule"""
DAY_CHOICES = [
('MON', 'Monday'),
('TUE', 'Tuesday'),
('WED', 'Wednesday'),
('THU', 'Thursday'),
('FRI', 'Friday'),
('SAT', 'Saturday'),
('SUN', 'Sunday'),
]
MEAL_TYPE = [
('BREAKFAST', 'Breakfast'),
('LUNCH', 'Lunch'),
('DINNER', 'Dinner'),
]
mess = models.ForeignKey(Mess, on_delete=models.CASCADE, related_name='menus')
day = models.CharField(max_length=3, choices=DAY_CHOICES)
meal_type = models.CharField(max_length=20, choices=MEAL_TYPE)
# Menu items
items = models.ManyToManyField(MenuItem, related_name='menu_schedules')
# Validity
effective_from = models.DateField()
effective_until = models.DateField(null=True, blank=True)
class Meta:
unique_together = ['mess', 'day', 'meal_type', 'effective_from']
class SpecialMenu(models.Model):
"""Special menus for events/festivals"""
mess = models.ForeignKey(Mess, on_delete=models.CASCADE, related_name='special_menus')
date = models.DateField()
occasion = models.CharField(max_length=200)
breakfast_items = models.TextField(blank=True)
lunch_items = models.TextField(blank=True)
dinner_items = models.TextField(blank=True)
created_by = models.ForeignKey(ExtraInfo, on_delete=models.SET_NULL, null=True)
# ==================== MEMBER REGISTRATION ====================
class MessRegistration(models.Model):
"""Mess member registration"""
STATUS = [
('ACTIVE', 'Active'),
('INACTIVE', 'Inactive'),
('SUSPENDED', 'Suspended'),
]
PREFERENCE = [
('VEG', 'Vegetarian'),
('NON_VEG', 'Non-Vegetarian'),
]
# Member (can be student, faculty, or staff)
member = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE, related_name='mess_registrations')
mess = models.ForeignKey(Mess, on_delete=models.CASCADE)
# Registration details
registration_date = models.DateField()
valid_from = models.DateField()
valid_until = models.DateField(null=True, blank=True)
# Preference
food_preference = models.CharField(max_length=20, choices=PREFERENCE, default='VEG')
status = models.CharField(max_length=20, choices=STATUS, default='ACTIVE')
class Meta:
ordering = ['-registration_date']
class MessCard(models.Model):
"""Mess cards for members"""
registration = models.OneToOneField(MessRegistration, on_delete=models.CASCADE, related_name='card')
card_number = models.CharField(max_length=50, unique=True)
issued_date = models.DateField()
is_active = models.BooleanField(default=True)
# For lost/replacement
replacement_count = models.IntegerField(default=0)
# ==================== MEAL TRACKING ====================
class MealEntry(models.Model):
"""Individual meal entries"""
MEAL_TYPE = [
('BREAKFAST', 'Breakfast'),
('LUNCH', 'Lunch'),
('DINNER', 'Dinner'),
]
registration = models.ForeignKey(MessRegistration, on_delete=models.CASCADE, related_name='meal_entries')
date = models.DateField()
meal_type = models.CharField(max_length=20, choices=MEAL_TYPE)
entry_time = models.TimeField()
# Verification
card_number = models.CharField(max_length=50, blank=True)
verified_by = models.CharField(max_length=100, blank=True) # Terminal/Staff ID
class Meta:
unique_together = ['registration', 'date', 'meal_type']
ordering = ['-date', '-entry_time']
class GuestMealEntry(models.Model):
"""Guest meal entries"""
MEAL_TYPE = [
('BREAKFAST', 'Breakfast'),
('LUNCH', 'Lunch'),
('DINNER', 'Dinner'),
]
mess = models.ForeignKey(Mess, on_delete=models.CASCADE)
date = models.DateField()
meal_type = models.CharField(max_length=20, choices=MEAL_TYPE)
# Host
host = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE, related_name='guest_meals')
# Guest details
guest_count = models.IntegerField(default=1)
guest_names = models.TextField(blank=True)
# Billing
rate = models.DecimalField(max_digits=8, decimal_places=2)
total_amount = models.DecimalField(max_digits=10, decimal_places=2)
entry_time = models.TimeField()
# ==================== BILLING ====================
class MessBillPeriod(models.Model):
"""Billing periods"""
name = models.CharField(max_length=50) # e.g., "January 2024"
mess = models.ForeignKey(Mess, on_delete=models.CASCADE)
start_date = models.DateField()
end_date = models.DateField()
# Rates
per_day_rate = models.DecimalField(max_digits=8, decimal_places=2)
breakfast_rate = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
lunch_rate = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
dinner_rate = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
is_finalized = models.BooleanField(default=False)
class Meta:
unique_together = ['mess', 'start_date', 'end_date']
class MessBill(models.Model):
"""Monthly mess bills"""
STATUS = [
('GENERATED', 'Generated'),
('PAID', 'Paid'),
('PARTIAL', 'Partially Paid'),
('OVERDUE', 'Overdue'),
]
registration = models.ForeignKey(MessRegistration, on_delete=models.CASCADE, related_name='bills')
billing_period = models.ForeignKey(MessBillPeriod, on_delete=models.CASCADE)
# Bill details
bill_number = models.CharField(max_length=50, unique=True)
bill_date = models.DateField()
# Days/Meals
total_days = models.IntegerField(default=0)
rebate_days = models.IntegerField(default=0)
chargeable_days = models.IntegerField(default=0)
# Amounts
basic_charges = models.DecimalField(max_digits=10, decimal_places=2, default=0)
extra_charges = models.DecimalField(max_digits=10, decimal_places=2, default=0) # Guest meals, etc.
rebate_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0)
fine_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0)
total_amount = models.DecimalField(max_digits=10, decimal_places=2)
# Payment
amount_paid = models.DecimalField(max_digits=10, decimal_places=2, default=0)
balance_due = models.DecimalField(max_digits=10, decimal_places=2, default=0)
status = models.CharField(max_length=20, choices=STATUS, default='GENERATED')
due_date = models.DateField()
class Meta:
unique_together = ['registration', 'billing_period']
class MessPayment(models.Model):
"""Payment records"""
PAYMENT_MODE = [
('CASH', 'Cash'),
('CARD', 'Card'),
('UPI', 'UPI'),
('BANK_TRANSFER', 'Bank Transfer'),
('FEE_DEDUCTION', 'Fee Account Deduction'),
]
bill = models.ForeignKey(MessBill, on_delete=models.CASCADE, related_name='payments')
amount = models.DecimalField(max_digits=10, decimal_places=2)
payment_date = models.DateTimeField()
payment_mode = models.CharField(max_length=20, choices=PAYMENT_MODE)
transaction_id = models.CharField(max_length=100, blank=True)
received_by = models.ForeignKey(ExtraInfo, on_delete=models.SET_NULL, null=True, blank=True)
remarks = models.TextField(blank=True)
# ==================== REBATE MANAGEMENT ====================
class RebateApplication(models.Model):
"""Mess rebate applications"""
STATUS = [
('PENDING', 'Pending'),
('APPROVED', 'Approved'),
('REJECTED', 'Rejected'),
('CANCELLED', 'Cancelled'),
]
REASON = [
('VACATION', 'Vacation'),
('MEDICAL', 'Medical'),
('INTERNSHIP', 'Internship'),
('CONFERENCE', 'Conference/Seminar'),
('OTHER', 'Other'),
]
registration = models.ForeignKey(MessRegistration, on_delete=models.CASCADE, related_name='rebate_applications')
from_date = models.DateField()
to_date = models.DateField()
total_days = models.IntegerField()
reason = models.CharField(max_length=20, choices=REASON)
reason_details = models.TextField(blank=True)
# Documentation
supporting_document = models.FileField(upload_to='mess/rebates/', blank=True)
status = models.CharField(max_length=20, choices=STATUS, default='PENDING')
# Approval
approved_by = models.ForeignKey(ExtraInfo, on_delete=models.SET_NULL, null=True, blank=True, related_name='approved_rebates')
approved_at = models.DateTimeField(null=True, blank=True)
approval_remarks = models.TextField(blank=True)
# Rebate calculation
rebate_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0)
applied_at = models.DateTimeField(auto_now_add=True)
# ==================== FEEDBACK ====================
class MessFeedback(models.Model):
"""Mess feedback"""
registration = models.ForeignKey(MessRegistration, on_delete=models.CASCADE, related_name='feedbacks')
date = models.DateField()
meal_type = models.CharField(max_length=20)
# Ratings (1-5)
food_quality = models.IntegerField()
quantity = models.IntegerField()
hygiene = models.IntegerField()
service = models.IntegerField()
overall = models.IntegerField()
comments = models.TextField(blank=True)
suggestions = models.TextField(blank=True)
submitted_at = models.DateTimeField(auto_now_add=True)
class MessComplaint(models.Model):
"""Mess complaints"""
STATUS = [
('OPEN', 'Open'),
('IN_PROGRESS', 'In Progress'),
('RESOLVED', 'Resolved'),
('CLOSED', 'Closed'),
]
CATEGORY = [
('FOOD_QUALITY', 'Food Quality'),
('HYGIENE', 'Hygiene'),
('SERVICE', 'Service'),
('TIMING', 'Timing'),
('QUANTITY', 'Quantity'),
('OTHER', 'Other'),
]
registration = models.ForeignKey(MessRegistration, on_delete=models.CASCADE, related_name='complaints')
category = models.CharField(max_length=20, choices=CATEGORY)
subject = models.CharField(max_length=200)
description = models.TextField()
# Evidence
image = models.ImageField(upload_to='mess/complaints/', blank=True)
status = models.CharField(max_length=20, choices=STATUS, default='OPEN')
# Resolution
resolved_by = models.ForeignKey(ExtraInfo, on_delete=models.SET_NULL, null=True, blank=True)
resolution = models.TextField(blank=True)
resolved_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
# ==================== INVENTORY (BASIC) ====================
class MessInventoryItem(models.Model):
"""Mess inventory items"""
mess = models.ForeignKey(Mess, on_delete=models.CASCADE, related_name='inventory')
name = models.CharField(max_length=100)
category = models.CharField(max_length=50) # Groceries, Vegetables, Dairy, etc.
unit = models.CharField(max_length=20) # kg, ltr, pcs, etc.
current_stock = models.DecimalField(max_digits=10, decimal_places=2, default=0)
minimum_stock = models.DecimalField(max_digits=10, decimal_places=2, default=0)
last_updated = models.DateTimeField(auto_now=True)
# ==================== MESS COMMITTEE ====================
class MessCommitteeMember(models.Model):
"""Mess committee members"""
ROLE = [
('CONVENER', 'Convener'),
('MEMBER', 'Member'),
('SECRETARY', 'Secretary'),
]
mess = models.ForeignKey(Mess, on_delete=models.CASCADE, related_name='committee')
member = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE)
role = models.CharField(max_length=20, choices=ROLE, default='MEMBER')
# Term
from_date = models.DateField()
to_date = models.DateField(null=True, blank=True)
is_active = models.BooleanField(default=True)