Загрузка данных


import os
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, send_file
from config import Config
from models import db, Employee, ServiceNote, SupplementDetail, CombinationDetail, SubstitutionDetail, AllowanceDetail
from datetime import datetime
from openpyxl import Workbook, load_workbook
from openpyxl.styles import Font, Alignment, Border, Side
from io import BytesIO

app = Flask(__name__)
app.config.from_object(Config)
db.init_app(app)

# Путь к папке с шаблонами Excel
TEMPLATE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'excel_templates')

# ==================== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ====================

def style_excel_cell(cell, font_bold=False, font_size=11, alignment='center', border=True):
    if border:
        thin_border = Border(
            left=Side(style='thin'), right=Side(style='thin'),
            top=Side(style='thin'), bottom=Side(style='thin')
        )
        cell.border = thin_border
    cell.font = Font(size=font_size, bold=font_bold)
    if alignment == 'center':
        cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
    elif alignment == 'left':
        cell.alignment = Alignment(horizontal='left', vertical='center', wrap_text=True)

def format_date(date_obj):
    return date_obj.strftime('%d.%m.%Y') if date_obj else ''

# ==================== ГЛАВНАЯ СТРАНИЦА ====================

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/api/notes')
def api_notes():
    page = request.args.get('page', 1, type=int)
    per_page = 15
    payment_type = request.args.get('type', '')
    
    query = ServiceNote.query
    if payment_type:
        query = query.filter(ServiceNote.payment_code == payment_type)
    
    notes = query.order_by(ServiceNote.created_at.desc()).paginate(page=page, per_page=per_page)
    
    items = []
    for note in notes.items:
        employee_name = '-'
        amount = '-'
        start_date = ''
        end_date = ''
        
        if note.payment_code in ['1106', '1006']:
            detail = SupplementDetail.query.filter_by(service_note_id=note.id).first()
            if detail and detail.employee:
                employee_name = detail.employee.full_name
                amount = f"{detail.amount} {'%' if detail.is_percent else 'руб'}"
                start_date = format_date(detail.start_date)
                end_date = format_date(detail.end_date)
        elif note.payment_code in ['1003', '1021', '1001']:
            detail = CombinationDetail.query.filter_by(service_note_id=note.id).first()
            if detail and detail.employee:
                employee_name = detail.employee.full_name
                amount = f"{detail.extra_pay} руб" if detail.extra_pay else '-'
                start_date = format_date(detail.start_date)
                end_date = format_date(detail.end_date)
        elif note.payment_code == '1019':
            detail = SubstitutionDetail.query.filter_by(service_note_id=note.id).first()
            if detail and detail.substitute:
                employee_name = detail.substitute.full_name
                amount = f"{detail.extra_pay_percent}%" if detail.extra_pay_percent else '-'
                start_date = format_date(detail.start_date)
                end_date = format_date(detail.end_date)
        elif note.payment_code == '9999':
            detail = AllowanceDetail.query.filter_by(service_note_id=note.id).first()
            if detail and detail.employee:
                employee_name = detail.employee.full_name
                amount = f"{detail.amount} {detail.unit or ''}"
                start_date = format_date(detail.start_date)
                end_date = format_date(detail.end_date)
        
        items.append({
            'id': note.id,
            'doc_number': note.doc_number or '-',
            'doc_date': format_date(note.doc_date),
            'payment_code': note.payment_code or '-',
            'employee_name': employee_name,
            'amount': str(amount),
            'start_date': start_date,
            'end_date': end_date
        })
    
    return jsonify({
        'items': items,
        'total': notes.total,
        'page': page,
        'pages': notes.pages,
        'per_page': per_page
    })

# ==================== API ДЛЯ СОТРУДНИКОВ ====================

@app.route('/api/employees')
def get_employees():
    employees = Employee.query.all()
    return jsonify([{
        'id': e.id,
        'tab_no': e.tab_no or '',
        'last_name': e.last_name or '',
        'first_name': e.first_name or '',
        'middle_name': e.middle_name or '',
        'department': e.department or '',
        'position': e.position or '',
        'position_code': e.position_code or ''
    } for e in employees])

