diff --git a/accounting/management/commands/__pycache__/__init__.cpython-311.pyc b/accounting/management/commands/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 89fee31..0000000 Binary files a/accounting/management/commands/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/accounting/management/commands/__pycache__/setup_accounts.cpython-311.pyc b/accounting/management/commands/__pycache__/setup_accounts.cpython-311.pyc deleted file mode 100644 index 8706e38..0000000 Binary files a/accounting/management/commands/__pycache__/setup_accounts.cpython-311.pyc and /dev/null differ diff --git a/assets/pasted-20260210-182651-b15404c1.png b/assets/pasted-20260210-182651-b15404c1.png new file mode 100644 index 0000000..0748b5f Binary files /dev/null and b/assets/pasted-20260210-182651-b15404c1.png differ diff --git a/core/__pycache__/fix_db_view.cpython-311.pyc b/core/__pycache__/fix_db_view.cpython-311.pyc deleted file mode 100644 index f355ba6..0000000 Binary files a/core/__pycache__/fix_db_view.cpython-311.pyc and /dev/null differ diff --git a/core/__pycache__/fix_view.cpython-311.pyc b/core/__pycache__/fix_view.cpython-311.pyc deleted file mode 100644 index ab4a8a2..0000000 Binary files a/core/__pycache__/fix_view.cpython-311.pyc and /dev/null differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 1e00527..74be992 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/templates/core/barcode_labels.html b/core/templates/core/barcode_labels.html index d184deb..174a3f2 100644 --- a/core/templates/core/barcode_labels.html +++ b/core/templates/core/barcode_labels.html @@ -200,7 +200,7 @@ margin: 0; } - /* Direct Print Mode */ + /* Direct Print Mode Overrides */ body.direct-print-mode @page { size: auto; margin: 0; @@ -209,53 +209,91 @@ .label-sheet { position: relative; width: 210mm; - height: 297mm; + min-height: 297mm; /* Ensure full page height for sheets */ margin: 0 auto; background: white; page-break-after: always; box-sizing: border-box; + display: block; /* Default fallback */ + } + + /* Direct print container override */ + body.direct-print-mode .label-sheet { + width: auto; + min-height: auto; + page-break-after: auto; display: block; - font-size: 0; /* Remove whitespace between inline-blocks */ } .label-item { box-sizing: border-box; - display: inline-block; /* More robust than flex for print grids */ - vertical-align: top; text-align: center; overflow: hidden; - font-size: initial; /* Reset font size */ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; /* Fills the grid cell or container */ + height: 100%; + page-break-inside: avoid; } + /* --- SHEET GRIDS --- */ + /* Avery L7651 (5x13 = 65) */ .sheet-l7651 { - padding: 10.7mm 9.75mm !important; + display: grid !important; + grid-template-columns: repeat(5, 38.1mm) !important; + grid-auto-rows: 21.2mm !important; + padding: 10.7mm 9.75mm !important; + gap: 0; } - .label-l7651 { - width: 38.1mm !important; - height: 21.2mm !important; - padding: 1mm; - } - - /* Other layouts ... */ - .sheet-a4-24 { padding: 0 !important; } - .label-a4-24 { width: 70mm; height: 37.125mm; padding: 2mm; } - - .sheet-a4-40 { padding: 0 !important; } - .label-a4-40 { width: 52.5mm; height: 29.7mm; padding: 1mm; } - - .sheet-l7656 { padding: 31.95mm 13mm !important; } - .label-l7656 { - width: 46mm; height: 11.1mm; padding: 0.5mm; + .label-l7651 { padding: 1mm; } + + /* Avery L7656 (4x21 = 84) */ + .sheet-l7656 { + display: grid !important; + grid-template-columns: repeat(4, 46mm) !important; + grid-auto-rows: 11.1mm !important; + padding: 31.95mm 13mm !important; + gap: 0; } + .label-l7656 { padding: 0.5mm; } .label-l7656 .inner-flex { display: flex; flex-direction: row; justify-content: space-around; align-items: center; height: 100%; width: 100%; } .label-l7656 svg { height: 8mm !important; width: auto; } .label-l7656 .label-text { font-size: 5pt !important; width: auto !important; margin: 0 2px; } - .sheet-l7156 { padding: 15.15mm 9.75mm !important; } - .label-l7156 { width: 63.5mm; height: 38.1mm; padding: 2mm; } + /* Avery L7156 (3x7 = 21) */ + .sheet-l7156 { + display: grid !important; + grid-template-columns: repeat(3, 63.5mm) !important; + grid-auto-rows: 38.1mm !important; + padding: 15.15mm 9.75mm !important; + gap: 0; + } + .label-l7156 { padding: 2mm; } + + /* A4 24 (3x8) */ + .sheet-a4-24 { + display: grid !important; + grid-template-columns: repeat(3, 63.5mm) !important; + grid-auto-rows: 33.9mm !important; + padding: 12.9mm 9.75mm !important; + gap: 0; + } + .label-a4-24 { padding: 2mm; } + + /* A4 40 (4x10) */ + .sheet-a4-40 { + display: grid !important; + grid-template-columns: repeat(4, 48.5mm) !important; + grid-auto-rows: 25.4mm !important; + padding: 21.5mm 8mm !important; + gap: 0; + } + .label-a4-40 { padding: 1mm; } .label-item svg { max-width: 100%; @@ -568,7 +606,8 @@ document.addEventListener('DOMContentLoaded', function() { // --- PREPARE PRINT (Full Queue) --- function preparePrint() { - if (document.body.classList.contains('direct-print-mode')) return; + // Ensure we are not in direct print mode + document.body.classList.remove('direct-print-mode'); printArea.innerHTML = ''; const labelType = document.getElementById('labelType').value; @@ -595,7 +634,6 @@ document.addEventListener('DOMContentLoaded', function() { for (let s = 0; s < allLabels.length; s += labelsPerSheet) { const sheetLabels = allLabels.slice(s, s + labelsPerSheet); const sheet = document.createElement('div'); - sheet.className = `label-sheet sheet-${labelType}`; // Add specific classes if (labelType === 'a4-24') sheet.classList.add('sheet-a4-24'); @@ -603,6 +641,9 @@ document.addEventListener('DOMContentLoaded', function() { else if (labelType === 'l7651') sheet.classList.add('sheet-l7651'); else if (labelType === 'l7656') sheet.classList.add('sheet-l7656'); else if (labelType === 'l7156') sheet.classList.add('sheet-l7156'); + + sheet.classList.add('label-sheet'); + sheet.classList.add('sheet-' + labelType); printArea.appendChild(sheet); @@ -664,4 +705,4 @@ document.addEventListener('DOMContentLoaded', function() { updatePreview(); }); -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/core/views.py b/core/views.py index a594402..4d8d00f 100644 --- a/core/views.py +++ b/core/views.py @@ -18,6 +18,8 @@ import logging import base64 import os from django.conf import settings as django_settings +from django.contrib.auth.models import User, Group, Permission +from django.contrib.contenttypes.models import ContentType from .models import ( SystemSetting, Customer, Supplier, Product, Category, Unit, @@ -166,6 +168,7 @@ def index(request): @login_required def inventory(request): + logger.info("Inventory view accessed") products = Product.objects.all().order_by('name_en') categories = Category.objects.all() units = Unit.objects.all() @@ -176,6 +179,7 @@ def inventory(request): next_30_days = today + datetime.timedelta(days=30) expired_products = products.filter(has_expiry=True, expiry_date__lt=today) + expiring_soon_products = products.filter(has_expiry=True, expiry_date__gte=today, expiry_date__lte=next_30_days) settings = SystemSetting.objects.first() if not settings: settings = SystemSetting.objects.create() @@ -296,11 +300,99 @@ def profile_view(request): @login_required def user_management(request): - return render(request, 'core/users.html') + if request.method == 'POST': + action = request.POST.get('action') + + try: + if action == 'add': + username = request.POST.get('username') + email = request.POST.get('email') + password = request.POST.get('password') + group_ids = request.POST.getlist('groups') + + if User.objects.filter(username=username).exists(): + messages.error(request, _("Username already exists.")) + else: + user = User.objects.create_user(username=username, email=email, password=password) + if group_ids: + user.groups.set(group_ids) + messages.success(request, _("User created successfully.")) + + elif action == 'edit_user': + user_id = request.POST.get('user_id') + user = get_object_or_404(User, id=user_id) + + user.email = request.POST.get('email') + password = request.POST.get('password') + if password: + user.set_password(password) + user.save() + + group_ids = request.POST.getlist('groups') + user.groups.set(group_ids) + messages.success(request, _("User updated successfully.")) + + elif action == 'toggle_status': + user_id = request.POST.get('user_id') + if int(user_id) == request.user.id: + messages.error(request, _("You cannot deactivate yourself.")) + else: + user = get_object_or_404(User, id=user_id) + user.is_active = not user.is_active + user.save() + status = _("activated") if user.is_active else _("deactivated") + messages.success(request, _(f"User {status}.")) + + elif action == 'add_group': + name = request.POST.get('name') + perm_ids = request.POST.getlist('permissions') + + if Group.objects.filter(name=name).exists(): + messages.error(request, _("Group name already exists.")) + else: + group = Group.objects.create(name=name) + if perm_ids: + group.permissions.set(perm_ids) + messages.success(request, _("Group created successfully.")) + + elif action == 'edit_group': + group_id = request.POST.get('group_id') + group = get_object_or_404(Group, id=group_id) + group.name = request.POST.get('name') + group.save() + + perm_ids = request.POST.getlist('permissions') + group.permissions.set(perm_ids) + messages.success(request, _("Group updated successfully.")) + + elif action == 'delete_group': + group_id = request.POST.get('group_id') + group = get_object_or_404(Group, id=group_id) + group.delete() + messages.success(request, _("Group deleted successfully.")) + + except Exception as e: + logger.error(f"User Management Error: {e}") + messages.error(request, _(f"An error occurred: {e}")) + + return redirect('user_management') + + users = User.objects.all().order_by('username') + groups = Group.objects.all().order_by('name') + # Filter permissions to exclude internal/system apps if desired, or show all + permissions = Permission.objects.select_related('content_type').order_by('content_type__app_label', 'content_type__model') + + return render(request, 'core/users.html', { + 'users': users, + 'groups': groups, + 'permissions': permissions + }) @login_required def group_details_api(request, pk): - return JsonResponse({}) + group = get_object_or_404(Group, pk=pk) + permissions = list(group.permissions.values_list('id', flat=True)) + return JsonResponse({'permissions': permissions}) # --- POS Views --- @@ -687,13 +779,18 @@ def edit_purchase(request, pk): decimal_places = site_settings.decimal_places or 2 cart_items = [] + logger.info(f"EDIT_PURCHASE: Processing {purchase.items.count()} items for purchase {pk}") for item in purchase.items.all().select_related('product'): + # Debugging attributes + if hasattr(item, 'unit_price'): + logger.warning(f"Item {item.id} has unit_price attribute! {item.unit_price}") + cart_items.append({ 'id': item.product.id, 'name_en': item.product.name_en, 'name_ar': item.product.name_ar, 'sku': item.product.sku, - 'cost_price': float(item.unit_price), + 'cost_price': float(item.cost_price), 'quantity': float(item.quantity) })