Compare commits
6 Commits
feature/cl
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 14cf5e84d3 | |||
| f77161da17 | |||
| 4d4dbba010 | |||
| 4f2dd12f23 | |||
| 996ecacb40 | |||
| 66b88fec03 |
@@ -3,6 +3,7 @@ from . import admin_bp
|
|||||||
from app.models import TenantModel, ProductModel, ARFieldModel, UserModel
|
from app.models import TenantModel, ProductModel, ARFieldModel, UserModel
|
||||||
from app.services import ProductService
|
from app.services import ProductService
|
||||||
from app.decorators.auth import tenant_access_required
|
from app.decorators.auth import tenant_access_required
|
||||||
|
import time
|
||||||
|
|
||||||
@admin_bp.route('/add', methods=['GET', 'POST'])
|
@admin_bp.route('/add', methods=['GET', 'POST'])
|
||||||
@tenant_access_required
|
@tenant_access_required
|
||||||
@@ -78,7 +79,9 @@ def add_product(tenant_id):
|
|||||||
})
|
})
|
||||||
|
|
||||||
field_suffix = field_name[1:] if field_name.startswith('_') else field_name
|
field_suffix = field_name[1:] if field_name.startswith('_') else field_name
|
||||||
image_path = f"/images/{product_id}_{field_suffix}.{extension}"
|
# Add timestamp for cache busting
|
||||||
|
timestamp = int(time.time())
|
||||||
|
image_path = f"/images/{product_id}_{field_suffix}.{extension}?v={timestamp}"
|
||||||
|
|
||||||
if field_name == '_image':
|
if field_name == '_image':
|
||||||
image_data = img_data
|
image_data = img_data
|
||||||
@@ -105,29 +108,6 @@ def add_product(tenant_id):
|
|||||||
"fieldType": field['fieldType']
|
"fieldType": field['fieldType']
|
||||||
})
|
})
|
||||||
|
|
||||||
# Handle backward compatibility fields
|
|
||||||
if '_name' not in [f['fieldName'] for f in custom_fields]:
|
|
||||||
name = request.form.get('name', '')
|
|
||||||
if name:
|
|
||||||
product_data.append({
|
|
||||||
"fieldName": "_name",
|
|
||||||
"label": "Product Name",
|
|
||||||
"value": name,
|
|
||||||
"editable": "true",
|
|
||||||
"fieldType": "TEXT"
|
|
||||||
})
|
|
||||||
|
|
||||||
if '_price' not in [f['fieldName'] for f in custom_fields]:
|
|
||||||
price = request.form.get('price', '')
|
|
||||||
if price:
|
|
||||||
product_data.append({
|
|
||||||
"fieldName": "_price",
|
|
||||||
"label": "Sale Price",
|
|
||||||
"value": f"${price}",
|
|
||||||
"editable": "true",
|
|
||||||
"fieldType": "TEXT"
|
|
||||||
})
|
|
||||||
|
|
||||||
# Save to database
|
# Save to database
|
||||||
ProductModel.save(product_id, tenant_id, product_data, image_data, image_mime_type)
|
ProductModel.save(product_id, tenant_id, product_data, image_data, image_mime_type)
|
||||||
|
|
||||||
@@ -217,7 +197,9 @@ def edit_product(tenant_id, product_id):
|
|||||||
})
|
})
|
||||||
|
|
||||||
field_suffix = field_name[1:] if field_name.startswith('_') else field_name
|
field_suffix = field_name[1:] if field_name.startswith('_') else field_name
|
||||||
existing_field["value"] = f"/images/{product_id}_{field_suffix}.{extension}"
|
# 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':
|
if field_name == '_image':
|
||||||
image_data = img_data
|
image_data = img_data
|
||||||
@@ -230,25 +212,6 @@ def edit_product(tenant_id, product_id):
|
|||||||
|
|
||||||
updated_product.append(existing_field)
|
updated_product.append(existing_field)
|
||||||
|
|
||||||
# Handle backward compatibility fields
|
|
||||||
if '_name' not in [f['fieldName'] for f in custom_fields]:
|
|
||||||
name = request.form.get('name', '')
|
|
||||||
if name:
|
|
||||||
for field in products[product_id]:
|
|
||||||
if field["fieldName"] == "_name":
|
|
||||||
field["value"] = name
|
|
||||||
updated_product.append(field)
|
|
||||||
break
|
|
||||||
|
|
||||||
if '_price' not in [f['fieldName'] for f in custom_fields]:
|
|
||||||
price = request.form.get('price', '')
|
|
||||||
if price:
|
|
||||||
for field in products[product_id]:
|
|
||||||
if field["fieldName"] == "_price":
|
|
||||||
field["value"] = f"${price}"
|
|
||||||
updated_product.append(field)
|
|
||||||
break
|
|
||||||
|
|
||||||
# Save to database
|
# Save to database
|
||||||
ProductModel.save(product_id, tenant_id, updated_product, image_data, image_mime_type)
|
ProductModel.save(product_id, tenant_id, updated_product, image_data, image_mime_type)
|
||||||
|
|
||||||
@@ -321,8 +284,8 @@ def manage_ar_fields(tenant_id):
|
|||||||
|
|
||||||
if action == 'add':
|
if action == 'add':
|
||||||
field_data = {
|
field_data = {
|
||||||
'fieldName': request.form.get('fieldName'),
|
'fieldName': request.form.get('fieldName', '').strip(),
|
||||||
'label': request.form.get('label'),
|
'label': request.form.get('label', '').strip(),
|
||||||
'fieldType': request.form.get('fieldType'),
|
'fieldType': request.form.get('fieldType'),
|
||||||
'editable': request.form.get('editable', 'true'),
|
'editable': request.form.get('editable', 'true'),
|
||||||
'displayOrder': int(request.form.get('displayOrder', 0))
|
'displayOrder': int(request.form.get('displayOrder', 0))
|
||||||
@@ -343,8 +306,8 @@ def manage_ar_fields(tenant_id):
|
|||||||
elif action == 'update':
|
elif action == 'update':
|
||||||
field_data = {
|
field_data = {
|
||||||
'id': int(request.form.get('field_id')),
|
'id': int(request.form.get('field_id')),
|
||||||
'fieldName': request.form.get('fieldName'),
|
'fieldName': request.form.get('fieldName', '').strip(),
|
||||||
'label': request.form.get('label'),
|
'label': request.form.get('label', '').strip(),
|
||||||
'fieldType': request.form.get('fieldType'),
|
'fieldType': request.form.get('fieldType'),
|
||||||
'editable': request.form.get('editable', 'true'),
|
'editable': request.form.get('editable', 'true'),
|
||||||
'displayOrder': int(request.form.get('displayOrder', 0))
|
'displayOrder': int(request.form.get('displayOrder', 0))
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ class ARFieldModel:
|
|||||||
"""Save or update a custom AR field"""
|
"""Save or update a custom AR field"""
|
||||||
tenant_id = tenant_id.lower()
|
tenant_id = tenant_id.lower()
|
||||||
|
|
||||||
|
# Strip whitespace from field name and label to prevent spacing issues
|
||||||
|
field_name = field_data['fieldName'].strip() if field_data.get('fieldName') else ''
|
||||||
|
label = field_data['label'].strip() if field_data.get('label') else ''
|
||||||
|
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
@@ -47,8 +51,8 @@ class ARFieldModel:
|
|||||||
display_order = ?, updated_at = CURRENT_TIMESTAMP
|
display_order = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = ? AND tenant_id = ?
|
WHERE id = ? AND tenant_id = ?
|
||||||
''', (
|
''', (
|
||||||
field_data['fieldName'],
|
field_name,
|
||||||
field_data['label'],
|
label,
|
||||||
field_data['fieldType'],
|
field_data['fieldType'],
|
||||||
field_data['editable'],
|
field_data['editable'],
|
||||||
field_data['displayOrder'],
|
field_data['displayOrder'],
|
||||||
@@ -63,8 +67,8 @@ class ARFieldModel:
|
|||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
''', (
|
''', (
|
||||||
tenant_id,
|
tenant_id,
|
||||||
field_data['fieldName'],
|
field_name,
|
||||||
field_data['label'],
|
label,
|
||||||
field_data['fieldType'],
|
field_data['fieldType'],
|
||||||
field_data['editable'],
|
field_data['editable'],
|
||||||
field_data['displayOrder']
|
field_data['displayOrder']
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class TenantModel:
|
|||||||
try:
|
try:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
'INSERT INTO tenants (id, name, username, password) VALUES (?, ?, ?, ?)',
|
'INSERT INTO tenants (id, name, username, password) VALUES (?, ?, ?, ?)',
|
||||||
(tenant_id, name, 'admin', 'password')
|
(tenant_id, name, 'admin', 'admin')
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
return TenantModel.get_by_id(tenant_id)
|
return TenantModel.get_by_id(tenant_id)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from typing import Dict, List, Any
|
from typing import Dict, List, Any
|
||||||
from app.models import ProductModel, ARFieldModel
|
from app.models import ProductModel, ARFieldModel, SettingsModel
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
class ProductService:
|
class ProductService:
|
||||||
@@ -20,8 +20,9 @@ class ProductService:
|
|||||||
if field_types.get(field['fieldName']) == 'IMAGE_URI' and field['value']:
|
if field_types.get(field['fieldName']) == 'IMAGE_URI' and field['value']:
|
||||||
# If it's already an absolute URL, leave it as is
|
# If it's already an absolute URL, leave it as is
|
||||||
if not field['value'].startswith('http'):
|
if not field['value'].startswith('http'):
|
||||||
# Build absolute URL using request host with tenant
|
# Build absolute URL using configured server URL
|
||||||
field['value'] = f"{request.url_root.rstrip('/')}/{tenant_id}{field['value']}"
|
server_url = SettingsModel.get_server_url()
|
||||||
|
field['value'] = f"{server_url}/{tenant_id}{field['value']}"
|
||||||
filtered_fields.append(field)
|
filtered_fields.append(field)
|
||||||
|
|
||||||
return filtered_fields
|
return filtered_fields
|
||||||
|
|||||||
@@ -62,25 +62,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<!-- Fallback fields for backward compatibility -->
|
|
||||||
{% if not custom_fields|selectattr('fieldName', 'equalto', '_name')|list %}
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="name" class="form-label">Product Name</label>
|
|
||||||
<input type="text" class="form-control" id="name" name="name">
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if not custom_fields|selectattr('fieldName', 'equalto', '_price')|list %}
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="price" class="form-label">Price</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-text">$</span>
|
|
||||||
<input type="text" class="form-control" id="price" name="price" pattern="[0-9]+(\.[0-9]{1,2})?" placeholder="49.99">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<a href="/{{ tenant_id }}/" class="btn btn-secondary">Cancel</a>
|
<a href="/{{ tenant_id }}/" class="btn btn-secondary">Cancel</a>
|
||||||
<button type="submit" class="btn btn-primary">Add Product</button>
|
<button type="submit" class="btn btn-primary">Add Product</button>
|
||||||
|
|||||||
@@ -111,6 +111,20 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// Trim whitespace from field name and label on form submission
|
||||||
|
document.getElementById('fieldForm').addEventListener('submit', function(e) {
|
||||||
|
var fieldNameInput = document.getElementById('fieldName');
|
||||||
|
var labelInput = document.getElementById('label');
|
||||||
|
|
||||||
|
// Trim whitespace from inputs before submission
|
||||||
|
if (fieldNameInput && !fieldNameInput.disabled) {
|
||||||
|
fieldNameInput.value = fieldNameInput.value.trim();
|
||||||
|
}
|
||||||
|
if (labelInput) {
|
||||||
|
labelInput.value = labelInput.value.trim();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function editField(button) {
|
function editField(button) {
|
||||||
// Get data from button attributes
|
// Get data from button attributes
|
||||||
var fieldData = {
|
var fieldData = {
|
||||||
@@ -121,7 +135,7 @@ function editField(button) {
|
|||||||
editable: button.getAttribute('data-editable'),
|
editable: button.getAttribute('data-editable'),
|
||||||
displayOrder: button.getAttribute('data-displayorder')
|
displayOrder: button.getAttribute('data-displayorder')
|
||||||
};
|
};
|
||||||
|
|
||||||
// Populate form
|
// Populate form
|
||||||
document.getElementById('formAction').value = 'update';
|
document.getElementById('formAction').value = 'update';
|
||||||
document.getElementById('fieldId').value = fieldData.id;
|
document.getElementById('fieldId').value = fieldData.id;
|
||||||
@@ -130,20 +144,20 @@ function editField(button) {
|
|||||||
document.getElementById('fieldType').value = fieldData.fieldType;
|
document.getElementById('fieldType').value = fieldData.fieldType;
|
||||||
document.getElementById('editable').value = fieldData.editable;
|
document.getElementById('editable').value = fieldData.editable;
|
||||||
document.getElementById('displayOrder').value = fieldData.displayOrder;
|
document.getElementById('displayOrder').value = fieldData.displayOrder;
|
||||||
|
|
||||||
// Disable field name when editing and show warning
|
// Disable field name when editing and show warning
|
||||||
document.getElementById('fieldName').disabled = true;
|
document.getElementById('fieldName').disabled = true;
|
||||||
document.getElementById('fieldNameWarning').style.display = 'block';
|
document.getElementById('fieldNameWarning').style.display = 'block';
|
||||||
|
|
||||||
// Update form heading
|
// Update form heading
|
||||||
var formCard = document.querySelector('.card:last-child .card-header h5');
|
var formCard = document.querySelector('.card:last-child .card-header h5');
|
||||||
if (formCard) {
|
if (formCard) {
|
||||||
formCard.textContent = 'Edit AR Field';
|
formCard.textContent = 'Edit AR Field';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change button text
|
// Change button text
|
||||||
document.querySelector('button[type="submit"]').textContent = 'Update Field';
|
document.querySelector('button[type="submit"]').textContent = 'Update Field';
|
||||||
|
|
||||||
// Scroll to form
|
// Scroll to form
|
||||||
document.getElementById('fieldForm').scrollIntoView({ behavior: 'smooth' });
|
document.getElementById('fieldForm').scrollIntoView({ behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
@@ -152,17 +166,17 @@ function resetForm() {
|
|||||||
document.getElementById('formAction').value = 'add';
|
document.getElementById('formAction').value = 'add';
|
||||||
document.getElementById('fieldId').value = '';
|
document.getElementById('fieldId').value = '';
|
||||||
document.getElementById('fieldForm').reset();
|
document.getElementById('fieldForm').reset();
|
||||||
|
|
||||||
// Enable field name and hide warning
|
// Enable field name and hide warning
|
||||||
document.getElementById('fieldName').disabled = false;
|
document.getElementById('fieldName').disabled = false;
|
||||||
document.getElementById('fieldNameWarning').style.display = 'none';
|
document.getElementById('fieldNameWarning').style.display = 'none';
|
||||||
|
|
||||||
// Reset form heading
|
// Reset form heading
|
||||||
var formCard = document.querySelector('.card:last-child .card-header h5');
|
var formCard = document.querySelector('.card:last-child .card-header h5');
|
||||||
if (formCard) {
|
if (formCard) {
|
||||||
formCard.textContent = 'Add/Edit AR Field';
|
formCard.textContent = 'Add/Edit AR Field';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset button text
|
// Reset button text
|
||||||
document.querySelector('button[type="submit"]').textContent = 'Save Field';
|
document.querySelector('button[type="submit"]').textContent = 'Save Field';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,35 +60,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<!-- Fallback fields for backward compatibility -->
|
|
||||||
{% if not custom_fields|selectattr('fieldName', 'equalto', '_name')|list %}
|
|
||||||
{% for field in product %}
|
|
||||||
{% if field.fieldName == '_name' %}
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="name" class="form-label">Product Name</label>
|
|
||||||
<input type="text" class="form-control" id="name" name="name" value="{{ field.value }}">
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if not custom_fields|selectattr('fieldName', 'equalto', '_price')|list %}
|
|
||||||
{% for field in product %}
|
|
||||||
{% if field.fieldName == '_price' %}
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="price" class="form-label">Price</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-text">$</span>
|
|
||||||
{% set price_value = field.value|replace('$', '') %}
|
|
||||||
<input type="text" class="form-control" id="price" name="price"
|
|
||||||
pattern="[0-9]+(\.[0-9]{1,2})?" value="{{ price_value }}" placeholder="49.99">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<a href="/{{ tenant_id }}/" class="btn btn-secondary">Cancel</a>
|
<a href="/{{ tenant_id }}/" class="btn btn-secondary">Cancel</a>
|
||||||
<button type="submit" class="btn btn-primary">Update Product</button>
|
<button type="submit" class="btn btn-primary">Update Product</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user