Spaces:
Running
Running
Update app.py
Browse files
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': {}, 'settings': {
|
| 67 |
'cafe_name': 'HongKong',
|
| 68 |
'wa_shift1': '+77470623684',
|
| 69 |
'wa_shift2': '+77470623684',
|
|
@@ -122,6 +122,7 @@ def load_data():
|
|
| 122 |
'orders': {},
|
| 123 |
'employees': [],
|
| 124 |
'workdays': {},
|
|
|
|
| 125 |
'settings': {
|
| 126 |
'cafe_name': 'Sova',
|
| 127 |
'wa_shift1': '+77470623684',
|
|
@@ -141,6 +142,7 @@ def load_data():
|
|
| 141 |
if 'orders' not in data: data['orders'] = {}
|
| 142 |
if 'employees' not in data: data['employees'] = []
|
| 143 |
if 'workdays' not in data: data['workdays'] = {}
|
|
|
|
| 144 |
if 'settings' not in data: data['settings'] = default_data['settings']
|
| 145 |
if 'logo_version' not in data['settings']: data['settings']['logo_version'] = '1'
|
| 146 |
except (FileNotFoundError, json.JSONDecodeError):
|
|
@@ -153,6 +155,7 @@ def load_data():
|
|
| 153 |
if 'orders' not in data: data['orders'] = {}
|
| 154 |
if 'employees' not in data: data['employees'] = []
|
| 155 |
if 'workdays' not in data: data['workdays'] = {}
|
|
|
|
| 156 |
if 'settings' not in data: data['settings'] = default_data['settings']
|
| 157 |
if 'logo_version' not in data['settings']: data['settings']['logo_version'] = '1'
|
| 158 |
except Exception:
|
|
@@ -177,9 +180,9 @@ def load_data():
|
|
| 177 |
|
| 178 |
for emp in data['employees']:
|
| 179 |
if 'pin' not in emp: emp['pin'] = '0000'
|
| 180 |
-
if 'daily_rate' not in emp: emp['daily_rate'] =
|
| 181 |
-
if 'target_amount' not in emp: emp['target_amount'] =
|
| 182 |
-
if 'bonus_percentage' not in emp: emp['bonus_percentage'] =
|
| 183 |
|
| 184 |
if not os.path.exists(DATA_FILE):
|
| 185 |
try:
|
|
@@ -198,6 +201,7 @@ def save_data(data):
|
|
| 198 |
if 'orders' not in data: data['orders'] = {}
|
| 199 |
if 'employees' not in data: data['employees'] = []
|
| 200 |
if 'workdays' not in data: data['workdays'] = {}
|
|
|
|
| 201 |
if 'settings' not in data: data['settings'] = {
|
| 202 |
'cafe_name': 'Sova',
|
| 203 |
'wa_shift1': '',
|
|
@@ -1229,6 +1233,7 @@ SALARY_TEMPLATE = '''
|
|
| 1229 |
const allOrders = {{ orders_json|safe }};
|
| 1230 |
const workdays = {{ workdays_json|safe }};
|
| 1231 |
const employees = {{ employees_json|safe }};
|
|
|
|
| 1232 |
const currencyCode = '{{ currency_code }}';
|
| 1233 |
|
| 1234 |
function getLocalDateStr(d) {
|
|
@@ -1249,7 +1254,7 @@ SALARY_TEMPLATE = '''
|
|
| 1249 |
const end = document.getElementById('salEnd').value;
|
| 1250 |
if (!start || !end) return;
|
| 1251 |
|
| 1252 |
-
let html = '<tr><th>Сотрудник</th><th>Дней отработано</th><th>Фикс. ЗП</th><th>Бонусы с продаж</th><th>Итого ЗП</th></tr>';
|
| 1253 |
|
| 1254 |
let empSales = {};
|
| 1255 |
allOrders.forEach(o => {
|
|
@@ -1264,7 +1269,7 @@ SALARY_TEMPLATE = '''
|
|
| 1264 |
employees.forEach(emp => {
|
| 1265 |
let daysWorked = 0;
|
| 1266 |
let fixedSal = 0;
|
| 1267 |
-
let
|
| 1268 |
|
| 1269 |
let currDate = new Date(start);
|
| 1270 |
const endDate = new Date(end);
|
|
@@ -1277,32 +1282,44 @@ SALARY_TEMPLATE = '''
|
|
| 1277 |
|
| 1278 |
if (worked || dailySales > 0) {
|
| 1279 |
daysWorked++;
|
| 1280 |
-
fixedSal += parseFloat(emp.daily_rate ||
|
| 1281 |
-
|
| 1282 |
-
const target = parseFloat(emp.target_amount || 0);
|
| 1283 |
-
const pct = parseFloat(emp.bonus_percentage || 0);
|
| 1284 |
-
|
| 1285 |
-
if (pct > 0 && dailySales >= target) {
|
| 1286 |
-
bonusSal += dailySales * (pct / 100);
|
| 1287 |
-
}
|
| 1288 |
}
|
|
|
|
| 1289 |
currDate.setDate(currDate.getDate() + 1);
|
| 1290 |
}
|
| 1291 |
|
| 1292 |
-
|
| 1293 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1294 |
html += `<tr>
|
| 1295 |
<td>${emp.name}</td>
|
| 1296 |
<td>${daysWorked}</td>
|
| 1297 |
<td>${fixedSal.toFixed(2)} ${currencyCode}</td>
|
| 1298 |
<td>${bonusSal.toFixed(2)} ${currencyCode}</td>
|
|
|
|
| 1299 |
<td style="font-weight:bold; color:var(--success);">${total.toFixed(2)} ${currencyCode}</td>
|
| 1300 |
</tr>`;
|
| 1301 |
}
|
| 1302 |
});
|
| 1303 |
|
| 1304 |
-
if (html === '<tr><th>Сотрудник</th><th>Дней отработано</th><th>Фикс. ЗП</th><th>Бонусы с продаж</th><th>Итого ЗП</th></tr>') {
|
| 1305 |
-
html += '<tr><td colspan="
|
| 1306 |
}
|
| 1307 |
document.getElementById('tableSalary').innerHTML = html;
|
| 1308 |
}
|
|
@@ -1387,6 +1404,10 @@ ADMIN_TEMPLATE = '''
|
|
| 1387 |
.orders-table th { padding: 12px; background: #1a1a1a; border-bottom: 2px solid var(--border); color: var(--primary); font-size: 0.85rem; text-transform: uppercase; }
|
| 1388 |
.orders-table td { padding: 12px; border-bottom: 1px solid var(--border); vertical-align: middle; color: var(--text); }
|
| 1389 |
.orders-table tr:hover { background: #1a1a1a; }
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1390 |
|
| 1391 |
@media (max-width: 600px) {
|
| 1392 |
.header-panel { flex-direction: column; align-items: stretch; text-align: center; }
|
|
@@ -1455,9 +1476,9 @@ ADMIN_TEMPLATE = '''
|
|
| 1455 |
<input type="hidden" name="action" value="add_employee">
|
| 1456 |
<div style="flex:1; min-width:150px;"><label>Имя</label><input type="text" name="employee_name" required autocomplete="off"></div>
|
| 1457 |
<div style="flex:1; min-width:100px;"><label>PIN (4 цифры)</label><input type="text" name="pin" pattern="\\d{4}" required autocomplete="off"></div>
|
| 1458 |
-
<div style="flex:1; min-width:100px;"><label>Ставка за день</label><input type="number" name="daily_rate" value="
|
| 1459 |
-
<div style="flex:1; min-width:100px;"><label>Цель (n сумма)</label><input type="number" name="target_amount" value="
|
| 1460 |
-
<div style="flex:1; min-width:100px;"><label>Бонус (%)</label><input type="number" name="bonus_percentage" value="
|
| 1461 |
<button type="submit" class="btn btn-dark" style="width:100%; margin-top:10px;"><i class="fas fa-plus"></i> Добавить</button>
|
| 1462 |
</form>
|
| 1463 |
</div>
|
|
@@ -1476,10 +1497,45 @@ ADMIN_TEMPLATE = '''
|
|
| 1476 |
<div style="flex:1; min-width:100px;"><label>Бонус %</label><input type="number" name="bonus_percentage" value="{{ emp.bonus_percentage }}" step="0.1" required></div>
|
| 1477 |
<button type="submit" class="btn btn-primary" title="Со��ранить"><i class="fas fa-save"></i></button>
|
| 1478 |
</form>
|
| 1479 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1480 |
<input type="hidden" name="action" value="delete_employee">
|
| 1481 |
<input type="hidden" name="employee_id" value="{{ emp.id }}">
|
| 1482 |
-
<button type="submit" class="btn btn-danger"><i class="fas fa-trash"></i> Удалить</button>
|
| 1483 |
</form>
|
| 1484 |
</div>
|
| 1485 |
{% endfor %}
|
|
@@ -1505,10 +1561,6 @@ ADMIN_TEMPLATE = '''
|
|
| 1505 |
<th>Действия</th>
|
| 1506 |
</tr>
|
| 1507 |
{% for order in orders.values()|sort(attribute='created_at', reverse=True) %}
|
| 1508 |
-
{% set raw_total = 0 %}
|
| 1509 |
-
{% for item in order.cart %}
|
| 1510 |
-
{% set raw_total = raw_total + (item.price|float * item.quantity|int) %}
|
| 1511 |
-
{% endfor %}
|
| 1512 |
<tr>
|
| 1513 |
<td>
|
| 1514 |
<a href="/order/{{ order.id }}" target="_blank" style="color:var(--info); font-weight:bold; text-decoration:none;">{{ order.id }}</a><br>
|
|
@@ -1518,7 +1570,7 @@ ADMIN_TEMPLATE = '''
|
|
| 1518 |
Столик: {{ order.table_number }}<br>
|
| 1519 |
Сотрудник: {{ order.employee_name|default('Не указан') }}
|
| 1520 |
</td>
|
| 1521 |
-
<td style="font-weight:600;">{{
|
| 1522 |
<td>
|
| 1523 |
<span style="text-transform:uppercase; font-size:0.8rem;">
|
| 1524 |
{% if order.payment_method == 'cash' %}Наличка
|
|
@@ -1900,12 +1952,14 @@ def admin_salary():
|
|
| 1900 |
orders = data.get('orders', {})
|
| 1901 |
employees = data.get('employees', [])
|
| 1902 |
workdays = data.get('workdays', {})
|
|
|
|
| 1903 |
|
| 1904 |
return render_template_string(
|
| 1905 |
SALARY_TEMPLATE,
|
| 1906 |
orders_json=json.dumps(list(orders.values())),
|
| 1907 |
employees_json=json.dumps(employees),
|
| 1908 |
workdays_json=json.dumps(workdays),
|
|
|
|
| 1909 |
currency_code=CURRENCY_CODE
|
| 1910 |
)
|
| 1911 |
|
|
@@ -1916,6 +1970,7 @@ def admin():
|
|
| 1916 |
categories = data.get('categories', [])
|
| 1917 |
orders = data.get('orders', {})
|
| 1918 |
employees = data.get('employees', [])
|
|
|
|
| 1919 |
settings = data.get('settings', {})
|
| 1920 |
|
| 1921 |
if request.method == 'POST':
|
|
@@ -1934,12 +1989,12 @@ def admin():
|
|
| 1934 |
elif action == 'add_employee':
|
| 1935 |
emp_name = request.form.get('employee_name', '').strip()
|
| 1936 |
pin = request.form.get('pin', '0000').strip()
|
| 1937 |
-
try: daily_rate = float(request.form.get('daily_rate',
|
| 1938 |
-
except: daily_rate =
|
| 1939 |
-
try: target_amount = float(request.form.get('target_amount',
|
| 1940 |
-
except: target_amount =
|
| 1941 |
-
try: bonus_percentage = float(request.form.get('bonus_percentage',
|
| 1942 |
-
except: bonus_percentage =
|
| 1943 |
|
| 1944 |
if emp_name:
|
| 1945 |
employees.append({
|
|
@@ -1959,12 +2014,12 @@ def admin():
|
|
| 1959 |
if e.get('id') == emp_id:
|
| 1960 |
e['name'] = request.form.get('name', '').strip()
|
| 1961 |
e['pin'] = request.form.get('pin', '0000').strip()
|
| 1962 |
-
try: e['daily_rate'] = float(request.form.get('daily_rate',
|
| 1963 |
-
except: e['daily_rate'] =
|
| 1964 |
-
try: e['target_amount'] = float(request.form.get('target_amount',
|
| 1965 |
-
except: e['target_amount'] =
|
| 1966 |
-
try: e['bonus_percentage'] = float(request.form.get('bonus_percentage',
|
| 1967 |
-
except: e['bonus_percentage'] =
|
| 1968 |
break
|
| 1969 |
data['employees'] = employees
|
| 1970 |
save_data(data)
|
|
@@ -1974,6 +2029,33 @@ def admin():
|
|
| 1974 |
data['employees'] = [e for e in employees if e.get('id') != emp_id]
|
| 1975 |
save_data(data)
|
| 1976 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1977 |
elif action == 'delete_order':
|
| 1978 |
order_id = request.form.get('order_id')
|
| 1979 |
if order_id in orders:
|
|
@@ -2118,6 +2200,7 @@ def admin():
|
|
| 2118 |
categories=categories,
|
| 2119 |
orders=orders,
|
| 2120 |
employees=employees,
|
|
|
|
| 2121 |
repo_id=REPO_ID,
|
| 2122 |
currency_code=CURRENCY_CODE,
|
| 2123 |
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'] = 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 |
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': '',
|
|
|
|
| 1233 |
const allOrders = {{ orders_json|safe }};
|
| 1234 |
const workdays = {{ workdays_json|safe }};
|
| 1235 |
const employees = {{ employees_json|safe }};
|
| 1236 |
+
const fines = {{ fines_json|safe }};
|
| 1237 |
const currencyCode = '{{ currency_code }}';
|
| 1238 |
|
| 1239 |
function getLocalDateStr(d) {
|
|
|
|
| 1254 |
const end = document.getElementById('salEnd').value;
|
| 1255 |
if (!start || !end) return;
|
| 1256 |
|
| 1257 |
+
let html = '<tr><th>Сотрудник</th><th>Дней отработано</th><th>Фикс. ЗП</th><th>Бонусы с продаж</th><th>Штрафы</th><th>Итого ЗП</th></tr>';
|
| 1258 |
|
| 1259 |
let empSales = {};
|
| 1260 |
allOrders.forEach(o => {
|
|
|
|
| 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);
|
|
|
|
| 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>
|
| 1313 |
<td>${fixedSal.toFixed(2)} ${currencyCode}</td>
|
| 1314 |
<td>${bonusSal.toFixed(2)} ${currencyCode}</td>
|
| 1315 |
+
<td style="color:var(--danger);">${totalFines.toFixed(2)} ${currencyCode}</td>
|
| 1316 |
<td style="font-weight:bold; color:var(--success);">${total.toFixed(2)} ${currencyCode}</td>
|
| 1317 |
</tr>`;
|
| 1318 |
}
|
| 1319 |
});
|
| 1320 |
|
| 1321 |
+
if (html === '<tr><th>Сотрудник</th><th>Дней отработано</th><th>Фикс. ЗП</th><th>Бонусы с продаж</th><th>Штрафы</th><th>Итого ЗП</th></tr>') {
|
| 1322 |
+
html += '<tr><td colspan="6">За выбранный период данных нет</td></tr>';
|
| 1323 |
}
|
| 1324 |
document.getElementById('tableSalary').innerHTML = html;
|
| 1325 |
}
|
|
|
|
| 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 |
<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 |
<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 %}
|
|
|
|
| 1561 |
<th>Действия</th>
|
| 1562 |
</tr>
|
| 1563 |
{% for order in orders.values()|sort(attribute='created_at', reverse=True) %}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1564 |
<tr>
|
| 1565 |
<td>
|
| 1566 |
<a href="/order/{{ order.id }}" target="_blank" style="color:var(--info); font-weight:bold; text-decoration:none;">{{ order.id }}</a><br>
|
|
|
|
| 1570 |
Столик: {{ order.table_number }}<br>
|
| 1571 |
Сотрудник: {{ order.employee_name|default('Не указан') }}
|
| 1572 |
</td>
|
| 1573 |
+
<td style="font-weight:600;">{{ order.total_price }} {{ currency_code }}</td>
|
| 1574 |
<td>
|
| 1575 |
<span style="text-transform:uppercase; font-size:0.8rem;">
|
| 1576 |
{% if order.payment_method == 'cash' %}Наличка
|
|
|
|
| 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,
|
| 1959 |
orders_json=json.dumps(list(orders.values())),
|
| 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 |
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 |
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 |
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 |
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 |
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
|