Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -79,7 +79,7 @@ def load_json_data(file_key):
|
|
| 79 |
with open(filepath, 'r', encoding='utf-8') as f:
|
| 80 |
return json.load(f)
|
| 81 |
except (FileNotFoundError, json.JSONDecodeError):
|
| 82 |
-
return []
|
| 83 |
|
| 84 |
def save_json_data(file_key, data):
|
| 85 |
filepath, lock = DATA_FILES[file_key]
|
|
@@ -374,7 +374,6 @@ def stock_in():
|
|
| 374 |
for i, variant in enumerate(product.get('variants', [])):
|
| 375 |
if variant.get('id') == variant_id:
|
| 376 |
variant['stock'] = variant.get('stock', 0) + quantity
|
| 377 |
-
# Update cost price if provided, using weighted average
|
| 378 |
if cost_price_str:
|
| 379 |
new_cost = to_decimal(cost_price_str)
|
| 380 |
old_stock = variant.get('stock', 0) - quantity
|
|
@@ -406,7 +405,6 @@ def get_product_by_barcode(barcode):
|
|
| 406 |
inventory = load_json_data('inventory')
|
| 407 |
product = find_item_by_field(inventory, 'barcode', barcode)
|
| 408 |
if product:
|
| 409 |
-
# Return only variants that are in stock
|
| 410 |
active_variants = [v for v in product.get('variants', []) if v.get('stock', 0) > 0]
|
| 411 |
if active_variants:
|
| 412 |
product_copy = product.copy()
|
|
@@ -656,7 +654,7 @@ def product_roi_report():
|
|
| 656 |
if t['type'] == 'sale':
|
| 657 |
total_qty_sold += item['quantity']
|
| 658 |
elif t['type'] == 'return':
|
| 659 |
-
total_qty_sold += item['quantity']
|
| 660 |
|
| 661 |
current_stock = to_decimal(str(variant.get('stock', 0)))
|
| 662 |
cost_price = to_decimal(variant.get('cost_price', '0'))
|
|
@@ -1006,7 +1004,10 @@ BASE_TEMPLATE = """
|
|
| 1006 |
.main-content { margin-left: 0; }
|
| 1007 |
}
|
| 1008 |
[data-bs-theme="dark"] body { background-color: #212529; color: #dee2e6; }
|
| 1009 |
-
[data-bs-theme="dark"] .card, [data-bs-theme="dark"] .modal-content, [data-bs-theme="dark"] .list-group-item, [data-bs-theme="dark"] .table { background-color: #343a40; }
|
|
|
|
|
|
|
|
|
|
| 1010 |
[data-bs-theme="dark"] .table-hover>tbody>tr:hover>* { color: var(--bs-table-hover-color); background-color: rgba(255, 255, 255, 0.075); }
|
| 1011 |
.product-card { cursor: pointer; }
|
| 1012 |
.product-card:hover { border-color: var(--bs-primary); }
|
|
@@ -1321,7 +1322,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 1321 |
document.querySelectorAll('.product-card').forEach(card => {
|
| 1322 |
const productName = card.querySelector('.card-title').textContent.toLowerCase();
|
| 1323 |
const show = productName.includes(term) || card.dataset.barcode.includes(term);
|
| 1324 |
-
card.
|
| 1325 |
});
|
| 1326 |
});
|
| 1327 |
|
|
@@ -1458,7 +1459,6 @@ INVENTORY_CONTENT = """
|
|
| 1458 |
{% endfor %}
|
| 1459 |
</div>
|
| 1460 |
|
| 1461 |
-
<!-- Add Product Modal -->
|
| 1462 |
<div class="modal fade" id="addProductModal" tabindex="-1">
|
| 1463 |
<div class="modal-dialog modal-lg">
|
| 1464 |
<div class="modal-content">
|
|
@@ -1475,7 +1475,6 @@ INVENTORY_CONTENT = """
|
|
| 1475 |
<hr>
|
| 1476 |
<h6>Варианты товара</h6>
|
| 1477 |
<div id="variants-container-add">
|
| 1478 |
-
<!-- Variant fields will be added here by JS -->
|
| 1479 |
</div>
|
| 1480 |
<button type="button" class="btn btn-sm btn-outline-success mt-2" id="add-variant-btn-add">Добавить вариант</button>
|
| 1481 |
</div>
|
|
@@ -1485,7 +1484,6 @@ INVENTORY_CONTENT = """
|
|
| 1485 |
</div>
|
| 1486 |
</div>
|
| 1487 |
|
| 1488 |
-
<!-- Edit Product Modals -->
|
| 1489 |
{% for p in inventory %}
|
| 1490 |
<div class="modal fade" id="editProductModal-{{ p.id }}" tabindex="-1">
|
| 1491 |
<div class="modal-dialog modal-lg">
|
|
@@ -1520,7 +1518,6 @@ INVENTORY_CONTENT = """
|
|
| 1520 |
</div>
|
| 1521 |
{% endfor %}
|
| 1522 |
|
| 1523 |
-
<!-- Stock In Modal -->
|
| 1524 |
<div class="modal fade" id="stockInModal" tabindex="-1">
|
| 1525 |
<div class="modal-dialog">
|
| 1526 |
<div class="modal-content">
|
|
@@ -1563,7 +1560,6 @@ INVENTORY_CONTENT = """
|
|
| 1563 |
INVENTORY_SCRIPTS = """
|
| 1564 |
<script>
|
| 1565 |
document.addEventListener('DOMContentLoaded', () => {
|
| 1566 |
-
// --- Barcode Scanner Logic for Modals ---
|
| 1567 |
let currentScanner = null;
|
| 1568 |
let currentScannerContainer = null;
|
| 1569 |
document.querySelectorAll('.scan-modal-btn').forEach(btn => {
|
|
@@ -1608,7 +1604,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 1608 |
});
|
| 1609 |
});
|
| 1610 |
|
| 1611 |
-
// --- Dynamic Variant Fields Logic ---
|
| 1612 |
const createVariantRow = () => {
|
| 1613 |
const div = document.createElement('div');
|
| 1614 |
div.className = 'row g-2 mb-2 align-items-center variant-row';
|
|
@@ -1639,7 +1634,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 1639 |
btn.addEventListener('click', e => e.target.closest('.variant-row').remove());
|
| 1640 |
});
|
| 1641 |
|
| 1642 |
-
// Auto-add first variant row on add modal show
|
| 1643 |
const addProductModal = document.getElementById('addProductModal');
|
| 1644 |
addProductModal.addEventListener('shown.bs.modal', () => {
|
| 1645 |
const container = document.getElementById('variants-container-add');
|
|
@@ -1648,7 +1642,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 1648 |
}
|
| 1649 |
});
|
| 1650 |
|
| 1651 |
-
// --- Stock In Modal Logic ---
|
| 1652 |
const inventoryData = JSON.parse('{{ inventory|tojson|safe }}');
|
| 1653 |
const productSelect = document.getElementById('stockin-product');
|
| 1654 |
const variantSelect = document.getElementById('stockin-variant');
|
|
|
|
| 79 |
with open(filepath, 'r', encoding='utf-8') as f:
|
| 80 |
return json.load(f)
|
| 81 |
except (FileNotFoundError, json.JSONDecodeError):
|
| 82 |
+
return []
|
| 83 |
|
| 84 |
def save_json_data(file_key, data):
|
| 85 |
filepath, lock = DATA_FILES[file_key]
|
|
|
|
| 374 |
for i, variant in enumerate(product.get('variants', [])):
|
| 375 |
if variant.get('id') == variant_id:
|
| 376 |
variant['stock'] = variant.get('stock', 0) + quantity
|
|
|
|
| 377 |
if cost_price_str:
|
| 378 |
new_cost = to_decimal(cost_price_str)
|
| 379 |
old_stock = variant.get('stock', 0) - quantity
|
|
|
|
| 405 |
inventory = load_json_data('inventory')
|
| 406 |
product = find_item_by_field(inventory, 'barcode', barcode)
|
| 407 |
if product:
|
|
|
|
| 408 |
active_variants = [v for v in product.get('variants', []) if v.get('stock', 0) > 0]
|
| 409 |
if active_variants:
|
| 410 |
product_copy = product.copy()
|
|
|
|
| 654 |
if t['type'] == 'sale':
|
| 655 |
total_qty_sold += item['quantity']
|
| 656 |
elif t['type'] == 'return':
|
| 657 |
+
total_qty_sold += item['quantity']
|
| 658 |
|
| 659 |
current_stock = to_decimal(str(variant.get('stock', 0)))
|
| 660 |
cost_price = to_decimal(variant.get('cost_price', '0'))
|
|
|
|
| 1004 |
.main-content { margin-left: 0; }
|
| 1005 |
}
|
| 1006 |
[data-bs-theme="dark"] body { background-color: #212529; color: #dee2e6; }
|
| 1007 |
+
[data-bs-theme="dark"] .card, [data-bs-theme="dark"] .modal-content, [data-bs-theme="dark"] .list-group-item, [data-bs-theme="dark"] .table, [data-bs-theme="dark"] .accordion-item { background-color: #343a40; }
|
| 1008 |
+
[data-bs-theme="dark"] .accordion-button { background-color: #3e444a; color: #fff; }
|
| 1009 |
+
[data-bs-theme="dark"] .accordion-button:not(.collapsed) { background-color: #495057;}
|
| 1010 |
+
[data-bs-theme="dark"] .accordion-button::after { filter: invert(1) grayscale(100) brightness(200%); }
|
| 1011 |
[data-bs-theme="dark"] .table-hover>tbody>tr:hover>* { color: var(--bs-table-hover-color); background-color: rgba(255, 255, 255, 0.075); }
|
| 1012 |
.product-card { cursor: pointer; }
|
| 1013 |
.product-card:hover { border-color: var(--bs-primary); }
|
|
|
|
| 1322 |
document.querySelectorAll('.product-card').forEach(card => {
|
| 1323 |
const productName = card.querySelector('.card-title').textContent.toLowerCase();
|
| 1324 |
const show = productName.includes(term) || card.dataset.barcode.includes(term);
|
| 1325 |
+
card.style.display = show ? '' : 'none';
|
| 1326 |
});
|
| 1327 |
});
|
| 1328 |
|
|
|
|
| 1459 |
{% endfor %}
|
| 1460 |
</div>
|
| 1461 |
|
|
|
|
| 1462 |
<div class="modal fade" id="addProductModal" tabindex="-1">
|
| 1463 |
<div class="modal-dialog modal-lg">
|
| 1464 |
<div class="modal-content">
|
|
|
|
| 1475 |
<hr>
|
| 1476 |
<h6>Варианты товара</h6>
|
| 1477 |
<div id="variants-container-add">
|
|
|
|
| 1478 |
</div>
|
| 1479 |
<button type="button" class="btn btn-sm btn-outline-success mt-2" id="add-variant-btn-add">Добавить вариант</button>
|
| 1480 |
</div>
|
|
|
|
| 1484 |
</div>
|
| 1485 |
</div>
|
| 1486 |
|
|
|
|
| 1487 |
{% for p in inventory %}
|
| 1488 |
<div class="modal fade" id="editProductModal-{{ p.id }}" tabindex="-1">
|
| 1489 |
<div class="modal-dialog modal-lg">
|
|
|
|
| 1518 |
</div>
|
| 1519 |
{% endfor %}
|
| 1520 |
|
|
|
|
| 1521 |
<div class="modal fade" id="stockInModal" tabindex="-1">
|
| 1522 |
<div class="modal-dialog">
|
| 1523 |
<div class="modal-content">
|
|
|
|
| 1560 |
INVENTORY_SCRIPTS = """
|
| 1561 |
<script>
|
| 1562 |
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
| 1563 |
let currentScanner = null;
|
| 1564 |
let currentScannerContainer = null;
|
| 1565 |
document.querySelectorAll('.scan-modal-btn').forEach(btn => {
|
|
|
|
| 1604 |
});
|
| 1605 |
});
|
| 1606 |
|
|
|
|
| 1607 |
const createVariantRow = () => {
|
| 1608 |
const div = document.createElement('div');
|
| 1609 |
div.className = 'row g-2 mb-2 align-items-center variant-row';
|
|
|
|
| 1634 |
btn.addEventListener('click', e => e.target.closest('.variant-row').remove());
|
| 1635 |
});
|
| 1636 |
|
|
|
|
| 1637 |
const addProductModal = document.getElementById('addProductModal');
|
| 1638 |
addProductModal.addEventListener('shown.bs.modal', () => {
|
| 1639 |
const container = document.getElementById('variants-container-add');
|
|
|
|
| 1642 |
}
|
| 1643 |
});
|
| 1644 |
|
|
|
|
| 1645 |
const inventoryData = JSON.parse('{{ inventory|tojson|safe }}');
|
| 1646 |
const productSelect = document.getElementById('stockin-product');
|
| 1647 |
const variantSelect = document.getElementById('stockin-variant');
|