import 'dart:typed_data'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart' show MediaType; import '../models/work_day.dart'; import '../models/work_interval.dart'; import '../models/month_start.dart'; import '../utils/helpers.dart'; class BookingApi { final http.Client client; BookingApi({required this.client}); /// Monatliche Buchungen holen (für YYYY-MM) Future> getBookingList(DateTime monthStart) async { final y = monthStart.year.toString().padLeft(4, '0'); final m = monthStart.month.toString().padLeft(2, '0'); final uri = Uri.parse( 'https://api.windesign.at/workinghours.php?module=booking&function=getList&date=$y-$m', ); final res = await client.get(uri); if (res.statusCode != 200) { throw Exception('booking/getList failed: ${res.statusCode} ${res.body}'); } final map = jsonDecode(res.body) as Map; if (map['error'] == true) { throw Exception('booking/getList error: ${map['errmsg']}'); } final list = (map['bookings'] as List?) ?? const []; final items = []; for (final e in list) { final row = e as Map; final dayStr = (row['bookingDay'] as String?) ?? ''; final date = DateTime.tryParse(dayStr); if (date == null) continue; String? code = (row['code'] as String?)?.trim(); if (code != null && code.isEmpty) code = null; final starts = [ row['come1'] as String?, row['come2'] as String?, row['come3'] as String?, row['come4'] as String?, row['come5'] as String?, ]; final ends = [ row['leave1'] as String?, row['leave2'] as String?, row['leave3'] as String?, row['leave4'] as String?, row['leave5'] as String?, ]; final intervals = []; for (int i = 0; i < 5; i++) { final s = parseDbTime(starts[i]); final e2 = parseDbTime(ends[i]); if (s != null && e2 != null) { intervals.add(WorkInterval(s, e2)); } } items.add(WorkDay( date: DateTime(date.year, date.month, date.day), intervals: intervals, targetMinutes: 0, // wird im UI ersetzt code: code, )); } return items; } /// Monatliche Startdaten (Startsaldo, Vacation, Overtime, Correction) Future getMonthStart(DateTime monthStart) async { final y = monthStart.year.toString().padLeft(4, '0'); final m = monthStart.month.toString().padLeft(2, '0'); final uri = Uri.parse( 'https://api.windesign.at/workinghours.php' '?module=monthlybooking&function=getList&date=$y-$m-01', ); final res = await client.get(uri); if (res.statusCode != 200) { throw Exception( 'monthlybooking/getList failed: ${res.statusCode} ${res.body}'); } final map = jsonDecode(res.body) as Map; if (map['error'] == true) { throw Exception('monthlybooking/getList error: ${map['errmsg']}'); } return MonthStart.fromJson(map); } /// Einen Tag speichern Future saveDay(WorkDay day) async { final y = day.date.year.toString().padLeft(4, '0'); final m = day.date.month.toString().padLeft(2, '0'); final d = day.date.day.toString().padLeft(2, '0'); final dateStr = '$y-$m-$d'; final starts = List.filled(5, null); final ends = List.filled(5, null); for (int i = 0; i < day.intervals.length && i < 5; i++) { starts[i] = fmtTimeOfDay(day.intervals[i].start); ends[i] = fmtTimeOfDay(day.intervals[i].end); } final lockCodes = {'G', 'U', 'SU', 'K'}; final payload = { 'module': 'booking', 'function': 'saveDay', 'date': dateStr, 'code': day.code, 'come1': lockCodes.contains(day.code) ? null : starts[0], 'leave1': lockCodes.contains(day.code) ? null : ends[0], 'come2': lockCodes.contains(day.code) ? null : starts[1], 'leave2': lockCodes.contains(day.code) ? null : ends[1], 'come3': lockCodes.contains(day.code) ? null : starts[2], 'leave3': lockCodes.contains(day.code) ? null : ends[2], 'come4': lockCodes.contains(day.code) ? null : starts[3], 'leave4': lockCodes.contains(day.code) ? null : ends[3], 'come5': lockCodes.contains(day.code) ? null : starts[4], 'leave5': lockCodes.contains(day.code) ? null : ends[4], }; final uri = Uri.parse('https://api.windesign.at/workinghours.php'); final res = await client.post( uri, headers: {'Content-Type': 'application/json'}, body: jsonEncode(payload), ); if (res.statusCode != 200) { throw Exception('booking/saveDay failed: ${res.statusCode} ${res.body}'); } final map = jsonDecode(res.body) as Map; if (map['error'] == true) { throw Exception('booking/saveDay error: ${map['errmsg']}'); } } /// Startwerte für einen Monat speichern (Upsert auf monthlybooking). Future saveMonthStart( DateTime monthStart, { required int starthours, required int startvacation, int overtime = 0, int correction = 0, }) async { final y = monthStart.year.toString().padLeft(4, '0'); final m = monthStart.month.toString().padLeft(2, '0'); final d = '01'; final uri = Uri.parse('https://api.windesign.at/workinghours.php'); final payload = { 'module': 'monthlybooking', 'function': 'saveStart', 'date': '$y-$m-$d', 'starthours': starthours, 'startvacation': startvacation, 'overtime': overtime, 'correction': correction, }; final res = await client.post( uri, headers: {'Content-Type': 'application/json'}, body: jsonEncode(payload), ); if (res.statusCode != 200) { throw Exception( 'monthlybooking/saveStart failed: ${res.statusCode} ${res.body}'); } final map = jsonDecode(res.body) as Map; if (map['error'] == true) { throw Exception('monthlybooking/saveStart error: ${map['errmsg']}'); } } // -------------------- PDF (salery / timesheet) -------------------- Future hasMonthlyPdf({ required String date, // "YYYY-MM-01" (oder YYYY-MM) required String type, // "salery" | "timesheet" }) async { final uri = Uri.parse('https://api.windesign.at/workinghours.php'); final res = await client.post( uri, headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'module': 'monthlybooking', 'function': 'hasDoc', 'date': date, 'doctype': type, }), ); if (res.statusCode != 200) { throw Exception( 'monthlybooking/hasDoc failed: ${res.statusCode} ${res.body}'); } final map = jsonDecode(res.body) as Map; if (map['error'] == true) { throw Exception('monthlybooking/hasDoc error: ${map['errmsg']}'); } return (map['exists'] as bool?) ?? false; } Future getMonthlyPdf({ required String date, // "YYYY-MM-01" required String type, // "salery" | "timesheet" }) async { final ymd = date.length == 7 ? '$date-01' : date; final uri = Uri.parse( 'https://api.windesign.at/workinghours.php?module=monthlybooking&function=getDoc&date=$ymd&doctype=$type', ); final res = await client.get(uri); if (res.statusCode == 200) { return res.bodyBytes; } // Versuch, JSON-Fehler zu lesen try { final map = jsonDecode(utf8.decode(res.bodyBytes)) as Map; final msg = map['errmsg'] ?? res.reasonPhrase ?? 'Unknown'; throw Exception('getDoc error: $msg'); } catch (_) { throw Exception('getDoc failed: ${res.statusCode}'); } } Future uploadMonthlyPdf({ required String date, // "YYYY-MM-01" required String type, // "salery" | "timesheet" required Uint8List bytes, String filename = 'document.pdf', }) async { final ymd = date.length == 7 ? '$date-01' : date; // Wichtig: POST auf Basis-URL (ohne module/function in der Query!) final uri = Uri.parse('https://api.windesign.at/workinghours.php'); final request = http.MultipartRequest('POST', uri) // Router-Parameter als Felder mitsenden: ..fields['module'] = 'monthlybooking' ..fields['function'] = 'saveDoc' // oder 'replaceDoc', je nach Server ..fields['date'] = ymd ..fields['doctype'] = type; // achte: 'doctype' vs. 'kind' – wie am Server request.files.add( http.MultipartFile.fromBytes( 'file', bytes, filename: filename, contentType: MediaType('application', 'pdf'), ), ); final streamed = await request.send(); final res = await http.Response.fromStream(streamed); if (res.statusCode != 200) { throw Exception( 'monthlybooking/saveDoc failed: ${res.statusCode} ${res.body}'); } final map = jsonDecode(res.body) as Map; if (map['error'] == true) { throw Exception('monthlybooking/saveDoc error: ${map['errmsg']}'); } } Future deleteMonthlyPdf({ required String date, // "YYYY-MM-01" required String type, // "salery" | "timesheet" }) async { final ymd = date.length == 7 ? '$date-01' : date; final uri = Uri.parse('https://api.windesign.at/workinghours.php'); final res = await client.post( uri, headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'module': 'monthlybooking', 'function': 'deleteDoc', 'date': ymd, 'doctype': type, }), ); if (res.statusCode != 200) { throw Exception( 'monthlybooking/deleteDoc failed: ${res.statusCode} ${res.body}'); } final map = jsonDecode(res.body) as Map; if (map['error'] == true) { throw Exception('monthlybooking/deleteDoc error: ${map['errmsg']}'); } } }