Update app.py
Browse files
app.py
CHANGED
|
@@ -1782,6 +1782,7 @@ SALES_SCREEN_CONTENT = """
|
|
| 1782 |
<button id="stop-scan-btn" class="btn btn-danger btn-sm mt-2">Остановить</button>
|
| 1783 |
</div>
|
| 1784 |
<input type="text" id="product-search" class="form-control mb-3" placeholder="Поиск по названию или штрих-коду...">
|
|
|
|
| 1785 |
|
| 1786 |
<div id="product-accordion" class="accordion">
|
| 1787 |
{% for letter, products in grouped_inventory %}
|
|
@@ -1896,7 +1897,6 @@ SALES_SCREEN_SCRIPTS = """
|
|
| 1896 |
<script>
|
| 1897 |
document.addEventListener('DOMContentLoaded', () => {
|
| 1898 |
const cart = {};
|
| 1899 |
-
const productGrid = document.getElementById('product-accordion');
|
| 1900 |
const cartItemsEl = document.getElementById('cart-items');
|
| 1901 |
const cartTotalEl = document.getElementById('cart-total');
|
| 1902 |
let audioCtx;
|
|
@@ -1907,6 +1907,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 1907 |
const cashierLoginModal = new bootstrap.Modal(document.getElementById('cashierLoginModal'));
|
| 1908 |
const startShiftModal = new bootstrap.Modal(document.getElementById('startShiftModal'));
|
| 1909 |
const customItemModal = new bootstrap.Modal(document.getElementById('customItemModal'));
|
|
|
|
| 1910 |
|
| 1911 |
const session = {
|
| 1912 |
cashier: null,
|
|
@@ -2026,7 +2027,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 2026 |
});
|
| 2027 |
}
|
| 2028 |
|
| 2029 |
-
|
|
|
|
| 2030 |
const card = e.target.closest('.product-card');
|
| 2031 |
if (card) {
|
| 2032 |
fetchAndHandleProduct(card.dataset.barcode);
|
|
@@ -2048,9 +2050,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 2048 |
cartItemsEl.addEventListener('click', e => {
|
| 2049 |
if (e.target.classList.contains('cart-qty-btn')) {
|
| 2050 |
const id = e.target.dataset.id;
|
| 2051 |
-
const op = parseInt(e.target.dataset.op);
|
| 2052 |
if (cart[id]) {
|
| 2053 |
-
const
|
|
|
|
| 2054 |
updateCartItemQuantity(id, newQuantity);
|
| 2055 |
}
|
| 2056 |
}
|
|
@@ -2075,30 +2078,75 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 2075 |
updateCartView();
|
| 2076 |
});
|
| 2077 |
|
| 2078 |
-
|
| 2079 |
-
|
| 2080 |
-
|
| 2081 |
-
|
| 2082 |
-
|
| 2083 |
-
|
| 2084 |
-
|
| 2085 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2086 |
});
|
| 2087 |
-
document.querySelectorAll('#product-accordion .accordion-item').forEach(accordionItem => {
|
| 2088 |
-
const collapseElement = accordionItem.querySelector('.accordion-collapse');
|
| 2089 |
-
const matchingCardsInGroup = accordionItem.querySelectorAll('.product-card:not([style*="display: none"])');
|
| 2090 |
-
const bsCollapse = bootstrap.Collapse.getOrCreateInstance(collapseElement, { toggle: false });
|
| 2091 |
|
| 2092 |
-
|
| 2093 |
-
|
| 2094 |
-
|
| 2095 |
-
|
| 2096 |
-
|
| 2097 |
-
|
| 2098 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2099 |
}
|
| 2100 |
}
|
| 2101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2102 |
});
|
| 2103 |
|
| 2104 |
const completeSale = (paymentMethod) => {
|
|
@@ -3424,4 +3472,3 @@ if __name__ == '__main__':
|
|
| 3424 |
for key in DATA_FILES.keys():
|
| 3425 |
load_json_data(key)
|
| 3426 |
app.run(debug=False, host='0.0.0.0', port=7860, use_reloader=False)
|
| 3427 |
-
|
|
|
|
| 1782 |
<button id="stop-scan-btn" class="btn btn-danger btn-sm mt-2">Остановить</button>
|
| 1783 |
</div>
|
| 1784 |
<input type="text" id="product-search" class="form-control mb-3" placeholder="Поиск по названию или штрих-коду...">
|
| 1785 |
+
<div id="product-search-results" class="d-grid gap-2" style="display: none; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));"></div>
|
| 1786 |
|
| 1787 |
<div id="product-accordion" class="accordion">
|
| 1788 |
{% for letter, products in grouped_inventory %}
|
|
|
|
| 1897 |
<script>
|
| 1898 |
document.addEventListener('DOMContentLoaded', () => {
|
| 1899 |
const cart = {};
|
|
|
|
| 1900 |
const cartItemsEl = document.getElementById('cart-items');
|
| 1901 |
const cartTotalEl = document.getElementById('cart-total');
|
| 1902 |
let audioCtx;
|
|
|
|
| 1907 |
const cashierLoginModal = new bootstrap.Modal(document.getElementById('cashierLoginModal'));
|
| 1908 |
const startShiftModal = new bootstrap.Modal(document.getElementById('startShiftModal'));
|
| 1909 |
const customItemModal = new bootstrap.Modal(document.getElementById('customItemModal'));
|
| 1910 |
+
const allProducts = {{ inventory|tojson|safe }};
|
| 1911 |
|
| 1912 |
const session = {
|
| 1913 |
cashier: null,
|
|
|
|
| 2027 |
});
|
| 2028 |
}
|
| 2029 |
|
| 2030 |
+
const productsContainer = document.querySelector('.col-lg-7 .card .card-body');
|
| 2031 |
+
productsContainer.addEventListener('click', e => {
|
| 2032 |
const card = e.target.closest('.product-card');
|
| 2033 |
if (card) {
|
| 2034 |
fetchAndHandleProduct(card.dataset.barcode);
|
|
|
|
| 2050 |
cartItemsEl.addEventListener('click', e => {
|
| 2051 |
if (e.target.classList.contains('cart-qty-btn')) {
|
| 2052 |
const id = e.target.dataset.id;
|
| 2053 |
+
const op = parseInt(e.target.dataset.op, 10);
|
| 2054 |
if (cart[id]) {
|
| 2055 |
+
const itemsPerPack = cart[id].items_per_pack || 1;
|
| 2056 |
+
let newQuantity = cart[id].quantity + (op * itemsPerPack);
|
| 2057 |
updateCartItemQuantity(id, newQuantity);
|
| 2058 |
}
|
| 2059 |
}
|
|
|
|
| 2078 |
updateCartView();
|
| 2079 |
});
|
| 2080 |
|
| 2081 |
+
const formatCurrencyJS = (value) => {
|
| 2082 |
+
try {
|
| 2083 |
+
const number = parseFloat(String(value).replace(/\\s/g, '').replace(',', '.'));
|
| 2084 |
+
if (isNaN(number)) return '0,00';
|
| 2085 |
+
return number.toLocaleString('ru-RU', {minimumFractionDigits: 2, maximumFractionDigits: 2});
|
| 2086 |
+
} catch (e) {
|
| 2087 |
+
return '0,00';
|
| 2088 |
+
}
|
| 2089 |
+
};
|
| 2090 |
+
|
| 2091 |
+
const productSearchInput = document.getElementById('product-search');
|
| 2092 |
+
const productAccordionEl = document.getElementById('product-accordion');
|
| 2093 |
+
const productSearchResultsEl = document.getElementById('product-search-results');
|
| 2094 |
+
|
| 2095 |
+
productSearchInput.addEventListener('input', e => {
|
| 2096 |
+
const term = e.target.value.toLowerCase().trim();
|
| 2097 |
+
|
| 2098 |
+
if (term === '') {
|
| 2099 |
+
productAccordionEl.style.display = '';
|
| 2100 |
+
productSearchResultsEl.style.display = 'none';
|
| 2101 |
+
productSearchResultsEl.innerHTML = '';
|
| 2102 |
+
document.querySelectorAll('#product-accordion .accordion-collapse.show').forEach(el => {
|
| 2103 |
+
bootstrap.Collapse.getOrCreateInstance(el).hide();
|
| 2104 |
+
});
|
| 2105 |
+
return;
|
| 2106 |
+
}
|
| 2107 |
+
|
| 2108 |
+
productAccordionEl.style.display = 'none';
|
| 2109 |
+
productSearchResultsEl.style.display = 'grid';
|
| 2110 |
+
|
| 2111 |
+
const filtered = allProducts.filter(p => p.name.toLowerCase().includes(term) || p.barcode.toLowerCase().includes(term));
|
| 2112 |
+
|
| 2113 |
+
filtered.sort((a, b) => {
|
| 2114 |
+
const aName = a.name.toLowerCase();
|
| 2115 |
+
const bName = b.name.toLowerCase();
|
| 2116 |
+
const aStarts = aName.startsWith(term);
|
| 2117 |
+
const bStarts = bName.startsWith(term);
|
| 2118 |
+
const aBarcode = a.barcode.toLowerCase() === term;
|
| 2119 |
+
const bBarcode = b.barcode.toLowerCase() === term;
|
| 2120 |
+
|
| 2121 |
+
if (aBarcode && !bBarcode) return -1;
|
| 2122 |
+
if (!aBarcode && bBarcode) return 1;
|
| 2123 |
+
if (aStarts && !bStarts) return -1;
|
| 2124 |
+
if (!aStarts && bStarts) return 1;
|
| 2125 |
+
|
| 2126 |
+
return aName.localeCompare(bName);
|
| 2127 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2128 |
|
| 2129 |
+
productSearchResultsEl.innerHTML = filtered.length > 0 ? filtered.map(p => {
|
| 2130 |
+
let priceText = 'Нет в наличии';
|
| 2131 |
+
if (p.variants && p.variants.length > 0) {
|
| 2132 |
+
const activeVariants = p.variants.filter(v => v.stock > 0);
|
| 2133 |
+
if (activeVariants.length > 0) {
|
| 2134 |
+
if (activeVariants.length === 1) {
|
| 2135 |
+
priceText = `${formatCurrencyJS(activeVariants[0].price)} ₸`;
|
| 2136 |
+
} else {
|
| 2137 |
+
const prices = activeVariants.map(v => parseFloat(v.price));
|
| 2138 |
+
priceText = `от ${formatCurrencyJS(Math.min(...prices))} ₸`;
|
| 2139 |
+
}
|
| 2140 |
}
|
| 2141 |
}
|
| 2142 |
+
return `
|
| 2143 |
+
<div class="card text-center product-card" data-barcode="${p.barcode}">
|
| 2144 |
+
<div class="card-body p-2">
|
| 2145 |
+
<h6 class="card-title small mb-1">${p.name}</h6>
|
| 2146 |
+
<p class="card-text fw-bold mb-0">${priceText}</p>
|
| 2147 |
+
</div>
|
| 2148 |
+
</div>`;
|
| 2149 |
+
}).join('') : '<p class="text-muted text-center col-12">Товары не найдены.</p>';
|
| 2150 |
});
|
| 2151 |
|
| 2152 |
const completeSale = (paymentMethod) => {
|
|
|
|
| 3472 |
for key in DATA_FILES.keys():
|
| 3473 |
load_json_data(key)
|
| 3474 |
app.run(debug=False, host='0.0.0.0', port=7860, use_reloader=False)
|
|
|