Kgshop commited on
Commit
3e23eaa
·
verified ·
1 Parent(s): 5ee204e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +85 -92
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:
@@ -1184,15 +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-danger { background: var(--danger); color: #fff; }
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="text"], input[type="number"], 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="text"]:focus, input[type="number"]: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; }
@@ -1211,54 +1211,22 @@ SALARY_TEMPLATE = '''
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 class="form-row" style="margin-bottom: 20px; align-items: flex-end;">
1217
- <div>
1218
- <label>Дата начала</label>
1219
- <input type="date" id="salStart">
1220
- </div>
1221
- <div>
1222
- <label>Дата конца</label>
1223
- <input type="date" id="salEnd">
1224
- </div>
1225
- <button class="btn btn-success" style="height: 44px; color:#000;" onclick="generateSalaryReport()"><i class="fas fa-calculator"></i> Посчитать ЗП</button>
1226
- </div>
1227
- <div style="overflow-x:auto;">
1228
- <table class="rep-table" id="tableSalary"></table>
1229
- </div>
1230
- </div>
1231
-
1232
- <div class="report-section" style="margin-top: 20px;">
1233
- <h3 style="color:var(--primary); margin-top:0; border-bottom: 1px dashed var(--border); padding-bottom: 10px; margin-bottom: 20px;">Управление штрафами</h3>
1234
-
1235
- <div style="background:#1a1a1a; border:1px solid var(--border); padding:15px; border-radius:12px; margin-bottom:20px;">
1236
- <form method="POST" style="display:flex; flex-wrap:wrap; gap:10px; align-items:flex-end;">
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>
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>
1249
- </div>
1250
- <div style="flex:1; min-width:150px;">
1251
- <label>Сумма</label>
1252
- <input type="number" name="amount" required min="0" step="0.01">
1253
- </div>
1254
- <div style="flex:2; min-width:200px;">
1255
- <label>Причина</label>
1256
- <input type="text" name="reason" required placeholder="Причина штрафа">
1257
- </div>
1258
- <button type="submit" class="btn btn-danger" style="height: 44px; white-space: nowrap;"><i class="fas fa-plus"></i> Добавить штраф</button>
1259
- </form>
1260
- </div>
1261
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1262
  <div style="overflow-x:auto;">
1263
  <table class="rep-table">
1264
  <tr><th>Дата</th><th>Сотрудник</th><th>Причина</th><th>Сумма</th><th>Действие</th></tr>
@@ -1271,12 +1239,12 @@ SALARY_TEMPLATE = '''
1271
  {% endfor %}
1272
  </td>
1273
  <td>{{ fine.reason }}</td>
1274
- <td style="color:var(--danger); font-weight:bold;">{{ fine.amount }} {{ currency_code }}</td>
1275
  <td>
1276
- <form method="POST" style="margin:0;" onsubmit="return confirm('Удалить штраф?');">
1277
  <input type="hidden" name="action" value="delete_fine">
1278
  <input type="hidden" name="fine_id" value="{{ fine.id }}">
1279
- <button type="submit" class="btn btn-dark" style="padding: 6px 12px;"><i class="fas fa-trash" style="color:var(--danger);"></i></button>
1280
  </form>
1281
  </td>
1282
  </tr>
@@ -1284,10 +1252,29 @@ SALARY_TEMPLATE = '''
1284
  </table>
1285
  </div>
1286
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1287
  </div>
1288
 
1289
  <script>
1290
  const allOrders = {{ orders_json|safe }};
 
1291
  const employees = {{ employees_json|safe }};
1292
  const fines = {{ fines_json|safe }};
1293
  const currencyCode = '{{ currency_code }}';
@@ -1302,6 +1289,7 @@ SALARY_TEMPLATE = '''
1302
  const firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
1303
  document.getElementById('salStart').value = getLocalDateStr(firstDay);
1304
  document.getElementById('salEnd').value = todayStr;
 
1305
  generateSalaryReport();
1306
  }
1307
 
@@ -1310,7 +1298,7 @@ SALARY_TEMPLATE = '''
1310
  const end = document.getElementById('salEnd').value;
1311
  if (!start || !end) return;
1312
 
1313
- let html = '<tr><th>Сотрудник</th><th>Дней с продажами</th><th>Фикс. ЗП</th><th>Бонусы с продаж</th><th>Штрафы</th><th>Итого ЗП</th></tr>';
1314
 
1315
  let empSales = {};
1316
  allOrders.forEach(o => {
@@ -1321,34 +1309,32 @@ SALARY_TEMPLATE = '''
1321
  empSales[o.employee_id][oDate] += o.total_price;
1322
  }
1323
  });
