Files
lixi e186ba3da9 新增:健康事件时间线接入真实数据(T2-14 时间线半边)
- 时间线页:occurred_at DESC cursor 分页 + 月分组组头,四态齐备;
  六类事件经 RecordTypeDot 映射(增补喂养/洗护/测量三型,色族复用
  已审计色对),类型 TagPill 双通道
- 事件录入表单:六类类型选择器、金额以元录入/整数分传输(money.dart
  换算)、UTC 时间戳约定与体重表单一致
- 事件编辑页:顶层 PATCH 差量提交(title/notes/amountCents + version),
  40902 照 T2-12 模式自动经时间线检索取新 version 重提(保留输入)
- 档案页:月度花费卡接 summary.monthlyExpense(分→元展示)、tz 透传
  设备时区固定偏移(T2-13 遗留③)、健康时间线导航入口
- 埋点:health_record 域 create 三事件 + viewed(recordType=health_event)
  挂通;新增 edit_succeeded/failed 封装(failureReason 含 conflict)
- 测试 224 → 250 全绿(+26);analyze 0 问题;format 无 diff

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-08 13:49:48 +08:00

336 lines
9.7 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// pets 域测试样本(契约 openapi.yaml v1.2.0 各 schema 全字段 JSON
/// 与共享假仓库。
library;
import 'package:patbond_flutter/features/pets/pet_models.dart';
import 'package:patbond_flutter/features/pets/pets_repository.dart';
Map<String, Object?> okListEnvelope(List<Object?> data) => {
'code': 0,
'message': 'ok',
'data': data,
};
Map<String, dynamic> samplePetJson({
Map<String, Object?> overrides = const {},
}) => {
'id': 'p-1',
'name': '豆豆',
'species': 'dog',
'breedId': 'b-1',
'breedDisplayName': '柴犬',
'customBreedName': null,
'sex': 'male',
'birthDate': '2024-03-15',
'birthDateEstimated': false,
'personality': null,
'microchipNo': null,
'sterilizedOn': null,
'status': 'active',
'myRole': 'owner',
'createdAt': '2026-09-01T10:00:00+08:00',
'updatedAt': '2026-09-02T10:00:00+08:00',
'version': 3,
...overrides,
};
Map<String, dynamic> sampleWeightJson() => {
'id': 'w-1',
'petId': 'p-1',
'weightKg': 4.35,
'measuredAt': '2026-09-07T09:00:00+08:00',
'source': 'manual',
'note': null,
'createdAt': '2026-09-07T09:01:00+08:00',
};
Map<String, dynamic> sampleVaccinationJson() => {
'id': 'vx-1',
'petId': 'p-1',
'vaccineId': 'v-1',
'vaccineName': '狂犬疫苗',
'seriesKey': 'rabies-initial',
'doseNo': 1,
'doseLabel': null,
'status': 'scheduled',
'plannedOn': '2026-10-01',
'administeredOn': null,
'nextDueOn': null,
'manufacturer': null,
'batchNo': null,
'notes': null,
'createdAt': '2026-09-01T10:00:00+08:00',
'updatedAt': '2026-09-01T10:00:00+08:00',
'version': 1,
};
Map<String, dynamic> sampleHealthEventJson() => {
'id': 'e-1',
'petId': 'p-1',
'eventType': 'medical',
'occurredAt': '2026-09-05T14:00:00+08:00',
'title': '皮肤检查',
'notes': null,
'amountCents': 12850,
'createdByUserId': 'u-1',
'createdAt': '2026-09-05T14:05:00+08:00',
'updatedAt': '2026-09-05T14:05:00+08:00',
'version': 1,
};
Map<String, dynamic> sampleCareReminderJson() => {
'id': 'r-1',
'petId': 'p-1',
'reminderType': 'checkup',
'title': '年度体检',
'dueAt': '2026-10-01T00:00:00+08:00',
'status': 'pending',
'completedAt': null,
'createdAt': '2026-09-01T10:00:00+08:00',
'updatedAt': '2026-09-01T10:00:00+08:00',
};
Map<String, dynamic> samplePetSummaryJson() => {
'petId': 'p-1',
'latestWeight': {'weightKg': 4.35, 'measuredAt': '2026-09-07T09:00:00+08:00'},
'vaccinationProgress': {'completedDoses': 2, 'totalDoses': 3},
'nextVaccination': {
'vaccinationId': 'vx-3',
'vaccineId': 'v-1',
'vaccineName': '狂犬疫苗',
'doseNo': 3,
'doseLabel': null,
'dueOn': '2026-08-01',
'source': 'planned',
},
'monthlyExpense': {
'month': '2026-09',
'timezone': 'Asia/Shanghai',
'amountCents': 12850,
},
};
Map<String, dynamic> sampleBreedJson({
String id = 'b-1',
String species = 'dog',
String code = 'shiba',
String displayName = '柴犬',
}) => {'id': id, 'species': species, 'code': code, 'displayName': displayName};
/// 快速构造 Petwidget/controller 测试共用)。
Pet buildPet(
String id, {
String name = '豆豆',
int version = 1,
Map<String, Object?> overrides = const {},
}) => Pet.fromJson(
samplePetJson(
overrides: {'id': id, 'name': name, 'version': version, ...overrides},
),
);
Map<String, dynamic> sampleVaccineCatalogJson({
String id = 'v-1',
String code = 'rabies',
String name = '狂犬疫苗',
String species = 'dog',
}) => {
'id': id,
'code': code,
'name': name,
'species': species,
'description': null,
};
/// 快速构造体重记录(widget 测试共用)。
WeightRecord buildWeight(
String id, {
double weightKg = 4.35,
String measuredAt = '2026-09-07T09:00:00+08:00',
Map<String, Object?> overrides = const {},
}) => WeightRecord.fromJson({
...sampleWeightJson(),
'id': id,
'weightKg': weightKg,
'measuredAt': measuredAt,
...overrides,
});
/// 快速构造疫苗记录。
Vaccination buildVaccination(
String id, {
Map<String, Object?> overrides = const {},
}) =>
Vaccination.fromJson({...sampleVaccinationJson(), 'id': id, ...overrides});
/// 快速构造健康事件。
HealthEvent buildHealthEvent(
String id, {
Map<String, Object?> overrides = const {},
}) =>
HealthEvent.fromJson({...sampleHealthEventJson(), 'id': id, ...overrides});
/// 快速构造照护提醒。
CareReminder buildReminder(
String id, {
Map<String, Object?> overrides = const {},
}) => CareReminder.fromJson({
...sampleCareReminderJson(),
'id': id,
...overrides,
});
/// 快速构造摘要(缺省为「三聚合齐备」样本;overrides 可置 null 验证空态)。
PetSummary buildSummary({Map<String, Object?> overrides = const {}}) =>
PetSummary.fromJson({...samplePetSummaryJson(), ...overrides});
/// 假仓库:各方法可注入行为;listPets 默认空列表,其余未注入的方法抛
/// UnimplementedError22 号报告 §7widget 测试注入假仓库的共享实现)。
class FakePetsRepository implements PetsRepository {
Future<List<Pet>> Function()? listPetsHandler;
Future<Pet> Function(CreatePetRequest)? createPetHandler;
Future<Pet> Function(String)? getPetHandler;
Future<Pet> Function(String, UpdatePetRequest)? updatePetHandler;
Future<List<Breed>> Function(PetSpecies?)? listBreedsHandler;
Future<CursorPage<WeightRecord>> Function(String, int?, String?)?
listWeightsHandler;
Future<WeightRecord> Function(String, CreateWeightRequest)?
createWeightHandler;
Future<List<VaccineCatalogItem>> Function(PetSpecies?)?
listVaccineCatalogHandler;
Future<List<Vaccination>> Function(String)? listVaccinationsHandler;
Future<Vaccination> Function(String, CreateVaccinationRequest)?
createVaccinationHandler;
Future<Vaccination> Function(String, UpdateVaccinationRequest)?
updateVaccinationHandler;
Future<CursorPage<HealthEvent>> Function(String, int?, String?)?
listHealthEventsHandler;
Future<HealthEvent> Function(String, CreateHealthEventRequest)?
createHealthEventHandler;
Future<HealthEvent> Function(String, UpdateHealthEventRequest)?
updateHealthEventHandler;
Future<List<CareReminder>> Function(String, CareReminderStatus?)?
listCareRemindersHandler;
Future<CareReminder> Function(String, CreateCareReminderRequest)?
createCareReminderHandler;
Future<CareReminder> Function(String, UpdateCareReminderRequest)?
updateCareReminderHandler;
Future<PetSummary> Function(String, String?)? getPetSummaryHandler;
@override
Future<List<Pet>> listPets() =>
listPetsHandler?.call() ?? Future.value(const []);
@override
Future<Pet> createPet(CreatePetRequest request) => createPetHandler!(request);
@override
Future<Pet> getPet(String petId) => getPetHandler!(petId);
@override
Future<Pet> updatePet(String petId, UpdatePetRequest request) =>
updatePetHandler!(petId, request);
@override
Future<List<Breed>> listBreeds({PetSpecies? species}) =>
listBreedsHandler?.call(species) ??
Future.value([Breed.fromJson(sampleBreedJson())]);
@override
Future<CursorPage<WeightRecord>> listWeights(
String petId, {
int? limit,
String? cursor,
}) => listWeightsHandler!(petId, limit, cursor);
@override
Future<WeightRecord> createWeight(
String petId,
CreateWeightRequest request,
) => createWeightHandler!(petId, request);
@override
Future<List<VaccineCatalogItem>> listVaccineCatalog({PetSpecies? species}) =>
listVaccineCatalogHandler?.call(species) ??
Future.value([VaccineCatalogItem.fromJson(sampleVaccineCatalogJson())]);
@override
Future<List<Vaccination>> listVaccinations(String petId) =>
listVaccinationsHandler!(petId);
@override
Future<Vaccination> createVaccination(
String petId,
CreateVaccinationRequest request,
) => createVaccinationHandler!(petId, request);
@override
Future<Vaccination> updateVaccination(
String vaccinationId,
UpdateVaccinationRequest request,
) => updateVaccinationHandler!(vaccinationId, request);
@override
Future<CursorPage<HealthEvent>> listHealthEvents(
String petId, {
int? limit,
String? cursor,
}) => listHealthEventsHandler!(petId, limit, cursor);
@override
Future<HealthEvent> createHealthEvent(
String petId,
CreateHealthEventRequest request,
) => createHealthEventHandler!(petId, request);
@override
Future<HealthEvent> updateHealthEvent(
String eventId,
UpdateHealthEventRequest request,
) => updateHealthEventHandler!(eventId, request);
@override
Future<List<CareReminder>> listCareReminders(
String petId, {
CareReminderStatus? status,
}) =>
listCareRemindersHandler?.call(petId, status) ??
// 缺省空列表:既有详情页测试不必逐个注入。
Future.value(const []);
@override
Future<CareReminder> createCareReminder(
String petId,
CreateCareReminderRequest request,
) => createCareReminderHandler!(petId, request);
@override
Future<CareReminder> updateCareReminder(
String reminderId,
UpdateCareReminderRequest request,
) => updateCareReminderHandler!(reminderId, request);
@override
Future<PetSummary> getPetSummary(String petId, {String? tz}) =>
getPetSummaryHandler?.call(petId, tz) ??
// 缺省给「无任何记录」空摘要(契约 null 语义),既有详情页测试
// 不必逐个注入。
Future.value(
PetSummary.fromJson({
'petId': petId,
'latestWeight': null,
'vaccinationProgress': null,
'nextVaccination': null,
'monthlyExpense': {
'month': '2026-09',
'timezone': 'UTC',
'amountCents': 0,
},
}),
);
@override
dynamic noSuchMethod(Invocation invocation) =>
throw UnimplementedError('${invocation.memberName}');
}