diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index c5c0f96..6cacbdf 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000..70c5bef Binary files /dev/null and b/core/__pycache__/forms.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 1258459..747a1b1 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index ae045be..27570f7 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 7cb98ef..ff8faf4 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index bbd2353..48cfb9d 100644 --- a/core/admin.py +++ b/core/admin.py @@ -8,7 +8,8 @@ from .models import ( SaleReturn, SaleReturnItem, PurchaseReturn, PurchaseReturnItem, SystemSetting, PaymentMethod, HeldSale, - LoyaltyTier, LoyaltyTransaction + LoyaltyTier, LoyaltyTransaction, + CashierCounterRegistry ) @admin.register(Category) @@ -94,3 +95,8 @@ class DeviceAdmin(admin.ModelAdmin): list_display = ('name', 'device_type', 'connection_type', 'ip_address', 'is_active') list_filter = ('device_type', 'connection_type', 'is_active') search_fields = ('name', 'ip_address') + +@admin.register(CashierCounterRegistry) +class CashierCounterRegistryAdmin(admin.ModelAdmin): + list_display = ('cashier', 'counter', 'assigned_at') + search_fields = ('cashier__username', 'cashier__first_name', 'counter__name') \ No newline at end of file diff --git a/core/forms.py b/core/forms.py new file mode 100644 index 0000000..0dce3c9 --- /dev/null +++ b/core/forms.py @@ -0,0 +1,20 @@ +from django import forms +from .models import CashierSession + +class CashierSessionStartForm(forms.ModelForm): + class Meta: + model = CashierSession + fields = ['opening_balance', 'notes'] + widgets = { + 'opening_balance': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}), + 'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), + } + +class CashierSessionCloseForm(forms.ModelForm): + class Meta: + model = CashierSession + fields = ['closing_balance', 'notes'] + widgets = { + 'closing_balance': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}), + 'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), + } diff --git a/core/migrations/0027_alter_device_device_type_cashiercounterregistry.py b/core/migrations/0027_alter_device_device_type_cashiercounterregistry.py new file mode 100644 index 0000000..14f45c3 --- /dev/null +++ b/core/migrations/0027_alter_device_device_type_cashiercounterregistry.py @@ -0,0 +1,34 @@ +# Generated by Django 5.2.7 on 2026-02-06 06:36 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0026_purchaseorder_purchase_purchase_order_and_more'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name='device', + name='device_type', + field=models.CharField(choices=[('counter', 'POS Counter'), ('printer', 'Printer'), ('scanner', 'Scanner'), ('scale', 'Weight Scale'), ('display', 'Customer Display'), ('other', 'Other')], max_length=20, verbose_name='Device Type'), + ), + migrations.CreateModel( + name='CashierCounterRegistry', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('assigned_at', models.DateTimeField(auto_now_add=True, verbose_name='Assigned At')), + ('cashier', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='counter_assignment', to=settings.AUTH_USER_MODEL, verbose_name='Cashier')), + ('counter', models.ForeignKey(limit_choices_to={'device_type': 'counter'}, on_delete=django.db.models.deletion.CASCADE, related_name='assigned_cashiers', to='core.device', verbose_name='Counter')), + ], + options={ + 'verbose_name': 'Cashier Counter Registry', + 'verbose_name_plural': 'Cashier Counter Registries', + }, + ), + ] diff --git a/core/migrations/0028_cashiersession.py b/core/migrations/0028_cashiersession.py new file mode 100644 index 0000000..ee1242a --- /dev/null +++ b/core/migrations/0028_cashiersession.py @@ -0,0 +1,30 @@ +# Generated by Django 5.2.7 on 2026-02-06 07:28 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0027_alter_device_device_type_cashiercounterregistry'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='CashierSession', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('start_time', models.DateTimeField(auto_now_add=True, verbose_name='Start Time')), + ('end_time', models.DateTimeField(blank=True, null=True, verbose_name='End Time')), + ('opening_balance', models.DecimalField(decimal_places=3, default=0, max_digits=15, verbose_name='Opening Balance')), + ('closing_balance', models.DecimalField(blank=True, decimal_places=3, max_digits=15, null=True, verbose_name='Closing Balance')), + ('status', models.CharField(choices=[('active', 'Active'), ('closed', 'Closed')], default='active', max_length=20, verbose_name='Status')), + ('notes', models.TextField(blank=True, verbose_name='Notes')), + ('counter', models.ForeignKey(blank=True, limit_choices_to={'device_type': 'counter'}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sessions', to='core.device', verbose_name='Counter')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sessions', to=settings.AUTH_USER_MODEL, verbose_name='Cashier')), + ], + ), + ] diff --git a/core/migrations/__pycache__/0027_alter_device_device_type_cashiercounterregistry.cpython-311.pyc b/core/migrations/__pycache__/0027_alter_device_device_type_cashiercounterregistry.cpython-311.pyc new file mode 100644 index 0000000..27d6ffb Binary files /dev/null and b/core/migrations/__pycache__/0027_alter_device_device_type_cashiercounterregistry.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0028_cashiersession.cpython-311.pyc b/core/migrations/__pycache__/0028_cashiersession.cpython-311.pyc new file mode 100644 index 0000000..a2bff5d Binary files /dev/null and b/core/migrations/__pycache__/0028_cashiersession.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index cc3eb67..ccc705e 100644 --- a/core/models.py +++ b/core/models.py @@ -412,6 +412,7 @@ class SystemSetting(models.Model): class Device(models.Model): DEVICE_TYPES = [ + ('counter', _('POS Counter')), ('printer', _('Printer')), ('scanner', _('Scanner')), ('scale', _('Weight Scale')), @@ -445,6 +446,35 @@ class UserProfile(models.Model): def __str__(self): return self.user.username +class CashierCounterRegistry(models.Model): + cashier = models.OneToOneField(User, on_delete=models.CASCADE, related_name="counter_assignment", verbose_name=_("Cashier")) + counter = models.ForeignKey(Device, on_delete=models.CASCADE, related_name="assigned_cashiers", verbose_name=_("Counter"), limit_choices_to={'device_type': 'counter'}) + assigned_at = models.DateTimeField(_("Assigned At"), auto_now_add=True) + + class Meta: + verbose_name = _("Cashier Counter Registry") + verbose_name_plural = _("Cashier Counter Registries") + + def __str__(self): + return f"{self.cashier.username} - {self.counter.name}" + +class CashierSession(models.Model): + STATUS_CHOICES = [ + ('active', _('Active')), + ('closed', _('Closed')), + ] + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='sessions', verbose_name=_("Cashier")) + counter = models.ForeignKey(Device, on_delete=models.SET_NULL, null=True, blank=True, related_name='sessions', verbose_name=_("Counter"), limit_choices_to={'device_type': 'counter'}) + start_time = models.DateTimeField(_("Start Time"), auto_now_add=True) + end_time = models.DateTimeField(_("End Time"), null=True, blank=True) + opening_balance = models.DecimalField(_("Opening Balance"), max_digits=15, decimal_places=3, default=0) + closing_balance = models.DecimalField(_("Closing Balance"), max_digits=15, decimal_places=3, null=True, blank=True) + status = models.CharField(_("Status"), max_length=20, choices=STATUS_CHOICES, default='active') + notes = models.TextField(_("Notes"), blank=True) + + def __str__(self): + return f"{self.user.username} - {self.start_time.strftime('%Y-%m-%d %H:%M')}" + @receiver(post_save, sender=User) def create_user_profile(sender, instance, created, **kwargs): if created: diff --git a/core/templates/base.html b/core/templates/base.html index d566c76..b8c7d18 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -296,11 +296,11 @@