This commit is contained in:
2025-10-11 11:30:12 +02:00
parent f679ea672b
commit 54736fa680
612 changed files with 25819 additions and 582 deletions
+113 -7
View File
@@ -1,6 +1,8 @@
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';
@@ -58,7 +60,7 @@ class BookingApi {
items.add(WorkDay(
date: DateTime(date.year, date.month, date.day),
intervals: intervals,
targetMinutes: 0, // wird im UI mit Tagesplan/Feiertag ersetzt
targetMinutes: 0, // wird im UI ersetzt
code: code,
));
}
@@ -85,14 +87,13 @@ class BookingApi {
return MonthStart.fromJson(map);
}
/// Einen Tag speichern: date (YYYY-MM-DD), code, come1..leave5 ("HH:mm" oder null/leer)
/// 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';
// Intervalle in 5 Slots abbilden
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++) {
@@ -100,13 +101,12 @@ class BookingApi {
ends[i] = fmtTimeOfDay(day.intervals[i].end);
}
// Codes, die Zeiten serverseitig leeren
final lockCodes = {'G', 'U', 'SU', 'K'};
final payload = <String, dynamic>{
'module': 'booking',
'function': 'saveDay',
'date': dateStr,
'code': day.code, // null → wird als NULL gespeichert
'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],
@@ -136,7 +136,6 @@ class BookingApi {
}
/// Startwerte für einen Monat speichern (Upsert auf monthlybooking).
/// `monthStart` = 1. des Monats.
Future<void> saveMonthStart(
DateTime monthStart, {
required int starthours,
@@ -146,7 +145,7 @@ class BookingApi {
}) async {
final y = monthStart.year.toString().padLeft(4, '0');
final m = monthStart.month.toString().padLeft(2, '0');
final d = '01'; // normalize
final d = '01';
final uri = Uri.parse('https://api.windesign.at/workinghours.php');
final payload = {
@@ -174,4 +173,111 @@ class BookingApi {
}
}
// -------------------- 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;
final uri = Uri.parse(
'https://api.windesign.at/workinghours.php?module=monthlybooking&function=saveDoc',
);
final request = http.MultipartRequest('POST', uri)
..fields['date'] = ymd
..fields['doctype'] = type;
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']}');
}
}
}