You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

301 lines
9.9 KiB
Dart

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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<List<WorkDay>> 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<String, dynamic>;
if (map['error'] == true) {
throw Exception('booking/getList error: ${map['errmsg']}');
}
final list = (map['bookings'] as List?) ?? const [];
final items = <WorkDay>[];
for (final e in list) {
final row = e as Map<String, dynamic>;
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 = <String?>[
row['come1'] as String?,
row['come2'] as String?,
row['come3'] as String?,
row['come4'] as String?,
row['come5'] as String?,
];
final ends = <String?>[
row['leave1'] as String?,
row['leave2'] as String?,
row['leave3'] as String?,
row['leave4'] as String?,
row['leave5'] as String?,
];
final intervals = <WorkInterval>[];
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<MonthStart> 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<String, dynamic>;
if (map['error'] == true) {
throw Exception('monthlybooking/getList error: ${map['errmsg']}');
}
return MonthStart.fromJson(map);
}
/// Einen Tag speichern
Future<void> 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<String?>.filled(5, null);
final ends = List<String?>.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 = <String, dynamic>{
'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<String, dynamic>;
if (map['error'] == true) {
throw Exception('booking/saveDay error: ${map['errmsg']}');
}
}
/// Startwerte für einen Monat speichern (Upsert auf monthlybooking).
Future<void> 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<String, dynamic>;
if (map['error'] == true) {
throw Exception('monthlybooking/saveStart error: ${map['errmsg']}');
}
}
// -------------------- PDF (salery / timesheet) --------------------
Future<bool> 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<String, dynamic>;
if (map['error'] == true) {
throw Exception('monthlybooking/hasDoc error: ${map['errmsg']}');
}
return (map['exists'] as bool?) ?? false;
}
Future<Uint8List> 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<String, dynamic>;
final msg = map['errmsg'] ?? res.reasonPhrase ?? 'Unknown';
throw Exception('getDoc error: $msg');
} catch (_) {
throw Exception('getDoc failed: ${res.statusCode}');
}
}
Future<void> 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<String, dynamic>;
if (map['error'] == true) {
throw Exception('monthlybooking/saveDoc error: ${map['errmsg']}');
}
}
Future<void> 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<String, dynamic>;
if (map['error'] == true) {
throw Exception('monthlybooking/deleteDoc error: ${map['errmsg']}');
}
}
}