Add barcode modal with configurable barcode type setting
- Add barcode_type column to tenants table (default: code128) - Add barcode type setting in tenant settings page (Code 128, EAN-13, QR Code) - Replace barcode page link with modal button on tenant dashboard - Add barcode modal that displays product barcode using tenant's configured type - Remove old generate_barcode_page route, function, and template - Clean up unused imports in admin.py
This commit is contained in:
22
src/app.py
22
src/app.py
@@ -112,6 +112,25 @@ def update_tenant_credentials(tenant_id):
|
|||||||
flash('Credentials updated successfully.', 'success')
|
flash('Credentials updated successfully.', 'success')
|
||||||
return redirect(f'/{tenant_id}/settings')
|
return redirect(f'/{tenant_id}/settings')
|
||||||
|
|
||||||
|
@app.route('/<tenant:tenant_id>/settings/barcode', methods=['POST'])
|
||||||
|
def update_tenant_barcode_type(tenant_id):
|
||||||
|
# Get tenant
|
||||||
|
tenant = database.get_tenant(tenant_id)
|
||||||
|
if tenant is None:
|
||||||
|
return jsonify({"error": "Tenant not found"}), 404
|
||||||
|
|
||||||
|
barcode_type = request.form.get('barcode_type', '').strip()
|
||||||
|
|
||||||
|
if not barcode_type:
|
||||||
|
flash('Barcode type is required.', 'error')
|
||||||
|
return redirect(f'/{tenant_id}/settings')
|
||||||
|
|
||||||
|
# Update barcode type
|
||||||
|
database.update_tenant_barcode_type(tenant_id, barcode_type)
|
||||||
|
|
||||||
|
flash('Barcode type updated successfully.', 'success')
|
||||||
|
return redirect(f'/{tenant_id}/settings')
|
||||||
|
|
||||||
def check_basic_auth(auth_header, tenant_id):
|
def check_basic_auth(auth_header, tenant_id):
|
||||||
"""Validate Basic authentication credentials for a tenant"""
|
"""Validate Basic authentication credentials for a tenant"""
|
||||||
if not auth_header or not auth_header.startswith('Basic '):
|
if not auth_header or not auth_header.startswith('Basic '):
|
||||||
@@ -381,14 +400,13 @@ def serve_barcode(tenant_id, filename):
|
|||||||
|
|
||||||
# Import product management routes
|
# Import product management routes
|
||||||
from routes.admin import (add_product, edit_product, delete_product,
|
from routes.admin import (add_product, edit_product, delete_product,
|
||||||
generate_barcode, generate_barcode_page, manage_ar_fields)
|
generate_barcode, manage_ar_fields)
|
||||||
|
|
||||||
# Register product management routes (remove /admin/ from paths)
|
# Register product management routes (remove /admin/ from paths)
|
||||||
app.add_url_rule('/<tenant:tenant_id>/add', 'admin.add_product', add_product, methods=['GET', 'POST'])
|
app.add_url_rule('/<tenant:tenant_id>/add', 'admin.add_product', add_product, methods=['GET', 'POST'])
|
||||||
app.add_url_rule('/<tenant:tenant_id>/edit/<product_id>', 'admin.edit_product', edit_product, methods=['GET', 'POST'])
|
app.add_url_rule('/<tenant:tenant_id>/edit/<product_id>', 'admin.edit_product', edit_product, methods=['GET', 'POST'])
|
||||||
app.add_url_rule('/<tenant:tenant_id>/delete/<product_id>', 'admin.delete_product', delete_product, methods=['POST'])
|
app.add_url_rule('/<tenant:tenant_id>/delete/<product_id>', 'admin.delete_product', delete_product, methods=['POST'])
|
||||||
app.add_url_rule('/<tenant:tenant_id>/generate_barcode/<product_id>/<code_type>', 'admin.generate_barcode', generate_barcode)
|
app.add_url_rule('/<tenant:tenant_id>/generate_barcode/<product_id>/<code_type>', 'admin.generate_barcode', generate_barcode)
|
||||||
app.add_url_rule('/<tenant:tenant_id>/generate_barcode_page/<product_id>', 'admin.generate_barcode_page', generate_barcode_page)
|
|
||||||
app.add_url_rule('/<tenant:tenant_id>/ar_fields', 'admin.manage_ar_fields', manage_ar_fields, methods=['GET', 'POST'])
|
app.add_url_rule('/<tenant:tenant_id>/ar_fields', 'admin.manage_ar_fields', manage_ar_fields, methods=['GET', 'POST'])
|
||||||
|
|
||||||
# Register API routes with tenant prefix
|
# Register API routes with tenant prefix
|
||||||
|
|||||||
@@ -117,6 +117,14 @@ def init_database():
|
|||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
|
# Add barcode_type column to tenants table if it doesn't exist
|
||||||
|
try:
|
||||||
|
cursor.execute('ALTER TABLE tenants ADD COLUMN barcode_type TEXT DEFAULT "code128"')
|
||||||
|
conn.commit()
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
# Column already exists
|
||||||
|
pass
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
def migrate_from_json():
|
def migrate_from_json():
|
||||||
@@ -377,7 +385,7 @@ def get_tenant(tenant_id: str) -> Optional[Dict[str, Any]]:
|
|||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
# Check if tenant exists
|
# Check if tenant exists
|
||||||
cursor.execute('SELECT id, name, username, password FROM tenants WHERE id = ?', (tenant_id,))
|
cursor.execute('SELECT id, name, username, password, created_at, barcode_type FROM tenants WHERE id = ?', (tenant_id,))
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
|
|
||||||
if row:
|
if row:
|
||||||
@@ -385,7 +393,9 @@ def get_tenant(tenant_id: str) -> Optional[Dict[str, Any]]:
|
|||||||
'id': row['id'],
|
'id': row['id'],
|
||||||
'name': row['name'],
|
'name': row['name'],
|
||||||
'username': row['username'],
|
'username': row['username'],
|
||||||
'password': row['password']
|
'password': row['password'],
|
||||||
|
'created_at': row['created_at'],
|
||||||
|
'barcode_type': row['barcode_type'] or 'code128'
|
||||||
}
|
}
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -402,7 +412,7 @@ def get_or_create_tenant(tenant_id: str, username: str = None, password: str = N
|
|||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
# Check if tenant exists
|
# Check if tenant exists
|
||||||
cursor.execute('SELECT id, name, username, password FROM tenants WHERE id = ?', (tenant_id,))
|
cursor.execute('SELECT id, name, username, password, created_at, barcode_type FROM tenants WHERE id = ?', (tenant_id,))
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
|
|
||||||
if row:
|
if row:
|
||||||
@@ -410,7 +420,9 @@ def get_or_create_tenant(tenant_id: str, username: str = None, password: str = N
|
|||||||
'id': row['id'],
|
'id': row['id'],
|
||||||
'name': row['name'],
|
'name': row['name'],
|
||||||
'username': row['username'],
|
'username': row['username'],
|
||||||
'password': row['password']
|
'password': row['password'],
|
||||||
|
'created_at': row['created_at'],
|
||||||
|
'barcode_type': row['barcode_type'] or 'code128'
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
# Create new tenant with default credentials
|
# Create new tenant with default credentials
|
||||||
@@ -427,11 +439,17 @@ def get_or_create_tenant(tenant_id: str, username: str = None, password: str = N
|
|||||||
# Initialize default AR fields for the new tenant
|
# Initialize default AR fields for the new tenant
|
||||||
init_default_ar_fields(tenant_id)
|
init_default_ar_fields(tenant_id)
|
||||||
|
|
||||||
|
# Get the created_at timestamp and barcode_type from the database
|
||||||
|
cursor.execute('SELECT created_at, barcode_type FROM tenants WHERE id = ?', (tenant_id,))
|
||||||
|
created_row = cursor.fetchone()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': tenant_id,
|
'id': tenant_id,
|
||||||
'name': display_name,
|
'name': display_name,
|
||||||
'username': default_username,
|
'username': default_username,
|
||||||
'password': default_password
|
'password': default_password,
|
||||||
|
'created_at': created_row['created_at'] if created_row else None,
|
||||||
|
'barcode_type': created_row['barcode_type'] if created_row else 'code128'
|
||||||
}
|
}
|
||||||
|
|
||||||
def update_tenant_credentials(tenant_id: str, username: str, password: str):
|
def update_tenant_credentials(tenant_id: str, username: str, password: str):
|
||||||
@@ -448,6 +466,20 @@ def update_tenant_credentials(tenant_id: str, username: str, password: str):
|
|||||||
''', (username, password, tenant_id))
|
''', (username, password, tenant_id))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
|
def update_tenant_barcode_type(tenant_id: str, barcode_type: str):
|
||||||
|
"""Update tenant barcode type"""
|
||||||
|
# Normalize tenant_id to lowercase
|
||||||
|
tenant_id = tenant_id.lower()
|
||||||
|
|
||||||
|
with get_db() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute('''
|
||||||
|
UPDATE tenants
|
||||||
|
SET barcode_type = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = ?
|
||||||
|
''', (barcode_type, tenant_id))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
def get_all_tenants() -> List[Dict[str, Any]]:
|
def get_all_tenants() -> List[Dict[str, Any]]:
|
||||||
"""Get all tenants"""
|
"""Get all tenants"""
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
|
|||||||
@@ -1,24 +1,9 @@
|
|||||||
from flask import render_template, request, redirect, url_for, flash, jsonify, send_file
|
from flask import render_template, request, redirect, url_for, flash, jsonify
|
||||||
import os
|
import os
|
||||||
import json
|
|
||||||
import uuid
|
|
||||||
import io
|
|
||||||
from werkzeug.utils import secure_filename
|
|
||||||
import sys
|
import sys
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
||||||
import database
|
import database
|
||||||
|
|
||||||
# Import barcode generator class, but handle the case if it fails
|
|
||||||
try:
|
|
||||||
from utils.barcode_generator import BarcodeGenerator
|
|
||||||
BARCODE_GENERATOR_AVAILABLE = True
|
|
||||||
except ImportError:
|
|
||||||
BARCODE_GENERATOR_AVAILABLE = False
|
|
||||||
class BarcodeGenerator:
|
|
||||||
@staticmethod
|
|
||||||
def check_dependencies():
|
|
||||||
return {'qrcode': False, 'barcode': False, 'pillow': False}
|
|
||||||
|
|
||||||
DATA_FOLDER = os.path.join(os.path.dirname(__file__), '../data')
|
DATA_FOLDER = os.path.join(os.path.dirname(__file__), '../data')
|
||||||
PRODUCTS_FILE = os.path.join(DATA_FOLDER, 'products.json')
|
PRODUCTS_FILE = os.path.join(DATA_FOLDER, 'products.json')
|
||||||
UPLOAD_FOLDER = os.path.join(DATA_FOLDER, 'images')
|
UPLOAD_FOLDER = os.path.join(DATA_FOLDER, 'images')
|
||||||
@@ -344,40 +329,6 @@ def generate_barcode(tenant_id, product_id, code_type):
|
|||||||
# Redirect to the main barcode endpoint which generates dynamically
|
# Redirect to the main barcode endpoint which generates dynamically
|
||||||
return redirect(f'/{tenant_id}/barcodes/{product_id}_{barcode_type}.png')
|
return redirect(f'/{tenant_id}/barcodes/{product_id}_{barcode_type}.png')
|
||||||
|
|
||||||
def generate_barcode_page(tenant_id, product_id):
|
|
||||||
"""Show a page with different barcode options for a product"""
|
|
||||||
products = load_products(tenant_id)
|
|
||||||
|
|
||||||
if product_id not in products:
|
|
||||||
flash('Product not found.')
|
|
||||||
return redirect(f'/{tenant_id}/')
|
|
||||||
|
|
||||||
# Extract product data
|
|
||||||
product_data = {}
|
|
||||||
for field in products[product_id]:
|
|
||||||
field_name = field["fieldName"][1:] if field["fieldName"].startswith('_') else field["fieldName"]
|
|
||||||
product_data[field_name] = field["value"]
|
|
||||||
|
|
||||||
# Add product ID
|
|
||||||
product_data['id'] = product_id
|
|
||||||
|
|
||||||
# Check if barcode generation is available
|
|
||||||
dependency_status = {}
|
|
||||||
if BARCODE_GENERATOR_AVAILABLE:
|
|
||||||
dependency_status = BarcodeGenerator.check_dependencies()
|
|
||||||
else:
|
|
||||||
dependency_status = {
|
|
||||||
'qrcode': False,
|
|
||||||
'barcode': False,
|
|
||||||
'pillow': False
|
|
||||||
}
|
|
||||||
|
|
||||||
return render_template('admin/generate_barcode.html',
|
|
||||||
product_id=product_id,
|
|
||||||
product=product_data,
|
|
||||||
dependencies=dependency_status,
|
|
||||||
tenant_id=tenant_id)
|
|
||||||
|
|
||||||
def manage_credentials(tenant_id):
|
def manage_credentials(tenant_id):
|
||||||
"""Manage tenant login credentials"""
|
"""Manage tenant login credentials"""
|
||||||
tenant = database.get_or_create_tenant(tenant_id)
|
tenant = database.get_or_create_tenant(tenant_id)
|
||||||
|
|||||||
@@ -1,150 +0,0 @@
|
|||||||
{% extends "layout.html" %}
|
|
||||||
|
|
||||||
{% block title %}Generate Barcodes - KCAP Demo Server{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="row mt-4">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
|
||||||
<h2>Generate Barcodes for Product: {{ product_id }}</h2>
|
|
||||||
<a href="/{{ tenant_id }}/" class="btn btn-outline-secondary">Back to Products</a>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
{% if not dependencies.qrcode or not dependencies.barcode or not dependencies.pillow %}
|
|
||||||
<div class="alert alert-warning mb-4">
|
|
||||||
<h4 class="alert-heading">Missing Dependencies</h4>
|
|
||||||
<p>Some barcode generation features are unavailable because required packages are not installed:</p>
|
|
||||||
<ul>
|
|
||||||
{% if not dependencies.qrcode %}
|
|
||||||
<li><strong>QR Code:</strong> The 'qrcode' package is required for QR code generation</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if not dependencies.barcode %}
|
|
||||||
<li><strong>Barcodes:</strong> The 'python-barcode' package is required for EAN-13 and Code 128 generation</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if not dependencies.pillow %}
|
|
||||||
<li><strong>Image Processing:</strong> The 'Pillow' package is required for image processing</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
<hr>
|
|
||||||
<p class="mb-0">To install the required dependencies, run: <code>pip install -r requirements.txt</code></p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3>QR Code</h3>
|
|
||||||
</div>
|
|
||||||
<div class="card-body text-center">
|
|
||||||
<img src="{{ url_for('admin.generate_barcode', tenant_id=tenant_id, product_id=product_id, code_type='qrcode') }}" alt="QR Code" class="img-fluid mb-3" style="max-width: 250px;">
|
|
||||||
<p class="text-muted">QR Code contains a link to the product AR info.</p>
|
|
||||||
<a href="{{ url_for('admin.generate_barcode', tenant_id=tenant_id, product_id=product_id, code_type='qrcode') }}" class="btn btn-primary" download="product_{{ product_id }}_qrcode.png">Download QR Code</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3>EAN-13 Barcode</h3>
|
|
||||||
</div>
|
|
||||||
<div class="card-body text-center">
|
|
||||||
<img src="{{ url_for('admin.generate_barcode', tenant_id=tenant_id, product_id=product_id, code_type='ean13') }}" alt="EAN-13 Barcode" class="img-fluid mb-3" style="max-width: 250px;">
|
|
||||||
<p class="text-muted">Standard EAN-13 barcode format.</p>
|
|
||||||
<a href="{{ url_for('admin.generate_barcode', tenant_id=tenant_id, product_id=product_id, code_type='ean13') }}" class="btn btn-primary" download="product_{{ product_id }}_ean13.png">Download EAN-13</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3>Code 128 Barcode</h3>
|
|
||||||
</div>
|
|
||||||
<div class="card-body text-center">
|
|
||||||
<img src="{{ url_for('admin.generate_barcode', tenant_id=tenant_id, product_id=product_id, code_type='code128') }}" alt="Code 128 Barcode" class="img-fluid mb-3" style="max-width: 250px;">
|
|
||||||
<p class="text-muted">High-density alphanumeric barcode.</p>
|
|
||||||
<a href="{{ url_for('admin.generate_barcode', tenant_id=tenant_id, product_id=product_id, code_type='code128') }}" class="btn btn-primary" download="product_{{ product_id }}_code128.png">Download Code 128</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mt-4">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3>Product Information</h3>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<p><strong>Product ID:</strong> {{ product_id }}</p>
|
|
||||||
<p><strong>Price:</strong> {{ product.price }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<p><strong>AR Info URL:</strong>
|
|
||||||
<a href="{{ url_for('get_ar_info', tenant_id=tenant_id, barcode=product_id) }}" target="_blank">
|
|
||||||
{{ url_for('get_ar_info', tenant_id=tenant_id, barcode=product_id, _external=True) }}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
{% if product.image %}
|
|
||||||
<img src="{{ product.image }}" alt="Product Image" class="img-thumbnail" style="max-height: 100px;">
|
|
||||||
{% else %}
|
|
||||||
<p>No product image available</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mt-4">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3>Print All Codes</h3>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<p>Use the button below to open a printable version of all barcodes for this product.</p>
|
|
||||||
<button class="btn btn-success" onclick="window.print()">Print Barcodes</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_css %}
|
|
||||||
<style>
|
|
||||||
@media print {
|
|
||||||
.navbar, .card-header, .btn, .text-muted, .alert {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
border: none;
|
|
||||||
margin-bottom: 1cm;
|
|
||||||
}
|
|
||||||
.card-body {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.row {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.col-md-4 {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
flex: 0 0 100%;
|
|
||||||
page-break-after: always;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -61,9 +61,12 @@
|
|||||||
<a href="/{{ tenant.id }}/edit/{{ product_id }}" class="btn btn-sm btn-outline-primary">
|
<a href="/{{ tenant.id }}/edit/{{ product_id }}" class="btn btn-sm btn-outline-primary">
|
||||||
<i class="bi bi-pencil"></i> Edit
|
<i class="bi bi-pencil"></i> Edit
|
||||||
</a>
|
</a>
|
||||||
<a href="/{{ tenant.id }}/generate_barcode_page/{{ product_id }}" class="btn btn-sm btn-outline-success">
|
<button type="button" class="btn btn-sm btn-outline-success"
|
||||||
<i class="bi bi-upc-scan"></i> Barcodes
|
data-bs-toggle="modal" data-bs-target="#barcodeModal"
|
||||||
</a>
|
data-product-id="{{ product_id }}"
|
||||||
|
data-product-name="{% set matching_fields = product_data|selectattr('fieldName', 'equalto', '_name')|list %}{{ matching_fields[0].value if matching_fields else product_id }}">
|
||||||
|
<i class="bi bi-upc-scan"></i> Barcode
|
||||||
|
</button>
|
||||||
<button type="button" class="btn btn-sm btn-outline-danger"
|
<button type="button" class="btn btn-sm btn-outline-danger"
|
||||||
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
||||||
data-product-id="{{ product_id }}"
|
data-product-id="{{ product_id }}"
|
||||||
@@ -98,6 +101,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Barcode Modal -->
|
||||||
|
<div class="modal fade" id="barcodeModal" tabindex="-1" aria-labelledby="barcodeModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="barcodeModalLabel">Product Barcode</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body text-center">
|
||||||
|
<h6 id="barcode-product-name" class="mb-3"></h6>
|
||||||
|
<div class="mb-3">
|
||||||
|
<img id="barcode-image" src="" alt="Product Barcode" class="img-fluid" style="max-width: 400px;">
|
||||||
|
</div>
|
||||||
|
<p class="text-muted"><small>Product ID: <code id="barcode-product-id"></code></small></p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Delete Confirmation Modal -->
|
<!-- Delete Confirmation Modal -->
|
||||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
@@ -121,6 +146,31 @@
|
|||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
<script>
|
<script>
|
||||||
|
// Barcode modal functionality
|
||||||
|
const barcodeModal = document.getElementById('barcodeModal');
|
||||||
|
if (barcodeModal) {
|
||||||
|
barcodeModal.addEventListener('show.bs.modal', function (event) {
|
||||||
|
// Button that triggered the modal
|
||||||
|
const button = event.relatedTarget;
|
||||||
|
|
||||||
|
// Extract product info from data attributes
|
||||||
|
const productId = button.getAttribute('data-product-id');
|
||||||
|
const productName = button.getAttribute('data-product-name');
|
||||||
|
|
||||||
|
// Update modal content
|
||||||
|
const modalProductName = document.getElementById('barcode-product-name');
|
||||||
|
const modalProductId = document.getElementById('barcode-product-id');
|
||||||
|
const barcodeImage = document.getElementById('barcode-image');
|
||||||
|
|
||||||
|
modalProductName.textContent = productName;
|
||||||
|
modalProductId.textContent = productId;
|
||||||
|
|
||||||
|
// Set barcode image source using tenant's barcode type setting
|
||||||
|
const barcodeType = '{{ tenant.barcode_type or "code128" }}';
|
||||||
|
barcodeImage.src = '/{{ tenant.id }}/barcodes/' + productId + '_' + barcodeType + '.png';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Delete confirmation modal functionality
|
// Delete confirmation modal functionality
|
||||||
const deleteModal = document.getElementById('deleteModal');
|
const deleteModal = document.getElementById('deleteModal');
|
||||||
if (deleteModal) {
|
if (deleteModal) {
|
||||||
|
|||||||
@@ -39,6 +39,29 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0"><i class="bi bi-upc-scan"></i> Barcode Settings</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="text-muted">Configure the default barcode type for products.</p>
|
||||||
|
<form method="POST" action="/{{ tenant.id }}/settings/barcode">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="barcode_type" class="form-label">Barcode Type</label>
|
||||||
|
<select class="form-select" id="barcode_type" name="barcode_type" required>
|
||||||
|
<option value="code128" {% if tenant.barcode_type == 'code128' %}selected{% endif %}>Code 128</option>
|
||||||
|
<option value="ean13" {% if tenant.barcode_type == 'ean13' %}selected{% endif %}>EAN-13</option>
|
||||||
|
<option value="qr" {% if tenant.barcode_type == 'qr' %}selected{% endif %}>QR Code</option>
|
||||||
|
</select>
|
||||||
|
<div class="form-text">This will be used when displaying product barcodes</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="bi bi-save"></i> Update Barcode Type
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Custom AR Fields Section -->
|
<!-- Custom AR Fields Section -->
|
||||||
@@ -90,12 +113,7 @@
|
|||||||
<dd class="col-sm-9">{{ tenant.name }}</dd>
|
<dd class="col-sm-9">{{ tenant.name }}</dd>
|
||||||
|
|
||||||
<dt class="col-sm-3">API Endpoint:</dt>
|
<dt class="col-sm-3">API Endpoint:</dt>
|
||||||
<dd class="col-sm-9">
|
<dd class="col-sm-9"><code>{{ server_url }}/{{ tenant.id }}/arinfo</code></dd>
|
||||||
<code>{{ server_url }}/{{ tenant.id }}/arinfo</code>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary ms-2" data-bs-toggle="modal" data-bs-target="#qrModal">
|
|
||||||
<i class="bi bi-qr-code"></i> QR Code
|
|
||||||
</button>
|
|
||||||
</dd>
|
|
||||||
|
|
||||||
<dt class="col-sm-3">Created:</dt>
|
<dt class="col-sm-3">Created:</dt>
|
||||||
<dd class="col-sm-9">{{ tenant.created_at }}</dd>
|
<dd class="col-sm-9">{{ tenant.created_at }}</dd>
|
||||||
@@ -106,26 +124,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- QR Code Modal -->
|
|
||||||
<div class="modal fade" id="qrModal" tabindex="-1" aria-labelledby="qrModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="qrModalLabel">API Endpoint QR Code</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body text-center">
|
|
||||||
<p class="text-muted mb-3">Scan this QR code to access the AR API endpoint:</p>
|
|
||||||
<div class="mb-3">
|
|
||||||
<img src="/{{ tenant.id }}/qrcode/arinfo" alt="AR API QR Code" class="img-fluid" style="max-width: 300px;">
|
|
||||||
</div>
|
|
||||||
<code class="d-block mt-2">{{ server_url }}/{{ tenant.id }}/arinfo</code>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user