Kgshop commited on
Commit
541ee4f
·
verified ·
1 Parent(s): 6427e57

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -23
app.py CHANGED
@@ -234,8 +234,16 @@ def sales_screen():
234
  active_inventory.append(p)
235
 
236
  active_inventory.sort(key=lambda x: x.get('name', '').lower())
 
 
 
 
 
 
 
 
237
  html = BASE_TEMPLATE.replace('__TITLE__', "Касса").replace('__CONTENT__', SALES_SCREEN_CONTENT).replace('__SCRIPTS__', SALES_SCREEN_SCRIPTS)
238
- return render_template_string(html, inventory=active_inventory, users=users, kassas=kassas)
239
 
240
  @app.route('/inventory', methods=['GET', 'POST'])
241
  @admin_required
@@ -717,13 +725,16 @@ def product_roi_report():
717
  if t['type'] == 'sale':
718
  total_qty_sold += item['quantity']
719
  elif t['type'] == 'return':
720
- total_qty_sold += item['quantity']
721
 
722
  current_stock = to_decimal(str(variant.get('stock', 0)))
723
  cost_price = to_decimal(variant.get('cost_price', '0'))
724
 
725
  inventory_value = current_stock * cost_price
726
- total_investment = total_cogs + inventory_value
 
 
 
727
  payback = total_revenue - total_investment
728
 
