Kgshop commited on
Commit
e97500a
·
verified ·
1 Parent(s): 0e82764

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +34 -150
app.py CHANGED
@@ -508,13 +508,6 @@ MAIN_HTML = '''
508
  .settings-link-box { display: flex; gap: 8px; align-items: center; width: 100%; }
509
  .settings-link-input { flex: 1; padding: 10px; border: 1px solid var(--border); border-radius: 8px; font-size: 0.85rem; background: var(--bg); outline: none; color: var(--text-main); }
510
 
511
- .cart-float { display: none; position: fixed; bottom: 24px; right: 24px; background: var(--text-main); color: white; padding: 14px 24px; border-radius: 30px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); cursor: pointer; z-index: 100; font-weight: 600; align-items: center; gap: 8px; transition: transform 0.2s; }
512
- .cart-float:active { transform: scale(0.95); }
513
-
514
- .invoice-table { width: 100%; border-collapse: collapse; margin-bottom: 16px; }
515
- .invoice-table th, .invoice-table td { padding: 10px; border-bottom: 1px solid var(--border); text-align: left; font-size:0.9rem; }
516
- .invoice-total { font-size: 1.2rem; font-weight: 700; text-align: right; }
517
-
518
  @media (min-width: 768px) {
519
  body { padding: 24px; }
520
  .top-actions { max-width: 400px; }
@@ -629,10 +622,6 @@ MAIN_HTML = '''
629
  </div>
630
  </div>
631
 
632
- <div class="cart-float" id="cart_btn" onclick="openCart()">
633
- Корзина (<span id="cart_count">0</span>)
634
- </div>
635
-
636
  <div id="imageZoomModal" class="modal-overlay" onclick="closeModal('imageZoomModal')" style="z-index:2000; align-items:center;">
637
  <img id="zoomedImage" style="max-width:90%; max-height:90%; object-fit:contain; border-radius:8px;">
638
  </div>
@@ -743,24 +732,6 @@ MAIN_HTML = '''
743
  </div>
744
  </div>
745
 
746
- <div id="cartModal" class="modal-overlay">
747
- <div class="modal-content">
748
- <button class="close-modal" onclick="closeModal('cartModal')">×</button>
749
- <h2>Корзина</h2>
750
- <div id="cart_items" style="margin-bottom:16px; margin-top:16px;"></div>
751
- <button id="checkout_btn" class="btn btn-success" style="width:100%;" onclick="submitCart()">Оформить накладную</button>
752
- </div>
753
- </div>
754
-
755
- <div id="invoiceModal" class="modal-overlay">
756
- <div class="modal-content">
757
- <button class="close-modal" onclick="closeInvoice()">×</button>
758
- <h2 style="text-align:center; margin-bottom:16px;">Накладная</h2>
759
- <div id="invoice_content"></div>
760
- <button class="btn btn-primary" style="width:100%; margin-top:20px;" onclick="closeInvoice()">Готово</button>
761
- </div>
762
- </div>
763
-
764
  <form id="delForm" method="POST" style="display:none;" onsubmit="return showLoader(this)">
765
  <input type="hidden" name="action" value="delete">
766
  <input type="hidden" name="index" id="del_index">
@@ -773,7 +744,6 @@ MAIN_HTML = '''
773
  const hostUrl = "{{ request.host_url }}";
774
  const currentEmp = {{ emp|tojson }};
775
  let activeLoc = 'all';
776
- let cart = [];
777
  let searchTimeout;
778
 
779
  function showLoader(formElement) {
@@ -968,13 +938,9 @@ MAIN_HTML = '''
968
  <div class="product-actions">
969
  ${varHtml}
970
  <div style="display:flex; gap:6px; align-items:center; width:100%;">
971
- <input type="number" id="amt_${pIdx}" class="qty-input" value="1" min="1">
972
- <button class="btn btn-success" style="padding:8px 6px; font-size:0.75rem; flex:1;" onclick="addToCart(${pIdx}, false, 'retail')">В корз (Розн)</button>
973
- <button class="btn btn-primary" style="padding:8px 6px; font-size:0.75rem; flex:1;" onclick="addToCart(${pIdx}, false, 'wholesale')">В корз (Опт)</button>
974
- </div>
975
- <div style="display:flex; gap:6px; width:100%; margin-top:2px;">
976
- <button class="btn btn-success" style="padding:8px 6px; font-size:0.75rem; flex:1; background:#059669;" onclick="addToCart(${pIdx}, true, 'retail')">Коробка (Розн)</button>
977
- <button class="btn btn-primary" style="padding:8px 6px; font-size:0.75rem; flex:1; background:#4338ca;" onclick="addToCart(${pIdx}, true, 'wholesale')">Коробка (Опт)</button>
978
  </div>
979
  `;
980
  if (role === 'admin') {
@@ -1086,149 +1052,65 @@ MAIN_HTML = '''
1086
  }
