Kgshop commited on
Commit
dce317c
·
verified ·
1 Parent(s): a38605a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +103 -103
app.py CHANGED
@@ -180,9 +180,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:
@@ -1171,7 +1171,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,14 +1184,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-dark { background: #1a1a1a; color: var(--primary); border: 1px solid var(--border); }
1188
 
1189
  .report-section { background: var(--surface); padding: 20px; border-radius: 16px; border: 1px solid var(--border); margin-bottom: 20px; }
1190
 
1191
  .form-row { display: flex; gap: 10px; flex-wrap: wrap; }
1192
  .form-row > * { flex: 1; min-width: 150px; }
1193
- 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); }
1194
- input[type="date"]:focus, input[type="number"]:focus, input[type="text"]:focus { border-color: var(--primary); background: #222; }
1195
  label { display: block; margin-bottom: 5px; color: var(--text-muted); font-size: 0.85rem; font-weight: 600; }
1196
 
1197
  .rep-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
@@ -1212,6 +1213,64 @@ SALARY_TEMPLATE = '''
1212
  </div>
1213
 
1214
  <div class="report-section">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1215
  <div class="form-row" style="margin-bottom: 20px; align-items: flex-end;">
1216
  <div>
1217
  <label>Дата начала</label>
@@ -1227,49 +1286,13 @@ SALARY_TEMPLATE = '''
1227
  <table class="rep-table" id="tableSalary"></table>
1228
  </div>
1229
  </div>
1230
-
1231
- <div class="report-section" style="margin-top:20px;">
1232
- <h2 style="color:var(--primary); margin-top:0;">Штрафы</h2>
1233
-
1234
- <div style="background:var(--surface); border:1px solid var(--border); padding:15px; border-radius:10px; margin-bottom:20px;">
1235
- <h3 style="margin-top:0; color:var(--danger);">Добавить штраф</h3>
1236
- <form method="POST" style="display:flex; flex-wrap:wrap; gap:10px;">
1237
- <input type="hidden" name="action" value="add_fine">
1238
- <div style="flex:1; min-width:150px;">
1239
- <label>Сотрудник</label>
1240
- <select name="employee_id" required style="width:100%; padding:12px; border:1px solid var(--border); border-radius:10px; background:#1a1a1a; color:var(--text); outline:none;">
1241
- {% for emp in employees %}
1242
- <option value="{{ emp.id }}">{{ emp.name }}</option>
1243
- {% endfor %}
1244
- </select>
1245
- </div>
1246
- <div style="flex:1; min-width:150px;">
1247
- <label>Дата</label>
1248
- <input type="date" name="date" required id="fineDateInput">
1249
- </div>
1250
- <div style="flex:1; min-width:100px;">
1251
- <label>Сумма</label>
1252
- <input type="number" name="amount" required>
1253
- </div>
1254
- <div style="flex:2; min-width:200px;">
1255
- <label>Причина</label>
1256
- <input type="text" name="reason" required autocomplete="off">
1257
- </div>
1258
- <button type="submit" class="btn" style="background:var(--danger); color:#fff; width:100%; margin-top:10px;"><i class="fas fa-plus"></i> Назначить штраф</button>
1259
- </form>
1260
- </div>
1261
-
1262
- <div style="overflow-x:auto;">
1263
- <table class="rep-table" id="tableFines"></table>
1264
- </div>
1265
- </div>
1266
  </div>
1267
 
1268
  <script>
1269
  const allOrders = {{ orders_json|safe }};
1270
  const workdays = {{ workdays_json|safe }};
1271
  const employees = {{ employees_json|safe }};
1272
- const allFines = {{ fines_json|safe }};
1273
  const currencyCode = '{{ currency_code }}';
1274
 
1275
  function getLocalDateStr(d) {
@@ -1282,7 +1305,8 @@ SALARY_TEMPLATE = '''
1282
  const firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
1283
  document.getElementById('salStart').value = getLocalDateStr(firstDay);
1284
  document.getElementById('salEnd').value = todayStr;
1285
- document.getElementById('fineDateInput').value = todayStr;
 
1286
  generateSalaryReport();
1287
  }
1288
 
@@ -1291,7 +1315,7 @@ SALARY_TEMPLATE = '''
1291
  const end = document.getElementById('salEnd').value;
1292
  if (!start || !end) return;
1293
 
1294
- let html = '<tr><th>Сотрудник</th><th>Дней (продажи > 0)</th><th>Фикс. ЗП</th><th>Бонусы с продаж</th><th>Штрафы</th><th>Итого ЗП</th></tr>';
1295
 
1296
  let empSales = {};
1297
  allOrders.forEach(o => {
@@ -1307,13 +1331,14 @@ SALARY_TEMPLATE = '''
1307
  let daysWorked = 0;
1308
  let fixedSal = 0;
1309
  let bonusSal = 0;
1310
- let fineSal = 0;
1311
 
1312
  let currDate = new Date(start);
1313
  const endDate = new Date(end);
1314
 
1315
  while(currDate <= endDate) {
1316
  const dateStr = getLocalDateStr(currDate);
 
1317
  const dailySales = (empSales[emp.id] && empSales[emp.id][dateStr]) ? empSales[emp.id][dateStr] : 0;
1318
 
1319
  if (dailySales > 0) {
@@ -1329,58 +1354,30 @@ SALARY_TEMPLATE = '''
1329
  }
1330
  currDate.setDate(currDate.getDate() + 1);
1331
  }
1332
-
1333
- allFines.forEach(f => {
1334
  if (f.employee_id === emp.id && f.date >= start && f.date <= end) {
1335
- fineSal += parseFloat(f.amount);
1336
  }
1337
  });
1338
 
1339
- const total = fixedSal + bonusSal - fineSal;
1340
- if (daysWorked > 0 || total !== 0 || fineSal > 0) {
1341
  html += `<tr>
1342
  <td>${emp.name}</td>
1343
  <td>${daysWorked}</td>
1344
  <td>${fixedSal.toFixed(2)} ${currencyCode}</td>
1345
  <td>${bonusSal.toFixed(2)} ${currencyCode}</td>
1346
- <td style="color:var(--danger);">${fineSal.toFixed(2)} ${currencyCode}</td>
1347
  <td style="font-weight:bold; color:var(--success);">${total.toFixed(2)} ${currencyCode}</td>
1348
  </tr>`;
1349
  }
1350
  });