@app.route('/api/employees/search')
def search_employees():
    query = request.args.get('q', '')
    if len(query) < 2:
        return jsonify([])
    
    employees = Employee.query.filter(
        (Employee.last_name.ilike(f'%{query}%')) |
        (Employee.first_name.ilike(f'%{query}%')) |
        (Employee.tab_no.ilike(f'%{query}%'))
    ).limit(20).all()
    
    return jsonify([{
        'id': e.id,
        'tab_no': e.tab_no,
        'full_name': e.full_name,
        'department': e.department,
        'position': e.position
    } for e in employees])

@app.route('/employees')
def employees_list():
    return render_template('employees.html')

# ==================== ПРОСМОТР СЛУЖЕБНОЙ ЗАПИСКИ ====================

@app.route('/note/<int:note_id>')
def view_note(note_id):
    note = ServiceNote.query.get_or_404(note_id)
    
    details = None
    if note.payment_code in ['1106', '1006']:
        details = SupplementDetail.query.filter_by(service_note_id=note_id).all()
    elif note.payment_code in ['1003', '1021', '1001']:
        details = CombinationDetail.query.filter_by(service_note_id=note_id).all()
    elif note.payment_code == '1019':
        details = SubstitutionDetail.query.filter_by(service_note_id=note_id).first()
    elif note.payment_code == '9999':
        details = AllowanceDetail.query.filter_by(service_note_id=note_id).all()
    
    return render_template('view_note.html', note=note, details=details)

# ==================== СТРАНИЦЫ ФОРМ ====================

@app.route('/note/new/<payment_code>')
def new_note(payment_code):
    return render_template(f'note_form_{payment_code}.html', payment_code=payment_code)

@app.route('/note/supplement', methods=['POST'])
def create_supplement_note():
    try:
        doc_number = request.form.get('doc_number')
        doc_date = datetime.strptime(request.form.get('doc_date'), '%Y-%m-%d').date()
        payment_code = request.form.get('payment_code')
        base_reason = request.form.get('base_reason')
        
        note = ServiceNote(doc_number=doc_number, doc_date=doc_date, payment_code=payment_code, base_reason=base_reason)
        db.session.add(note)
        db.session.flush()
        
        employee_ids = request.form.getlist('employee_id')
        start_dates = request.form.getlist('start_date')
        end_dates = request.form.getlist('end_date')
        amounts = request.form.getlist('amount')
        is_percents = request.form.getlist('is_percent')
        work_contents = request.form.getlist('work_content')
        
        for i in range(len(employee_ids)):
            if employee_ids[i]:
                detail = SupplementDetail(
                    service_note_id=note.id,
                    employee_id=int(employee_ids[i]),
                    start_date=datetime.strptime(start_dates[i], '%Y-%m-%d').date(),
                    end_date=datetime.strptime(end_dates[i], '%Y-%m-%d').date(),
                    amount=float(amounts[i]) if amounts[i] else 0,
                    is_percent=(is_percents[i] == 'on' if i < len(is_percents) else False),
                    work_content=work_contents[i] if i < len(work_contents) else ''
                )
                db.session.add(detail)
        
        db.session.commit()
        flash('Служебная записка создана!', 'success')
        return redirect(url_for('index'))
    except Exception as e:
        db.session.rollback()
        flash(f'Ошибка: {str(e)}', 'error')
        return redirect(request.referrer)

