diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index b46e844..05a5292 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 64ce50c..43a98ce 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 5d50632..780774b 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 aebc6d8..d2136e6 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) + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 6f485b3..2195584 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 8c5c3aa..48da3d8 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 f7d89ae..5f36b04 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 41dcb05..efce641 100644 --- a/core/models.py +++ b/core/models.py @@ -255,6 +255,9 @@ 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'}" @@ -304,7 +307,7 @@ class SaleReturn(models.Model): created_at = models.DateTimeField(auto_now_add=True) def __str__(self): - return f"Sale Return #{self.id} - {self.customer.name if self.customer else 'Guest'}" + return f"Return #{self.id} for Sale #{self.sale.id if self.sale else 'N/A'}" class SaleReturnItem(models.Model): sale_return = models.ForeignKey(SaleReturn, on_delete=models.CASCADE, related_name="items") @@ -326,72 +329,74 @@ class PurchaseReturn(models.Model): created_at = models.DateTimeField(auto_now_add=True) def __str__(self): - return f"Purchase Return #{self.id} - {self.supplier.name if self.supplier else 'N/A'}" + return f"Return #{self.id} for Purchase #{self.purchase.id if self.purchase 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.created_at.strftime('%Y-%m-%d %H:%M')}" + return f"Held Sale {self.id} - {self.customer.name if self.customer else 'Guest'}" -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) +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') def __str__(self): - return self.business_name - + return self.user.username 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') @@ -402,24 +407,33 @@ class Device(models.Model): created_at = models.DateTimeField(auto_now_add=True) def __str__(self): - return f"{self.name} ({self.get_device_type_display()})" + return self.name -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) +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) def __str__(self): - return self.user.username + return f"LOP #{self.id} - {self.supplier.name if self.supplier else 'N/A'}" -@receiver(post_save, sender=User) -def create_user_profile(sender, instance, created, **kwargs): - if created: - UserProfile.objects.create(user=instance) +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 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 + def __str__(self): + return f"{self.product.name_en} x {self.quantity}" \ No newline at end of file diff --git a/core/templates/base.html b/core/templates/base.html index ab40dcd..6ea5e87 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -88,11 +88,16 @@