1351
 
1352
- if (html === '<tr><th>Сотрудник</th><th>Дней (продажи > 0)</th><th>Фикс. ЗП</th><th>Бонусы с продаж</th><th>Штрафы</th><th>Итого ЗП</th></tr>') {
1353
  html += '<tr><td colspan="6">За выбранный период данных нет</td></tr>';
1354
  }
1355
  document.getElementById('tableSalary').innerHTML = html;
1356
- renderFinesTable(start, end);
1357
- }
1358
-
1359
- function renderFinesTable(start, end) {
1360
- let html = '<tr><th>Дата</th><th>Сотрудник</th><th>Сумма</th><th>Причина</th><th>Действие</th></tr>';
1361
- let found = false;
1362
- allFines.forEach(f => {
1363
- if (f.date >= start && f.date <= end) {
1364
- found = true;
1365
- const emp = employees.find(e => e.id === f.employee_id);
1366
- const empName = emp ? emp.name : 'Удален';
1367
- html += `<tr>
1368
- <td>${f.date}</td>
1369
- <td>${empName}</td>
1370
- <td style="color:var(--danger);">${f.amount} ${currencyCode}</td>
1371
- <td>${f.reason}</td>
1372
- <td>
1373
- <form method="POST" style="margin:0;" onsubmit="return confirm('Удалить штраф?');">
1374
- <input type="hidden" name="action" value="delete_fine">
1375
- <input type="hidden" name="fine_id" value="${f.id}">
1376
- <button type="submit" style="background:var(--danger); color:#fff; border:none; padding:5px 10px; border-radius:5px; cursor:pointer;"><i class="fas fa-trash"></i></button>
1377
- </form>
1378
- </td>
1379
- </tr>`;
1380
- }
1381
- });
1382
- if (!found) html += '<tr><td colspan="5">Штрафов за период нет</td></tr>';
1383
- document.getElementById('tableFines').innerHTML = html;
1384
  }
1385
 
