backend.components.reports.reportController
1import base64 2import io 3import os 4from collections import Counter 5 6from flask import Blueprint, request, jsonify, render_template, make_response, send_file, current_app 7from xhtml2pdf import pisa 8from xhtml2pdf.files import pisaFileObject 9from backend.components.reports.reportService import get_report_data 10from datetime import datetime 11 12bp = Blueprint('reports', __name__, url_prefix='/api/raport') 13 14 15@bp.route('', methods=['GET']) 16def generate_report(): 17 """ 18 Retrieves worker entry reports based on provided filters. 19 20 This endpoint allows filtering entries (Entry) by date, worker, and validation status (valid/invalid). 21 Returns data in JSON format (including Base64 encoded face image), which can be used to generate tables or PDF files. 22 23 **Parameters**: 24 - `date_from` (str): Start date of the range (format YYYY-MM-DD or ISO). Optional. 25 - `date_to` (str): End date of the range (format YYYY-MM-DD or ISO). If only date is provided, it covers the whole day (until 23:59:59). Optional. 26 - `pracownik_id` (int): Worker ID to filter by. Optional. 27 - `wejscia_niepoprawne` (bool): Flag - if present, includes invalid entries (error code != 0). 28 - `wejscia_poprawne` (bool): Flag - if present, includes valid entries (error code == 0). 29 30 **Returns**: 31 - `JSON` - Object containing count, filters used, calculated statistics, and the list of entry data. 32 33 --- 34 tags: 35 - Reports 36 parameters: 37 - name: date_from 38 in: query 39 type: string 40 required: false 41 description: Start date of the range (format YYYY-MM-DD or ISO). 42 - name: date_to 43 in: query 44 type: string 45 required: false 46 description: End date of the range (format YYYY-MM-DD or ISO). If only date is provided, covers the whole day (until 23:59:59). 47 - name: pracownik_id 48 in: query 49 type: integer 50 required: false 51 description: Worker ID to filter by. 52 - name: wejscia_niepoprawne 53 in: query 54 type: string 55 required: false 56 allowEmptyValue: true 57 description: > 58 Presence flag. If this parameter is included in the URL (regardless of its value), 59 invalid entries (error code != 0) will be included in the report. 60 - name: wejscia_poprawne 61 in: query 62 type: string 63 required: false 64 allowEmptyValue: true 65 description: > 66 Presence flag. If this parameter is included in the URL (regardless of its value), 67 valid entries (error code == 0) will be included in the report. 68 responses: 69 200: 70 description: Report successfully generated. 71 schema: 72 type: object 73 properties: 74 count: 75 type: integer 76 description: Number of entries found. 77 filters: 78 type: object 79 description: Filters used in the query. 80 statistics: 81 type: object 82 description: Aggregated statistics for the selected period. 83 properties: 84 total_entries: 85 type: integer 86 description: Total number of entries in the result set. 87 valid_entries: 88 type: integer 89 description: Number of successful entries (code 0). 90 invalid_entries: 91 type: integer 92 description: Number of failed entries (code != 0). 93 success_rate_percent: 94 type: number 95 format: float 96 description: Percentage of valid entries. 97 most_invalid_attempts_worker: 98 type: object 99 description: Worker with the most invalid attempts. 100 properties: 101 name: 102 type: string 103 count: 104 type: integer 105 most_valid_entries_worker: 106 type: object 107 description: Worker with the most valid entries. 108 properties: 109 name: 110 type: string 111 count: 112 type: integer 113 daily_traffic: 114 type: object 115 description: Dictionary mapping dates (YYYY-MM-DD) to entry counts. 116 additionalProperties: 117 type: integer 118 data: 119 type: array 120 items: 121 type: object 122 properties: 123 id: 124 type: integer 125 date: 126 type: string 127 format: date-time 128 code: 129 type: integer 130 description: Response code (0 = success). 131 message: 132 type: string 133 worker_id: 134 type: integer 135 worker_name: 136 type: string 137 face_image: 138 type: string 139 description: Base64 encoded face image (or null). 140 400: 141 description: Parameter validation error. 142 500: 143 description: Internal server error. 144 """ 145 146 try: 147 date_from_str = request.args.get('date_from') 148 date_to_str = request.args.get('date_to') 149 worker_id = request.args.get('pracownik_id', type=int) 150 151 show_invalid = 'wejscia_niepoprawne' in request.args 152 show_valid = 'wejscia_poprawne' in request.args 153 154 date_from = None 155 date_to = None 156 157 if date_from_str: 158 try: 159 date_from = datetime.fromisoformat(date_from_str) 160 except ValueError: 161 return jsonify({'error': 'Nieprawidłowy format date_from. Oczekiwano YYYY-MM-DD'}), 400 162 163 if date_to_str: 164 try: 165 date_to = datetime.fromisoformat(date_to_str) 166 if len(date_to_str) == 10: 167 date_to = date_to.replace(hour=23, minute=59, second=59, microsecond=999999) 168 except ValueError: 169 return jsonify({'error': 'Nieprawidłowy format date_to'}), 400 170 171 results = get_report_data( 172 date_from=date_from, 173 date_to=date_to, 174 worker_id=worker_id, 175 show_valid=show_valid, 176 show_invalid=show_invalid 177 ) 178 179 data, statistics = _calculate_statistics(results) 180 json_data = [] 181 182 for item in data: 183 # Kodowanie obrazu do Base64 184 encoded_image = None 185 if item['face_image_bytes']: 186 encoded_image = base64.b64encode(item['face_image_bytes']).decode('utf-8') 187 188 json_data.append({ 189 **item, 190 'date': item['date'].isoformat(), 191 'face_image': encoded_image, # obraz base64 # @TODO - zmioana str na base64 usunąć "" 192 'face_image_bytes': None # Nie wysyłamy bajtów w JSON 193 }) 194 195 return jsonify({ 196 'count': len(json_data), 197 'filters': { 198 'date_from': date_from_str, 199 'date_to': date_to_str, 200 'worker_id': worker_id, 201 'show_valid': show_valid, 202 'show_invalid': show_invalid 203 }, 204 'statistics': statistics, 205 'data': json_data 206 }) 207 208 except Exception as e: 209 print(f"Błąd raportu: {e}") 210 return jsonify({'error': str(e)}), 500 211 212@bp.route('/pdf', methods=['GET']) 213def generate_pdf_report(): 214 """ 215 Generates a report in PDF format based on provided filters. 216 217 Retrieves exactly the same parameters as the JSON version. 218 219 **Parameters**: 220 - `date_from` (str): Start date of the range (format YYYY-MM-DD or ISO). Optional. 221 - `date_to` (str): End date of the range (format YYYY-MM-DD or ISO). Optional. 222 - `pracownik_id` (int): Worker ID to filter by. Optional. 223 - `wejscia_niepoprawne` (bool): Flag - if present, includes invalid entries. 224 - `wejscia_poprawne` (bool): Flag - if present, includes valid entries. 225 226 **Returns**: 227 - `application/pdf` - A downloadable PDF file. 228 229 --- 230 tags: 231 - Reports 232 parameters: 233 - name: date_from 234 in: query 235 type: string 236 required: false 237 description: Start date of the range (format YYYY-MM-DD or ISO). 238 - name: date_to 239 in: query 240 type: string 241 required: false 242 description: End date of the range (format YYYY-MM-DD or ISO). 243 - name: pracownik_id 244 in: query 245 type: integer 246 required: false 247 description: Worker ID to filter by. 248 - name: wejscia_niepoprawne 249 in: query 250 type: boolean 251 required: false 252 allowEmptyValue: true 253 description: Flag - if present, includes invalid entries. 254 - name: wejscia_poprawne 255 in: query 256 type: boolean 257 required: false 258 allowEmptyValue: true 259 description: Flag - if present, includes valid entries. 260 responses: 261 200: 262 description: PDF report generated successfully. 263 content: 264 application/pdf: 265 schema: 266 type: string 267 format: binary 268 400: 269 description: Parameter validation error. 270 500: 271 description: Internal server error. 272 """ 273 try: 274 date_from_str = request.args.get('date_from') 275 date_to_str = request.args.get('date_to') 276 worker_id = request.args.get('pracownik_id', type=int) 277 278 show_invalid = 'wejscia_niepoprawne' in request.args 279 show_valid = 'wejscia_poprawne' in request.args 280 281 date_from = None 282 date_to = None 283 284 if date_from_str: 285 try: 286 date_from = datetime.fromisoformat(date_from_str) 287 except ValueError: 288 return jsonify({'error': 'Nieprawidłowy format date_from'}), 400 289 290 if date_to_str: 291 try: 292 date_to = datetime.fromisoformat(date_to_str) 293 # Jeśli podano samą datę (format YYYY-MM-DD, bez godziny), ustawiamy czas na koniec dnia, 294 # aby uwzględnić wszystkie wpisy z tego dnia (domyślnie fromisoformat ustawia 00:00:00). 295 if len(date_to_str) == 10: 296 date_to = date_to.replace(hour=23, minute=59, second=59, microsecond=999999) 297 except ValueError: 298 return jsonify({'error': 'Nieprawidłowy format date_to'}), 400 299 300 # --- 2. Pobranie danych --- 301 results = get_report_data( 302 date_from=date_from, 303 date_to=date_to, 304 worker_id=worker_id, 305 show_valid=show_valid, 306 show_invalid=show_invalid 307 ) 308 309 # --- 3. Przygotowanie danych dla szablonu HTML (z użyciem funkcji pomocniczej) --- 310 data, statistics = _calculate_statistics(results) 311 312 report_data = [] 313 for item in data: 314 # Konwersja obrazka na Base64 (dla HTML) 315 img_b64 = None 316 if item['face_image_bytes']: 317 img_b64 = base64.b64encode(item['face_image_bytes']).decode('utf-8') 318 319 report_data.append({ 320 'id': item['id'], 321 'date': item['date'].strftime('%Y-%m-%d %H:%M:%S'), 322 'code': item['code'], 323 'message': item['message'], 324 'worker_id': item['worker_id'], 325 'worker_name': item['worker_name'], 326 'face_image_b64': img_b64 327 }) 328 329 font_path = os.path.join(current_app.config['STATIC_FOLDER'], 'fonts', 'Roboto-Regular.ttf') 330 331 context = { 332 'data': report_data, 333 'count': statistics['total_entries'], 334 'statistics': statistics, 335 'generation_date': datetime.now().strftime('%Y-%m-%d %H:%M'), 336 'filters': { 337 'date_from': date_from_str, 338 'date_to': date_to_str, 339 'worker_id': worker_id, 340 'show_valid': show_valid, 341 'show_invalid': show_invalid 342 }, 343 'font_path': font_path 344 } 345 346 # --- 4. Renderowanie HTML i konwersja na PDF --- 347 html_content = render_template('reports/report_pdf.html', **context) 348 349 pdf_output = io.BytesIO() 350 351 # On windows, file paths to the font break 352 # known issue, applying a monkeypatch 353 # https://github.com/xhtml2pdf/xhtml2pdf/issues/623#issuecomment-1372719452 354 pisaFileObject.getNamedFile = lambda self: self.uri 355 pisa_status = pisa.CreatePDF( 356 io.BytesIO(html_content.encode('utf-8')), 357 dest=pdf_output, 358 encoding='utf-8' 359 ) 360 361 if pisa_status.err: 362 return jsonify({'error': 'Błąd podczas generowania PDF'}), 500 363 364 pdf_output.seek(0) 365 366 return send_file( 367 pdf_output, 368 mimetype='application/pdf', 369 as_attachment=True, 370 download_name=f"raport_{datetime.now().strftime('%Y%m%d_%H%M')}.pdf" 371 ) 372 373 except Exception as e: 374 print(f"Błąd generowania PDF: {e}") 375 return jsonify({'error': str(e)}), 500 376 377def _calculate_statistics(results): 378 """ 379 Funkcja pomocnicza do obliczania statystyk na podstawie wyników. 380 Używana zarówno przez endpoint JSON, jak i PDF. 381 """ 382 stats_total = 0 383 stats_valid = 0 384 stats_invalid = 0 385 386 daily_counter = Counter() 387 cheater_counter = Counter() 388 top_worker_counter = Counter() 389 390 processed_data = [] 391 392 for entry, worker in results: 393 stats_total += 1 394 worker_name = worker.name if worker else f'Nieznany (ID: {entry.worker_id})' 395 day_str = entry.date.strftime('%Y-%m-%d') 396 397 # 1. Statystyki dzienne 398 daily_counter[day_str] += 1 399 400 # 2. Statystyki poprawności 401 if entry.code == 0: 402 stats_valid += 1 403 top_worker_counter[worker_name] += 1 404 else: 405 stats_invalid += 1 406 cheater_counter[worker_name] += 1 407 408 # Przygotowanie danych do listy (bez kodowania obrazka, enpoint zwracający JSON zajmuje się konwersją do base64) 409 entry_data = { 410 'id': entry.id, 411 'date': entry.date, 412 'code': entry.code, 413 'message': entry.message, 414 'worker_id': entry.worker_id, 415 'worker_name': worker_name, 416 'face_image_bytes': entry.face_image # Surowe bajty 417 } 418 processed_data.append(entry_data) 419 420 # --- Finalne statystyki --- 421 most_cheating = cheater_counter.most_common(1) 422 most_cheating_data = {'name': most_cheating[0][0], 'count': most_cheating[0][1]} if most_cheating else None 423 424 most_active = top_worker_counter.most_common(1) 425 most_active_data = {'name': most_active[0][0], 'count': most_active[0][1]} if most_active else None 426 427 sorted_daily_traffic = dict(sorted(daily_counter.items())) 428 429 statistics = { 430 'total_entries': stats_total, 431 'valid_entries': stats_valid, 432 'invalid_entries': stats_invalid, 433 'success_rate_percent': round((stats_valid / stats_total * 100), 2) if stats_total > 0 else 0, 434 'most_invalid_attempts_worker': most_cheating_data, 435 'most_valid_entries_worker': most_active_data, 436 'daily_traffic': sorted_daily_traffic 437 } 438 439 return processed_data, statistics
bp =
<Blueprint 'reports'>
@bp.route('', methods=['GET'])
def
generate_report():
16@bp.route('', methods=['GET']) 17def generate_report(): 18 """ 19 Retrieves worker entry reports based on provided filters. 20 21 This endpoint allows filtering entries (Entry) by date, worker, and validation status (valid/invalid). 22 Returns data in JSON format (including Base64 encoded face image), which can be used to generate tables or PDF files. 23 24 **Parameters**: 25 - `date_from` (str): Start date of the range (format YYYY-MM-DD or ISO). Optional. 26 - `date_to` (str): End date of the range (format YYYY-MM-DD or ISO). If only date is provided, it covers the whole day (until 23:59:59). Optional. 27 - `pracownik_id` (int): Worker ID to filter by. Optional. 28 - `wejscia_niepoprawne` (bool): Flag - if present, includes invalid entries (error code != 0). 29 - `wejscia_poprawne` (bool): Flag - if present, includes valid entries (error code == 0). 30 31 **Returns**: 32 - `JSON` - Object containing count, filters used, calculated statistics, and the list of entry data. 33 34 --- 35 tags: 36 - Reports 37 parameters: 38 - name: date_from 39 in: query 40 type: string 41 required: false 42 description: Start date of the range (format YYYY-MM-DD or ISO). 43 - name: date_to 44 in: query 45 type: string 46 required: false 47 description: End date of the range (format YYYY-MM-DD or ISO). If only date is provided, covers the whole day (until 23:59:59). 48 - name: pracownik_id 49 in: query 50 type: integer 51 required: false 52 description: Worker ID to filter by. 53 - name: wejscia_niepoprawne 54 in: query 55 type: string 56 required: false 57 allowEmptyValue: true 58 description: > 59 Presence flag. If this parameter is included in the URL (regardless of its value), 60 invalid entries (error code != 0) will be included in the report. 61 - name: wejscia_poprawne 62 in: query 63 type: string 64 required: false 65 allowEmptyValue: true 66 description: > 67 Presence flag. If this parameter is included in the URL (regardless of its value), 68 valid entries (error code == 0) will be included in the report. 69 responses: 70 200: 71 description: Report successfully generated. 72 schema: 73 type: object 74 properties: 75 count: 76 type: integer 77 description: Number of entries found. 78 filters: 79 type: object 80 description: Filters used in the query. 81 statistics: 82 type: object 83 description: Aggregated statistics for the selected period. 84 properties: 85 total_entries: 86 type: integer 87 description: Total number of entries in the result set. 88 valid_entries: 89 type: integer 90 description: Number of successful entries (code 0). 91 invalid_entries: 92 type: integer 93 description: Number of failed entries (code != 0). 94 success_rate_percent: 95 type: number 96 format: float 97 description: Percentage of valid entries. 98 most_invalid_attempts_worker: 99 type: object 100 description: Worker with the most invalid attempts. 101 properties: 102 name: 103 type: string 104 count: 105 type: integer 106 most_valid_entries_worker: 107 type: object 108 description: Worker with the most valid entries. 109 properties: 110 name: 111 type: string 112 count: 113 type: integer 114 daily_traffic: 115 type: object 116 description: Dictionary mapping dates (YYYY-MM-DD) to entry counts. 117 additionalProperties: 118 type: integer 119 data: 120 type: array 121 items: 122 type: object 123 properties: 124 id: 125 type: integer 126 date: 127 type: string 128 format: date-time 129 code: 130 type: integer 131 description: Response code (0 = success). 132 message: 133 type: string 134 worker_id: 135 type: integer 136 worker_name: 137 type: string 138 face_image: 139 type: string 140 description: Base64 encoded face image (or null). 141 400: 142 description: Parameter validation error. 143 500: 144 description: Internal server error. 145 """ 146 147 try: 148 date_from_str = request.args.get('date_from') 149 date_to_str = request.args.get('date_to') 150 worker_id = request.args.get('pracownik_id', type=int) 151 152 show_invalid = 'wejscia_niepoprawne' in request.args 153 show_valid = 'wejscia_poprawne' in request.args 154 155 date_from = None 156 date_to = None 157 158 if date_from_str: 159 try: 160 date_from = datetime.fromisoformat(date_from_str) 161 except ValueError: 162 return jsonify({'error': 'Nieprawidłowy format date_from. Oczekiwano YYYY-MM-DD'}), 400 163 164 if date_to_str: 165 try: 166 date_to = datetime.fromisoformat(date_to_str) 167 if len(date_to_str) == 10: 168 date_to = date_to.replace(hour=23, minute=59, second=59, microsecond=999999) 169 except ValueError: 170 return jsonify({'error': 'Nieprawidłowy format date_to'}), 400 171 172 results = get_report_data( 173 date_from=date_from, 174 date_to=date_to, 175 worker_id=worker_id, 176 show_valid=show_valid, 177 show_invalid=show_invalid 178 ) 179 180 data, statistics = _calculate_statistics(results) 181 json_data = [] 182 183 for item in data: 184 # Kodowanie obrazu do Base64 185 encoded_image = None 186 if item['face_image_bytes']: 187 encoded_image = base64.b64encode(item['face_image_bytes']).decode('utf-8') 188 189 json_data.append({ 190 **item, 191 'date': item['date'].isoformat(), 192 'face_image': encoded_image, # obraz base64 # @TODO - zmioana str na base64 usunąć "" 193 'face_image_bytes': None # Nie wysyłamy bajtów w JSON 194 }) 195 196 return jsonify({ 197 'count': len(json_data), 198 'filters': { 199 'date_from': date_from_str, 200 'date_to': date_to_str, 201 'worker_id': worker_id, 202 'show_valid': show_valid, 203 'show_invalid': show_invalid 204 }, 205 'statistics': statistics, 206 'data': json_data 207 }) 208 209 except Exception as e: 210 print(f"Błąd raportu: {e}") 211 return jsonify({'error': str(e)}), 500
Retrieves worker entry reports based on provided filters.
This endpoint allows filtering entries (Entry) by date, worker, and validation status (valid/invalid). Returns data in JSON format (including Base64 encoded face image), which can be used to generate tables or PDF files.
Parameters:
date_from(str): Start date of the range (format YYYY-MM-DD or ISO). Optional.date_to(str): End date of the range (format YYYY-MM-DD or ISO). If only date is provided, it covers the whole day (until 23:59:59). Optional.pracownik_id(int): Worker ID to filter by. Optional.wejscia_niepoprawne(bool): Flag - if present, includes invalid entries (error code != 0).wejscia_poprawne(bool): Flag - if present, includes valid entries (error code == 0).
Returns:
JSON- Object containing count, filters used, calculated statistics, and the list of entry data.
tags:
- Reports parameters:
- name: date_from in: query type: string required: false description: Start date of the range (format YYYY-MM-DD or ISO).
- name: date_to in: query type: string required: false description: End date of the range (format YYYY-MM-DD or ISO). If only date is provided, covers the whole day (until 23:59:59).
- name: pracownik_id in: query type: integer required: false description: Worker ID to filter by.
- name: wejscia_niepoprawne in: query type: string required: false allowEmptyValue: true description: > Presence flag. If this parameter is included in the URL (regardless of its value), invalid entries (error code != 0) will be included in the report.
- name: wejscia_poprawne in: query type: string required: false allowEmptyValue: true description: > Presence flag. If this parameter is included in the URL (regardless of its value), valid entries (error code == 0) will be included in the report. responses: 200: description: Report successfully generated. schema: type: object properties: count: type: integer description: Number of entries found. filters: type: object description: Filters used in the query. statistics: type: object description: Aggregated statistics for the selected period. properties: total_entries: type: integer description: Total number of entries in the result set. valid_entries: type: integer description: Number of successful entries (code 0). invalid_entries: type: integer description: Number of failed entries (code != 0). success_rate_percent: type: number format: float description: Percentage of valid entries. most_invalid_attempts_worker: type: object description: Worker with the most invalid attempts. properties: name: type: string count: type: integer most_valid_entries_worker: type: object description: Worker with the most valid entries. properties: name: type: string count: type: integer daily_traffic: type: object description: Dictionary mapping dates (YYYY-MM-DD) to entry counts. additionalProperties: type: integer data: type: array items: type: object properties: id: type: integer date: type: string format: date-time code: type: integer description: Response code (0 = success). message: type: string worker_id: type: integer worker_name: type: string face_image: type: string description: Base64 encoded face image (or null). 400: description: Parameter validation error. 500: description: Internal server error.
@bp.route('/pdf', methods=['GET'])
def
generate_pdf_report():
213@bp.route('/pdf', methods=['GET']) 214def generate_pdf_report(): 215 """ 216 Generates a report in PDF format based on provided filters. 217 218 Retrieves exactly the same parameters as the JSON version. 219 220 **Parameters**: 221 - `date_from` (str): Start date of the range (format YYYY-MM-DD or ISO). Optional. 222 - `date_to` (str): End date of the range (format YYYY-MM-DD or ISO). Optional. 223 - `pracownik_id` (int): Worker ID to filter by. Optional. 224 - `wejscia_niepoprawne` (bool): Flag - if present, includes invalid entries. 225 - `wejscia_poprawne` (bool): Flag - if present, includes valid entries. 226 227 **Returns**: 228 - `application/pdf` - A downloadable PDF file. 229 230 --- 231 tags: 232 - Reports 233 parameters: 234 - name: date_from 235 in: query 236 type: string 237 required: false 238 description: Start date of the range (format YYYY-MM-DD or ISO). 239 - name: date_to 240 in: query 241 type: string 242 required: false 243 description: End date of the range (format YYYY-MM-DD or ISO). 244 - name: pracownik_id 245 in: query 246 type: integer 247 required: false 248 description: Worker ID to filter by. 249 - name: wejscia_niepoprawne 250 in: query 251 type: boolean 252 required: false 253 allowEmptyValue: true 254 description: Flag - if present, includes invalid entries. 255 - name: wejscia_poprawne 256 in: query 257 type: boolean 258 required: false 259 allowEmptyValue: true 260 description: Flag - if present, includes valid entries. 261 responses: 262 200: 263 description: PDF report generated successfully. 264 content: 265 application/pdf: 266 schema: 267 type: string 268 format: binary 269 400: 270 description: Parameter validation error. 271 500: 272 description: Internal server error. 273 """ 274 try: 275 date_from_str = request.args.get('date_from') 276 date_to_str = request.args.get('date_to') 277 worker_id = request.args.get('pracownik_id', type=int) 278 279 show_invalid = 'wejscia_niepoprawne' in request.args 280 show_valid = 'wejscia_poprawne' in request.args 281 282 date_from = None 283 date_to = None 284 285 if date_from_str: 286 try: 287 date_from = datetime.fromisoformat(date_from_str) 288 except ValueError: 289 return jsonify({'error': 'Nieprawidłowy format date_from'}), 400 290 291 if date_to_str: 292 try: 293 date_to = datetime.fromisoformat(date_to_str) 294 # Jeśli podano samą datę (format YYYY-MM-DD, bez godziny), ustawiamy czas na koniec dnia, 295 # aby uwzględnić wszystkie wpisy z tego dnia (domyślnie fromisoformat ustawia 00:00:00). 296 if len(date_to_str) == 10: 297 date_to = date_to.replace(hour=23, minute=59, second=59, microsecond=999999) 298 except ValueError: 299 return jsonify({'error': 'Nieprawidłowy format date_to'}), 400 300 301 # --- 2. Pobranie danych --- 302 results = get_report_data( 303 date_from=date_from, 304 date_to=date_to, 305 worker_id=worker_id, 306 show_valid=show_valid, 307 show_invalid=show_invalid 308 ) 309 310 # --- 3. Przygotowanie danych dla szablonu HTML (z użyciem funkcji pomocniczej) --- 311 data, statistics = _calculate_statistics(results) 312 313 report_data = [] 314 for item in data: 315 # Konwersja obrazka na Base64 (dla HTML) 316 img_b64 = None 317 if item['face_image_bytes']: 318 img_b64 = base64.b64encode(item['face_image_bytes']).decode('utf-8') 319 320 report_data.append({ 321 'id': item['id'], 322 'date': item['date'].strftime('%Y-%m-%d %H:%M:%S'), 323 'code': item['code'], 324 'message': item['message'], 325 'worker_id': item['worker_id'], 326 'worker_name': item['worker_name'], 327 'face_image_b64': img_b64 328 }) 329 330 font_path = os.path.join(current_app.config['STATIC_FOLDER'], 'fonts', 'Roboto-Regular.ttf') 331 332 context = { 333 'data': report_data, 334 'count': statistics['total_entries'], 335 'statistics': statistics, 336 'generation_date': datetime.now().strftime('%Y-%m-%d %H:%M'), 337 'filters': { 338 'date_from': date_from_str, 339 'date_to': date_to_str, 340 'worker_id': worker_id, 341 'show_valid': show_valid, 342 'show_invalid': show_invalid 343 }, 344 'font_path': font_path 345 } 346 347 # --- 4. Renderowanie HTML i konwersja na PDF --- 348 html_content = render_template('reports/report_pdf.html', **context) 349 350 pdf_output = io.BytesIO() 351 352 # On windows, file paths to the font break 353 # known issue, applying a monkeypatch 354 # https://github.com/xhtml2pdf/xhtml2pdf/issues/623#issuecomment-1372719452 355 pisaFileObject.getNamedFile = lambda self: self.uri 356 pisa_status = pisa.CreatePDF( 357 io.BytesIO(html_content.encode('utf-8')), 358 dest=pdf_output, 359 encoding='utf-8' 360 ) 361 362 if pisa_status.err: 363 return jsonify({'error': 'Błąd podczas generowania PDF'}), 500 364 365 pdf_output.seek(0) 366 367 return send_file( 368 pdf_output, 369 mimetype='application/pdf', 370 as_attachment=True, 371 download_name=f"raport_{datetime.now().strftime('%Y%m%d_%H%M')}.pdf" 372 ) 373 374 except Exception as e: 375 print(f"Błąd generowania PDF: {e}") 376 return jsonify({'error': str(e)}), 500
Generates a report in PDF format based on provided filters.
Retrieves exactly the same parameters as the JSON version.
Parameters:
date_from(str): Start date of the range (format YYYY-MM-DD or ISO). Optional.date_to(str): End date of the range (format YYYY-MM-DD or ISO). Optional.pracownik_id(int): Worker ID to filter by. Optional.wejscia_niepoprawne(bool): Flag - if present, includes invalid entries.wejscia_poprawne(bool): Flag - if present, includes valid entries.
Returns:
application/pdf- A downloadable PDF file.
tags:
- Reports parameters:
- name: date_from in: query type: string required: false description: Start date of the range (format YYYY-MM-DD or ISO).
- name: date_to in: query type: string required: false description: End date of the range (format YYYY-MM-DD or ISO).
- name: pracownik_id in: query type: integer required: false description: Worker ID to filter by.
- name: wejscia_niepoprawne in: query type: boolean required: false allowEmptyValue: true description: Flag - if present, includes invalid entries.
- name: wejscia_poprawne in: query type: boolean required: false allowEmptyValue: true description: Flag - if present, includes valid entries. responses: 200: description: PDF report generated successfully. content: application/pdf: schema: type: string format: binary 400: description: Parameter validation error. 500: description: Internal server error.