v87: +Word/PDF export, +hse.sk.kz integration (server.py)

This commit is contained in:
Dauren777 2026-06-10 05:12:25 +00:00
parent 8584419acc
commit 507281dfee
5 changed files with 224 additions and 13 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
__pycache__/

View File

@ -83,7 +83,9 @@ tr:hover{background:#FAFBFC}
<a class=" active" id="snav_events" onclick="switchTab('events')"><span>Мероприятия</span></a>
<a class="" id="snav_analytics" onclick="switchTab('analytics')"><span>Аналитика</span></a>
<a class="" id="snav_reports" onclick="switchTab('reports')"><span>Отчётность</span></a>
<a class="" id="snav_reports" onclick="switchTab('reports')"><span>Отчётность</span></a>
<a class="" id="snav_ai" onclick="switchTab('ai')"><span>ИИ-помощник</span></a>
<a class="" id="snav_hse" onclick="switchTab('hse')"><span>HSE.sk.kz</span></a>
<div class="logout"><button class="btn btn-sm btn-r" style="width:100%" onclick="doLogout()">Выйти</button></div>
</div>
<div id="main">
@ -137,9 +139,29 @@ tr:hover{background:#FAFBFC}
</select>
<button class="btn btn-sm btn-g" onclick="dlCSV()">CSV</button>
<button class="btn btn-sm" onclick="dlHTML()">HTML</button>
<button class="btn btn-sm btn-o" onclick="dlWord()">Word</button>
<button class="btn btn-sm btn-r" onclick="dlPdf()">PDF</button>
</div>
<div class="card" id="rp_preview"></div>
</div>
<div id="tab_hse" style="display:none">
<div class="card"><h3>Интеграция с HSE.sk.kz</h3>
<p style="font-size:13px;color:#64748B;margin-bottom:16px">Направление подписанного сводного отчёта по месяцам в систему hse.sk.kz</p>
<div style="margin-bottom:12px"><strong>Месяц:</strong>
<input type="month" id="hse_month" style="padding:6px 10px;border:1px solid #E2E8F0;border-radius:6px;margin-left:8px">
</div>
<div style="margin-bottom:12px"><strong>Формат:</strong>
<select id="hse_fmt" style="padding:6px 10px;border:1px solid #E2E8F0;border-radius:6px;margin-left:8px">
<option value="word">Word (.docx)</option>
<option value="pdf">PDF</option>
</select>
</div>
<div style="margin-bottom:12px"><strong>API Ключ:</strong>
<input id="hse_key" type="password" placeholder="Ключ API HSE.sk.kz" style="padding:6px 10px;border:1px solid #E2E8F0;border-radius:6px;margin-left:8px;width:300px">
</div>
<button class="btn btn-sm btn-g" onclick="hseSend()" id="hse_btn">Отправить отчёт в HSE.sk.kz</button>
<div id="hse_result" style="margin-top:12px;font-size:13px"></div>
</div></div>
<div id="tab_ai" style="display:none">
<div class="card">
<div class="chat-q">
@ -255,8 +277,8 @@ function saveEv(){
function switchTab(t){
tab=t;
var tabs=["events","analytics","reports","ai"];
var tn={events:"Мероприятия",analytics:"Аналитика",reports:"Отчётность",ai:"ИИ-помощник"};
var tabs=["events","analytics","reports","ai","hse"];
var tn={events:"Мероприятия",analytics:"Аналитика",reports:"Отчётность",ai:"ИИ-помощник",hse:"HSE.sk.kz"};
for(var i=0;i<tabs.length;i++){
var el=document.getElementById("tab_"+tabs[i]);
if(el)el.style.display="none";
@ -270,6 +292,7 @@ function switchTab(t){
if(t==="analytics")renderAnalytics();
if(t==="reports")renderReports();
if(t==="ai")renderAI()
if(t==="hse"){var hm=document.getElementById("hse_month");if(hm&&!hm.value)hm.value=new Date().toISOString().slice(0,7)}
}
function daysRem(due){
@ -532,17 +555,58 @@ function saveBackup(){
a.download="backup_"+new Date().toISOString().slice(0,10)+".json";
a.click()
}
function loadBackup(inp){
if(!inp.files||!inp.files[0])return;
var fr=new FileReader();
fr.onload=function(){
try{
var d=JSON.parse(fr.result);
if(!d||!d.length){alert("\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442");return}
var out=[];
for(var i=0;i<d.length;i++){
out.push({id:d[i].id,s:d[i].s||"wait",p:d[i].p||0,done:d[i].done||"\u2014",h:d[i].h||[]})
}
function dlWord(){
var fl=getFilteredEvs();
var month=parseInt(document.getElementById("rp_month").value,10)+1;
var year=document.getElementById("rp_year").value;
var hh="<html xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:w='urn:schemas-microsoft-com:office:word' xmlns='http://www.w3.org/TR/REC-html40'><head><meta charset='utf-8'><title>Отчёт План ПБ "+month+"."+year+"</title><style>@page{size:A4;margin:20mm}body{font:12pt 'Times New Roman'}h2{font-size:16pt;text-align:center}table{border-collapse:collapse;width:100%}th,td{border:1px solid #000;padding:4px 8px;font-size:11pt}th{background:#ddd}</style></head><body><h2>План производственной безопасности</h2><p style='text-align:center'>AO «Казахтелеком» за "+month+"."+year+"</p><br><table><tr><th>N</th><th>Мероприятие</th><th>Филиал</th><th>Срок</th><th>Статус</th><th>Прогресс</th><th>Кол-во</th><th>Примечание</th></tr>";
for(var i=0;i<fl.length;i++){
var e=fl[i];
hh+="<tr><td>"+e.id+"</td><td>"+esc(e.t)+"</td><td>"+brs[e.b]+"</td><td>"+e.due+"</td><td>"+stn[e.s]+"</td><td>"+(e.p||0)+"%</td><td>"+(e.q||"")+"</td><td>"+esc(e.n||"")+"</td></tr>"
}
hh+="</table><p><br><em>Отчёт сформирован: "+new Date().toLocaleDateString("ru-RU")+"</em></p></body></html>";
var blob=new Blob([hh],{type:"application/msword"});
var a=document.createElement("a");
a.href=URL.createObjectURL(blob);
a.download="report_pb_"+year+"_"+month+".doc";
a.click()
}
function dlPdf(){
var fl=getFilteredEvs();
var month=parseInt(document.getElementById("rp_month").value,10)+1;
var year=document.getElementById("rp_year").value;
var hh="<!DOCTYPE html><html><head><meta charset='utf-8'><title>Отчёт План ПБ "+month+"."+year+"</title><style>body{font:14px Arial;padding:20px}table{border-collapse:collapse;width:100%}th,td{border:1px solid #ccc;padding:6px 10px;font-size:12px;text-align:left}th{background:#0B1A2E;color:#fff}@media print{body{padding:10mm}table{page-break-inside:auto}tr{page-break-inside:avoid}}</style></head><body><h2>План производственной безопасности</h2><p>AO «Казахтелеком» за "+month+"."+year+"</p><br><table><tr><th>N</th><th>Мероприятие</th><th>Филиал</th><th>Срок</th><th>Статус</th><th>Прогресс</th><th>Кол-во</th><th>Примечание</th></tr>";
for(var i=0;i<fl.length;i++){
var e=fl[i];
hh+="<tr><td>"+e.id+"</td><td>"+esc(e.t)+"</td><td>"+brs[e.b]+"</td><td>"+e.due+"</td><td>"+stn[e.s]+"</td><td>"+(e.p||0)+"%</td><td>"+(e.q||"")+"</td><td>"+esc(e.n||"")+"</td></tr>"
}
hh+="</table><p><br><em>Отчёт сформирован: "+new Date().toLocaleDateString("ru-RU")+"</em></p><script>window.onload=function(){window.print()}<\/script></body></html>";
var w=window.open("","_blank","width=900,height=700");
w.document.write(hh);
w.document.close()
}
function hseSend(){
var btn=document.getElementById("hse_btn");
var result=document.getElementById("hse_result");
btn.disabled=true;btn.textContent="Отправка...";result.innerHTML="";
var month=document.getElementById("hse_month").value;
var fmt=document.getElementById("hse_fmt").value;
var apiKey=document.getElementById("hse_key").value;
if(!month){result.innerHTML="<span style='color:#EF4444'>Выберите месяц</span>";btn.disabled=false;btn.textContent="Отправить отчёт в HSE.sk.kz";return}
if(!apiKey){result.innerHTML="<span style='color:#EF4444'>Введите API ключ</span>";btn.disabled=false;btn.textContent="Отправить отчёт в HSE.sk.kz";return}
var fl=getFilteredEvs();
var total=fl.length;
var done=0;for(var i=0;i<fl.length;i++){if(fl[i].s==="done")done++}
var pct=total?Math.round(done/total*100):0;
var payload={month:month,events:fl.map(function(e){return{id:e.id,title:e.t,branch:brs[e.b],deadline:e.due,status:e.s,progress:e.p||0,quantity:e.q||"",note:e.n||""}}),summary:{total:total,done:done,pct:pct}};
try{
fetch("http://localhost:5000/api/hse/send",{method:"POST",headers:{"Content-Type":"application/json","Authorization":"Bearer hse-integration"},body:JSON.stringify({month:month,api_key:apiKey,format:fmt,report:payload})}).then(function(r){return r.json()}).then(function(d){
if(d.ok){result.innerHTML="<span style='color:#10B981'>Отчёт за "+month+" отправлен в HSE.sk.kz</span>"}
else{result.innerHTML="<span style='color:#EF4444'>Ошибка: "+(d.error||"соединение")+"</span>"}
}).catch(function(e){result.innerHTML="<span style='color:#EF4444'>Сервер не запущен. Запустите <code>python3 server.py</code></span>"}).finally(function(){btn.disabled=false;btn.textContent="Отправить отчёт в HSE.sk.kz"})
}catch(e){}
}
localStorage.setItem("se5",JSON.stringify(out));
loadEv();
renderEv();

5
requirements.txt Normal file
View File

@ -0,0 +1,5 @@
flask>=3.0
flask-cors>=4.0
python-docx>=1.0
reportlab>=4.0
requests>=2.31

133
server.py Normal file
View File

@ -0,0 +1,133 @@
import json
import io
import os
from datetime import datetime
from flask import Flask, request, jsonify
from flask_cors import CORS
import requests as http_requests
app = Flask(__name__)
CORS(app)
HSE_API_URL = "https://hse.sk.kz/api/v1"
DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
os.makedirs(DATA_DIR, exist_ok=True)
def make_docx(report):
from docx import Document
from docx.shared import Pt
doc = Document()
doc.styles["Normal"].font.size = Pt(11)
doc.add_heading("План ПБ — Казахтелеком", level=1)
s = report.get("summary", {})
doc.add_paragraph(
f"Дата: {datetime.now().strftime('%d.%m.%Y')} | "
f"Всего: {s.get('total', 0)} | "
f"Выполнено: {s.get('done', 0)} ({s.get('pct', 0)}%)"
)
events = report.get("events", [])
table = doc.add_table(rows=1, cols=6)
table.style = "Light Grid Accent 1"
for i, h in enumerate(["N", "Мероприятие", "Филиал", "Срок", "Статус", "%"]):
table.rows[0].cells[i].text = h
for e in events:
row = table.add_row().cells
row[0].text = str(e.get("id", ""))
row[1].text = str(e.get("title", ""))[:100]
row[2].text = str(e.get("branch", ""))
row[3].text = str(e.get("deadline", ""))
row[4].text = str(e.get("status", ""))
row[5].text = str(e.get("progress", 0)) + "%"
buf = io.BytesIO()
doc.save(buf)
buf.seek(0)
return buf
def make_pdf(report):
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import mm
from reportlab.lib.colors import HexColor
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
buf = io.BytesIO()
doc = SimpleDocTemplate(buf, pagesize=A4, rightMargin=20 * mm, leftMargin=20 * mm,
topMargin=20 * mm, bottomMargin=20 * mm)
styles = getSampleStyleSheet()
story = [Paragraph("План ПБ — Казахтелеком", styles["Title"]), Spacer(1, 10)]
s = report.get("summary", {})
story.append(Paragraph(
f"Всего: {s.get('total', 0)} | Выполнено: {s.get('done', 0)} ({s.get('pct', 0)}%)",
styles["Normal"]
))
story.append(Spacer(1, 10))
data = [["N", "Мероприятие", "Филиал", "Срок", "Статус", "%"]]
for e in report.get("events", []):
data.append([
str(e.get("id", "")), str(e.get("title", ""))[:80],
str(e.get("branch", ""))[:25], str(e.get("deadline", "")),
str(e.get("status", "")), str(e.get("progress", 0)) + "%",
])
table = Table(data, colWidths=[20, 220, 80, 50, 60, 40])
table.setStyle(TableStyle([
("FONTSIZE", (0, 0), (-1, 0), 9), ("FONTSIZE", (0, 1), (-1, -1), 8),
("BACKGROUND", (0, 0), (-1, 0), HexColor("#003366")),
("TEXTCOLOR", (0, 0), (-1, 0), HexColor("#FFFFFF")),
("GRID", (0, 0), (-1, -1), 0.5, HexColor("#CCCCCC")),
("VALIGN", (0, 0), (-1, -1), "TOP"),
]))
story.append(table)
doc.build(story)
buf.seek(0)
return buf
@app.route("/api/hse/send", methods=["POST"])
def hse_send():
data = request.get_json()
month = data.get("month", "")
api_key = data.get("api_key", "")
fmt = data.get("format", "word")
report = data.get("report", {})
endpoint = data.get("endpoint", f"{HSE_API_URL}/documents/upload")
if not api_key:
return jsonify({"ok": False, "error": "API key required"}), 400
if fmt == "pdf":
buf = make_pdf(report)
mime = "application/pdf"
ext = "pdf"
else:
buf = make_docx(report)
mime = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
ext = "docx"
try:
files = {"file": (f"hse_report_{month}.{ext}", buf.getvalue(), mime)}
headers = {"Authorization": f"Bearer {api_key}"}
payload = {
"title": f"Сводный отчет по ПБ за {month}",
"description": "Автоматический отчет платформы мониторинга ПБ",
"type": "safety_report",
"period": month,
}
r = http_requests.post(endpoint, files=files, data=payload, headers=headers, timeout=30)
if r.ok:
return jsonify({"ok": True, "hse_response": r.json() if r.text else {"status": r.status_code}})
return jsonify({"ok": False, "error": f"HSE API error: {r.status_code}", "detail": r.text[:500]}), 502
except Exception as e:
return jsonify({"ok": False, "error": str(e)}), 502
@app.route("/api/health", methods=["GET"])
def health():
return jsonify({"ok": True, "time": datetime.now().isoformat()})
if __name__ == "__main__":
print("HSE Integration Server — http://0.0.0.0:5000")
app.run(host="0.0.0.0", port=5000, debug=False)

8
start.sh Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
export PATH="$HOME/.local/bin:$PATH"
cd "$(dirname "$0")"
echo "=== HSE Integration Server ==="
echo "Installing..."
pip3 install -r requirements.txt --break-system-packages -q 2>/dev/null
echo "Starting on http://0.0.0.0:5000"
python3 server.py