256 lines
8.4 KiB
Dart
256 lines
8.4 KiB
Dart
import '../../services/generic_api_service.dart';
|
|
// import '../auth_manager.dart';
|
|
import 'house_note_model.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
import 'dart:io';
|
|
import 'package:http/http.dart' as http;
|
|
import 'dart:convert'; // 補上這行,json.decode 才能運作
|
|
// import 'package:device_info_plus/device_info_plus.dart'; // [修正] 補上裝置資訊引用
|
|
import 'package:shared_preferences/shared_preferences.dart'; // [新增]
|
|
|
|
class HouseNoteApiService {
|
|
final GenericApiService _apiService = GenericApiService();
|
|
static String? _cachedUserId;
|
|
|
|
// 替代 _getDeviceId 的方案:獲取本地生成的唯一 User ID
|
|
Future<String> _getAppUserId() async {
|
|
if (_cachedUserId != null) return _cachedUserId!;
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
// 嘗試從本地記憶體讀取舊有的 ID
|
|
String? localId = prefs.getString('app_user_unique_id');
|
|
|
|
if (localId == null) {
|
|
// 第一次安裝,產生一個新的隨機 ID
|
|
localId = const Uuid().v4(); //
|
|
await prefs.setString('app_user_unique_id', localId);
|
|
}
|
|
|
|
_cachedUserId = localId;
|
|
return localId;
|
|
}
|
|
|
|
// 獲取所有房屋列表
|
|
Future<List<House>> fetchHouses() async {
|
|
String userId = await _getAppUserId();
|
|
// 格式:當前頁^每頁筆數^排序欄位^*^^^過濾欄位^過濾值
|
|
// 我們在 queryFilter 的最後方加上 device_id 的過濾條件
|
|
String queryFilter = "1^100^created_at^*^^^device_id^$userId";
|
|
|
|
// 依據建立時間降冪排序
|
|
// String queryFilter = "1^100^created_at^*^^^";
|
|
return await _apiService.fetchList<House>(
|
|
tableName: "hhp_houses",
|
|
pk: "id",
|
|
queryFilter: queryFilter,
|
|
fromJson: (json) => House.fromJson(json),
|
|
);
|
|
}
|
|
|
|
// 創建新物件 (UUID 在前端生成)
|
|
Future<List<House>> createHouse(House house) async {
|
|
String userId = await _getAppUserId();
|
|
|
|
final Map<String, dynamic> data = {
|
|
"id": house.id,
|
|
"device_id": userId, // 【關鍵】寫入手機識別碼
|
|
"title": house.title,
|
|
"address": house.address,
|
|
"latitude": house.latitude,
|
|
"longitude": house.longitude,
|
|
"total_price": house.totalPrice,
|
|
"total_ping": house.totalPing,
|
|
"property_type": house.propertyType,
|
|
"age": house.age,
|
|
"summary_notes": house.summaryNotes,
|
|
"created_at": DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now()),
|
|
};
|
|
|
|
return await _apiService.fetchList<House>(
|
|
tableName: "hhp_houses",
|
|
pk: "id",
|
|
queryFilter: "", // 【關鍵修正】補上必填參數,傳入空字串即可
|
|
action: "C",
|
|
data: data,
|
|
fromJson: (json) => House.fromJson(json),
|
|
);
|
|
}
|
|
|
|
// 獲取預設檢查清單
|
|
Future<List<CheckItem>> fetchDefaultCheckItems() async {
|
|
return await _apiService.fetchList<CheckItem>(
|
|
tableName: "hhp_check_items",
|
|
pk: "id",
|
|
queryFilter: "1^200^sort_order^*^^^is_default^1",
|
|
fromJson: (json) => CheckItem.fromJson(json),
|
|
);
|
|
}
|
|
|
|
// 上傳照片檔案 (參考 expense_api)
|
|
Future<String?> uploadImage(File imageFile) async {
|
|
try {
|
|
final request = http.MultipartRequest(
|
|
'POST',
|
|
Uri.parse("${GenericApiService.BASE_IP}/upload/house_hunter/images")
|
|
);
|
|
// 取得本地檔案名稱
|
|
String picFileName = "${GenericApiService.BASE_IP}/upload/house_hunter/images/${imageFile.path.split('/').last}";
|
|
|
|
request.files.add(await http.MultipartFile.fromPath('file', imageFile.path));
|
|
|
|
final streamedResponse = await request.send();
|
|
final response = await http.Response.fromStream(streamedResponse);
|
|
|
|
if (response.statusCode == 200) {
|
|
// final data = json.decode(response.body);
|
|
// return data['fileName'];
|
|
return picFileName; // 這是回傳後端產生的檔名
|
|
}
|
|
} catch (e) {
|
|
print("圖片上傳異常: $e");
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// 儲存照片關聯資訊
|
|
Future<bool> saveHousePhoto(String houseId, String fileName, String note) async {
|
|
final Map<String, dynamic> data = {
|
|
"id": const Uuid().v4(),
|
|
"house_id": houseId,
|
|
"file_path": fileName,
|
|
"description": note,
|
|
"created_at": DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now()),
|
|
};
|
|
|
|
final result = await _apiService.fetchList<dynamic>(
|
|
tableName: "hhp_house_photos",
|
|
pk: "id",
|
|
queryFilter: "",
|
|
action: "C",
|
|
data: data,
|
|
fromJson: (json) => json,
|
|
);
|
|
return result.isNotEmpty;
|
|
}
|
|
|
|
// 獲取特定房屋的所有評估紀錄 (需 Join 項目名稱)
|
|
Future<List<HouseEvaluation>> fetchEvaluations(String houseId) async {
|
|
// 這裡假設後端會處理 Join hhp_check_items 取得名稱
|
|
String queryFilter = "1^100^id^*^^hhpm02^house_id^$houseId";
|
|
return await _apiService.fetchList<HouseEvaluation>(
|
|
tableName: "hhp_house_evaluations",
|
|
pk: "id",
|
|
queryFilter: queryFilter,
|
|
fromJson: (json) => HouseEvaluation.fromJson(json),
|
|
);
|
|
}
|
|
|
|
// 獲取特定房屋的所有照片
|
|
Future<List<HousePhoto>> fetchPhotos(String houseId) async {
|
|
String queryFilter = "1^100^created_at^*^^^house_id^$houseId";
|
|
return await _apiService.fetchList<HousePhoto>(
|
|
tableName: "hhp_house_photos",
|
|
pk: "id",
|
|
queryFilter: queryFilter,
|
|
fromJson: (json) => HousePhoto.fromJson(json),
|
|
);
|
|
}
|
|
|
|
// house_note_api.dart 新增方法
|
|
|
|
// 1. 獲取所有可用標籤庫 (hhp_tags)
|
|
Future<List<Tag>> fetchAllTags() async {
|
|
return await _apiService.fetchList<Tag>(
|
|
tableName: "hhp_tags",
|
|
pk: "id",
|
|
queryFilter: "1^100^id^*^^^",
|
|
fromJson: (json) => Tag.fromJson(json),
|
|
);
|
|
}
|
|
|
|
// 2. 獲取特定房屋已選取的標籤 ID 清單 (hhp_house_tags)
|
|
Future<List<String>> fetchHouseTagIds(String houseId) async {
|
|
final List<dynamic> result = await _apiService.fetchList<dynamic>(
|
|
tableName: "hhp_house_tags",
|
|
pk: "house_id", // 複合主鍵通常用其中一個,視 API 規範而定
|
|
queryFilter: "1^100^tag_id^*^^^house_id^$houseId",
|
|
fromJson: (json) => json,
|
|
);
|
|
return result.map((e) => e['tag_id'].toString()).toList();
|
|
}
|
|
|
|
// 3. 儲存/更新房屋標籤 (hhp_house_tags)
|
|
// 邏輯:通常是先刪除舊的再寫入新的,這裡簡化為寫入邏輯
|
|
Future<void> updateHouseTags(String houseId, List<String> selectedTagIds) async {
|
|
// A. 刪除該房屋舊有的所有標籤關聯 (假設 action 'D' 為刪除)
|
|
await _apiService.fetchList<dynamic>(
|
|
tableName: "hhp_house_tags",
|
|
pk: "house_id",
|
|
queryFilter: "house_id^$houseId",
|
|
action: "D",
|
|
fromJson: (json) => json,
|
|
);
|
|
|
|
// B. 逐一寫入新的標籤關聯
|
|
for (String tagId in selectedTagIds) {
|
|
await _apiService.fetchList<dynamic>(
|
|
tableName: "hhp_house_tags",
|
|
pk: "house_id",
|
|
queryFilter: "",
|
|
action: "C",
|
|
data: {
|
|
"house_id": houseId,
|
|
"tag_id": tagId,
|
|
},
|
|
fromJson: (json) => json,
|
|
);
|
|
}
|
|
}
|
|
|
|
// 1. 獲取所有檢查大類 (hhp_check_categories)
|
|
Future<List<CheckCategory>> fetchCategories() async {
|
|
return await _apiService.fetchList<CheckCategory>(
|
|
tableName: "hhp_check_categories",
|
|
pk: "id",
|
|
queryFilter: "1^100^sort_order^*^^^",
|
|
fromJson: (json) => CheckCategory.fromJson(json),
|
|
);
|
|
}
|
|
|
|
// 2. 儲存/更新評估紀錄
|
|
// 由於評估通常是多筆,我們這裡實作單筆提交,呼叫端可用迴圈或 Future.wait
|
|
Future<bool> saveSingleEvaluation(HouseEvaluation eval) async {
|
|
final Map<String, dynamic> data = {
|
|
"id": eval.id ?? const Uuid().v4(),
|
|
"house_id": eval.houseId,
|
|
"check_item_id": eval.checkItemId,
|
|
"score": eval.score,
|
|
"item_memo": eval.itemMemo ?? "",
|
|
"updated_at": DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now()),
|
|
};
|
|
|
|
final result = await _apiService.fetchList<dynamic>(
|
|
tableName: "hhp_house_evaluations",
|
|
pk: "id",
|
|
queryFilter: "",
|
|
action: "C", // 使用 Create/Update
|
|
data: data,
|
|
fromJson: (json) => json,
|
|
);
|
|
return result.isNotEmpty;
|
|
}
|
|
|
|
// 獲取所有檢查項目 (包含非預設項目,用於表單與名稱對應)
|
|
Future<List<CheckItem>> fetchAllCheckItems() async {
|
|
return await _apiService.fetchList<CheckItem>(
|
|
tableName: "hhp_check_items",
|
|
pk: "id",
|
|
// 撈取所有項目,以 sort_order 排序
|
|
queryFilter: "1^200^sort_order^*^^^",
|
|
fromJson: (json) => CheckItem.fromJson(json),
|
|
);
|
|
}
|
|
}
|
|
|