729
  product_stats.append({
@@ -972,6 +983,8 @@ def return_transaction(transaction_id):
972
  return_items = []
973
  inventory_updates = {}
974
  for item in original_transaction['items']:
 
 
975
  return_items.append({**item, 'quantity': -item['quantity'], 'total': str(-to_decimal(item['total']))})
976
  product = find_item_by_field(inventory, 'id', item['product_id'])
977
  if product:
@@ -992,7 +1005,7 @@ def return_transaction(transaction_id):
992
  'kassa_id': original_transaction['kassa_id'],
993
  'kassa_name': original_transaction['kassa_name'],
994
  'items': return_items,
995
- 'total_amount': str(-total_amount),
996
  'payment_method': original_transaction['payment_method']
997
  }
998
  transactions.append(return_transaction)
@@ -1016,7 +1029,7 @@ def return_transaction(transaction_id):
1016
  for i, k in enumerate(kassas):
1017
  if k['id'] == original_transaction['kassa_id']:
1018
  current_balance = to_decimal(k.get('balance', '0'))
1019
- kassas[i]['balance'] = str(current_balance - total_amount)
1020
  kassas[i].setdefault('history', []).append({
1021
  'type': 'return', 'amount': str(-total_amount), 'timestamp': now_iso,
1022
  'transaction_id': return_transaction['id']
@@ -1253,20 +1266,34 @@ SALES_SCREEN_CONTENT = """
1253
  <button id="stop-scan-btn" class="btn btn-danger btn-sm mt-2">Остановить</button>
1254
  </div>
1255
  <input type="text" id="product-search" class="form-control mb-3" placeholder="Поиск по названию или штрих-коду...">
1256
- <div id="product-grid" class="d-grid gap-2" style="grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));">
1257
- {% for p in inventory %}
1258
- <div class="card text-center product-card" data-barcode="{{ p.barcode }}">
1259
- <div class="card-body p-2">
1260
- <h6 class="card-title small mb-1">{{ p.name }}</h6>
1261
- <p class="card-text fw-bold mb-0">
1262
- {% if p.variants|length > 1 %}
1263
- от {{ format_currency_py(p.variants|map(attribute='price')|min) }} с
1264
- {% elif p.variants|length == 1 %}
1265
- {{ format_currency_py(p.variants[0].price) }} с
1266
- {% else %}
1267
- Нет в наличии
1268
- {% endif %}
1269
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1270
  </div>
1271
  </div>
1272
  {% endfor %}
@@ -1301,7 +1328,7 @@ SALES_SCREEN_SCRIPTS = """
1301
  <script>
1302
  document.addEventListener('DOMContentLoaded', () => {
1303
  const cart = {};
1304
- const productGrid = document.getElementById('product-grid');
1305
  const cartItemsEl = document.getElementById('cart-items');
1306
  const cartTotalEl = document.getElementById('cart-total');
1307
  let audioCtx;
@@ -1464,11 +1491,30 @@ document.addEventListener('DOMContentLoaded', () => {
1464
 
1465
  document.getElementById('product-search').addEventListener('input', e => {
1466
  const term = e.target.value.toLowerCase();
1467
- document.querySelectorAll('.product-card').forEach(card => {
 
 
1468
  const productName = card.querySelector('.card-title').textContent.toLowerCase();
1469
- const show = productName.includes(term) || card.dataset.barcode.includes(term);
 
1470
  card.style.display = show ? '' : 'none';
1471
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1472
  });
1473
 
1474
  const completeSale = (paymentMethod) => {
@@ -1511,7 +1557,7 @@ document.addEventListener('DOMContentLoaded', () => {
1511
  if (phone && receiptUrl) {
1512
  const fullPhone = '996' + phone;
1513
  const message = encodeURIComponent(`Ваш чек: ${receiptUrl}`);
1514
- window.open(`https.wa.me/${fullPhone}?text=${message}`, '_blank');
1515
  } else {
1516
  alert('Введите номер телефона.');
1517
  }
@@ -1566,6 +1612,7 @@ INVENTORY_CONTENT = """
1566
  <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addProductModal"><i class="fas fa-plus me-2"></i>Добавить товар</button>
1567
  <button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#stockInModal"><i class="fas fa-truck-loading me-2"></i>Оприходовать</button>
1568
  </div>
 
1569
 
1570
  <div class="accordion" id="inventoryAccordion">
1571
  {% for p in inventory %}
@@ -1814,6 +1861,20 @@ document.addEventListener('DOMContentLoaded', () => {
1814
  }
1815
  }
1816
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1817
  });
1818
  </script>
1819
  """
 
234
  active_inventory.append(p)
235
 
236
  active_inventory.sort(key=lambda x: x.get('name', '').lower())
237
+
238
+ grouped_inventory = defaultdict(list)
239
+ for p in active_inventory:
240
+ first_letter = p.get('name', '#')[0].upper()
241
+ grouped_inventory[first_letter].append(p)
242
+
243
+ sorted_grouped_inventory = sorted(grouped_inventory.items())
244
+
245
  html = BASE_TEMPLATE.replace('__TITLE__', "Касса").replace('__CONTENT__', SALES_SCREEN_CONTENT).replace('__SCRIPTS__', SALES_SCREEN_SCRIPTS)
246
+ return render_template_string(html, inventory=active_inventory, users=users, kassas=kassas, grouped_inventory=sorted_grouped_inventory)
247
 
248
  @app.route('/inventory', methods=['GET', 'POST'])
249
  @admin_required
 
725
  if t['type'] == 'sale':
726
  total_qty_sold += item['quantity']
727
  elif t['type'] == 'return':
728
+ total_qty_sold -= item['quantity'] # Corrected to subtract for returns
729
 
730
  current_stock = to_decimal(str(variant.get('stock', 0)))
731
  cost_price = to_decimal(variant.get('cost_price', '0'))
732
 
733
  inventory_value = current_stock * cost_price
734
+
735
+ # For simplicity, calculate total investment as historical COGS + current inventory value.
736
+ # This is a basic model, more advanced models might consider purchase dates, etc.
737
+ total_investment = total_cogs + inventory_value
738
  payback = total_revenue - total_investment
739
 
740
  product_stats.append({
 
983
  return_items = []
984
  inventory_updates = {}
985
  for item in original_transaction['items']:
986
+ # For a return, quantity becomes negative, and total becomes negative.
987
+ # This will reverse the effect on stock and total amount.
988
  return_items.append({**item, 'quantity': -item['quantity'], 'total': str(-to_decimal(item['total']))})
989
  product = find_item_by_field(inventory, 'id', item['product_id'])
990
  if product:
 
1005
  'kassa_id': original_transaction['kassa_id'],
1006
  'kassa_name': original_transaction['kassa_name'],
1007
  'items': return_items,
1008
+ 'total_amount': str(-total_amount), # Total amount for return is negative
1009
  'payment_method': original_transaction['payment_method']
1010
  }
1011
  transactions.append(return_transaction)
 
1029
  for i, k in enumerate(kassas):
1030
  if k['id'] == original_transaction['kassa_id']:
1031
  current_balance = to_decimal(k.get('balance', '0'))
1032
+ kassas[i]['balance'] = str(current_balance - total_amount) # Subtract total amount for cash return
1033
  kassas[i].setdefault('history', []).append({
1034
  'type': 'return', 'amount': str(-total_amount), 'timestamp': now_iso,
1035
  'transaction_id': return_transaction['id']
 
1266
  <button id="stop-scan-btn" class="btn btn-danger btn-sm mt-2">Остановить</button>
1267
  </div>
1268
  <input type="text" id="product-search" class="form-control mb-3" placeholder="Поиск по названию или штрих-коду...">
1269
+
1270
+ <div id="product-accordion" class="accordion">
1271
+ {% for letter, products in grouped_inventory %}
1272
+ <div class="accordion-item">
1273
+ <h2 class="accordion-header" id="heading-{{ letter }}">
1274
+ <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-{{ letter }}" aria-expanded="false" aria-controls="collapse-{{ letter }}">
1275
+ {{ letter }}
1276
+ </button>
1277
+ </h2>
1278
+ <div id="collapse-{{ letter }}" class="accordion-collapse collapse" aria-labelledby="heading-{{ letter }}" data-bs-parent="#product-accordion">
1279
+ <div class="accordion-body d-grid gap-2" style="grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));">
1280
+ {% for p in products %}
1281
+ <div class="card text-center product-card" data-barcode="{{ p.barcode }}">
1282
+ <div class="card-body p-2">
1283
+ <h6 class="card-title small mb-1">{{ p.name }}</h6>
1284
+ <p class="card-text fw-bold mb-0">
1285
+ {% if p.variants|length > 1 %}
1286
+ от {{ format_currency_py(p.variants|map(attribute='price')|min) }} с
1287
+ {% elif p.variants|length == 1 %}
1288
+ {{ format_currency_py(p.variants[0].price) }} с
1289
+ {% else %}
1290
+ Нет в наличии
1291
+ {% endif %}
1292
+ </p>
1293
+ </div>
1294
+ </div>
1295
+ {% endfor %}
1296
+ </div>
1297
  </div>
1298
  </div>
1299
  {% endfor %}
 
1328
  <script>
1329
  document.addEventListener('DOMContentLoaded', () => {
1330
  const cart = {};
1331
+ const productGrid = document.getElementById('product-accordion');
1332
  const cartItemsEl = document.getElementById('cart-items');
1333
  const cartTotalEl = document.getElementById('cart-total');
1334
  let audioCtx;
 
1491
 
1492
  document.getElementById('product-search').addEventListener('input', e => {
1493
  const term = e.target.value.toLowerCase();
1494
+ const productCards = document.querySelectorAll('#product-accordion .product-card');
1495
+
1496
+ productCards.forEach(card => {
1497
  const productName = card.querySelector('.card-title').textContent.toLowerCase();
1498
+ const barcode = card.dataset.barcode.toLowerCase();
1499
+ const show = productName.includes(term) || barcode.includes(term);
1500
  card.style.display = show ? '' : 'none';
1501
  });
1502
+
1503
+ document.querySelectorAll('#product-accordion .accordion-item').forEach(accordionItem => {
1504
+ const collapseElement = accordionItem.querySelector('.accordion-collapse');
1505
+ const matchingCardsInGroup = accordionItem.querySelectorAll('.product-card:not([style*="display: none"])');
1506
+ const bsCollapse = bootstrap.Collapse.getOrCreateInstance(collapseElement, { toggle: false });
1507
+
1508
+ if (term === '') {
1509
+ bsCollapse.hide();
1510
+ } else {
1511
+ if (matchingCardsInGroup.length > 0) {
1512
+ bsCollapse.show();
1513
+ } else {
1514
+ bsCollapse.hide();
1515
+ }
1516
+ }
1517
+ });
1518
  });
1519
 
1520
  const completeSale = (paymentMethod) => {
 
1557
  if (phone && receiptUrl) {
1558
  const fullPhone = '996' + phone;
1559
  const message = encodeURIComponent(`Ваш чек: ${receiptUrl}`);
1560
+ window.open(`https://wa.me/${fullPhone}?text=${message}`, '_blank');
1561
  } else {
1562
  alert('Введите номер телефона.');
1563
  }
 
1612
  <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addProductModal"><i class="fas fa-plus me-2"></i>Добавить товар</button>
1613
  <button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#stockInModal"><i class="fas fa-truck-loading me-2"></i>Оприходовать</button>
1614
  </div>
1615
+ <input type="text" id="inventory-search" class="form-control mb-3" placeholder="Поиск по названию, варианту или штрих-коду...">
1616
 
1617
  <div class="accordion" id="inventoryAccordion">
1618
  {% for p in inventory %}
 
1861
  }
1862
  }
1863
  });
1864
+
1865
+ document.getElementById('inventory-search').addEventListener('input', e => {
1866
+ const term = e.target.value.toLowerCase();
1867
+ document.querySelectorAll('#inventoryAccordion .accordion-item').forEach(item => {
1868
+ const productName = item.querySelector('.accordion-button strong').textContent.toLowerCase();
1869
+ const barcode = item.querySelector('.accordion-button small').textContent.toLowerCase();
1870
+
1871
+ const variantNameElements = item.querySelectorAll('.accordion-body table tbody tr td:first-child');
1872
+ const variantMatch = Array.from(variantNameElements).some(td => td.textContent.toLowerCase().includes(term));
1873
+
1874
+ const show = productName.includes(term) || barcode.includes(term) || variantMatch;
1875
+ item.style.display = show ? '' : 'none';
1876
+ });
1877
+ });
1878
  });
1879
  </script>
1880
  """