Kgshop commited on
Commit
bd28a2a
·
verified ·
1 Parent(s): fa57d7e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +154 -126
app.py CHANGED
@@ -63,13 +63,21 @@ def download_db_from_hf(specific_file=None, retries=3, delay=5):
63
  try:
64
  if file_name == DATA_FILE:
65
  with open(file_name, 'w', encoding='utf-8') as f:
66
- json.dump({'products': [], 'categories': [], 'orders': {}, 'employees': [], 'workdays': {}, 'fines': [], 'settings': {
67
- 'cafe_name': 'HongKong',
68
- 'wa_shift1': '+77470623684',
69
- 'wa_shift2': '+77470623684',
70
- 'active_shift': 1,
71
- 'logo_version': '1'
72
- }}, f)
 
 
 
 
 
 
 
 
73
  except Exception:
74
  pass
75
  success = False
@@ -180,9 +188,9 @@ def load_data():
180
 
181
  for emp in data['employees']:
182
  if 'pin' not in emp: emp['pin'] = '0000'
183
- if 'daily_rate' not in emp: emp['daily_rate'] = 0
184
- if 'target_amount' not in emp: emp['target_amount'] = 0
185
- if 'bonus_percentage' not in emp: emp['bonus_percentage'] = 0
186
 
187
  if not os.path.exists(DATA_FILE):
188
  try:
@@ -945,7 +953,7 @@ ORDER_TEMPLATE = '''
945
  <div class="totals-area">
946
  <div class="totals-row">
947
  <span>Итого:</span>
948
- <span>{{ raw_total }}</span>
949
  </div>
950
  {% set discount = order.discount|default(0)|float %}
951
  {% if discount > 0 %}
@@ -1171,7 +1179,7 @@ SALARY_TEMPLATE = '''
1171
  <head>
1172
  <meta charset="UTF-8">
1173
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
1174
- <title>Отчет по ЗП и Штрафы</title>
1175
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
1176
  <style>
