diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 05a5292..b46e844 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 43a98ce..64ce50c 100644 Binary files a/config/__pycache__/urls.cpython-311.pyc and b/config/__pycache__/urls.cpython-311.pyc differ diff --git a/config/settings.py b/config/settings.py index 780774b..5d50632 100644 --- a/config/settings.py +++ b/config/settings.py @@ -61,7 +61,7 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', 'core', 'accounting', - # 'hr', + 'hr', ] MIDDLEWARE = [ diff --git a/config/urls.py b/config/urls.py index d2136e6..aebc6d8 100644 --- a/config/urls.py +++ b/config/urls.py @@ -9,10 +9,10 @@ urlpatterns = [ path("i18n/", include("django.conf.urls.i18n")), path("", include("core.urls")), path("accounting/", include("accounting.urls")), - # path("hr/", include("hr.urls")), + path("hr/", include("hr.urls")), ] if settings.DEBUG: urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets") urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) - urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 2195584..6f485b3 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 48da3d8..8c5c3aa 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 5f36b04..f7d89ae 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index efce641..41dcb05 100644 --- a/core/models.py +++ b/core/models.py @@ -255,9 +255,6 @@ class Purchase(models.Model): notes = models.TextField(_("Notes"), blank=True) created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="purchases") created_at = models.DateTimeField(auto_now_add=True) - - # New field to link back to LOP - lop = models.OneToOneField('LOP', on_delete=models.SET_NULL, null=True, blank=True, related_name="purchase") def __str__(self): return f"Purchase #{self.id} - {self.supplier.name if self.supplier else 'N/A'}" @@ -307,7 +304,7 @@ class SaleReturn(models.Model): created_at = models.DateTimeField(auto_now_add=True) def __str__(self): - return f"Return #{self.id} for Sale #{self.sale.id if self.sale else 'N/A'}" + return f"Sale Return #{self.id} - {self.customer.name if self.customer else 'Guest'}" class SaleReturnItem(models.Model): sale_return = models.ForeignKey(SaleReturn, on_delete=models.CASCADE, related_name="items") @@ -329,74 +326,72 @@ class PurchaseReturn(models.Model): created_at = models.DateTimeField(auto_now_add=True) def __str__(self): - return f"Return #{self.id} for Purchase #{self.purchase.id if self.purchase else 'N/A'}" + return f"Purchase Return #{self.id} - {self.supplier.name if self.supplier else 'N/A'}" class PurchaseReturnItem(models.Model): purchase_return = models.ForeignKey(PurchaseReturn, on_delete=models.CASCADE, related_name="items") product = models.ForeignKey(Product, on_delete=models.CASCADE) quantity = models.DecimalField(_("Quantity"), max_digits=15, decimal_places=2) cost_price = models.DecimalField(_("Cost Price"), max_digits=12, decimal_places=3) + expiry_date = models.DateField(_("Expiry Date"), null=True, blank=True) line_total = models.DecimalField(_("Line Total"), max_digits=15, decimal_places=3) def __str__(self): return f"{self.product.name_en} x {self.quantity}" -class SystemSetting(models.Model): - business_name = models.CharField(_("Business Name"), max_length=200, default="Meezan Accounting") - address = models.TextField(_("Address"), blank=True) - phone = models.CharField(_("Phone"), max_length=50, blank=True) - email = models.EmailField(_("Email"), blank=True) - vat_number = models.CharField(_("VAT Number"), max_length=50, blank=True) - logo = models.ImageField(_("Company Logo"), upload_to="company_logos/", blank=True, null=True) - currency = models.CharField(_("Currency"), max_length=10, default="SAR") - vat_percentage = models.DecimalField(_("Default VAT %"), max_digits=5, decimal_places=2, default=15.00) - footer_text = models.TextField(_("Invoice Footer Text"), blank=True) - decimal_places = models.IntegerField(_("Decimal Places"), default=2) - - # Wablas (WhatsApp) Integration - wablas_enabled = models.BooleanField(_("Enable Wablas WhatsApp"), default=False) - wablas_server_url = models.URLField(_("Wablas Server URL"), blank=True, help_text=_("e.g., https://tegal.wablas.com")) - wablas_api_token = models.CharField(_("Wablas API Token"), max_length=255, blank=True) - wablas_secret_key = models.CharField(_("Wablas Secret Key"), max_length=255, blank=True, help_text=_("Used for verifying webhooks")) - - def __str__(self): - return "System Settings" - class HeldSale(models.Model): - user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="held_sales", null=True, blank=True) # Changed to match migration - created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="held_sales_created") # Added to match migration potentially customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, null=True, blank=True, related_name="held_sales") cart_data = models.JSONField(_("Cart Data")) total_amount = models.DecimalField(_("Total Amount"), max_digits=15, decimal_places=3) notes = models.TextField(_("Notes"), blank=True) + created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="held_sales") created_at = models.DateTimeField(auto_now_add=True) def __str__(self): - return f"Held Sale {self.id} - {self.customer.name if self.customer else 'Guest'}" + return f"Held Sale #{self.id} - {self.created_at.strftime('%Y-%m-%d %H:%M')}" -class UserProfile(models.Model): - user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile') - image = models.ImageField(_("Profile Picture"), upload_to='profile_pics/', null=True, blank=True) - phone = models.CharField(_("Phone Number"), max_length=20, blank=True) - bio = models.TextField(_("Bio"), blank=True) - role = models.CharField(max_length=20, choices=[('admin', 'Admin'), ('manager', 'Manager'), ('cashier', 'Cashier')], default='cashier') +class SystemSetting(models.Model): + business_name = models.CharField(_("Business Name"), max_length=200, default="Meezan Accounting") + address = models.TextField(_("Address"), blank=True) + phone = models.CharField(_("Phone"), max_length=20, blank=True) + email = models.EmailField(_("Email"), blank=True) + currency_symbol = models.CharField(_("Currency Symbol"), max_length=10, default="OMR") + tax_rate = models.DecimalField(_("Tax Rate (%)"), max_digits=5, decimal_places=2, default=0) + decimal_places = models.PositiveSmallIntegerField(_("Decimal Places"), default=3) + logo = models.ImageField(_("Logo"), upload_to="business_logos/", blank=True, null=True) + vat_number = models.CharField(_("VAT Number"), max_length=50, blank=True) + registration_number = models.CharField(_("Registration Number"), max_length=50, blank=True) + + # Loyalty Settings + loyalty_enabled = models.BooleanField(_("Enable Loyalty System"), default=False) + points_per_currency = models.DecimalField(_("Points Earned per Currency Unit"), max_digits=10, decimal_places=2, default=1.0) + currency_per_point = models.DecimalField(_("Currency Value per Point"), max_digits=10, decimal_places=3, default=0.010) + min_points_to_redeem = models.PositiveIntegerField(_("Minimum Points to Redeem"), default=100) + + # WhatsApp (Wablas) Settings + wablas_enabled = models.BooleanField(_("Enable WhatsApp Gateway"), default=False) + wablas_token = models.CharField(_("Wablas API Token"), max_length=255, blank=True) + wablas_server_url = models.URLField(_("Wablas Server URL"), blank=True, help_text="Example: https://console.wablas.com") + wablas_secret_key = models.CharField(_("Wablas Secret Key"), max_length=255, blank=True) def __str__(self): - return self.user.username + return self.business_name + class Device(models.Model): DEVICE_TYPES = [ - ('printer', 'Printer'), - ('scanner', 'Scanner'), - ('scale', 'Weight Scale'), - ('display', 'Customer Display'), - ('other', 'Other'), + ('printer', _('Printer')), + ('scanner', _('Scanner')), + ('scale', _('Weight Scale')), + ('display', _('Customer Display')), + ('other', _('Other')), ] CONNECTION_TYPES = [ - ('network', 'Network (IP)'), - ('usb', 'USB'), - ('bluetooth', 'Bluetooth'), + ('network', _('Network (IP)')), + ('usb', _('USB')), + ('bluetooth', _('Bluetooth')), ] + name = models.CharField(_("Device Name"), max_length=100) device_type = models.CharField(_("Device Type"), max_length=20, choices=DEVICE_TYPES) connection_type = models.CharField(_("Connection Type"), max_length=20, choices=CONNECTION_TYPES, default='network') @@ -407,33 +402,24 @@ class Device(models.Model): created_at = models.DateTimeField(auto_now_add=True) def __str__(self): - return self.name + return f"{self.name} ({self.get_device_type_display()})" -class LOP(models.Model): - STATUS_CHOICES = [ - ('draft', _('Draft')), - ('converted', _('Converted to Purchase')), - ('cancelled', _('Cancelled')), - ] - - supplier = models.ForeignKey(Supplier, on_delete=models.SET_NULL, null=True, related_name="lops") - lop_number = models.CharField(_("LOP Number"), max_length=50, blank=True) - total_amount = models.DecimalField(_("Total Amount"), max_digits=15, decimal_places=3) - status = models.CharField(_("Status"), max_length=20, choices=STATUS_CHOICES, default='draft') - date = models.DateField(_("Date"), default=timezone.now) - notes = models.TextField(_("Notes"), blank=True) - created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="lops") - created_at = models.DateTimeField(auto_now_add=True) +class UserProfile(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile") + image = models.ImageField(_("Profile Picture"), upload_to="profile_pics/", blank=True, null=True) + phone = models.CharField(_("Phone Number"), max_length=20, blank=True) + bio = models.TextField(_("Bio"), blank=True) def __str__(self): - return f"LOP #{self.id} - {self.supplier.name if self.supplier else 'N/A'}" + return self.user.username -class LOPItem(models.Model): - lop = models.ForeignKey(LOP, on_delete=models.CASCADE, related_name="items") - product = models.ForeignKey(Product, on_delete=models.CASCADE) - quantity = models.DecimalField(_("Quantity"), max_digits=15, decimal_places=2) - cost_price = models.DecimalField(_("Cost Price"), max_digits=12, decimal_places=3) - line_total = models.DecimalField(_("Line Total"), max_digits=15, decimal_places=3) +@receiver(post_save, sender=User) +def create_user_profile(sender, instance, created, **kwargs): + if created: + UserProfile.objects.create(user=instance) - def __str__(self): - return f"{self.product.name_en} x {self.quantity}" \ No newline at end of file +@receiver(post_save, sender=User) +def save_user_profile(sender, instance, **kwargs): + if not hasattr(instance, 'profile'): + UserProfile.objects.create(user=instance) + instance.profile.save() \ No newline at end of file diff --git a/core/templates/base.html b/core/templates/base.html index 6ea5e87..ab40dcd 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -88,16 +88,11 @@