|
|
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']}');
|
|
|
}
|
|
|
}
|
|
|
}
|