@app.route('/note/combination', methods=['POST'])
def create_combination_note():
    try:
        doc_number = request.form.get('doc_number')
        doc_date = datetime.strptime(request.form.get('doc_date'), '%Y-%m-%d').date()
        payment_code = request.form.get('payment_code')
        base_reason = request.form.get('base_reason')
        
        note = ServiceNote(doc_number=doc_number, doc_date=doc_date, payment_code=payment_code, base_reason=base_reason)
        db.session.add(note)
        db.session.flush()
        
        employee_ids = request.form.getlist('employee_id')
        combined_positions = request.form.getlist('combined_position')
        position_identifiers = request.form.getlist('position_identifier')
        departments = request.form.getlist('department')
        start_dates = request.form.getlist('start_date')
        end_dates = request.form.getlist('end_date')
        extra_pays = request.form.getlist('extra_pay')
        reasons = request.form.getlist('reason')
        
        for i in range(len(employee_ids)):
            if employee_ids[i]:
                detail = CombinationDetail(
                    service_note_id=note.id,
                    employee_id=int(employee_ids[i]),
                    combined_position=combined_positions[i] if i < len(combined_positions) else '',
                    position_identifier=position_identifiers[i] if i < len(position_identifiers) else '',
                    department=departments[i] if i < len(departments) else '',
                    start_date=datetime.strptime(start_dates[i], '%Y-%m-%d').date(),
                    end_date=datetime.strptime(end_dates[i], '%Y-%m-%d').date(),
                    extra_pay=float(extra_pays[i]) if extra_pays[i] else 0,
                    payment_code=payment_code,
                    reason=reasons[i] if i < len(reasons) else ''
                )
                db.session.add(detail)
        
        db.session.commit()
        flash('Служебная записка о совмещении создана!', 'success')
        return redirect(url_for('index'))
    except Exception as e:
        db.session.rollback()
        flash(f'Ошибка: {str(e)}', 'error')
        return redirect(request.referrer)

@app.route('/note/substitution', methods=['POST'])
def create_substitution_note():
    try:
        doc_number = request.form.get('doc_number')
        doc_date = datetime.strptime(request.form.get('doc_date'), '%Y-%m-%d').date()
        payment_code = request.form.get('payment_code')
        base_reason = request.form.get('base_reason')
        
        note = ServiceNote(doc_number=doc_number, doc_date=doc_date, payment_code=payment_code, base_reason=base_reason)
        db.session.add(note)
        db.session.flush()
        
        substitute_ids = request.form.getlist('substitute_employee_id')
        absent_ids = request.form.getlist('absent_employee_id')
        start_dates = request.form.getlist('start_date')
        end_dates = request.form.getlist('end_date')
        extra_pays = request.form.getlist('extra_pay_percent')
        duties_list = request.form.getlist('duties')
        absence_reasons = request.form.getlist('absence_reason')
        
        for i in range(len(substitute_ids)):
            if substitute_ids[i] and absent_ids[i]:
                detail = SubstitutionDetail(
                    service_note_id=note.id,
                    substitute_employee_id=int(substitute_ids[i]),
                    absent_employee_id=int(absent_ids[i]),
                    start_date=datetime.strptime(start_dates[i], '%Y-%m-%d').date(),
                    end_date=datetime.strptime(end_dates[i], '%Y-%m-%d').date(),
                    extra_pay_percent=float(extra_pays[i]) if extra_pays[i] else 20,
                    payment_code=payment_code,
                    duties=duties_list[i] if i < len(duties_list) else '',
                    absence_reason=absence_reasons[i] if i < len(absence_reasons) else ''
                )
                db.session.add(detail)
        
        db.session.commit()
        flash('Заявка на исполнение обязанностей создана!', 'success')
        return redirect(url_for('index'))
    except Exception as e:
        db.session.rollback()
        flash(f'Ошибка: {str(e)}', 'error')
        return redirect(request.referrer)

@app.route('/note/allowance', methods=['POST'])
def create_allowance_note():
    try:
        doc_number = request.form.get('doc_number')
        doc_date = datetime.strptime(request.form.get('doc_date'), '%Y-%m-%d').date()
        payment_code = request.form.get('payment_code')
        base_reason = request.form.get('base_reason')
        
        note = ServiceNote(doc_number=doc_number, doc_date=doc_date, payment_code=payment_code, base_reason=base_reason)
        db.session.add(note)
        db.session.flush()
        
        employee_ids = request.form.getlist('employee_id')
        allowance_names = request.form.getlist('allowance_name')
        units = request.form.getlist('unit')
        amounts = request.form.getlist('amount')
        statuses = request.form.getlist('status')
        start_dates = request.form.getlist('start_date')
        end_dates = request.form.getlist('end_date')
        
        for i in range(len(employee_ids)):
            if employee_ids[i]:
                detail = AllowanceDetail(
                    service_note_id=note.id,
                    employee_id=int(employee_ids[i]),
                    allowance_name=allowance_names[i] if i < len(allowance_names) else '',
                    payment_code=request.form.get('allowance_payment_code', ''),
                    unit=units[i] if i < len(units) else '',
                    amount=float(amounts[i]) if amounts[i] else 0,
                    status=statuses[i] if i < len(statuses) else 'установление',
                    start_date=datetime.strptime(start_dates[i], '%Y-%m-%d').date(),
                    end_date=datetime.strptime(end_dates[i], '%Y-%m-%d').date() if end_dates[i] else None
                )
                db.session.add(detail)
        
        db.session.commit()
        flash('Служебная записка об установлении/снятии доплаты создана!', 'success')
        return redirect(url_for('index'))
    except Exception as e:
        db.session.rollback()
        flash(f'Ошибка: {str(e)}', 'error')
        return redirect(request.referrer)