1177
  :root { --primary: #D4AF37; --bg: #000000; --surface: #111111; --border: #333333; --danger: #ff4757; --success: #2ed573; --info: #D4AF37; --warning: #ffa502; --text: #ffffff; --text-muted: #888888; }
@@ -1184,15 +1192,15 @@ SALARY_TEMPLATE = '''
1184
  .btn { padding: 12px 20px; border: none; border-radius: 10px; font-weight: 700; cursor: pointer; color: #000; text-decoration: none; display: inline-flex; align-items: center; gap: 8px; font-size: 0.95rem; transition: opacity 0.2s; }
1185
  .btn:active { opacity: 0.8; }
1186
  .btn-success { background: var(--success); }
1187
- .btn-danger { background: var(--danger); }
1188
  .btn-dark { background: #1a1a1a; color: var(--primary); border: 1px solid var(--border); }
1189
 
1190
  .report-section { background: var(--surface); padding: 20px; border-radius: 16px; border: 1px solid var(--border); margin-bottom: 20px; }
1191
 
1192
  .form-row { display: flex; gap: 10px; flex-wrap: wrap; }
1193
  .form-row > * { flex: 1; min-width: 150px; }
1194
- input[type="date"], input[type="number"], input[type="text"] { width: 100%; padding: 12px 15px; border: 1px solid var(--border); border-radius: 10px; font-size: 0.95rem; outline: none; transition: border-color 0.2s; background: #1a1a1a; color: var(--text); }
1195
- input[type="date"]:focus, input[type="number"]:focus, input[type="text"]:focus { border-color: var(--primary); background: #222; }
1196
  label { display: block; margin-bottom: 5px; color: var(--text-muted); font-size: 0.85rem; font-weight: 600; }
1197
 
1198
  .rep-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
@@ -1208,68 +1216,11 @@ SALARY_TEMPLATE = '''
1208
  <body>
1209
  <div class="container">
1210
  <div class="header-panel">
1211
- <h1><i class="fas fa-money-bill-wave"></i> Отчет по ЗП и Штрафы</h1>
1212
  <a href="/admin" class="btn btn-dark"><i class="fas fa-arrow-left"></i> Назад в админку</a>
1213
  </div>
1214
 
1215
  <div class="report-section">
1216
- <div style="background:var(--surface); border:1px solid var(--border); padding:15px; border-radius:10px; margin-bottom:20px;">
1217
- <h3 style="margin-top:0; color:var(--primary);">Добавить штраф</h3>
1218
- <form method="POST" style="display:flex; flex-wrap:wrap; gap:10px;">
1219
- <input type="hidden" name="action" value="add_fine">
1220
- <div style="flex:1; min-width:150px;">
1221
- <label>Сотрудник</label>
1222
- <select name="employee_id" required style="width:100%; padding:12px 15px; border:1px solid var(--border); border-radius:10px; background:#1a1a1a; color:#fff; outline:none;">
1223
- {% for emp in employees %}
1224
- <option value="{{ emp.id }}">{{ emp.name }}</option>
1225
- {% endfor %}
1226
- </select>
1227
- </div>
1228
- <div style="flex:1; min-width:150px;">
1229
- <label>Дата</label>
1230
- <input type="date" name="date" required>
1231
- </div>
1232
- <div style="flex:1; min-width:150px;">
1233
- <label>Сумма</label>
1234
- <input type="number" name="amount" required step="0.01">
1235
- </div>
1236
- <div style="flex:2; min-width:200px;">
1237
- <label>Причина</label>
1238
- <input type="text" name="reason" required>
1239
- </div>
1240
- <div style="display:flex; align-items:flex-end;">
1241
- <button type="submit" class="btn btn-danger" style="height:44px;"><i class="fas fa-minus-circle"></i> Добавить</button>
1242
- </div>
1243
- </form>
1244
- </div>
1245
-
1246
- <h3 style="color:var(--primary); margin-top:20px;">Список штрафов</h3>
1247
- <div style="overflow-x:auto;">
1248
- <table class="rep-table" style="margin-bottom:20px;">
1249
- <tr><th>Сотрудник</th><th>Дата</th><th>Причина</th><th>Сумма</th><th>Действие</th></tr>
1250
- {% for fine in fines|sort(attribute='date', reverse=True) %}
1251
- <tr>
1252
- <td>
1253
- {% for e in employees %}{% if e.id == fine.employee_id %}{{ e.name }}{% endif %}{% endfor %}
1254
- </td>
1255
- <td>{{ fine.date }}</td>
1256
- <td>{{ fine.reason }}</td>
1257
- <td style="color:var(--danger);">-{{ fine.amount }} {{ currency_code }}</td>
1258
- <td>
1259
- <form method="POST" style="margin:0;" onsubmit="return confirm('Удалить штраф?');">
1260
- <input type="hidden" name="action" value="delete_fine">
1261
- <input type="hidden" name="fine_id" value="{{ fine.id }}">
1262
- <button type="submit" class="btn btn-danger" style="padding:5px 10px; border-radius:5px;"><i class="fas fa-trash"></i></button>
1263
- </form>
1264
- </td>
1265
- </tr>
1266
- {% endfor %}
1267
- </table>
1268
- </div>
1269
-
1270
- <hr style="border:1px dashed var(--border); margin:20px 0;">
1271
- <h3 style="color:var(--primary); margin-top:20px;">Отчет по ЗП</h3>
1272
-
1273
  <div class="form-row" style="margin-bottom: 20px; align-items: flex-end;">
1274
  <div>
1275
  <label>Дата начала</label>
@@ -1285,6 +1236,44 @@ SALARY_TEMPLATE = '''
1285
  <table class="rep-table" id="tableSalary"></table>
1286
  </div>
1287
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1288
  </div>
1289
 
1290
  <script>
@@ -1304,6 +1293,7 @@ SALARY_TEMPLATE = '''
1304
  const firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
1305
  document.getElementById('salStart').value = getLocalDateStr(firstDay);
1306
  document.getElementById('salEnd').value = todayStr;
 
1307
  generateSalaryReport();
1308
  }
