adding devices

This commit is contained in:
Flatlogic Bot 2026-02-05 12:52:12 +00:00
parent 5c5625595e
commit a123d9bb27
10 changed files with 728 additions and 43 deletions

View File

@ -191,12 +191,7 @@
<div class="px-4 mt-2">
<div class="d-flex gap-2 mb-2">
<select id="customerSelect" class="form-select form-select-sm shadow-none" onchange="onCustomerChange()">
<option value="">{% trans "Walking Customer" %}</option>
{% for customer in customers %}
<option value="{{ customer.id }}">{{ customer.name }}</option>
{% endfor %}
</select>
<div class="position-relative w-100"><input type="hidden" id="customerSelect" value="" onchange="onCustomerChange()"><div class="input-group input-group-sm"><span class="input-group-text bg-white border-end-0"><i class="bi bi-search"></i></span><input type="text" id="customerSearchInput" class="form-control border-start-0 shadow-none" placeholder="{% trans 'Search Name / Phone...' %}" autocomplete="off"><button class="btn btn-outline-secondary border-start-0" type="button" onclick="clearCustomerSelection()" id="clearCustomerBtn" style="display:none;"><i class="bi bi-x"></i></button></div><div id="customerSearchResults" class="list-group position-absolute w-100 shadow-sm d-none" style="z-index: 1050; max-height: 250px; overflow-y: auto; top: 100%;"></div></div>
<button class="btn btn-sm btn-outline-primary shadow-none" data-bs-toggle="modal" data-bs-target="#addCustomerModal">
<i class="bi bi-person-plus"></i>
</button>
@ -1013,6 +1008,8 @@
if (data.success) {
cart = data.items;
document.getElementById('customerSelect').value = data.customer_id || "";
document.getElementById('customerSearchInput').value = data.customer_name || "";
document.getElementById('clearCustomerBtn').style.display = data.customer_id ? 'block' : 'none';
renderCart();
onCustomerChange();
updateHeldCount();
@ -1082,6 +1079,74 @@
}
});
}
// Customer Search Logic
let searchTimeout;
document.getElementById('customerSearchInput').addEventListener('input', function(e) {
const query = e.target.value.trim();
const resultsContainer = document.getElementById('customerSearchResults');
const clearBtn = document.getElementById('clearCustomerBtn');
if (query.length > 0) {
clearBtn.style.display = 'block';
} else {
clearBtn.style.display = 'none';
resultsContainer.classList.add('d-none');
return;
}
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
fetch(`{% url 'search_customers_api' %}?q=${encodeURIComponent(query)}`)
.then(res => res.json())
.then(data => {
resultsContainer.innerHTML = '';
if (data.results.length > 0) {
data.results.forEach(c => {
const item = document.createElement('button');
item.className = 'list-group-item list-group-item-action py-2 text-start';
item.innerHTML = `
<div>
<div class="fw-bold small">${c.name}</div>
<div class="text-muted" style="font-size: 0.7rem;">${c.phone || ''}</div>
</div>
`;
item.onclick = () => selectCustomer(c.id, c.name);
resultsContainer.appendChild(item);
});
resultsContainer.classList.remove('d-none');
} else {
resultsContainer.innerHTML = '<div class="list-group-item text-muted small">{% trans "No results found" %}</div>';
resultsContainer.classList.remove('d-none');
}
});
}, 300);
});
function selectCustomer(id, name) {
document.getElementById('customerSelect').value = id;
document.getElementById('customerSearchInput').value = name;
document.getElementById('customerSearchResults').classList.add('d-none');
document.getElementById('clearCustomerBtn').style.display = 'block';
onCustomerChange();
}
function clearCustomerSelection() {
document.getElementById('customerSelect').value = '';
document.getElementById('customerSearchInput').value = '';
document.getElementById('customerSearchResults').classList.add('d-none');
document.getElementById('clearCustomerBtn').style.display = 'none';
onCustomerChange();
}
document.addEventListener('click', function(e) {
const container = document.getElementById('customerSearchResults');
const input = document.getElementById('customerSearchInput');
const clearBtn = document.getElementById('clearCustomerBtn');
if (container && !container.contains(e.target) && e.target !== input && e.target !== clearBtn) {
container.classList.add('d-none');
}
});
</script>
{% endlocalize %}
{% endblock %}