# ==================== ЭКСПОРТ В EXCEL ====================

@app.route('/export/1106/<int:note_id>')
def export_1106(note_id):
    try:
        note = ServiceNote.query.get_or_404(note_id)
        details = SupplementDetail.query.filter_by(service_note_id=note_id).all()
        
        template_path = os.path.join(TEMPLATE_DIR, 'v-9.7_rzo.xlsx')
        wb = load_workbook(template_path)
        ws = wb['СЗ']  # Явно выбираем лист
        
        # Заполняем шапку
        ws['C4'] = f"№ {note.doc_number} от {format_date(note.doc_date)}"
        
        # Данные начинаются с 24 строки
        start_row = 24
        for idx, detail in enumerate(details):
            emp = detail.employee
            row = start_row + idx
            
            ws.cell(row=row, column=1, value=idx + 1) # № п/п
            ws.cell(row=row, column=2, value=emp.tab_no if emp else '') # Таб. номер
            ws.cell(row=row, column=3, value=emp.full_name if emp else '') # ФИО
            ws.cell(row=row, column=4, value=emp.department if emp else '') # Подразделение
            ws.cell(row=row, column=5, value=emp.position if emp else '') # Должность
            ws.cell(row=row, column=6, value=format_date(detail.start_date)) # Период с
            ws.cell(row=row, column=7, value=format_date(detail.end_date)) # Период по
            ws.cell(row=row, column=8, value=detail.amount) # Доплата
            ws.cell(row=row, column=9, value=note.payment_code) # Код ВО
            ws.cell(row=row, column=10, value=detail.work_content or '') # Основание
            ws.cell(row=row, column=11, value=detail.work_type or '') # Вид работы

        output = BytesIO()
        wb.save(output)
        output.seek(0)
        return send_file(output, download_name=f'СЗ_В-9.7_{note.doc_number}.xlsx', as_attachment=True)
    except Exception as e:
        print(f"!!! ОШИБКА ЭКСПОРТА 1106: {e}")
        return f"Ошибка при формировании Excel: {e}", 500


@app.route('/export/1003/<int:note_id>')
def export_1003(note_id):
    try:
        note = ServiceNote.query.get_or_404(note_id)
        details = CombinationDetail.query.filter_by(service_note_id=note_id).all()
        
        template_path = os.path.join(TEMPLATE_DIR, 'v-9.4_comb.xlsx')
        wb = load_workbook(template_path)
        ws = wb['СЗ']
        
        # Данные начинаются с 21 строки
        start_row = 21
        for idx, detail in enumerate(details):
            emp = detail.employee
            row = start_row + idx
            
            ws.cell(row=row, column=1, value=idx + 1)
            ws.cell(row=row, column=2, value=emp.tab_no if emp else '')
            ws.cell(row=row, column=3, value=emp.full_name if emp else '')
            ws.cell(row=row, column=4, value=detail.combined_position or '')
            ws.cell(row=row, column=5, value=detail.position_identifier or '')
            ws.cell(row=row, column=6, value=detail.department or '')
            ws.cell(row=row, column=7, value=format_date(detail.start_date))
            ws.cell(row=row, column=8, value=format_date(detail.end_date))
            ws.cell(row=row, column=9, value=f"{detail.extra_pay}%" if detail.extra_pay else '')
            ws.cell(row=row, column=10, value=detail.payment_code or '')
            ws.cell(row=row, column=14, value=detail.reason or '')
            ws.cell(row=row, column=15, value=detail.duties or '')

        output = BytesIO()
        wb.save(output)
        output.seek(0)
        return send_file(output, download_name=f'СЗ_В-9.4_{note.doc_number}.xlsx', as_attachment=True)
    except Exception as e:
        print(f"!!! ОШИБКА ЭКСПОРТА 1003: {e}")
        return f"Ошибка при формировании Excel: {e}", 500


