Remove automatic addition of ProductName and Price fields when they're not defined as custom AR fields. This ensures tenants using only custom fields won't see unwanted default fields in their products. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
338 lines
13 KiB
Python
338 lines
13 KiB
Python
from flask import render_template, request, redirect, url_for, flash, jsonify, current_app, session
|
|
from . import admin_bp
|
|
from app.models import TenantModel, ProductModel, ARFieldModel, UserModel
|
|
from app.services import ProductService
|
|
from app.decorators.auth import tenant_access_required
|
|
import time
|
|
|
|
@admin_bp.route('/add', methods=['GET', 'POST'])
|
|
@tenant_access_required
|
|
def add_product(tenant_id):
|
|
"""Add a new product"""
|
|
custom_fields = ARFieldModel.get_all(tenant_id)
|
|
|
|
# Prevent adding products if no fields are configured
|
|
if not custom_fields:
|
|
flash('Cannot add products. Please configure AR fields first.', 'warning')
|
|
return redirect(url_for('admin.manage_ar_fields', tenant_id=tenant_id))
|
|
|
|
if request.method == 'POST':
|
|
product_id = request.form.get('product_id')
|
|
|
|
if not product_id:
|
|
flash('Product ID is required.')
|
|
return render_template('admin/add_product.html', tenant_id=tenant_id, custom_fields=custom_fields)
|
|
|
|
# Check if product ID already exists
|
|
products = ProductModel.get_all(tenant_id)
|
|
if product_id in products:
|
|
flash('Product ID already exists.')
|
|
return render_template('admin/add_product.html', tenant_id=tenant_id, custom_fields=custom_fields)
|
|
|
|
# Handle backward compatibility image upload
|
|
image_data = None
|
|
image_mime_type = None
|
|
|
|
# Create product data structure based on custom fields
|
|
product_data = []
|
|
|
|
# Always include _id field
|
|
product_data.append({
|
|
"fieldName": "_id",
|
|
"label": "Item ID",
|
|
"value": product_id,
|
|
"editable": "false",
|
|
"fieldType": "TEXT"
|
|
})
|
|
|
|
# Process images after product is saved
|
|
images_to_save = []
|
|
|
|
# Add other fields based on custom configuration
|
|
for field in custom_fields:
|
|
field_name = field['fieldName']
|
|
if field_name == '_id':
|
|
continue
|
|
|
|
if field['fieldType'] == 'IMAGE_URI':
|
|
image_field_name = f'image_{field_name}'
|
|
if image_field_name in request.files:
|
|
file = request.files[image_field_name]
|
|
allowed_extensions = current_app.config['ALLOWED_EXTENSIONS']
|
|
if file and file.filename and ProductService.allowed_file(file.filename, allowed_extensions):
|
|
file.seek(0)
|
|
img_data = file.read()
|
|
|
|
extension = file.filename.rsplit('.', 1)[1].lower()
|
|
mime_types = {
|
|
'jpg': 'image/jpeg',
|
|
'jpeg': 'image/jpeg',
|
|
'png': 'image/png',
|
|
'gif': 'image/gif'
|
|
}
|
|
mime_type = mime_types.get(extension, 'image/jpeg')
|
|
|
|
images_to_save.append({
|
|
'field_name': field_name,
|
|
'data': img_data,
|
|
'mime_type': mime_type
|
|
})
|
|
|
|
field_suffix = field_name[1:] if field_name.startswith('_') else field_name
|
|
# Add timestamp for cache busting
|
|
timestamp = int(time.time())
|
|
image_path = f"/images/{product_id}_{field_suffix}.{extension}?v={timestamp}"
|
|
|
|
if field_name == '_image':
|
|
image_data = img_data
|
|
image_mime_type = mime_type
|
|
else:
|
|
image_path = ""
|
|
else:
|
|
image_path = ""
|
|
|
|
product_data.append({
|
|
"fieldName": field_name,
|
|
"label": field['label'],
|
|
"value": image_path,
|
|
"editable": field['editable'],
|
|
"fieldType": field['fieldType']
|
|
})
|
|
else:
|
|
value = request.form.get(f'field_{field_name}', '')
|
|
product_data.append({
|
|
"fieldName": field_name,
|
|
"label": field['label'],
|
|
"value": value,
|
|
"editable": field['editable'],
|
|
"fieldType": field['fieldType']
|
|
})
|
|
|
|
# Save to database
|
|
ProductModel.save(product_id, tenant_id, product_data, image_data, image_mime_type)
|
|
|
|
# Save additional images
|
|
for img in images_to_save:
|
|
ProductModel.save_image(product_id, tenant_id, img['field_name'], img['data'], img['mime_type'])
|
|
|
|
# Associate the tenant with the user if not already associated
|
|
user_id = session['user']['id']
|
|
if not UserModel.has_access_to_tenant(user_id, tenant_id):
|
|
UserModel.add_tenant(user_id, tenant_id)
|
|
|
|
flash('Product added successfully!')
|
|
return redirect(f'/{tenant_id}/')
|
|
|
|
return render_template('admin/add_product.html', tenant_id=tenant_id, custom_fields=custom_fields)
|
|
|
|
@admin_bp.route('/edit/<product_id>', methods=['GET', 'POST'])
|
|
@tenant_access_required
|
|
def edit_product(tenant_id, product_id):
|
|
"""Edit an existing product"""
|
|
products = ProductModel.get_all(tenant_id)
|
|
custom_fields = ARFieldModel.get_all(tenant_id)
|
|
|
|
if product_id not in products:
|
|
flash('Product not found.')
|
|
return redirect(f'/{tenant_id}/')
|
|
|
|
if request.method == 'POST':
|
|
image_data = None
|
|
image_mime_type = None
|
|
images_to_save = []
|
|
|
|
# Update fields based on form data
|
|
updated_product = []
|
|
|
|
# Always include _id field
|
|
for field in products[product_id]:
|
|
if field["fieldName"] == "_id":
|
|
updated_product.append(field)
|
|
break
|
|
|
|
# Process each custom field
|
|
for custom_field in custom_fields:
|
|
field_name = custom_field['fieldName']
|
|
if field_name == '_id':
|
|
continue
|
|
|
|
# Find existing field data
|
|
existing_field = None
|
|
for field in products[product_id]:
|
|
if field["fieldName"] == field_name:
|
|
existing_field = field.copy()
|
|
break
|
|
|
|
if not existing_field:
|
|
existing_field = {
|
|
"fieldName": field_name,
|
|
"label": custom_field['label'],
|
|
"value": "",
|
|
"editable": custom_field['editable'],
|
|
"fieldType": custom_field['fieldType']
|
|
}
|
|
|
|
if custom_field['fieldType'] == 'IMAGE_URI':
|
|
image_field_name = f'image_{field_name}'
|
|
if image_field_name in request.files and request.files[image_field_name].filename:
|
|
file = request.files[image_field_name]
|
|
allowed_extensions = current_app.config['ALLOWED_EXTENSIONS']
|
|
if ProductService.allowed_file(file.filename, allowed_extensions):
|
|
file.seek(0)
|
|
img_data = file.read()
|
|
|
|
extension = file.filename.rsplit('.', 1)[1].lower()
|
|
mime_types = {
|
|
'jpg': 'image/jpeg',
|
|
'jpeg': 'image/jpeg',
|
|
'png': 'image/png',
|
|
'gif': 'image/gif'
|
|
}
|
|
mime_type = mime_types.get(extension, 'image/jpeg')
|
|
|
|
images_to_save.append({
|
|
'field_name': field_name,
|
|
'data': img_data,
|
|
'mime_type': mime_type
|
|
})
|
|
|
|
field_suffix = field_name[1:] if field_name.startswith('_') else field_name
|
|
# Add timestamp for cache busting
|
|
timestamp = int(time.time())
|
|
existing_field["value"] = f"/images/{product_id}_{field_suffix}.{extension}?v={timestamp}"
|
|
|
|
if field_name == '_image':
|
|
image_data = img_data
|
|
image_mime_type = mime_type
|
|
else:
|
|
value = request.form.get(f'field_{field_name}', '')
|
|
if field_name == '_price' and value and not value.startswith('$'):
|
|
value = f"${value}"
|
|
existing_field["value"] = value
|
|
|
|
updated_product.append(existing_field)
|
|
|
|
# Save to database
|
|
ProductModel.save(product_id, tenant_id, updated_product, image_data, image_mime_type)
|
|
|
|
# Save additional images
|
|
for img in images_to_save:
|
|
ProductModel.save_image(product_id, tenant_id, img['field_name'], img['data'], img['mime_type'])
|
|
|
|
flash('Product updated successfully!')
|
|
return redirect(f'/{tenant_id}/')
|
|
|
|
return render_template('admin/edit_product.html',
|
|
product_id=product_id,
|
|
product=products[product_id],
|
|
tenant_id=tenant_id,
|
|
custom_fields=custom_fields)
|
|
|
|
@admin_bp.route('/delete/<product_id>', methods=['POST'])
|
|
@tenant_access_required
|
|
def delete_product(tenant_id, product_id):
|
|
"""Delete a product"""
|
|
products = ProductModel.get_all(tenant_id)
|
|
|
|
if product_id not in products:
|
|
flash('Product not found.')
|
|
return redirect(f'/{tenant_id}/')
|
|
|
|
ProductModel.delete(product_id, tenant_id)
|
|
flash('Product deleted successfully!')
|
|
return redirect(f'/{tenant_id}/')
|
|
|
|
@admin_bp.route('/generate_barcode/<product_id>/<code_type>')
|
|
@tenant_access_required
|
|
def generate_barcode(tenant_id, product_id, code_type):
|
|
"""Redirect to the main barcode generation endpoint"""
|
|
products = ProductModel.get_all(tenant_id)
|
|
if product_id not in products:
|
|
return jsonify({"error": "Product not found"}), 404
|
|
|
|
type_mapping = {
|
|
'qrcode': 'qr',
|
|
'ean13': 'ean13',
|
|
'code128': 'code128'
|
|
}
|
|
|
|
barcode_type = type_mapping.get(code_type, code_type)
|
|
return redirect(f'/{tenant_id}/barcodes/{product_id}_{barcode_type}.png')
|
|
|
|
@admin_bp.route('/barcodes', methods=['GET'])
|
|
@tenant_access_required
|
|
def view_all_barcodes(tenant_id):
|
|
"""Display all product barcodes for printing"""
|
|
products = ProductModel.get_all(tenant_id)
|
|
tenant = TenantModel.get_or_create(tenant_id)
|
|
|
|
barcode_type = tenant.get('barcode_type', 'qr')
|
|
|
|
return render_template('admin/all_barcodes.html',
|
|
products=products,
|
|
tenant=tenant,
|
|
barcode_type=barcode_type)
|
|
|
|
@admin_bp.route('/ar_fields', methods=['GET', 'POST'])
|
|
@tenant_access_required
|
|
def manage_ar_fields(tenant_id):
|
|
"""Manage custom AR content fields"""
|
|
tenant = TenantModel.get_or_create(tenant_id)
|
|
|
|
if request.method == 'POST':
|
|
action = request.form.get('action')
|
|
|
|
if action == 'add':
|
|
field_data = {
|
|
'fieldName': request.form.get('fieldName', '').strip(),
|
|
'label': request.form.get('label', '').strip(),
|
|
'fieldType': request.form.get('fieldType'),
|
|
'editable': request.form.get('editable', 'true'),
|
|
'displayOrder': int(request.form.get('displayOrder', 0))
|
|
}
|
|
|
|
if field_data['fieldName'] and field_data['label']:
|
|
ARFieldModel.save(tenant_id, field_data)
|
|
flash('Custom field added successfully!')
|
|
else:
|
|
flash('Field name and label are required.')
|
|
|
|
elif action == 'delete':
|
|
field_id = request.form.get('field_id')
|
|
if field_id:
|
|
ARFieldModel.delete(tenant_id, int(field_id))
|
|
flash('Custom field deleted successfully!')
|
|
|
|
elif action == 'update':
|
|
field_data = {
|
|
'id': int(request.form.get('field_id')),
|
|
'fieldName': request.form.get('fieldName', '').strip(),
|
|
'label': request.form.get('label', '').strip(),
|
|
'fieldType': request.form.get('fieldType'),
|
|
'editable': request.form.get('editable', 'true'),
|
|
'displayOrder': int(request.form.get('displayOrder', 0))
|
|
}
|
|
|
|
if field_data['fieldName'] and field_data['label']:
|
|
ARFieldModel.save(tenant_id, field_data)
|
|
flash('Custom field updated successfully!')
|
|
else:
|
|
flash('Field name and label are required.')
|
|
|
|
return redirect(url_for('admin.manage_ar_fields', tenant_id=tenant_id))
|
|
|
|
# Get existing custom fields
|
|
custom_fields = ARFieldModel.get_all(tenant_id)
|
|
|
|
# Available field types
|
|
field_types = [
|
|
'TEXT',
|
|
'IMAGE_URI'
|
|
]
|
|
|
|
return render_template('admin/ar_fields.html',
|
|
tenant=tenant,
|
|
tenant_id=tenant_id,
|
|
custom_fields=custom_fields,
|
|
field_types=field_types)
|