1087
  }
1088
 
1089
- function addToCart(pIdx, isBox, pType) {
1090
  let p = data.products[pIdx];
 
1091
  let vSelect = document.getElementById('var_'+pIdx);
1092
  let vIdx = (vSelect && vSelect.value !== '') ? parseInt(vSelect.value) : -1;
1093
 
1094
- let amt = isBox ? parseInt(p.box_qty || 1) : parseInt(document.getElementById('amt_'+pIdx).value);
1095
-
1096
- let validLocs = (activeLoc === 'all')
1097
- ? ((role === 'employee' && currentEmp && currentEmp.shop_id)
1098
- ? [currentEmp.shop_id, ...data.settings.warehouses.map(w=>w.id)]
1099
- : [...data.settings.warehouses.map(w=>w.id), ...data.settings.shops.map(s=>s.id)])
1100
- : [activeLoc];
1101
-
1102
- let getLocStock = (stocksObj) => {
1103
- let s = 0;
1104
- for (let l of validLocs) {
1105
- if (stocksObj && stocksObj[l]) s += parseInt(stocksObj[l]);
1106
- }
1107
- return s;
1108
- };
1109
 
1110
- let stock = (vIdx !== -1) ? getLocStock(p.variants[vIdx].stocks) : getLocStock(p.stocks);
 
 
 
 
 
1111
 
1112
- if(amt <= 0 || isNaN(amt)) return alert('Неверное количество');
1113
- if(activeLoc !== 'all' && amt > stock) return alert('Превышен остаток');
1114
 
1115
  let basePrice = pType === 'wholesale' ? parseInt(p.price_wholesale) : parseInt(p.price_retail);
1116
  if (role !== 'public') {
1117
- if (pType === 'wholesale') {
1118
- let customPriceInput = document.getElementById('price_whl_' + pIdx);
1119
- if (customPriceInput) basePrice = parseInt(customPriceInput.value) || basePrice;
1120
- } else {
1121
- let customPriceInput = document.getElementById('price_ret_' + pIdx);
1122
- if (customPriceInput) basePrice = parseInt(customPriceInput.value) || basePrice;
1123
- }
1124
  }
1125
 
1126
  let vName = '';
1127
  let vPrice = 0;
1128
  if (vIdx !== -1) {
1129
- let variant = p.variants[vIdx];
1130
- vName = ' (' + variant.name + ')';
1131
- vPrice = parseInt(variant.price) || 0;
1132
  }
1133
 
1134
  let finalPrice = basePrice + vPrice;
1135
  let finalName = p.name + vName + (pType === 'wholesale' ? ' [Опт]' : ' [Розн]');
1136
 
1137
- let existing = cart.find(i => i.pIndex === pIdx && i.vIndex === vIdx && i.loc === activeLoc && i.pName === finalName && i.price === finalPrice);
1138
- let cartTotalForProduct = cart.filter(i => i.pIndex === pIdx && i.vIndex === vIdx && i.loc === activeLoc).reduce((s, i) => s + i.amt, 0);
1139
 
1140
- if(activeLoc !== 'all' && (cartTotalForProduct + amt > stock)) return alert('Превышен остаток в корзине');
 
 
 
 
 
 
 
1141
 
1142
- if(existing) {
1143
- existing.amt += amt;
1144
- } else {
1145
- cart.push({pIndex: pIdx, vIndex: vIdx, pName: finalName, price: finalPrice, amt: amt, loc: activeLoc});
1146
- }
1147
- updateCartUI();
1148
- }
1149
-
1150
- function updateCartUI() {
1151
- let totalItems = cart.reduce((sum, item) => sum + item.amt, 0);
1152
- const btn = document.getElementById('cart_btn');
1153
- document.getElementById('cart_count').innerText = totalItems;
1154
- btn.style.display = totalItems > 0 ? 'flex' : 'none';
1155
- }
1156
-
1157
- function openCart() {
1158
- let html = '';
1159
- let total = 0;
1160
- cart.forEach((c, i) => {
1161
- let sum = c.amt * c.price;
1162
- total += sum;
1163
- html += `<div style="display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid var(--border); padding:10px 0;">
1164
- <div>
1165
- <div style="font-weight:600;">${c.pName}</div>
1166
- <div style="font-size:0.85rem; color:var(--text-muted);">${c.amt} шт x ${c.price} ₸</div>
1167
- </div>
1168
- <div style="display:flex; align-items:center; gap:12px;">
1169
- <span style="font-weight:700;">${sum} ₸</span>
1170
- <button style="border:none; background:var(--danger); color:white; border-radius:6px; padding:6px 10px; cursor:pointer; font-weight:700;" onclick="removeFromCart(${i})">×</button>
1171
- </div>
1172
- </div>`;
1173
- });
1174
- html += `<div style="text-align:right; font-size:1.2rem; font-weight:700; margin-top:16px;">Итого: ${total} ₸</div>`;
1175
- document.getElementById('cart_items').innerHTML = html;
1176
- openModal('cartModal');
1177
- }
1178
-
1179
- function removeFromCart(idx) {
1180
- cart.splice(idx, 1);
1181
- updateCartUI();
1182
- if(cart.length === 0) {
1183
- closeModal('cartModal');
1184
- } else {
1185
- openCart();
1186
- }
1187
- }
1188
-
1189
- async function submitCart() {
1190
- if (cart.length === 0) return;
1191
- const btn = document.getElementById('checkout_btn');
1192
- if (btn.disabled) return;
1193
- btn.disabled = true;
1194
  document.getElementById('loader').style.display = 'flex';
1195
-
1196
  let fd = new FormData();
1197
  fd.append('action', 'checkout');
1198
- fd.append('cart', JSON.stringify(cart));
1199
 
1200
  try {
1201
  await fetch(window.location.href, {method: 'POST', body: fd});
1202
-
1203
- let dateStr = new Date().toLocaleString('ru-RU');
1204
- let html = `<p style="color:var(--text-muted); margin-bottom:16px;">Дата: ${dateStr}</p>
1205
- <table class="invoice-table">
1206
- <tr><th>Товар</th><th>Кол.</th><th>Сумма</th></tr>`;
1207
- let total = 0;
1208
- cart.forEach(c => {
1209
- let sum = c.amt * c.price;
1210
- total += sum;
1211
- html += `<tr><td>${c.pName}</td><td>${c.amt}</td><td>${sum} ₸</td></tr>`;
1212
- });
1213
- html += `</table><div class="invoice-total">Итого к оплате: ${total} ₸</div>`;
1214
-
1215
- document.getElementById('invoice_content').innerHTML = html;
1216
- closeModal('cartModal');
1217
- openModal('invoiceModal');
1218
- cart = [];
1219
- updateCartUI();
1220
  } catch (e) {
1221
- alert('Ошибка при оформлении');
1222
- } finally {
1223
  document.getElementById('loader').style.display = 'none';
1224
- btn.disabled = false;
1225
  }
1226
  }
