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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +132 -121
app.py CHANGED
@@ -63,7 +63,7 @@ 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',
@@ -122,7 +122,7 @@ def load_data():
122
  'orders': {},
123
  'employees': [],
124
  'workdays': {},
125
- 'fines': {},
126
  'settings': {
127
  'cafe_name': 'Sova',
128
  'wa_shift1': '+77470623684',
@@ -142,7 +142,7 @@ def load_data():
142
  if 'orders' not in data: data['orders'] = {}
143
  if 'employees' not in data: data['employees'] = []
144
  if 'workdays' not in data: data['workdays'] = {}
145
- if 'fines' not in data: data['fines'] = {}
146
  if 'settings' not in data: data['settings'] = default_data['settings']
147
  if 'logo_version' not in data['settings']: data['settings']['logo_version'] = '1'
148
  except (FileNotFoundError, json.JSONDecodeError):
@@ -155,7 +155,7 @@ def load_data():
155
  if 'orders' not in data: data['orders'] = {}
156
  if 'employees' not in data: data['employees'] = []
157
  if 'workdays' not in data: data['workdays'] = {}
158
- if 'fines' not in data: data['fines'] = {}
159
  if 'settings' not in data: data['settings'] = default_data['settings']
160
  if 'logo_version' not in data['settings']: data['settings']['logo_version'] = '1'
161
  except Exception:
@@ -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'] = 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:
@@ -201,7 +201,7 @@ def save_data(data):
201
  if 'orders' not in data: data['orders'] = {}
202
  if 'employees' not in data: data['employees'] = []
203
  if 'workdays' not in data: data['workdays'] = {}
204
- if 'fines' not in data: data['fines'] = {}
205
  if 'settings' not in data: data['settings'] = {
206
  'cafe_name': 'Sova',
207
  'wa_shift1': '',
@@ -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"] { 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 { 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; }
@@ -1207,11 +1208,68 @@ SALARY_TEMPLATE = '''
1207
  <body>
1208
  <div class="container">
1209
  <div class="header-panel">
1210
- <h1><i class="fas fa-money-bill-wave"></i> Отчет по ЗП</h1>
1211
  <a href="/admin" class="btn btn-dark"><i class="fas fa-arrow-left"></i> Назад в админку</a>
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>
@@ -1269,7 +1327,14 @@ SALARY_TEMPLATE = '''
1269
  employees.forEach(emp => {
1270
  let daysWorked = 0;
1271
  let fixedSal = 0;
1272
- let totalSales = 0;
 
 
 
 
 
 
 
1273
 
1274
  let currDate = new Date(start);
1275
  const endDate = new Date(end);
@@ -1280,33 +1345,23 @@ SALARY_TEMPLATE = '''
1280
  const worked = workdays[dateStr] && workdays[dateStr].includes(emp.id);
1281
  const dailySales = (empSales[emp.id] && empSales[emp.id][dateStr]) ? empSales[emp.id][dateStr] : 0;
1282
 
1283
- if (worked || dailySales > 0) {
1284
  daysWorked++;
1285
- fixedSal += parseFloat(emp.daily_rate || 230000);
 
 
 
 
 
 
 
1286
  }
1287
- totalSales += dailySales;
1288
  currDate.setDate(currDate.getDate() + 1);
1289
  }
1290
 
1291
- let bonusSal = 0;
1292
- const target = parseFloat(emp.target_amount || 1500000);
1293
- const pct = parseFloat(emp.bonus_percentage || 10);
1294
-
1295
- if (pct > 0 && totalSales > target) {
1296
- bonusSal = (totalSales - target) * (pct / 100);
1297
- }
1298
-
1299
- let totalFines = 0;
1300
- if (fines[emp.id]) {
1301
- fines[emp.id].forEach(f => {
1302
- if (f.date >= start && f.date <= end) {
1303
- totalFines += parseFloat(f.amount || 0);
1304
- }
1305
- });
1306
- }
1307
-
1308
  const total = fixedSal + bonusSal - totalFines;
1309
- if (daysWorked > 0 || total > 0 || totalFines > 0) {
1310
  html += `<tr>
1311
  <td>${emp.name}</td>
1312
  <td>${daysWorked}</td>
@@ -1404,10 +1459,6 @@ ADMIN_TEMPLATE = '''
1404
  .orders-table th { padding: 12px; background: #1a1a1a; border-bottom: 2px solid var(--border); color: var(--primary); font-size: 0.85rem; text-transform: uppercase; }
1405
  .orders-table td { padding: 12px; border-bottom: 1px solid var(--border); vertical-align: middle; color: var(--text); }
1406
  .orders-table tr:hover { background: #1a1a1a; }
1407
-
1408
- .rep-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; color: var(--text); }
1409
- .rep-table th, .rep-table td { padding: 8px 5px; border-bottom: 1px solid var(--border); text-align: left; }
1410
- .rep-table th { color: var(--text-muted); font-weight: normal; }
1411
 
1412
  @media (max-width: 600px) {
1413
  .header-panel { flex-direction: column; align-items: stretch; text-align: center; }
@@ -1476,9 +1527,9 @@ ADMIN_TEMPLATE = '''
1476
  <input type="hidden" name="action" value="add_employee">
1477
  <div style="flex:1; min-width:150px;"><label>Имя</label><input type="text" name="employee_name" required autocomplete="off"></div>
1478
  <div style="flex:1; min-width:100px;"><label>PIN (4 цифры)</label><input type="text" name="pin" pattern="\\d{4}" required autocomplete="off"></div>
1479
- <div style="flex:1; min-width:100px;"><label>Ставка за день</label><input type="number" name="daily_rate" value="230000" required></div>
1480
- <div style="flex:1; min-width:100px;"><label>Цель (n сумма)</label><input type="number" name="target_amount" value="1500000" required></div>
1481
- <div style="flex:1; min-width:100px;"><label>Бонус (%)</label><input type="number" name="bonus_percentage" value="10" step="0.1" required></div>
1482
  <button type="submit" class="btn btn-dark" style="width:100%; margin-top:10px;"><i class="fas fa-plus"></i> Добавить</button>
1483
  </form>
1484
  </div>
@@ -1497,45 +1548,10 @@ ADMIN_TEMPLATE = '''
1497
  <div style="flex:1; min-width:100px;"><label>Бонус %</label><input type="number" name="bonus_percentage" value="{{ emp.bonus_percentage }}" step="0.1" required></div>
1498
  <button type="submit" class="btn btn-primary" title="Сохранить"><i class="fas fa-save"></i></button>
1499
  </form>
1500
-
1501
- <div style="margin-top:15px; border-top:1px dashed var(--border); padding-top:15px;">
1502
- <h4 style="margin:0 0 10px 0; color:var(--primary);">Штрафы</h4>
1503
- <form method="POST" style="display:flex; flex-wrap:wrap; gap:10px; align-items:flex-end; margin-bottom:10px;">
1504
- <input type="hidden" name="action" value="add_fine">
1505
- <input type="hidden" name="employee_id" value="{{ emp.id }}">
1506
- <div style="flex:1; min-width:120px;"><label>Дата</label><input type="date" name="fine_date" required></div>
1507
- <div style="flex:1; min-width:100px;"><label>Сумма</label><input type="number" name="fine_amount" required></div>
1508
- <div style="flex:2; min-width:150px;"><label>Причина</label><input type="text" name="fine_reason" required autocomplete="off"></div>
1509
- <button type="submit" class="btn btn-danger" style="padding:12px 15px;"><i class="fas fa-plus"></i></button>
1510
- </form>
1511
- {% if fines.get(emp.id) %}
1512
- <table class="rep-table" style="font-size:0.85rem;">
1513
- <tr><th>Дата</th><th>Причина</th><th>Сумма</th><th>Удалить</th></tr>
1514
- {% for f in fines.get(emp.id) %}
1515
- <tr>
1516
- <td>{{ f.date }}</td>
1517
- <td>{{ f.reason }}</td>
1518
- <td>{{ f.amount }} {{ currency_code }}</td>
1519
- <td>
1520
- <form method="POST" style="margin:0;" onsubmit="return confirm('Удалить штраф?');">
1521
- <input type="hidden" name="action" value="delete_fine">
1522
- <input type="hidden" name="employee_id" value="{{ emp.id }}">
1523
- <input type="hidden" name="fine_id" value="{{ f.id }}">
1524
- <button type="submit" class="btn btn-danger" style="padding:3px 8px; font-size:0.75rem;"><i class="fas fa-trash"></i></button>
1525
- </form>
1526
- </td>
1527
- </tr>
1528
- {% endfor %}
1529
- </table>
1530
- {% else %}
1531
- <div style="font-size:0.85rem; color:var(--text-muted);">Нет штрафов</div>
1532
- {% endif %}
1533
- </div>
1534
-
1535
- <form method="POST" style="margin-top:15px; text-align:right;" onsubmit="return confirm('Удалить сотрудника?');">
1536
  <input type="hidden" name="action" value="delete_employee">
1537
  <input type="hidden" name="employee_id" value="{{ emp.id }}">
1538
- <button type="submit" class="btn btn-danger" style="margin-top: 10px;"><i class="fas fa-trash"></i> Удалить сотрудника</button>
1539
  </form>
1540
  </div>
1541
  {% endfor %}
@@ -1946,13 +1962,35 @@ def admin_reports():
1946
  currency_code=CURRENCY_CODE
1947
  )
1948
 
1949
- @app.route('/admin/salary')
1950
  def admin_salary():
1951
  data = load_data()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1952
  orders = data.get('orders', {})
1953
  employees = data.get('employees', [])
1954
  workdays = data.get('workdays', {})
1955
- fines = data.get('fines', {})
1956
 
1957
  return render_template_string(
1958
  SALARY_TEMPLATE,
@@ -1960,6 +1998,8 @@ def admin_salary():
1960
  employees_json=json.dumps(employees),
1961
  workdays_json=json.dumps(workdays),
1962
  fines_json=json.dumps(fines),
 
 
1963
  currency_code=CURRENCY_CODE
1964
  )
1965
 
@@ -1970,7 +2010,6 @@ def admin():
1970
  categories = data.get('categories', [])
1971
  orders = data.get('orders', {})
1972
  employees = data.get('employees', [])
1973
- fines = data.get('fines', {})
1974
  settings = data.get('settings', {})
1975
 
1976
  if request.method == 'POST':
@@ -1989,12 +2028,12 @@ def admin():
1989
  elif action == 'add_employee':
1990
  emp_name = request.form.get('employee_name', '').strip()
1991
  pin = request.form.get('pin', '0000').strip()
1992
- try: daily_rate = float(request.form.get('daily_rate', 230000))
1993
- except: daily_rate = 230000
1994
- try: target_amount = float(request.form.get('target_amount', 1500000))
1995
- except: target_amount = 1500000
1996
- try: bonus_percentage = float(request.form.get('bonus_percentage', 10))
1997
- except: bonus_percentage = 10
1998
 
1999
  if emp_name:
2000
  employees.append({
@@ -2014,12 +2053,12 @@ def admin():
2014
  if e.get('id') == emp_id:
2015
  e['name'] = request.form.get('name', '').strip()
2016
  e['pin'] = request.form.get('pin', '0000').strip()
2017
- try: e['daily_rate'] = float(request.form.get('daily_rate', 230000))
2018
- except: e['daily_rate'] = 230000
2019
- try: e['target_amount'] = float(request.form.get('target_amount', 1500000))
2020
- except: e['target_amount'] = 1500000
2021
- try: e['bonus_percentage'] = float(request.form.get('bonus_percentage', 10))
2022
- except: e['bonus_percentage'] = 10
2023
  break
2024
  data['employees'] = employees
2025
  save_data(data)
@@ -2029,33 +2068,6 @@ def admin():
2029
  data['employees'] = [e for e in employees if e.get('id') != emp_id]
2030
  save_data(data)
2031
 
2032
- elif action == 'add_fine':
2033
- emp_id = request.form.get('employee_id')
2034
- fine_date = request.form.get('fine_date')
2035
- try: fine_amount = float(request.form.get('fine_amount', 0))
2036
- except: fine_amount = 0
2037
- fine_reason = request.form.get('fine_reason', '').strip()
2038
-
2039
- if emp_id and fine_date and fine_amount > 0:
2040
- if 'fines' not in data:
2041
- data['fines'] = {}
2042
- if emp_id not in data['fines']:
2043
- data['fines'][emp_id] = []
2044
- data['fines'][emp_id].append({
2045
- 'id': uuid4().hex,
2046
- 'date': fine_date,
2047
- 'amount': fine_amount,
2048
- 'reason': fine_reason
2049
- })
2050
- save_data(data)
2051
-
2052
- elif action == 'delete_fine':
2053
- emp_id = request.form.get('employee_id')
2054
- fine_id = request.form.get('fine_id')
2055
- if 'fines' in data and emp_id in data['fines']:
2056
- data['fines'][emp_id] = [f for f in data['fines'][emp_id] if f.get('id') != fine_id]
2057
- save_data(data)
2058
-
2059
  elif action == 'delete_order':
2060
  order_id = request.form.get('order_id')
2061
  if order_id in orders:
@@ -2200,7 +2212,6 @@ def admin():
2200
  categories=categories,
2201
  orders=orders,
2202
  employees=employees,
2203
- fines=data.get('fines', {}),
2204
  repo_id=REPO_ID,
2205
  currency_code=CURRENCY_CODE,
2206
  settings=settings
 
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',
 
122
  'orders': {},
123
  'employees': [],
124
  'workdays': {},
125
+ 'fines': [],
126
  'settings': {
127
  'cafe_name': 'Sova',
128
  'wa_shift1': '+77470623684',
 
142
  if 'orders' not in data: data['orders'] = {}
143
  if 'employees' not in data: data['employees'] = []
144
  if 'workdays' not in data: data['workdays'] = {}
145
+ if 'fines' not in data: data['fines'] = []
146
  if 'settings' not in data: data['settings'] = default_data['settings']
147
  if 'logo_version' not in data['settings']: data['settings']['logo_version'] = '1'
148
  except (FileNotFoundError, json.JSONDecodeError):
 
155
  if 'orders' not in data: data['orders'] = {}
156
  if 'employees' not in data: data['employees'] = []
157
  if 'workdays' not in data: data['workdays'] = {}
158
+ if 'fines' not in data: data['fines'] = []
159
  if 'settings' not in data: data['settings'] = default_data['settings']
160
  if 'logo_version' not in data['settings']: data['settings']['logo_version'] = '1'
161
  except Exception:
 
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:
 
201
  if 'orders' not in data: data['orders'] = {}
202
  if 'employees' not in data: data['employees'] = []
203
  if 'workdays' not in data: data['workdays'] = {}
204
+ if 'fines' not in data: data['fines'] = []
205
  if 'settings' not in data: data['settings'] = {
206
  'cafe_name': 'Sova',
207
  'wa_shift1': '',
 
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"] { 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
  <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>
 
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);
 
1345
  const worked = workdays[dateStr] && workdays[dateStr].includes(emp.id);
1346
  const dailySales = (empSales[emp.id] && empSales[emp.id][dateStr]) ? empSales[emp.id][dateStr] : 0;
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>
 
1459
  .orders-table th { padding: 12px; background: #1a1a1a; border-bottom: 2px solid var(--border); color: var(--primary); font-size: 0.85rem; text-transform: uppercase; }
1460
  .orders-table td { padding: 12px; border-bottom: 1px solid var(--border); vertical-align: middle; color: var(--text); }
1461
  .orders-table tr:hover { background: #1a1a1a; }
 
 
 
 
1462
 
1463
  @media (max-width: 600px) {
1464
  .header-panel { flex-direction: column; align-items: stretch; text-align: center; }
 
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>
 
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>
1551
+ <form method="POST" style="margin-top:10px; text-align:right;" onsubmit="return confirm('Удалить сотрудника?');">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1552
  <input type="hidden" name="action" value="delete_employee">
1553
  <input type="hidden" name="employee_id" value="{{ emp.id }}">
1554
+ <button type="submit" class="btn btn-danger"><i class="fas fa-trash"></i> Удалить</button>
1555
  </form>
1556
  </div>
1557
  {% endfor %}
 
1962
  currency_code=CURRENCY_CODE
1963
  )
1964
 
1965
+ @app.route('/admin/salary', methods=['GET', 'POST'])
1966
  def admin_salary():
1967
  data = load_data()
1968
+
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', {})
1991
  employees = data.get('employees', [])
1992
  workdays = data.get('workdays', {})
1993
+ fines = data.get('fines', [])
1994
 
1995
  return render_template_string(
1996
  SALARY_TEMPLATE,
 
1998
  employees_json=json.dumps(employees),
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
 
 
2010
  categories = data.get('categories', [])
2011
  orders = data.get('orders', {})
2012
  employees = data.get('employees', [])
 
2013
  settings = data.get('settings', {})
2014
 
2015
  if request.method == 'POST':
 
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
  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)
 
2068
  data['employees'] = [e for e in employees if e.get('id') != emp_id]
2069
  save_data(data)
2070
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2071
  elif action == 'delete_order':
2072
  order_id = request.form.get('order_id')
2073
  if order_id in orders:
 
2212
  categories=categories,
2213
  orders=orders,
2214
  employees=employees,
 
2215
  repo_id=REPO_ID,
2216
  currency_code=CURRENCY_CODE,
2217
  settings=settings