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.