1386
  window.onload = () => {
@@ -1530,9 +1527,9 @@ ADMIN_TEMPLATE = '''
1530
  <form method="POST" style="display:flex; flex-wrap:wrap; gap:10px;">
1531
  <input type="hidden" name="action" value="add_employee">
1532
  <div style="flex:1; min-width:150px;"><label>Имя</label><input type="text" name="employee_name" required autocomplete="off"></div>
1533
- <div style="flex:1; min-width:100px;"><label>PIN (4 цифры)</label><input type="text" name="pin" pattern="\d{4}" required autocomplete="off"></div>
1534
  <div style="flex:1; min-width:100px;"><label>Ставка за день</label><input type="number" name="daily_rate" value="230000" required></div>
1535
- <div style="flex:1; min-width:100px;"><label>Цель (n сумма)</label><input type="number" name="target_amount" value="0" required></div>
1536
  <div style="flex:1; min-width:100px;"><label>Бонус (%)</label><input type="number" name="bonus_percentage" value="10" step="0.1" required></div>
1537
  <button type="submit" class="btn btn-dark" style="width:100%; margin-top:10px;"><i class="fas fa-plus"></i> Добавить</button>
1538
  </form>
@@ -1546,7 +1543,7 @@ ADMIN_TEMPLATE = '''
1546
  <input type="hidden" name="action" value="edit_employee">
1547
  <input type="hidden" name="employee_id" value="{{ emp.id }}">
1548
  <div style="flex:1; min-width:150px;"><label>Имя</label><input type="text" name="name" value="{{ emp.name }}" required></div>
1549
- <div style="flex:1; min-width:100px;"><label>PIN</label><input type="text" name="pin" value="{{ emp.pin }}" pattern="\d{4}" required></div>
1550
  <div style="flex:1; min-width:100px;"><label>Ставка</label><input type="number" name="daily_rate" value="{{ emp.daily_rate }}" required></div>
1551
  <div style="flex:1; min-width:100px;"><label>Цель (n)</label><input type="number" name="target_amount" value="{{ emp.target_amount }}" required></div>
1552
  <div style="flex:1; min-width:100px;"><label>Бонус %</label><input type="number" name="bonus_percentage" value="{{ emp.bonus_percentage }}" step="0.1" required></div>
@@ -1969,40 +1966,43 @@ def admin_reports():
1969
  @app.route('/admin/salary', methods=['GET', 'POST'])
1970
  def admin_salary():
1971
  data = load_data()
1972
- orders = data.get('orders', {})
1973
- employees = data.get('employees', [])
1974
- workdays = data.get('workdays', {})
1975
- fines = data.get('fines', [])
1976
-
1977
  if request.method == 'POST':
1978
  action = request.form.get('action')
1979
  if action == 'add_fine':
1980
  emp_id = request.form.get('employee_id')
1981
- fine_date = request.form.get('date')
1982
  amount = float(request.form.get('amount', 0))
1983
- reason = request.form.get('reason', '').strip()
1984
- fines.append({
1985
  'id': uuid4().hex,
1986
  'employee_id': emp_id,
1987
- 'date': fine_date,
1988
  'amount': amount,
1989
  'reason': reason
1990
  })
1991
- data['fines'] = fines
1992
  save_data(data)
1993
  return redirect(url_for('admin_salary'))
1994
  elif action == 'delete_fine':
1995
  fine_id = request.form.get('fine_id')
1996
- data['fines'] = [f for f in fines if f.get('id') != fine_id]
1997
  save_data(data)
1998
  return redirect(url_for('admin_salary'))
1999
 
 
 
 
 
 
 
 
2000
  return render_template_string(
2001
  SALARY_TEMPLATE,
2002
  orders_json=json.dumps(list(orders.values())),
2003
  employees_json=json.dumps(employees),
2004
  workdays_json=json.dumps(workdays),
2005
- fines_json=json.dumps(data.get('fines', [])),
 
2006
  employees=employees,
2007
  currency_code=CURRENCY_CODE
2008
  )
@@ -2033,11 +2033,11 @@ def admin():
2033
  emp_name = request.form.get('employee_name', '').strip()
2034
  pin = request.form.get('pin', '0000').strip()
2035
  try: daily_rate = float(request.form.get('daily_rate', 0))
2036
- except: daily_rate = 0
2037
  try: target_amount = float(request.form.get('target_amount', 0))
2038
- except: target_amount = 0
2039
  try: bonus_percentage = float(request.form.get('bonus_percentage', 0))
2040
- except: bonus_percentage = 0
2041
 
2042
  if emp_name:
2043
  employees.append({
@@ -2058,11 +2058,11 @@ def admin():
2058
  e['name'] = request.form.get('name', '').strip()
2059
  e['pin'] = request.form.get('pin', '0000').strip()
2060
  try: e['daily_rate'] = float(request.form.get('daily_rate', 0))
2061
- except: e['daily_rate'] = 0
2062
  try: e['target_amount'] = float(request.form.get('target_amount', 0))
2063
- except: e['target_amount'] = 0
2064
  try: e['bonus_percentage'] = float(request.form.get('bonus_percentage', 0))
2065
- except: e['bonus_percentage'] = 0
2066
  break
2067
  data['employees'] = employees
2068
  save_data(data)
@@ -2239,4 +2239,4 @@ if __name__ == '__main__':
2239
  threading.Thread(target=periodic_backup, daemon=True).start()
2240
 
2241
  port = int(os.environ.get('PORT', 7860))
2242
- app.run(host='0.0.0.0', port=port)
 
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'] = 230000
184
+ if 'target_amount' not in emp: emp['target_amount'] = 1500000
185
+ if 'bonus_percentage' not in emp: emp['bonus_percentage'] = 10
186
 
187
  if not os.path.exists(DATA_FILE):
188
  try:
 
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
  .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"], 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); }
1195
+ input[type="date"]:focus, input[type="number"]:focus, input[type="text"]:focus, select: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; }
 
1213
  </div>
1214
 
1215
  <div class="report-section">
1216
+ <h2 style="margin-top:0; color:var(--primary); font-size:1.2rem; margin-bottom: 15px;">Управление штрафами</h2>
1217
+ <form method="POST" style="display:flex; gap:10px; flex-wrap:wrap; align-items:flex-end;">
1218
+ <input type="hidden" name="action" value="add_fine">
1219
+ <div style="flex:1; min-width:150px;">
1220
+ <label>Сотрудник</label>
1221
+ <select name="employee_id" required>
1222
+ {% for emp in employees %}
1223
+ <option value="{{ emp.id }}">{{ emp.name }}</option>
1224
+ {% endfor %}
1225
+ </select>
1226
+ </div>
1227
+ <div style="flex:1; min-width:120px;">
1228
+ <label>Дата</label>
1229
+ <input type="date" name="date" required id="fineDate">
1230
+ </div>
1231
+ <div style="flex:1; min-width:120px;">
1232
+ <label>Сумма</label>
1233
+ <input type="number" name="amount" required step="1" min="0">
1234
+ </div>
1235
+ <div style="flex:2; min-width:150px;">
1236
+ <label>Причина</label>
1237
+ <input type="text" name="reason" required autocomplete="off">
1238
+ </div>
1239
+ <button type="submit" class="btn btn-danger" style="height:44px; color:#fff;"><i class="fas fa-plus"></i> Добавить штраф</button>
1240
+ </form>
1241
+
1242
+ <h3 style="margin-top:25px; margin-bottom:10px; font-size:1.1rem; color:var(--primary);">История штрафов</h3>
1243
+ <div style="overflow-x:auto;">
1244
+ <table class="rep-table">
1245
+ <tr><th>Сотрудник</th><th>Дата</th><th>Причина</th><th>Сумма</th><th>Действия</th></tr>
1246
+ {% for fine in fines %}
1247
+ <tr>
1248
+ <td>
1249
+ {% for emp in employees %}
1250
+ {% if emp.id == fine.employee_id %}{{ emp.name }}{% endif %}
1251
+ {% endfor %}
1252
+ </td>
1253
+ <td>{{ fine.date }}</td>
1254
+ <td>{{ fine.reason }}</td>
1255
+ <td style="color:var(--danger); font-weight:bold;">-{{ fine.amount }} {{ currency_code }}</td>
1256
+ <td>
1257
+ <form method="POST" style="margin:0;" onsubmit="return confirm('Удалить штраф?');">
1258
+ <input type="hidden" name="action" value="delete_fine">
1259
+ <input type="hidden" name="fine_id" value="{{ fine.id }}">
1260
+ <button type="submit" class="btn btn-dark" style="padding:5px 10px; border-color:var(--danger); color:var(--danger);"><i class="fas fa-trash"></i></button>
1261
+ </form>
1262
+ </td>
1263
+ </tr>
1264
+ {% endfor %}
1265
+ {% if not fines %}
1266
+ <tr><td colspan="5" style="text-align:center; padding:15px; color:var(--text-muted);">Нет штрафов</td></tr>
1267
+ {% endif %}
1268
+ </table>
1269
+ </div>
1270
+ </div>
1271
+
1272
+ <div class="report-section">
1273
+ <h2 style="margin-top:0; color:var(--primary); font-size:1.2rem; margin-bottom: 15px;">Генерация Зарплаты</h2>
1274
  <div class="form-row" style="margin-bottom: 20px; align-items: flex-end;">
1275
  <div>
1276
  <label>Дата начала</label>
 
1286
  <table class="rep-table" id="tableSalary"></table>
1287
  </div>
1288
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1289
  </div>
1290
 
1291
  <script>
1292
  const allOrders = {{ orders_json|safe }};
1293
  const workdays = {{ workdays_json|safe }};
1294
  const employees = {{ employees_json|safe }};
1295
+ const fines = {{ fines_json|safe }};
1296
  const currencyCode = '{{ currency_code }}';
1297
 
1298
  function getLocalDateStr(d) {
 
1305
  const firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
1306
  document.getElementById('salStart').value = getLocalDateStr(firstDay);
1307
  document.getElementById('salEnd').value = todayStr;
1308
+ const fineDateInput = document.getElementById('fineDate');
1309
+ if(fineDateInput) { fineDateInput.value = todayStr; }
1310
  generateSalaryReport();
1311
  }
1312
 
 
1315
  const end = document.getElementById('salEnd').value;
1316
  if (!start || !end) return;
1317
 
1318
+ let html = '<tr><th>Сотрудник</th><th>Дней отработано</th><th>Фикс. ЗП</th><th>Бонусы с продаж</th><th>Штрафы</th><th>Итого ЗП</th></tr>';
1319
 
1320
  let empSales = {};
1321
  allOrders.forEach(o => {
 
1331
  let daysWorked = 0;
1332
  let fixedSal = 0;
1333
  let bonusSal = 0;
1334
+ let totalFines = 0;
1335
 
1336
  let currDate = new Date(start);
1337
  const endDate = new Date(end);
1338
 
1339
  while(currDate <= endDate) {
1340
  const dateStr = getLocalDateStr(currDate);
1341
+
1342
  const dailySales = (empSales[emp.id] && empSales[emp.id][dateStr]) ? empSales[emp.id][dateStr] : 0;
1343
 
1344
  if (dailySales > 0) {
 
1354
  }
1355
  currDate.setDate(currDate.getDate() + 1);
1356
  }
1357
+
1358
+ fines.forEach(f => {
1359
  if (f.employee_id === emp.id && f.date >= start && f.date <= end) {
1360
+ totalFines += parseFloat(f.amount);
1361
  }
1362
  });
1363
 
1364
+ const total = fixedSal + bonusSal - totalFines;
1365
+ if (daysWorked > 0 || total > 0 || totalFines > 0) {
1366
  html += `<tr>
1367
  <td>${emp.name}</td>
1368
  <td>${daysWorked}</td>
1369
  <td>${fixedSal.toFixed(2)} ${currencyCode}</td>
1370
  <td>${bonusSal.toFixed(2)} ${currencyCode}</td>
1371
+ <td style="color:var(--danger);">-${totalFines.toFixed(2)} ${currencyCode}</td>
1372
  <td style="font-weight:bold; color:var(--success);">${total.toFixed(2)} ${currencyCode}</td>
1373
  </tr>`;
1374
  }
1375
  });
1376
 
1377
+ if (html === '<tr><th>Сотрудник</th><th>Дней отработано</th><th>Фикс. ЗП</th><th>Бонусы с продаж</th><th>Штрафы</th><th>Итого ЗП</th></tr>') {
1378
  html += '<tr><td colspan="6">За выбранный период данных нет</td></tr>';
1379
  }
1380
  document.getElementById('tableSalary').innerHTML = html;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1381
  }
1382
 
1383
  window.onload = () => {
 
1527
  <form method="POST" style="display:flex; flex-wrap:wrap; gap:10px;">
1528
  <input type="hidden" name="action" value="add_employee">
1529
  <div style="flex:1; min-width:150px;"><label>Имя</label><input type="text" name="employee_name" required autocomplete="off"></div>
1530
+ <div style="flex:1; min-width:100px;"><label>PIN (4 цифры)</label><input type="text" name="pin" pattern="\\d{4}" required autocomplete="off"></div>
1531
  <div style="flex:1; min-width:100px;"><label>Ставка за день</label><input type="number" name="daily_rate" value="230000" required></div>
1532
+ <div style="flex:1; min-width:100px;"><label>Цель (n сумма)</label><input type="number" name="target_amount" value="1500000" required></div>
1533
  <div style="flex:1; min-width:100px;"><label>Бонус (%)</label><input type="number" name="bonus_percentage" value="10" step="0.1" required></div>
1534
  <button type="submit" class="btn btn-dark" style="width:100%; margin-top:10px;"><i class="fas fa-plus"></i> Добавить</button>
1535
  </form>
 
1543
  <input type="hidden" name="action" value="edit_employee">
1544
  <input type="hidden" name="employee_id" value="{{ emp.id }}">
1545
  <div style="flex:1; min-width:150px;"><label>Имя</label><input type="text" name="name" value="{{ emp.name }}" required></div>
1546
+ <div style="flex:1; min-width:100px;"><label>PIN</label><input type="text" name="pin" value="{{ emp.pin }}" pattern="\\d{4}" required></div>
1547
  <div style="flex:1; min-width:100px;"><label>Ставка</label><input type="number" name="daily_rate" value="{{ emp.daily_rate }}" required></div>
1548
  <div style="flex:1; min-width:100px;"><label>Цель (n)</label><input type="number" name="target_amount" value="{{ emp.target_amount }}" required></div>
1549
  <div style="flex:1; min-width:100px;"><label>Бонус %</label><input type="number" name="bonus_percentage" value="{{ emp.bonus_percentage }}" step="0.1" required></div>
 
1966
  @app.route('/admin/salary', methods=['GET', 'POST'])
1967
  def admin_salary():
1968
  data = load_data()
1969
+
 
 
 
 
1970
  if request.method == 'POST':
1971
  action = request.form.get('action')
1972
  if action == 'add_fine':
1973
  emp_id = request.form.get('employee_id')
1974
+ date_fine = request.form.get('date')
1975
  amount = float(request.form.get('amount', 0))
1976
+ reason = request.form.get('reason', '')
1977
+ data.setdefault('fines', []).append({
1978
  'id': uuid4().hex,
1979
  'employee_id': emp_id,
1980
+ 'date': date_fine,
1981
  'amount': amount,
1982
  'reason': reason
1983
  })
 
1984
  save_data(data)
1985
  return redirect(url_for('admin_salary'))
1986
  elif action == 'delete_fine':
1987
  fine_id = request.form.get('fine_id')
1988
+ data['fines'] = [f for f in data.get('fines', []) if f.get('id') != fine_id]
1989
  save_data(data)
1990
  return redirect(url_for('admin_salary'))
1991
 
1992
+ orders = data.get('orders', {})
1993
+ employees = data.get('employees', [])
1994
+ workdays = data.get('workdays', {})
1995
+ fines = data.get('fines', [])
1996
+
1997
+ fines = sorted(fines, key=lambda x: x.get('date', ''), reverse=True)
1998
+
1999
  return render_template_string(
2000
  SALARY_TEMPLATE,
2001
  orders_json=json.dumps(list(orders.values())),
2002
  employees_json=json.dumps(employees),
2003
  workdays_json=json.dumps(workdays),
2004
+ fines_json=json.dumps(fines),
2005
+ fines=fines,
2006
  employees=employees,
2007
  currency_code=CURRENCY_CODE
2008
  )
 
2033
  emp_name = request.form.get('employee_name', '').strip()
2034
  pin = request.form.get('pin', '0000').strip()
2035
  try: daily_rate = float(request.form.get('daily_rate', 0))
2036
+ except: daily_rate = 230000
2037
  try: target_amount = float(request.form.get('target_amount', 0))
2038
+ except: target_amount = 1500000
2039
  try: bonus_percentage = float(request.form.get('bonus_percentage', 0))
2040
+ except: bonus_percentage = 10
2041
 
2042
  if emp_name:
2043
  employees.append({
 
2058
  e['name'] = request.form.get('name', '').strip()
2059
  e['pin'] = request.form.get('pin', '0000').strip()
2060
  try: e['daily_rate'] = float(request.form.get('daily_rate', 0))
2061
+ except: e['daily_rate'] = 230000
2062
  try: e['target_amount'] = float(request.form.get('target_amount', 0))
2063
+ except: e['target_amount'] = 1500000
2064
  try: e['bonus_percentage'] = float(request.form.get('bonus_percentage', 0))
2065
+ except: e['bonus_percentage'] = 10
2066
  break
2067
  data['employees'] = employees
2068
  save_data(data)
 
2239
  threading.Thread(target=periodic_backup, daemon=True).start()
2240
 
2241
  port = int(os.environ.get('PORT', 7860))
2242
+ app.run(host='0.0.0.0', port=port)