1309
 
@@ -1312,7 +1302,7 @@ SALARY_TEMPLATE = '''
1312
  const end = document.getElementById('salEnd').value;
1313
  if (!start || !end) return;
1314
 
1315
- let html = '<tr><th>Сотрудник</th><th>Дней отработано</th><th>Фикс. ЗП</th><th>Бонусы с продаж</th><th>Штрафы</th><th>Итого ЗП</th></tr>';
1316
 
1317
  let empSales = {};
1318
  allOrders.forEach(o => {
@@ -1323,18 +1313,19 @@ SALARY_TEMPLATE = '''
1323
  empSales[o.employee_id][oDate] += o.total_price;
1324
  }
1325
  });
 
 
 
 
 
 
 
 
1326
 
1327
  employees.forEach(emp => {
1328
  let daysWorked = 0;
1329
  let fixedSal = 0;
1330
  let bonusSal = 0;
1331
- let totalFines = 0;
1332
-
1333
- fines.forEach(f => {
1334
- if (f.employee_id === emp.id && f.date >= start && f.date <= end) {
1335
- totalFines += parseFloat(f.amount || 0);
1336
- }
1337
- });
1338
 
1339
  let currDate = new Date(start);
1340
  const endDate = new Date(end);
@@ -1347,36 +1338,67 @@ SALARY_TEMPLATE = '''
1347
 
1348
  if (worked) {
1349
  daysWorked++;
1350
- fixedSal += parseFloat(emp.daily_rate || 0);
1351
  }
1352
-
1353
- const target = parseFloat(emp.target_amount || 0);
1354
- const pct = parseFloat(emp.bonus_percentage || 0);
1355
 
1356
  if (pct > 0 && dailySales > target) {
1357
  bonusSal += (dailySales - target) * (pct / 100);
1358
  }
1359
-
1360
  currDate.setDate(currDate.getDate() + 1);
1361
  }
1362
-
 
1363
  const total = fixedSal + bonusSal - totalFines;
1364
- if (daysWorked > 0 || fixedSal > 0 || bonusSal > 0 || totalFines > 0) {
 
1365
  html += `<tr>
1366
  <td>${emp.name}</td>
1367
  <td>${daysWorked}</td>
1368
- <td>${fixedSal.toFixed(2)} ${currencyCode}</td>
1369
- <td>${bonusSal.toFixed(2)} ${currencyCode}</td>
1370
- <td style="color:var(--danger);">${totalFines.toFixed(2)} ${currencyCode}</td>
1371
- <td style="font-weight:bold; color:var(--success);">${total.toFixed(2)} ${currencyCode}</td>
1372
  </tr>`;
1373
  }
1374
  });
1375
 
1376
- if (html === '<tr><th>Сотрудник</th><th>Дней отработано</th><th>Фикс. ЗП</th><th>Бонусы с продаж</th><th>Штрафы</th><th>Итого ЗП</th></tr>') {
1377
- html += '<tr><td colspan="6">За выбранный период данных нет</td></tr>';
1378
- }
1379
  document.getElementById('tableSalary').innerHTML = html;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1380
  }
1381
 
1382
  window.onload = () => {
@@ -1527,9 +1549,9 @@ ADMIN_TEMPLATE = '''
1527
  <input type="hidden" name="action" value="add_employee">
1528
  <div style="flex:1; min-width:150px;"><label>Имя</label><input type="text" name="employee_name" required autocomplete="off"></div>
1529
  <div style="flex:1; min-width:100px;"><label>PIN (4 цифры)</label><input type="text" name="pin" pattern="\\d{4}" required autocomplete="off"></div>
1530
- <div style="flex:1; min-width:100px;"><label>Ставка за день</label><input type="number" name="daily_rate" value="0" required></div>
1531
- <div style="flex:1; min-width:100px;"><label>Цель (n сумма)</label><input type="number" name="target_amount" value="0" required></div>
1532
- <div style="flex:1; min-width:100px;"><label>Бонус (%)</label><input type="number" name="bonus_percentage" value="0" step="0.1" required></div>
1533
  <button type="submit" class="btn btn-dark" style="width:100%; margin-top:10px;"><i class="fas fa-plus"></i> Добавить</button>
1534
  </form>
1535
  </div>
@@ -1544,7 +1566,7 @@ ADMIN_TEMPLATE = '''
1544
  <div style="flex:1; min-width:150px;"><label>Имя</label><input type="text" name="name" value="{{ emp.name }}" required></div>
1545
  <div style="flex:1; min-width:100px;"><label>PIN</label><input type="text" name="pin" value="{{ emp.pin }}" pattern="\\d{4}" required></div>
1546
  <div style="flex:1; min-width:100px;"><label>Ставка</label><input type="number" name="daily_rate" value="{{ emp.daily_rate }}" required></div>
1547
- <div style="flex:1; min-width:100px;"><label>Цель (n)</label><input type="number" name="target_amount" value="{{ emp.target_amount }}" required></div>
1548
  <div style="flex:1; min-width:100px;"><label>Бонус %</label><input type="number" name="bonus_percentage" value="{{ emp.bonus_percentage }}" step="0.1" required></div>
1549
  <button type="submit" class="btn btn-primary" title="Сохранить"><i class="fas fa-save"></i></button>
1550
  </form>
@@ -1969,22 +1991,29 @@ def admin_salary():
1969
  if request.method == 'POST':
1970
  action = request.form.get('action')
1971
  if action == 'add_fine':
1972
- fine = {
1973
- 'id': uuid4().hex,
1974
- 'employee_id': request.form.get('employee_id'),
1975
- 'date': request.form.get('date'),
1976
- 'amount': float(request.form.get('amount', 0)),
1977
- 'reason': request.form.get('reason', '')
1978
- }
1979
- if 'fines' not in data:
1980
- data['fines'] = []
1981
- data['fines'].append(fine)
1982
- save_data(data)
 
 
 
 
 
 
 
1983
  elif action == 'delete_fine':
1984
  fine_id = request.form.get('fine_id')
1985
- if 'fines' in data:
1986
- data['fines'] = [f for f in data['fines'] if f.get('id') != fine_id]
1987
- save_data(data)
1988
  return redirect(url_for('admin_salary'))
1989
 
1990
  orders = data.get('orders', {})
@@ -1999,7 +2028,6 @@ def admin_salary():
1999
  workdays_json=json.dumps(workdays),
2000
  fines_json=json.dumps(fines),
2001
  employees=employees,
2002
- fines=fines,
2003
  currency_code=CURRENCY_CODE
2004
  )
2005
 
@@ -2028,12 +2056,12 @@ def admin():
2028
  elif action == 'add_employee':
2029
  emp_name = request.form.get('employee_name', '').strip()
2030
  pin = request.form.get('pin', '0000').strip()
2031
- try: daily_rate = float(request.form.get('daily_rate', 0))
2032
- except: daily_rate = 0
2033
- try: target_amount = float(request.form.get('target_amount', 0))
2034
- except: target_amount = 0
2035
- try: bonus_percentage = float(request.form.get('bonus_percentage', 0))
2036
- except: bonus_percentage = 0
2037
 
2038
  if emp_name:
2039
  employees.append({
@@ -2053,12 +2081,12 @@ def admin():
2053
  if e.get('id') == emp_id:
2054
  e['name'] = request.form.get('name', '').strip()
2055
  e['pin'] = request.form.get('pin', '0000').strip()
2056
- try: e['daily_rate'] = float(request.form.get('daily_rate', 0))
2057
- except: e['daily_rate'] = 0
2058
- try: e['target_amount'] = float(request.form.get('target_amount', 0))
2059
- except: e['target_amount'] = 0
2060
- try: e['bonus_percentage'] = float(request.form.get('bonus_percentage', 0))
2061
- except: e['bonus_percentage'] = 0
2062
  break
2063
  data['employees'] = employees
2064
  save_data(data)
 
63
  try:
64
  if file_name == DATA_FILE:
65
  with open(file_name, 'w', encoding='utf-8') as f:
66
+ json.dump({
67
+ 'products': [],
68
+ 'categories': [],
69
+ 'orders': {},
70
+ 'employees': [],
71
+ 'workdays': {},
72
+ 'fines': [],
73
+ 'settings': {
74
+ 'cafe_name': 'HongKong',
75
+ 'wa_shift1': '+77470623684',
76
+ 'wa_shift2': '+77470623684',
77
+ 'active_shift': 1,
78
+ 'logo_version': '1'
79
+ }
80
+ }, f)
81
  except Exception:
82
  pass
83
  success = False
 
188
 
189
  for emp in data['employees']:
190
  if 'pin' not in emp: emp['pin'] = '0000'
191
+ if 'daily_rate' not in emp or emp['daily_rate'] == 0: emp['daily_rate'] = 230000
192
+ if 'target_amount' not in emp or emp['target_amount'] == 0: emp['target_amount'] = 1500000
193
+ if 'bonus_percentage' not in emp or emp['bonus_percentage'] == 0: emp['bonus_percentage'] = 10
194
 
195
  if not os.path.exists(DATA_FILE):
196
  try:
 
953
  <div class="totals-area">
954
  <div class="totals-row">
955
  <span>Итого:</span>
956
+ <span>{{ order.total_price }}</span>
957
  </div>
958
  {% set discount = order.discount|default(0)|float %}
959
  {% if discount > 0 %}
 
1179
  <head>
1180
  <meta charset="UTF-8">
1181
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
1182
+ <title>Отчет по ЗП</title>
1183
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
1184
  <style>
1185
  :root { --primary: #D4AF37; --bg: #000000; --surface: #111111; --border: #333333; --danger: #ff4757; --success: #2ed573; --info: #D4AF37; --warning: #ffa502; --text: #ffffff; --text-muted: #888888; }
 
1192
  .btn { padding: 12px 20px; border: none; border-radius: 10px; font-weight: 700; cursor: pointer; color: #000; text-decoration: none; display: inline-flex; align-items: center; gap: 8px; font-size: 0.95rem; transition: opacity 0.2s; }
1193
  .btn:active { opacity: 0.8; }
1194
  .btn-success { background: var(--success); }
1195
+ .btn-danger { background: var(--danger); color: #fff; }
1196
  .btn-dark { background: #1a1a1a; color: var(--primary); border: 1px solid var(--border); }
1197
 
1198
  .report-section { background: var(--surface); padding: 20px; border-radius: 16px; border: 1px solid var(--border); margin-bottom: 20px; }
1199
 
1200
  .form-row { display: flex; gap: 10px; flex-wrap: wrap; }
1201
  .form-row > * { flex: 1; min-width: 150px; }
1202
+ input[type="date"], input[type="number"], input[type="text"], select { width: 100%; padding: 12px 15px; border: 1px solid var(--border); border-radius: 10px; font-size: 0.95rem; outline: none; transition: border-color 0.2s; background: #1a1a1a; color: var(--text); }
1203
+ input:focus, select:focus { border-color: var(--primary); background: #222; }
1204
  label { display: block; margin-bottom: 5px; color: var(--text-muted); font-size: 0.85rem; font-weight: 600; }
1205
 
1206
  .rep-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
 
1216
  <body>
1217
  <div class="container">
1218
  <div class="header-panel">
1219
+ <h1><i class="fas fa-money-bill-wave"></i> Отчет по ЗП</h1>
1220
  <a href="/admin" class="btn btn-dark"><i class="fas fa-arrow-left"></i> Назад в админку</a>
1221
  </div>
1222
 
1223
  <div class="report-section">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1224
  <div class="form-row" style="margin-bottom: 20px; align-items: flex-end;">
1225
  <div>
1226
  <label>Дата начала</label>
 
1236
  <table class="rep-table" id="tableSalary"></table>
1237
  </div>
1238
  </div>
1239
+
1240
+ <div class="report-section">
1241
+ <h2 style="color: var(--primary); margin-top: 0; margin-bottom: 15px;"><i class="fas fa-exclamation-triangle"></i> Добавить штраф</h2>
1242
+ <form method="POST" action="/admin/salary" style="display:flex; flex-direction:column; gap:12px;">
1243
+ <input type="hidden" name="action" value="add_fine">
1244
+ <div class="form-row">
1245
+ <div>
1246
+ <label>Сотрудник</label>
1247
+ <select name="employee_id" required>
1248
+ <option value="">Выберите сотрудника</option>
1249
+ {% for emp in employees %}
1250
+ <option value="{{ emp.id }}">{{ emp.name }}</option>
1251
+ {% endfor %}
1252
+ </select>
1253
+ </div>
1254
+ <div>
1255
+ <label>Сумма штрафа (сум)</label>
1256
+ <input type="number" name="amount" placeholder="Например, 50000" required>
1257
+ </div>
1258
+ <div>
1259
+ <label>Дата</label>
1260
+ <input type="date" name="date" id="fineDate" required>
1261
+ </div>
1262
+ </div>
1263
+ <div>
1264
+ <label>Причина штрафа</label>
1265
+ <input type="text" name="reason" placeholder="Причина..." required autocomplete="off">
1266
+ </div>
1267
+ <button type="submit" class="btn btn-danger" style="align-self: flex-start;"><i class="fas fa-plus"></i> Наложить штраф</button>
1268
+ </form>
1269
+ </div>
1270
+
1271
+ <div class="report-section">
1272
+ <h2 style="color: var(--primary); margin-top: 0; margin-bottom: 15px;"><i class="fas fa-list"></i> Список штрафов за период</h2>
1273
+ <div style="overflow-x:auto;">
1274
+ <table class="rep-table" id="tableFines"></table>
1275
+ </div>
1276
+ </div>
1277
  </div>
1278
 
1279
  <script>
 
1293
  const firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
1294
  document.getElementById('salStart').value = getLocalDateStr(firstDay);
1295
  document.getElementById('salEnd').value = todayStr;
1296
+ document.getElementById('fineDate').value = todayStr;
1297
  generateSalaryReport();
1298
  }
1299
 
 
1302
  const end = document.getElementById('salEnd').value;
1303
  if (!start || !end) return;
1304
 
1305
+ let html = '<tr><th>Сотрудник</th><th>Отработано дней</th><th>Фикс. ЗП</th><th>Бонусы (>1.5M)</th><th>Штрафы</th><th>Итого ЗП</th></tr>';
1306
 
1307
  let empSales = {};
1308
  allOrders.forEach(o => {
 
1313
  empSales[o.employee_id][oDate] += o.total_price;
1314
  }
1315
  });
1316
+
1317
+ let empFines = {};
1318
+ fines.forEach(f => {
1319
+ if (f.date >= start && f.date <= end) {
1320
+ if (!empFines[f.employee_id]) empFines[f.employee_id] = 0;
1321
+ empFines[f.employee_id] += parseFloat(f.amount || 0);
1322
+ }
1323
+ });
1324
 
1325
  employees.forEach(emp => {
1326
  let daysWorked = 0;
1327
  let fixedSal = 0;
1328
  let bonusSal = 0;
 
 
 
 
 
 
 
1329
 
1330
  let currDate = new Date(start);
1331
  const endDate = new Date(end);
 
1338
 
1339
  if (worked) {
1340
  daysWorked++;
1341
+ fixedSal += parseFloat(emp.daily_rate || 230000);
1342
  }
1343
+
1344
+ const target = parseFloat(emp.target_amount || 1500000);
1345
+ const pct = parseFloat(emp.bonus_percentage || 10);
1346
 
1347
  if (pct > 0 && dailySales > target) {
1348
  bonusSal += (dailySales - target) * (pct / 100);
1349
  }
1350
+
1351
  currDate.setDate(currDate.getDate() + 1);
1352
  }
1353
+
1354
+ const totalFines = empFines[emp.id] || 0;
1355
  const total = fixedSal + bonusSal - totalFines;
1356
+
1357
+ if (daysWorked > 0 || total !== 0 || bonusSal > 0 || totalFines > 0) {
1358
  html += `<tr>
1359
  <td>${emp.name}</td>
1360
  <td>${daysWorked}</td>
1361
+ <td>${fixedSal.toFixed(0)} ${currencyCode}</td>
1362
+ <td>${bonusSal.toFixed(0)} ${currencyCode}</td>
1363
+ <td style="color:var(--danger);">${totalFines.toFixed(0)} ${currencyCode}</td>
1364
+ <td style="font-weight:bold; color:var(--success);">${total.toFixed(0)} ${currencyCode}</td>
1365
  </tr>`;
1366
  }
1367
  });
1368
 
 
 
 
1369
  document.getElementById('tableSalary').innerHTML = html;
1370
+ renderFinesTable(start, end);
1371
+ }
1372
+
1373
+ function renderFinesTable(start, end) {
1374
+ let html = '<tr><th>Дата</th><th>Сотрудник</th><th>Причина</th><th>Сумма</th><th>Действие</th></tr>';
1375
+ let count = 0;
1376
+
1377
+ fines.forEach(f => {
1378
+ if (f.date >= start && f.date <= end) {
1379
+ count++;
1380
+ const emp = employees.find(e => e.id === f.employee_id);
1381
+ const empName = emp ? emp.name : 'Удален';
1382
+ html += `<tr>
1383
+ <td>${f.date}</td>
1384
+ <td>${empName}</td>
1385
+ <td>${f.reason}</td>
1386
+ <td style="color:var(--danger); font-weight:bold;">-${f.amount} ${currencyCode}</td>
1387
+ <td>
1388
+ <form method="POST" action="/admin/salary" style="margin:0;" onsubmit="return confirm('Удалить штраф?');">
1389
+ <input type="hidden" name="action" value="delete_fine">
1390
+ <input type="hidden" name="fine_id" value="${f.id}">
1391
+ <button type="submit" class="btn btn-danger" style="padding:4px 8px; font-size:0.8rem;"><i class="fas fa-trash"></i></button>
1392
+ </form>
1393
+ </td>
1394
+ </tr>`;
1395
+ }
1396
+ });
1397
+
1398
+ if (count === 0) {
1399
+ html += '<tr><td colspan="5">Штрафов за выбранный период нет</td></tr>';
1400
+ }
1401
+ document.getElementById('tableFines').innerHTML = html;
1402
  }
1403
 
1404
  window.onload = () => {
 
1549
  <input type="hidden" name="action" value="add_employee">
1550
  <div style="flex:1; min-width:150px;"><label>Имя</label><input type="text" name="employee_name" required autocomplete="off"></div>
1551
  <div style="flex:1; min-width:100px;"><label>PIN (4 цифры)</label><input type="text" name="pin" pattern="\\d{4}" required autocomplete="off"></div>
1552
+ <div style="flex:1; min-width:100px;"><label>Ставка за день</label><input type="number" name="daily_rate" value="230000" required></div>
1553
+ <div style="flex:1; min-width:100px;"><label>Цель (план)</label><input type="number" name="target_amount" value="1500000" required></div>
1554
+ <div style="flex:1; min-width:100px;"><label>Бонус (%)</label><input type="number" name="bonus_percentage" value="10" step="0.1" required></div>
1555
  <button type="submit" class="btn btn-dark" style="width:100%; margin-top:10px;"><i class="fas fa-plus"></i> Добавить</button>
1556
  </form>
1557
  </div>
 
1566
  <div style="flex:1; min-width:150px;"><label>Имя</label><input type="text" name="name" value="{{ emp.name }}" required></div>
1567
  <div style="flex:1; min-width:100px;"><label>PIN</label><input type="text" name="pin" value="{{ emp.pin }}" pattern="\\d{4}" required></div>
1568
  <div style="flex:1; min-width:100px;"><label>Ставка</label><input type="number" name="daily_rate" value="{{ emp.daily_rate }}" required></div>
1569
+ <div style="flex:1; min-width:100px;"><label>Цель (план)</label><input type="number" name="target_amount" value="{{ emp.target_amount }}" required></div>
1570
  <div style="flex:1; min-width:100px;"><label>Бонус %</label><input type="number" name="bonus_percentage" value="{{ emp.bonus_percentage }}" step="0.1" required></div>
1571
  <button type="submit" class="btn btn-primary" title="Сохранить"><i class="fas fa-save"></i></button>
1572
  </form>
 
1991
  if request.method == 'POST':
1992
  action = request.form.get('action')
1993
  if action == 'add_fine':
1994
+ emp_id = request.form.get('employee_id')
1995
+ try: amount = float(request.form.get('amount', 0))
1996
+ except: amount = 0
1997
+ reason = request.form.get('reason', '').strip()
1998
+ date_str = request.form.get('date', get_current_date())
1999
+
2000
+ if emp_id and amount > 0:
2001
+ fines = data.get('fines', [])
2002
+ fines.append({
2003
+ 'id': uuid4().hex,
2004
+ 'employee_id': emp_id,
2005
+ 'amount': amount,
2006
+ 'reason': reason,
2007
+ 'date': date_str
2008
+ })
2009
+ data['fines'] = fines
2010
+ save_data(data)
2011
+
2012
  elif action == 'delete_fine':
2013
  fine_id = request.form.get('fine_id')
2014
+ data['fines'] = [f for f in data.get('fines', []) if f.get('id') != fine_id]
2015
+ save_data(data)
2016
+
2017
  return redirect(url_for('admin_salary'))
2018
 
2019
  orders = data.get('orders', {})
 
2028
  workdays_json=json.dumps(workdays),
2029
  fines_json=json.dumps(fines),
2030
  employees=employees,
 
2031
  currency_code=CURRENCY_CODE
2032
  )
2033
 
 
2056
  elif action == 'add_employee':
2057
  emp_name = request.form.get('employee_name', '').strip()
2058
  pin = request.form.get('pin', '0000').strip()
2059
+ try: daily_rate = float(request.form.get('daily_rate', 230000))
2060
+ except: daily_rate = 230000
2061
+ try: target_amount = float(request.form.get('target_amount', 1500000))
2062
+ except: target_amount = 1500000
2063
+ try: bonus_percentage = float(request.form.get('bonus_percentage', 10))
2064
+ except: bonus_percentage = 10
2065
 
2066
  if emp_name:
2067
  employees.append({
 
2081
  if e.get('id') == emp_id:
2082
  e['name'] = request.form.get('name', '').strip()
2083
  e['pin'] = request.form.get('pin', '0000').strip()
2084
+ try: e['daily_rate'] = float(request.form.get('daily_rate', 230000))
2085
+ except: e['daily_rate'] = 230000
2086
+ try: e['target_amount'] = float(request.form.get('target_amount', 1500000))
2087
+ except: e['target_amount'] = 1500000
2088
+ try: e['bonus_percentage'] = float(request.form.get('bonus_percentage', 10))
2089
+ except: e['bonus_percentage'] = 10
2090
  break
2091
  data['employees'] = employees
2092
  save_data(data)