1227
 
1228
- function closeInvoice() {
1229
- window.location.reload();
1230
- }
1231
-
1232
  function openTransfer(pIdx) {
1233
  let p = data.products[pIdx];
1234
  let vSelect = document.getElementById('var_' + pIdx);
@@ -1308,7 +1190,9 @@ MAIN_HTML = '''
1308
  locSelect.innerHTML = [...data.settings.warehouses, ...data.settings.shops].map(l => `<option value="${l.id}">${l.name}</option>`).join('');
1309
 
1310
  let lastLoc = localStorage.getItem('lastFormLoc');
1311
- if (lastLoc && locSelect.querySelector(`option[value="${lastLoc}"]`)) {
 
 
1312
  locSelect.value = lastLoc;
1313
  }
1314
 
@@ -1320,7 +1204,7 @@ MAIN_HTML = '''
1320
  document.getElementById('edit_variants_container').innerHTML = '';
1321
  if (p.variants && p.variants.length > 0) {
1322
  p.variants.forEach(v => {
1323
- addVariantField('edit_variants_container', v.name, v.price, 0);
1324
  });
1325
  }
1326
 
 
508
  .settings-link-box { display: flex; gap: 8px; align-items: center; width: 100%; }
509
  .settings-link-input { flex: 1; padding: 10px; border: 1px solid var(--border); border-radius: 8px; font-size: 0.85rem; background: var(--bg); outline: none; color: var(--text-main); }
510
 
 
 
 
 
 
 
 
511
  @media (min-width: 768px) {
512
  body { padding: 24px; }
513
  .top-actions { max-width: 400px; }
 
622
  </div>
623
  </div>
624
 
 
 
 
 
625
  <div id="imageZoomModal" class="modal-overlay" onclick="closeModal('imageZoomModal')" style="z-index:2000; align-items:center;">
626
  <img id="zoomedImage" style="max-width:90%; max-height:90%; object-fit:contain; border-radius:8px;">
627
  </div>
 
732
  </div>
733
  </div>
734
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
735
  <form id="delForm" method="POST" style="display:none;" onsubmit="return showLoader(this)">
736
  <input type="hidden" name="action" value="delete">
737
  <input type="hidden" name="index" id="del_index">
 
744
  const hostUrl = "{{ request.host_url }}";
745
  const currentEmp = {{ emp|tojson }};
746
  let activeLoc = 'all';
 
747
  let searchTimeout;
748
 
749
  function showLoader(formElement) {
 
938
  <div class="product-actions">
939
  ${varHtml}
940
  <div style="display:flex; gap:6px; align-items:center; width:100%;">
941
+ <input type="number" id="amt_${pIdx}" class="qty-input" value="1" min="1" style="flex:1;">
942
+ <button class="btn btn-danger" style="padding:8px 6px; font-size:0.75rem; flex:1;" onclick="directSell(${pIdx}, 'retail')">Списать (Розн)</button>
943
+ <button class="btn btn-danger" style="padding:8px 6px; font-size:0.75rem; flex:1;" onclick="directSell(${pIdx}, 'wholesale')">Списать (Опт)</button>
 
 
 
 
944
  </div>
945
  `;
946
  if (role === 'admin') {
 
1052
  }
1053
  }
1054
 
1055
+ async function directSell(pIdx, pType) {
1056
  let p = data.products[pIdx];
1057
+ let amt = parseInt(document.getElementById('amt_'+pIdx).value);
1058
  let vSelect = document.getElementById('var_'+pIdx);
1059
  let vIdx = (vSelect && vSelect.value !== '') ? parseInt(vSelect.value) : -1;
1060
 
1061
+ if(amt <= 0 || isNaN(amt)) return alert('Неверное количество');
1062
+ if(activeLoc === 'all') return alert('Выберите локацию для списания');
 
 
 
 
 
 
 
 
 
 
 
 
 
1063
 
1064
+ let stock = 0;
1065
+ if (vIdx !== -1) {
1066
+ stock = parseInt(p.variants[vIdx].stocks[activeLoc] || 0);
1067
+ } else {
1068
+ stock = parseInt(p.stocks[activeLoc] || 0);
1069
+ }
1070
 
1071
+ if(amt > stock) return alert('Превышен остаток');
 
1072
 
1073
  let basePrice = pType === 'wholesale' ? parseInt(p.price_wholesale) : parseInt(p.price_retail);
1074
  if (role !== 'public') {
1075
+ let customPriceInput = document.getElementById(pType === 'wholesale' ? 'price_whl_' + pIdx : 'price_ret_' + pIdx);
1076
+ if (customPriceInput) basePrice = parseInt(customPriceInput.value) || basePrice;
 
 
 
 
 
1077
  }
1078
 
1079
  let vName = '';
1080
  let vPrice = 0;
1081
  if (vIdx !== -1) {
1082
+ vName = ' (' + p.variants[vIdx].name + ')';
1083
+ vPrice = parseInt(p.variants[vIdx].price) || 0;
 
1084
  }
1085
 
1086
  let finalPrice = basePrice + vPrice;
1087
  let finalName = p.name + vName + (pType === 'wholesale' ? ' [Опт]' : ' [Розн]');
1088
 
1089
+ if (!confirm(`Списать ${amt} шт. товара "${finalName}"?`)) return;
 
1090
 
1091
+ let singleItemCart = [{
1092
+ pIndex: pIdx,
1093
+ vIndex: vIdx,
1094
+ pName: finalName,
1095
+ price: finalPrice,
1096
+ amt: amt,
1097
+ loc: activeLoc
1098
+ }];
1099
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1100
  document.getElementById('loader').style.display = 'flex';
 
1101
  let fd = new FormData();
1102
  fd.append('action', 'checkout');
1103
+ fd.append('cart', JSON.stringify(singleItemCart));
1104
 
1105
  try {
1106
  await fetch(window.location.href, {method: 'POST', body: fd});
1107
+ window.location.reload();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1108
  } catch (e) {
1109
+ alert('Ошибка при списании');
 
1110
  document.getElementById('loader').style.display = 'none';
 
1111
  }
1112
  }
1113
 
 
 
 
 
1114
  function openTransfer(pIdx) {
1115
  let p = data.products[pIdx];
1116
  let vSelect = document.getElementById('var_' + pIdx);
 
1190
  locSelect.innerHTML = [...data.settings.warehouses, ...data.settings.shops].map(l => `<option value="${l.id}">${l.name}</option>`).join('');
1191
 
1192
  let lastLoc = localStorage.getItem('lastFormLoc');
1193
+ if (activeLoc !== 'all' && locSelect.querySelector(`option[value="${activeLoc}"]`)) {
1194
+ locSelect.value = activeLoc;
1195
+ } else if (lastLoc && locSelect.querySelector(`option[value="${lastLoc}"]`)) {
1196
  locSelect.value = lastLoc;
1197
  }
1198
 
 
1204
  document.getElementById('edit_variants_container').innerHTML = '';
1205
  if (p.variants && p.variants.length > 0) {
1206
  p.variants.forEach(v => {
1207
+ addVariantField('edit_variants_container', v.name, v.price, '');
1208
  });
1209
  }
1210