View File

@ -35,6 +35,11 @@
<i class="bi bi-credit-card me-2"></i>{% trans "Payment Methods" %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link fw-bold px-4" id="devices-tab" data-bs-toggle="pill" data-bs-target="#devices" type="button" role="tab">
<i class="bi bi-printer me-2"></i>{% trans "Devices" %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link fw-bold px-4" id="loyalty-tab" data-bs-toggle="pill" data-bs-target="#loyalty" type="button" role="tab">
<i class="bi bi-star me-2"></i>{% trans "Loyalty System" %}
@ -289,6 +294,147 @@
</div>
</div>
<!-- Devices Tab -->
<div class="tab-pane fade" id="devices" role="tabpanel">
<div class="card shadow-sm border-0 glassmorphism mb-4">
<div class="card-header bg-transparent border-0 py-3 d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0 fw-bold">{% trans "Hardware Devices" %}</h5>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addDeviceModal">
<i class="bi bi-plus-lg me-1"></i> {% trans "Add Device" %}
</button>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">{% trans "Name" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Connection" %}</th>
<th>{% trans "Status" %}</th>
<th class="text-end pe-4">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for device in devices %}
<tr>
<td class="ps-4 fw-bold">{{ device.name }}</td>
<td>{{ device.get_device_type_display }}</td>
<td>
{{ device.get_connection_type_display }}
{% if device.ip_address %}
<small class="d-block text-muted">{{ device.ip_address }}{% if device.port %}:{{ device.port }}{% endif %}</small>
{% endif %}
</td>
<td>
{% if device.is_active %}
<span class="badge bg-success-soft text-success">{% trans "Active" %}</span>
{% else %}
<span class="badge bg-danger-soft text-danger">{% trans "Inactive" %}</span>
{% endif %}
</td>
<td class="text-end pe-4">
<button class="btn btn-sm btn-light text-primary" data-bs-toggle="modal" data-bs-target="#editDeviceModal{{ device.id }}">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-light text-danger" data-bs-toggle="modal" data-bs-target="#deleteDeviceModal{{ device.id }}">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
<!-- Edit Device Modal -->
<div class="modal fade" id="editDeviceModal{{ device.id }}" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0">
<form action="{% url 'edit_device' device.id %}" method="post">
{% csrf_token %}
<div class="modal-header">
<h5 class="modal-title fw-bold">{% trans "Edit Device" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label fw-semibold">{% trans "Device Name" %}</label>
<input type="text" name="name" class="form-control" value="{{ device.name }}" required>
</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Type" %}</label>
<select name="device_type" class="form-select">
<option value="printer" {% if device.device_type == 'printer' %}selected{% endif %}>{% trans "Printer" %}</option>
<option value="scanner" {% if device.device_type == 'scanner' %}selected{% endif %}>{% trans "Scanner" %}</option>
<option value="scale" {% if device.device_type == 'scale' %}selected{% endif %}>{% trans "Weight Scale" %}</option>
<option value="display" {% if device.device_type == 'display' %}selected{% endif %}>{% trans "Customer Display" %}</option>
<option value="other" {% if device.device_type == 'other' %}selected{% endif %}>{% trans "Other" %}</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Connection" %}</label>
<select name="connection_type" class="form-select">
<option value="network" {% if device.connection_type == 'network' %}selected{% endif %}>{% trans "Network (IP)" %}</option>
<option value="usb" {% if device.connection_type == 'usb' %}selected{% endif %}>{% trans "USB" %}</option>
<option value="bluetooth" {% if device.connection_type == 'bluetooth' %}selected{% endif %}>{% trans "Bluetooth" %}</option>
</select>
</div>
<div class="col-md-8">
<label class="form-label fw-semibold">{% trans "IP Address" %}</label>
<input type="text" name="ip_address" class="form-control" value="{{ device.ip_address|default:'' }}">
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">{% trans "Port" %}</label>
<input type="number" name="port" class="form-control" value="{{ device.port|default:'' }}">
</div>
</div>
<div class="form-check form-switch mt-3">
<input class="form-check-input" type="checkbox" name="is_active" {% if device.is_active %}checked{% endif %}>
<label class="form-check-label">{% trans "Active" %}</label>
</div>
</div>
<div class="modal-footer bg-light border-0">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<button type="submit" class="btn btn-primary">{% trans "Save Changes" %}</button>
</div>
</form>
</div>
</div>
</div>
<!-- Delete Device Modal -->
<div class="modal fade" id="deleteDeviceModal{{ device.id }}" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0">
<div class="modal-header">
<h5 class="modal-title fw-bold">{% trans "Confirm Delete" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>{% trans "Are you sure you want to delete" %} <strong>{{ device.name }}</strong>?</p>
</div>
<div class="modal-footer bg-light border-0">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<a href="{% url 'delete_device' device.id %}" class="btn btn-danger">{% trans "Delete" %}</a>
</div>
</div>
</div>
</div>
{% empty %}
<tr>
<td colspan="5" class="text-center py-5">
<div class="text-muted">
<i class="bi bi-printer fs-1 d-block mb-3"></i>
{% trans "No devices configured." %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Loyalty Tab -->
<div class="tab-pane fade" id="loyalty" role="tabpanel">
<div class="card shadow-sm border-0 glassmorphism mb-4">
@ -557,6 +703,63 @@
</div>
</div>
<!-- Add Device Modal -->
<div class="modal fade" id="addDeviceModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<form action="{% url 'add_device' %}" method="post">
{% csrf_token %}
<div class="modal-header">
<h5 class="modal-title fw-bold">{% trans "Add New Device" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label fw-semibold">{% trans "Device Name" %}</label>
<input type="text" name="name" class="form-control" required placeholder="e.g. Kitchen Printer">
</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Type" %}</label>
<select name="device_type" class="form-select">
<option value="printer">{% trans "Printer" %}</option>
<option value="scanner">{% trans "Scanner" %}</option>
<option value="scale">{% trans "Weight Scale" %}</option>
<option value="display">{% trans "Customer Display" %}</option>
<option value="other">{% trans "Other" %}</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Connection" %}</label>
<select name="connection_type" class="form-select">
<option value="network" selected>{% trans "Network (IP)" %}</option>
<option value="usb">{% trans "USB" %}</option>
<option value="bluetooth">{% trans "Bluetooth" %}</option>
</select>
</div>
<div class="col-md-8">
<label class="form-label fw-semibold">{% trans "IP Address" %}</label>
<input type="text" name="ip_address" class="form-control" placeholder="192.168.1.100">
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">{% trans "Port" %}</label>
<input type="number" name="port" class="form-control" placeholder="9100">
</div>
</div>
<div class="form-check form-switch mt-3">
<input class="form-check-input" type="checkbox" name="is_active" checked>
<label class="form-check-label">{% trans "Active" %}</label>
</div>
</div>
<div class="modal-footer bg-light border-0">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button>
<button type="submit" class="btn btn-primary">{% trans "Save Device" %}</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}

View File

@ -80,6 +80,7 @@ urlpatterns = [
path('customers/edit/<int:pk>/', views.edit_customer, name='edit_customer'),
path('customers/delete/<int:pk>/', views.delete_customer, name='delete_customer'),
path('api/add-customer-ajax/', views.add_customer_ajax, name='add_customer_ajax'),
path('api/search-customers/', views.search_customers_api, name='search_customers_api'),
# Suppliers
path('suppliers/add/', views.add_supplier, name='add_supplier'),
@ -118,7 +119,13 @@ urlpatterns = [
path('settings/loyalty/edit/<int:pk>/', views.edit_loyalty_tier, name='edit_loyalty_tier'),
path('settings/loyalty/delete/<int:pk>/', views.delete_loyalty_tier, name='delete_loyalty_tier'),
path('api/customer-loyalty/<int:pk>/', views.get_customer_loyalty_api, name='get_customer_loyalty_api'),
# WhatsApp
path('api/send-invoice-whatsapp/', views.send_invoice_whatsapp, name='send_invoice_whatsapp'),
path('api/test-whatsapp/', views.test_whatsapp_connection, name='test_whatsapp_connection'),
# Devices
path('settings/devices/add/', views.add_device, name='add_device'),
path('settings/devices/edit/<int:pk>/', views.edit_device, name='edit_device'),
path('settings/devices/delete/<int:pk>/', views.delete_device, name='delete_device'),
]

View File

@ -23,7 +23,7 @@ from .models import ( Expense, ExpenseCategory,
Quotation, QuotationItem,
SaleReturn, SaleReturnItem, PurchaseReturn, PurchaseReturnItem,
PaymentMethod, HeldSale, LoyaltyTier, LoyaltyTransaction
)
, Device)
import json
from datetime import timedelta
from django.utils import timezone
@ -1019,11 +1019,13 @@ def settings_view(request):
payment_methods = PaymentMethod.objects.all().order_by("name_en")
loyalty_tiers = LoyaltyTier.objects.all().order_by("min_points")
devices = Device.objects.all().order_by("name")
context = {
"settings": settings,
"payment_methods": payment_methods,
"loyalty_tiers": loyalty_tiers
"loyalty_tiers": loyalty_tiers,
"devices": devices
}
return render(request, "core/settings.html", context)
@ -1645,6 +1647,7 @@ def recall_held_sale_api(request, pk):
data = {
'success': True,
'customer_id': held_sale.customer.id if held_sale.customer else None,
'customer_name': held_sale.customer.name if held_sale.customer else "",
'items': held_sale.cart_data,
'total_amount': float(held_sale.total_amount),
'notes': held_sale.notes
@ -2355,3 +2358,62 @@ def test_whatsapp_connection(request):
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
return JsonResponse({'success': False, 'error': _("Invalid request method.")})
@login_required
def add_device(request):
if request.method == 'POST':
name = request.POST.get('name')
device_type = request.POST.get('device_type')
connection_type = request.POST.get('connection_type')
ip_address = request.POST.get('ip_address')
port = request.POST.get('port')
is_active = request.POST.get('is_active') == 'on'
Device.objects.create(
name=name,
device_type=device_type,
connection_type=connection_type,
ip_address=ip_address if ip_address else None,
port=port if port else None,
is_active=is_active
)
messages.success(request, _("Device added successfully!"))
return redirect(reverse('settings') + '#devices')
@login_required
def edit_device(request, pk):
device = get_object_or_404(Device, pk=pk)
if request.method == 'POST':
device.name = request.POST.get('name')
device.device_type = request.POST.get('device_type')
device.connection_type = request.POST.get('connection_type')
device.ip_address = request.POST.get('ip_address')
device.port = request.POST.get('port')
device.is_active = request.POST.get('is_active') == 'on'
if not device.ip_address:
device.ip_address = None
if not device.port:
device.port = None
device.save()
messages.success(request, _("Device updated successfully!"))
return redirect(reverse('settings') + '#devices')
@login_required
def delete_device(request, pk):
device = get_object_or_404(Device, pk=pk)
device.delete()
messages.success(request, _("Device deleted successfully!"))
return redirect(reverse('settings') + '#devices')
@login_required
def search_customers_api(request):
query = request.GET.get('q', '')
if query:
customers = Customer.objects.filter(
Q(name__icontains=query) | Q(phone__icontains=query)
).values('id', 'name', 'phone')[:20]
else:
customers = []
return JsonResponse({'results': list(customers)})

View File

@ -1,35 +0,0 @@
@login_required
def edit_product(request, pk):
product = get_object_or_404(Product, pk=pk)
if request.method == 'POST':
product.name_en = request.POST.get('name_en')
product.name_ar = request.POST.get('name_ar')
product.sku = request.POST.get('sku')
product.category = get_object_or_404(Category, id=request.POST.get('category'))
unit_id = request.POST.get('unit')
product.unit = get_object_or_404(Unit, id=unit_id) if unit_id else None
supplier_id = request.POST.get('supplier')
product.supplier = get_object_or_404(Supplier, id=supplier_id) if supplier_id else None
product.cost_price = request.POST.get('cost_price', 0)
product.price = request.POST.get('price', 0)
product.vat = request.POST.get('vat', 0)
product.description = request.POST.get('description', '')
product.opening_stock = request.POST.get('opening_stock', 0)
product.stock_quantity = request.POST.get('stock_quantity', 0)
product.min_stock_level = request.POST.get('min_stock_level', 0)
product.is_active = request.POST.get('is_active') == 'on'
product.has_expiry = request.POST.get('has_expiry') == 'on'
product.expiry_date = request.POST.get('expiry_date')
if not product.has_expiry:
product.expiry_date = None
if 'image' in request.FILES:
product.image = request.FILES['image']
product.save()
messages.success(request, _("Product updated successfully!"))
return redirect(reverse('inventory') + '#items')
return redirect(reverse('inventory') + '#items')

38
debug_settings.py Normal file
View File

@ -0,0 +1,38 @@
file_path = 'core/templates/core/settings.html'
with open(file_path, 'r') as f:
content = f.read()
print("File length:", len(content))
# Check context for Nav Tab
if 'id="devices-tab"' in content:
print("Devices tab already exists.")
else:
context_str = 'id="whatsapp-tab" data-bs-toggle="pill" data-bs-target="#whatsapp" type="button" role="tab">
<i class="bi bi-whatsapp me-2"></i>{% trans "WhatsApp Gateway" %}
</button>
</li>'
if context_str in content:
print("Found Nav Tab context.")
else:
print("Nav Tab context NOT found. Dumping nearby content:")
# Find rough location
idx = content.find('id="whatsapp-tab"')
if idx != -1:
print(content[idx:idx+300])
# Check context for Tab Pane
if 'id="devices" role="tabpanel"' in content:
print("Devices pane already exists.")
else:
# Try to find the end of tab content
# Look for Add Tier Modal
idx = content.find('<!-- Add Tier Modal -->')
if idx != -1:
print("Found Add Tier Modal at index:", idx)
print("Preceding content:")
print(content[idx-100:idx])
else:
print("Add Tier Modal NOT found.")

260
patch_settings_html.py Normal file
View File

@ -0,0 +1,260 @@
file_path = 'core/templates/core/settings.html'
with open(file_path, 'r') as f:
content = f.read()
# 1. Add Nav Tab
if 'id="devices-tab"' not in content:
whatsapp_tab_end = 'id="whatsapp-tab" data-bs-toggle="pill" data-bs-target="#whatsapp" type="button" role="tab">\n <i class="bi bi-whatsapp me-2"></i>{% trans "WhatsApp Gateway" %}\n </button>\n li>'
insert_str = """
<li class="nav-item" role="presentation">
<button class="nav-link fw-bold px-4" id="devices-tab" data-bs-toggle="pill" data-bs-target="#devices" type="button" role="tab">
<i class="bi bi-hdd-network me-2"></i>{% trans "Devices" %}
</button>
</li>"
if whatsapp_tab_end in content:
content = content.replace(whatsapp_tab_end, whatsapp_tab_end + insert_str)
print("Added Devices Tab Nav.")
else:
# Fallback search if exact string match fails due to whitespace
print("Could not find exact match for Nav Tab insertion. Trying simpler match.")
simple_search = '{% trans "WhatsApp Gateway" %}'
parts = content.split(simple_search)
if len(parts) > 1:
# Reconstruct slightly differently but risky
pass
# 2. Add Tab Content
if 'id="devices" role="tabpanel"' not in content:
devices_pane = """
<!-- Devices Tab -->
<div class="tab-pane fade" id="devices" role="tabpanel">
<div class="card shadow-sm border-0 glassmorphism mb-4">
<div class="card-header bg-transparent border-0 py-3 d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0 fw-bold">{% trans "Connected Devices" %}</h5>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addDeviceModal">
<i class="bi bi-plus-lg me-1"></i> {% trans "Add Device" %}
</button>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">{% trans "Device Name" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Connection" %}</th>
<th>{% trans "IP / Port" %}</th>
<th>{% trans "Status" %}</th>
<th class="text-end pe-4">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for device in devices %}
<tr>
<td class="ps-4 fw-bold">{{ device.name }}</td>
<td>
<span class="badge bg-secondary-soft text-secondary">{{ device.get_device_type_display }}</span>
</td>
<td>{{ device.get_connection_type_display }}</td>
<td>
{% if device.ip_address %}
{{ device.ip_address }}{% if device.port %}:{{ device.port }}{% endif %}
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td>
{% if device.is_active %}
<span class="badge bg-success-soft text-success">{% trans "Active" %}</span>
{% else %}
<span class="badge bg-danger-soft text-danger">{% trans "Inactive" %}</span>
{% endif %}
</td>
<td class="text-end pe-4">
<button class="btn btn-sm btn-light text-primary" data-bs-toggle="modal" data-bs-target="#editDeviceModal{{ device.id }}">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-light text-danger" data-bs-toggle="modal" data-bs-target="#deleteDeviceModal{{ device.id }}">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
<!-- Edit Device Modal -->
<div class="modal fade" id="editDeviceModal{{ device.id }}" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0">
<form action="{% url 'edit_device' device.id %}" method="post">
{% csrf_token %}
<div class="modal-header">
<h5 class="modal-title fw-bold">{% trans "Edit Device" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row g-3">
<div class="col-12">
<label class="form-label fw-semibold">{% trans "Device Name" %}</label>
<input type="text" name="name" class="form-control" value="{{ device.name }}" required>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Device Type" %}</label>
<select name="device_type" class="form-select">
<option value="printer" {% if device.device_type == 'printer' %}selected{% endif %}>{% trans "Printer" %}</option>
<option value="scanner" {% if device.device_type == 'scanner' %}selected{% endif %}>{% trans "Scanner" %}</option>
<option value="scale" {% if device.device_type == 'scale' %}selected{% endif %}>{% trans "Weight Scale" %}</option>
<option value="display" {% if device.device_type == 'display' %}selected{% endif %}>{% trans "Customer Display" %}</option>
<option value="other" {% if device.device_type == 'other' %}selected{% endif %}>{% trans "Other" %}</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Connection Type" %}</label>
<select name="connection_type" class="form-select">
<option value="network" {% if device.connection_type == 'network' %}selected{% endif %}>{% trans "Network (IP)" %}</option>
<option value="usb" {% if device.connection_type == 'usb' %}selected{% endif %}>{% trans "USB" %}</option>
<option value="bluetooth" {% if device.connection_type == 'bluetooth' %}selected{% endif %}>{% trans "Bluetooth" %}</option>
</select>
</div>
<div class="col-md-8">
<label class="form-label fw-semibold">{% trans "IP Address" %}</label>
<input type="text" name="ip_address" class="form-control" value="{{ device.ip_address|default:'' }}" placeholder="192.168.1.100">
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">{% trans "Port" %}</label>
<input type="number" name="port" class="form-control" value="{{ device.port|default:'' }}" placeholder="9100">
</div>
<div class="col-12">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="is_active" {% if device.is_active %}checked{% endif %}>
<label class="form-check-label">{% trans "Active" %}</label>
</div>
</div>
</div>
</div>
<div class="modal-footer bg-light border-0">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<button type="submit" class="btn btn-primary">{% trans "Save Changes" %}</button>
</div>
</form>
</div>
</div>
</div>
<!-- Delete Device Modal -->
<div class="modal fade" id="deleteDeviceModal{{ device.id }}" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0">
<div class="modal-header">
<h5 class="modal-title fw-bold">{% trans "Delete Device" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>{% trans "Are you sure you want to delete" %} <strong>{{ device.name }}</strong>?</p>
</div>
<div class="modal-footer bg-light border-0">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<a href="{% url 'delete_device' device.id %}" class="btn btn-danger">{% trans "Delete" %}</a>
</div>
</div>
</div>
</div>
{% empty %}
<tr>
<td colspan="6" class="text-center py-5">
<div class="text-muted">
<i class="bi bi-hdd-network fs-1 d-block mb-3"></i>
{% trans "No devices configured." %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
"
parts = content.split('<!-- Add Tier Modal -->')
if len(parts) > 1:
last_div = parts[0].rfind('</div>')
second_last_div = parts[0].rfind('</div>', 0, last_div)
if second_last_div != -1:
new_part0 = parts[0][:second_last_div] + devices_pane + parts[0][second_last_div:]
content = new_part0 + '<!-- Add Tier Modal -->' + parts[1]
print("Added Devices Tab Pane.")
else:
print("Could not find insertion point for Devices Pane.")
# 3. Add Add Device Modal
if 'id="addDeviceModal"' not in content:
modal_content = """
<!-- Add Device Modal -->
<div class="modal fade" id="addDeviceModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<form action="{% url 'add_device' %}" method="post">
{% csrf_token %}
<div class="modal-header">
<h5 class="modal-title fw-bold">{% trans "Add New Device" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row g-3">
<div class="col-12">
<label class="form-label fw-semibold">{% trans "Device Name" %}</label>
<input type="text" name="name" class="form-control" required placeholder="e.g. Kitchen Printer">
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Device Type" %}</label>
<select name="device_type" class="form-select">
<option value="printer">{% trans "Printer" %}</option>
<option value="scanner">{% trans "Scanner" %}</option>
<option value="scale">{% trans "Weight Scale" %}</option>
<option value="display">{% trans "Customer Display" %}</option>
<option value="other">{% trans "Other" %}</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Connection Type" %}</label>
<select name="connection_type" class="form-select">
<option value="network" selected>{% trans "Network (IP)" %}</option>
<option value="usb">{% trans "USB" %}</option>
<option value="bluetooth">{% trans "Bluetooth" %}</option>
</select>
</div>
<div class="col-md-8">
<label class="form-label fw-semibold">{% trans "IP Address" %}</label>
<input type="text" name="ip_address" class="form-control" placeholder="192.168.1.100">
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">{% trans "Port" %}</label>
<input type="number" name="port" class="form-control" placeholder="9100">
</div>
<div class="col-12">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="is_active" checked>
<label class="form-check-label">{% trans "Active" %}</label>
</div>
</div>
</div>
</div>
<div class="modal-footer bg-light border-0">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button>
<button type="submit" class="btn btn-primary">{% trans "Add Device" %}</button>
</div>
</form>
</div>
</div>
</div>
"
content = content.replace('{% endblock %}', modal_content + '\n{% endblock %}')
print("Added Add Device Modal.")
with open(file_path, 'w') as f:
f.write(content)

85
patch_views.py Normal file
View File

@ -0,0 +1,85 @@
import re
file_path = 'core/views.py'
with open(file_path, 'r') as f:
content = f.read()
# 1. Add Device to imports
if 'Device' not in content:
pattern = r'(from \.models import \(.*?)(\))'
replacement = r'\1, Device\2'
content = re.sub(pattern, replacement, content, flags=re.DOTALL)
print("Added Device to imports.")
# 2. Update settings_view
if 'devices = Device.objects.all()' not in content:
# Find the lines before context creation
search_str = 'loyalty_tiers = LoyaltyTier.objects.all().order_by("min_points")'
insert_str = '\n devices = Device.objects.all().order_by("name")'
content = content.replace(search_str, search_str + insert_str)
# Update context
context_search = '"loyalty_tiers": loyalty_tiers'
context_insert = ',\n "devices": devices'
content = content.replace(context_search, context_search + context_insert)
print("Updated settings_view.")
# 3. Add Device views
new_views = """
@login_required
def add_device(request):
if request.method == 'POST':
name = request.POST.get('name')
device_type = request.POST.get('device_type')
connection_type = request.POST.get('connection_type')
ip_address = request.POST.get('ip_address')
port = request.POST.get('port')
is_active = request.POST.get('is_active') == 'on'
Device.objects.create(
name=name,
device_type=device_type,
connection_type=connection_type,
ip_address=ip_address if ip_address else None,
port=port if port else None,
is_active=is_active
)
messages.success(request, _("Device added successfully!"))
return redirect(reverse('settings') + '#devices')
@login_required
def edit_device(request, pk):
device = get_object_or_404(Device, pk=pk)
if request.method == 'POST':
device.name = request.POST.get('name')
device.device_type = request.POST.get('device_type')
device.connection_type = request.POST.get('connection_type')
device.ip_address = request.POST.get('ip_address')
device.port = request.POST.get('port')
device.is_active = request.POST.get('is_active') == 'on'
if not device.ip_address:
device.ip_address = None
if not device.port:
device.port = None
device.save()
messages.success(request, _("Device updated successfully!"))
return redirect(reverse('settings') + '#devices')
@login_required
def delete_device(request, pk):
device = get_object_or_404(Device, pk=pk)
device.delete()
messages.success(request, _("Device deleted successfully!"))
return redirect(reverse('settings') + '#devices')
"""
if 'def add_device(request):' not in content:
content += new_views
print("Added Device views.")
with open(file_path, 'w') as f:
f.write(content)