1324
-
1325
- let empFines = {};
1326
- fines.forEach(f => {
1327
- if (f.date >= start && f.date <= end) {
1328
- if (!empFines[f.employee_id]) empFines[f.employee_id] = 0;
1329
- empFines[f.employee_id] += parseFloat(f.amount);
1330
- }
1331
- });
1332
 
1333
  employees.forEach(emp => {
1334
  let daysWorked = 0;
1335
  let fixedSal = 0;
1336
  let bonusSal = 0;
 
1337
 
1338
  let currDate = new Date(start);
1339
  const endDate = new Date(end);
1340
 
1341
  while(currDate <= endDate) {
1342
  const dateStr = getLocalDateStr(currDate);
1343
-
1344
  const dailySales = (empSales[emp.id] && empSales[emp.id][dateStr]) ? empSales[emp.id][dateStr] : 0;
1345
 
1346
  if (dailySales > 0) {
1347
  daysWorked++;
1348
- fixedSal += parseFloat(emp.daily_rate || 0);
1349
 
1350
- const target = parseFloat(emp.target_amount || 0);
1351
- const pct = parseFloat(emp.bonus_percentage || 0);
 
 
 
 
 
 
 
1352
 
1353
  if (pct > 0 && dailySales > target) {
1354
  bonusSal += (dailySales - target) * (pct / 100);
@@ -1356,22 +1342,27 @@ SALARY_TEMPLATE = '''
1356
  }
1357
  currDate.setDate(currDate.getDate() + 1);
1358
  }
 
 
 
 
 
 
1359
 
1360
- const totalFines = empFines[emp.id] || 0;
1361
- const total = fixedSal + bonusSal - totalFines;
1362
- if (daysWorked > 0 || total > 0 || totalFines > 0) {
1363
  html += `<tr>
1364
  <td>${emp.name}</td>
1365
  <td>${daysWorked}</td>
1366
  <td>${fixedSal.toFixed(2)} ${currencyCode}</td>
1367
  <td>${bonusSal.toFixed(2)} ${currencyCode}</td>
1368
- <td style="color:var(--danger);">${totalFines.toFixed(2)} ${currencyCode}</td>
1369
  <td style="font-weight:bold; color:var(--success);">${total.toFixed(2)} ${currencyCode}</td>
1370
  </tr>`;
1371
  }
1372
  });
1373
 
1374
- if (html === '<tr><th>Сотрудник</th><th>Дней с продажами</th><th>Фикс. ЗП</th><th>Бонусы с продаж</th><th>Штрафы</th><th>Итого ЗП</th></tr>') {
1375
  html += '<tr><td colspan="6">За выбранный период данных нет</td></tr>';
1376
  }
1377
  document.getElementById('tableSalary').innerHTML = html;
@@ -1977,24 +1968,26 @@ def admin_salary():
1977
  'reason': request.form.get('reason', '')
1978
  })
1979
  save_data(data)
1980
- return redirect(url_for('admin_salary'))
1981
  elif action == 'delete_fine':
1982
  fine_id = request.form.get('fine_id')
1983
- data['fines'] = [f for f in data.get('fines', []) if f.get('id') != fine_id]
1984
- save_data(data)
1985
- return redirect(url_for('admin_salary'))
 
1986
 
1987
  orders = data.get('orders', {})
1988
  employees = data.get('employees', [])
 
1989
  fines = data.get('fines', [])
1990
 
1991
  return render_template_string(
1992
  SALARY_TEMPLATE,
1993
  orders_json=json.dumps(list(orders.values())),
1994
- employees=employees,
1995
  employees_json=json.dumps(employees),
1996
- fines=fines,
1997
  fines_json=json.dumps(fines),
 
 
1998
  currency_code=CURRENCY_CODE
1999
  )
2000
 
@@ -2023,12 +2016,12 @@ def admin():
2023
  elif action == 'add_employee':
2024
  emp_name = request.form.get('employee_name', '').strip()
2025
  pin = request.form.get('pin', '0000').strip()
2026
- try: daily_rate = float(request.form.get('daily_rate', 0))
2027
- except: daily_rate = 0
2028
- try: target_amount = float(request.form.get('target_amount', 0))
2029
- except: target_amount = 0
2030
- try: bonus_percentage = float(request.form.get('bonus_percentage', 0))
2031
- except: bonus_percentage = 0
2032
 
2033
  if emp_name:
2034
  employees.append({
@@ -2048,12 +2041,12 @@ def admin():
2048
  if e.get('id') == emp_id:
2049
  e['name'] = request.form.get('name', '').strip()
2050
  e['pin'] = request.form.get('pin', '0000').strip()
2051
- try: e['daily_rate'] = float(request.form.get('daily_rate', 0))
2052
- except: e['daily_rate'] = 0
2053
- try: e['target_amount'] = float(request.form.get('target_amount', 0))
2054
- except: e['target_amount'] = 0
2055
- try: e['bonus_percentage'] = float(request.form.get('bonus_percentage', 0))
2056
- except: e['bonus_percentage'] = 0
2057
  break
2058
  data['employees'] = employees
2059
  save_data(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'] = 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:
 
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; }
 
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
+ <h3 style="color:var(--primary); margin-top:0;">Управление штрафами</h3>
1217
+ <form method="POST" style="display:flex; gap:10px; flex-wrap:wrap; margin-bottom: 20px;">
1218
+ <input type="hidden" name="action" value="add_fine">
1219
+ <select name="employee_id" required style="flex:1; min-width:150px;">
1220
+ <option value="">Выберите сотрудника</option>
1221
+ {% for emp in employees %}
1222
+ <option value="{{ emp.id }}">{{ emp.name }}</option>
1223
+ {% endfor %}
1224
+ </select>
1225
+ <input type="date" name="date" id="fineDate" required style="flex:1; min-width:150px;">
1226
+ <input type="number" name="amount" placeholder="Сумма штрафа" required style="flex:1; min-width:150px;">
1227
+ <input type="text" name="reason" placeholder="Причина" required style="flex:2; min-width:200px;">
1228
+ <button type="submit" class="btn btn-danger"><i class="fas fa-plus"></i> Добавить штраф</button>
1229
+ </form>
1230
  <div style="overflow-x:auto;">
1231
  <table class="rep-table">
1232
  <tr><th>Дата</th><th>Сотрудник</th><th>Причина</th><th>Сумма</th><th>Действие</th></tr>
 
1239
  {% endfor %}
1240
  </td>
1241
  <td>{{ fine.reason }}</td>
1242
+ <td>{{ fine.amount }} {{ currency_code }}</td>
1243
  <td>
1244
+ <form method="POST" onsubmit="return confirm('Удалить штраф?');" style="margin:0;">
1245
  <input type="hidden" name="action" value="delete_fine">
1246
  <input type="hidden" name="fine_id" value="{{ fine.id }}">
1247
+ <button type="submit" class="btn btn-dark" style="padding:5px 10px; font-size:0.8rem;"><i class="fas fa-trash" style="color:var(--danger);"></i></button>
1248
  </form>
1249
  </td>
1250
  </tr>
 
1252
  </table>
1253
  </div>
1254
  </div>
1255
+
1256
+ <div class="report-section">
1257
+ <h3 style="color:var(--primary); margin-top:0;">Расчет зарплаты</h3>
1258
+ <div class="form-row" style="margin-bottom: 20px; align-items: flex-end;">
1259
+ <div>
1260
+ <label>Дата начала</label>
1261
+ <input type="date" id="salStart">
1262
+ </div>
1263
+ <div>
1264
+ <label>Дата конца</label>
1265
+ <input type="date" id="salEnd">
1266
+ </div>
1267
+ <button class="btn btn-success" style="height: 44px; color:#000;" onclick="generateSalaryReport()"><i class="fas fa-calculator"></i> Посчитать ЗП</button>
1268
+ </div>
1269
+ <div style="overflow-x:auto;">
1270
+ <table class="rep-table" id="tableSalary"></table>
1271
+ </div>
1272
+ </div>
1273
  </div>
1274
 
1275
  <script>
1276
  const allOrders = {{ orders_json|safe }};
1277
+ const workdays = {{ workdays_json|safe }};
1278
  const employees = {{ employees_json|safe }};
1279
  const fines = {{ fines_json|safe }};
1280
  const currencyCode = '{{ currency_code }}';
 
1289
  const firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
1290
  document.getElementById('salStart').value = getLocalDateStr(firstDay);
1291
  document.getElementById('salEnd').value = todayStr;
1292
+ document.getElementById('fineDate').value = todayStr;
1293
  generateSalaryReport();
1294
  }
1295
 
 
1298
  const end = document.getElementById('salEnd').value;
1299
  if (!start || !end) return;
1300
 
1301
+ let html = '<tr><th>Сотрудник</th><th>Дней отработано</th><th>Фикс. ЗП</th><th>Бонусы с продаж</th><th>Штрафы</th><th>Итого ЗП</th></tr>';
1302
 
1303
  let empSales = {};
1304
  allOrders.forEach(o => {
 
1309
  empSales[o.employee_id][oDate] += o.total_price;
1310
  }
1311
  });
 
 
 
 
 
 
 
 
1312
 
1313
  employees.forEach(emp => {
1314
  let daysWorked = 0;
1315
  let fixedSal = 0;
1316
  let bonusSal = 0;
1317
+ let employeeFines = 0;
1318
 
1319
  let currDate = new Date(start);
1320
  const endDate = new Date(end);
1321
 
1322
  while(currDate <= endDate) {
1323
  const dateStr = getLocalDateStr(currDate);
 
1324
  const dailySales = (empSales[emp.id] && empSales[emp.id][dateStr]) ? empSales[emp.id][dateStr] : 0;
1325
 
1326
  if (dailySales > 0) {
1327
  daysWorked++;
 
1328
 
1329
+ let rate = parseFloat(emp.daily_rate);
1330
+ if (isNaN(rate) || rate === 0) rate = 230000;
1331
+ fixedSal += rate;
1332
+
1333
+ let target = parseFloat(emp.target_amount);
1334
+ if (isNaN(target) || target === 0) target = 1500000;
1335
+
1336
+ let pct = parseFloat(emp.bonus_percentage);
1337
+ if (isNaN(pct) || pct === 0) pct = 10;
1338
 
1339
  if (pct > 0 && dailySales > target) {
1340
  bonusSal += (dailySales - target) * (pct / 100);
 
1342
  }
1343
  currDate.setDate(currDate.getDate() + 1);
1344
  }
1345
+
1346
+ fines.forEach(f => {
1347
+ if (f.employee_id === emp.id && f.date >= start && f.date <= end) {
1348
+ employeeFines += parseFloat(f.amount);
1349
+ }
1350
+ });
1351
 
1352
+ const total = fixedSal + bonusSal - employeeFines;
1353
+ if (daysWorked > 0 || total !== 0 || employeeFines > 0) {
 
1354
  html += `<tr>
1355
  <td>${emp.name}</td>
1356
  <td>${daysWorked}</td>
1357
  <td>${fixedSal.toFixed(2)} ${currencyCode}</td>
1358
  <td>${bonusSal.toFixed(2)} ${currencyCode}</td>
1359
+ <td style="color:var(--danger);">${employeeFines.toFixed(2)} ${currencyCode}</td>
1360
  <td style="font-weight:bold; color:var(--success);">${total.toFixed(2)} ${currencyCode}</td>
1361
  </tr>`;
1362
  }
1363
  });
1364
 
1365
+ if (html === '<tr><th>Сотрудник</th><th>Дней отработано</th><th>Фикс. ЗП</th><th>Бонусы с продаж</th><th>Штрафы</th><th>Итого ЗП</th></tr>') {
1366
  html += '<tr><td colspan="6">За выбранный период данных нет</td></tr>';
1367
  }
1368
  document.getElementById('tableSalary').innerHTML = html;
 
1968
  'reason': request.form.get('reason', '')
1969
  })
1970
  save_data(data)
 
1971
  elif action == 'delete_fine':
1972
  fine_id = request.form.get('fine_id')
1973
+ if 'fines' in data:
1974
+ data['fines'] = [f for f in data['fines'] if f.get('id') != fine_id]
1975
+ save_data(data)
1976
+ return redirect(url_for('admin_salary'))
1977
 
1978
  orders = data.get('orders', {})
1979
  employees = data.get('employees', [])
1980
+ workdays = data.get('workdays', {})
1981
  fines = data.get('fines', [])
1982
 
1983
  return render_template_string(
1984
  SALARY_TEMPLATE,
1985
  orders_json=json.dumps(list(orders.values())),
 
1986
  employees_json=json.dumps(employees),
1987
+ workdays_json=json.dumps(workdays),
1988
  fines_json=json.dumps(fines),
1989
+ employees=employees,
1990
+ fines=fines,
1991
  currency_code=CURRENCY_CODE
1992
  )
1993
 
 
2016
  elif action == 'add_employee':
2017
  emp_name = request.form.get('employee_name', '').strip()
2018
  pin = request.form.get('pin', '0000').strip()
2019
+ try: daily_rate = float(request.form.get('daily_rate', 230000))
2020
+ except: daily_rate = 230000
2021
+ try: target_amount = float(request.form.get('target_amount', 1500000))
2022
+ except: target_amount = 1500000
2023
+ try: bonus_percentage = float(request.form.get('bonus_percentage', 10))
2024
+ except: bonus_percentage = 10
2025
 
2026
  if emp_name:
2027
  employees.append({
 
2041
  if e.get('id') == emp_id:
2042
  e['name'] = request.form.get('name', '').strip()
2043
  e['pin'] = request.form.get('pin', '0000').strip()
2044
+ try: e['daily_rate'] = float(request.form.get('daily_rate', 230000))
2045
+ except: e['daily_rate'] = 230000
2046
+ try: e['target_amount'] = float(request.form.get('target_amount', 1500000))
2047
+ except: e['target_amount'] = 1500000
2048
+ try: e['bonus_percentage'] = float(request.form.get('bonus_percentage', 10))
2049
+ except: e['bonus_percentage'] = 10
2050
  break
2051
  data['employees'] = employees
2052
  save_data(data)