@app.route('/export/1019/<int:note_id>')
def export_1019(note_id):
    try:
        note = ServiceNote.query.get_or_404(note_id)
        detail = SubstitutionDetail.query.filter_by(service_note_id=note_id).first()
        
        template_path = os.path.join(TEMPLATE_DIR, 'v-9.10_subst.xlsx')
        wb = load_workbook(template_path)
        ws = wb['Заявка'] # В этом файле лист называется "Заявка"
        
        if detail:
            # Заполняем конкретные ячейки согласно шаблону В-9.10 (координаты из вашего примера)
            ws['C4'] = f"Заявка № {note.doc_number}"
            ws['H4'] = format_date(note.doc_date)
            ws['C6'] = detail.absence_reason or ''
            
            # Отсутствующий работник
            if detail.absent:
                ws['A8'] = detail.absent.full_name
                ws['I8'] = detail.absent.tab_no
                ws['A10'] = f"{detail.absent.position}, {detail.absent.department}"
                
            # Замещающий работник
            if detail.substitute:
                ws['A14'] = detail.substitute.full_name
                ws['I14'] = detail.substitute.tab_no
                ws['A16'] = f"{detail.substitute.position}, {detail.substitute.department}"
                
            ws['B18'] = format_date(detail.start_date)
            ws['G18'] = format_date(detail.end_date)
            ws['G29'] = detail.extra_pay_percent

        output = BytesIO()
        wb.save(output)
        output.seek(0)
        return send_file(output, download_name=f'Заявка_В-9.10_{note.doc_number}.xlsx', as_attachment=True)
    except Exception as e:
        print(f"!!! ОШИБКА ЭКСПОРТА 1019: {e}")
        return f"Ошибка при формировании Excel: {e}", 500


@app.route('/export/9999/<int:note_id>')
def export_9999(note_id):
    try:
        note = ServiceNote.query.get_or_404(note_id)
        details = AllowanceDetail.query.filter_by(service_note_id=note_id).all()
        
        template_path = os.path.join(TEMPLATE_DIR, 'v-17.1_allow.xlsx')
        wb = load_workbook(template_path)
        ws = wb['СЗ']
        
        # Данные начинаются с 21 строки
        start_row = 21
        for idx, detail in enumerate(details):
            emp = detail.employee
            row = start_row + idx
            
            ws.cell(row=row, column=1, value=idx + 1)
            ws.cell(row=row, column=2, value=emp.tab_no if emp else '')
            ws.cell(row=row, column=3, value=emp.full_name if emp else '')
            ws.cell(row=row, column=4, value=detail.allowance_name or '')
            ws.cell(row=row, column=5, value=detail.payment_code or '')
            ws.cell(row=row, column=6, value=detail.unit or '')
            ws.cell(row=row, column=7, value=detail.amount)
            ws.cell(row=row, column=8, value=detail.status or 'установить')
            ws.cell(row=row, column=9, value=format_date(detail.start_date))
            ws.cell(row=row, column=10, value=format_date(detail.end_date))

        output = BytesIO()
        wb.save(output)
        output.seek(0)
        return send_file(output, download_name=f'СЗ_В-17.1_{note.doc_number}.xlsx', as_attachment=True)
    except Exception as e:
        print(f"!!! ОШИБКА ЭКСПОРТА 9999: {e}")
        return f"Ошибка при формировании Excel: {e}", 500


# ==================== ЗАПУСК ====================

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True, host='0.0.0.0', port=5000)