feat:消费机更新

feat:人员调用钱包部门不调动的bug
This commit is contained in:
dzc committed 2026-09-09 11:56:19 +08:00
1 parent 842402e364
commit 60d9380a65
144 files changed
+27832 -1666

No files matched your search

@@ -5,6 +5,7 @@ import 'package:pj_supermarket_cloud/common/index.dart';
// face
@dao
abstract class FaceDao {
/// 保存一条已完成文件和原生特征校验的人脸记录。
@insert
Future<int?> save(FaceEntity entity);
@@ -48,6 +49,59 @@ abstract class FaceDao {
""")
Future<int?> findCount(String userName);
/// 统计已完成质量检测且低于指定阈值的人脸数量,阈值不写入样本以支持即时重分类。
@Query('''
SELECT COUNT(0) FROM face
WHERE qualityStatus = 'detected'
AND qualityScore IS NOT NULL
AND qualityScore < :threshold
AND (:userName = '-1' OR userName LIKE '%' || :userName || '%')
''')
Future<int?> findLowQualityCount(String userName, double threshold);
/// 分页查询已检测且低于阈值的人脸,结果按本地自增编号稳定排序。
@Query('''
SELECT * FROM face
WHERE qualityStatus = 'detected'
AND qualityScore IS NOT NULL
AND qualityScore < :threshold
AND (:userName = '-1' OR userName LIKE '%' || :userName || '%')
ORDER BY id ASC
LIMIT :pageSize OFFSET :offset
''')
Future<List<FaceEntity>> findLowQuality(
String userName,
double threshold,
int pageSize,
int offset,
);
/// 查询全部已检测且低于阈值的人脸,用于一键删除前获取完整服务端删除集合。
@Query('''
SELECT * FROM face
WHERE qualityStatus = 'detected'
AND qualityScore IS NOT NULL
AND qualityScore < :threshold
ORDER BY id ASC
''')
Future<List<FaceEntity>> findAllLowQuality(double threshold);
/// 统计尚未完成质量检测的历史样本数量。
@Query("SELECT COUNT(0) FROM face WHERE qualityStatus = 'pending'")
Future<int?> findPendingQualityCount();
/// 统计最近一次质量检测失败的样本数量。
@Query("SELECT COUNT(0) FROM face WHERE qualityStatus = 'failed'")
Future<int?> findFailedQualityCount();
/// 查询待检测或失败后重试的本地样本,按编号稳定排序。
@Query("""
SELECT * FROM face
WHERE qualityStatus IN ('pending', 'failed')
ORDER BY id ASC
""")
Future<List<FaceEntity>> findQualityAuditCandidates();
@Query('''
SELECT * FROM face WHERE id = :id
''')
@@ -75,6 +129,12 @@ abstract class FaceDao {
int offset,
);
/// 查询本机全部人脸,用于完整部门快照计算集合差异。
@Query('''
SELECT * FROM face ORDER BY sampleId ASC
''')
Future<List<FaceEntity>> getAll();
@Query('''
SELECT DISTINCT a.deptId FROM face a WHERE a.deptId IS NOT NULL
''')
@@ -0,0 +1,40 @@
import 'package:floor/floor.dart';
import 'package:pj_supermarket_cloud/common/index.dart';
/// `file_upload_record` 本机文件上传状态的数据访问接口。
@dao
abstract class FileUploadRecordDao {
/// 保存文件状态并返回主键;相同主键时替换原记录以保留同一路径的最新状态。
@Insert(onConflict: OnConflictStrategy.replace)
Future<int> save(FileUploadRecordEntity entity);
/// 按主键更新已经存在的文件上传状态。
@update
Future<void> edit(FileUploadRecordEntity entity);
/// 查询指定设备与业务类型的全部文件状态,并按最近更新时间倒序返回。
@Query('''
SELECT * FROM file_upload_record
WHERE hardId = :hardId AND bizType = :bizType
ORDER BY updateTime DESC
''')
Future<List<FileUploadRecordEntity>> getAll(
String hardId,
String bizType,
);
/// 按设备、业务类型和相对路径查询唯一状态记录;不存在时返回空。
@Query('''
SELECT * FROM file_upload_record
WHERE hardId = :hardId
AND bizType = :bizType
AND relativePath = :relativePath
LIMIT 1
''')
Future<FileUploadRecordEntity?> getByPath(
String hardId,
String bizType,
String relativePath,
);
}
@@ -0,0 +1,131 @@
import 'package:floor/floor.dart';
import 'package:pj_supermarket_cloud/common/index.dart';
/// `order_evidence_upload_task` 本地补传队列的数据访问接口。
@dao
abstract class OrderEvidenceUploadTaskDao {
/// 保存任务并返回主键;同一主键冲突时以最新状态整体替换。
@Insert(onConflict: OnConflictStrategy.replace)
Future<int> save(OrderEvidenceUploadTaskEntity entity);
/// 按主键更新已经存在的任务,不改变证据位置唯一键。
@update
Future<void> edit(OrderEvidenceUploadTaskEntity entity);
/// 按订单号和证据位置查询唯一任务;不存在时返回空。
@Query('''
SELECT * FROM order_evidence_upload_task
WHERE androidOrderCode = :androidOrderCode
AND evidenceType = :evidenceType
AND evidenceRole = :evidenceRole
AND sequenceNo = :sequenceNo
LIMIT 1
''')
Future<OrderEvidenceUploadTaskEntity?> getByPosition(
String androidOrderCode,
String evidenceType,
String evidenceRole,
int sequenceNo,
);
/// 按服务端订单 ID 和证据位置查询预约核销任务;不存在时返回空。
@Query('''
SELECT * FROM order_evidence_upload_task
WHERE serverOrderId = :serverOrderId
AND evidenceType = :evidenceType
AND evidenceRole = :evidenceRole
AND sequenceNo = :sequenceNo
LIMIT 1
''')
Future<OrderEvidenceUploadTaskEntity?> getByServerOrderPosition(
String serverOrderId,
String evidenceType,
String evidenceRole,
int sequenceNo,
);
/// 查询同一终端订单的全部任务,按类型、角色和序号稳定排序。
@Query('''
SELECT * FROM order_evidence_upload_task
WHERE androidOrderCode = :androidOrderCode
ORDER BY evidenceType, evidenceRole, sequenceNo
''')
Future<List<OrderEvidenceUploadTaskEntity>> getByOrderCode(
String androidOrderCode,
);
/// 查询同一识别会话创建的参考图与现场图任务,用于支付前绑定订单或取消清理。
@Query('''
SELECT * FROM order_evidence_upload_task
WHERE recognitionSessionId = :recognitionSessionId
ORDER BY evidenceRole, sequenceNo
''')
Future<List<OrderEvidenceUploadTaskEntity>> getByRecognitionSession(
String recognitionSessionId,
);
/// 查询到达重试时间的活动任务,每轮最多由调用方传入 20 条并按创建时间串行处理。
@Query('''
SELECT * FROM order_evidence_upload_task
WHERE status IN ('WAITING_ORDER', 'READY', 'UPLOADING', 'RETRY_WAIT')
AND (androidOrderCode IS NOT NULL OR serverOrderId IS NOT NULL)
AND (nextRetryAt IS NULL OR nextRetryAt <= :now)
ORDER BY createTime ASC, id ASC
LIMIT :limit
''')
Future<List<OrderEvidenceUploadTaskEntity>> getDueTasks(
DateTime now,
int limit,
);
/// 查询需要向服务端报告永久缺失的已绑定终态任务,已报告记录不会重复返回。
@Query('''
SELECT * FROM order_evidence_upload_task
WHERE status IN ('PERMANENT_FAILED', 'EXPIRED')
AND (androidOrderCode IS NOT NULL OR serverOrderId IS NOT NULL)
AND unavailableReportedAt IS NULL
AND (nextRetryAt IS NULL OR nextRetryAt <= :now)
ORDER BY updateTime ASC, id ASC
LIMIT :limit
''')
Future<List<OrderEvidenceUploadTaskEntity>> getUnavailableReportTasks(
DateTime now,
int limit,
);
/// 查询已超过 24 小时绑定期限的等待订单任务,调用方删除图片后转为 EXPIRED。
@Query('''
SELECT * FROM order_evidence_upload_task
WHERE status = 'WAITING_ORDER' AND expiresAt <= :now
ORDER BY expiresAt ASC
''')
Future<List<OrderEvidenceUploadTaskEntity>> getExpiredWaitingTasks(
DateTime now,
);
/// 查询已超过 30 天图片保留期且仍持有现场文件的非成功任务。
@Query('''
SELECT * FROM order_evidence_upload_task
WHERE localFilePath IS NOT NULL
AND status NOT IN ('SUCCESS', 'CANCELLED')
AND expiresAt <= :now
ORDER BY expiresAt ASC
''')
Future<List<OrderEvidenceUploadTaskEntity>> getExpiredImageTasks(
DateTime now,
);
/// 删除完成时间早于指定时间的成功任务元数据,其他失败终态不自动删除。
@Query('''
DELETE FROM order_evidence_upload_task
WHERE status = 'SUCCESS'
AND completedAt IS NOT NULL
AND completedAt < :before
''')
Future<void> deleteSuccessfulBefore(DateTime before);
/// 查询全部任务,仅用于迁移验证和针对性测试,不参与生产批量处理。
@Query('SELECT * FROM order_evidence_upload_task ORDER BY id ASC')
Future<List<OrderEvidenceUploadTaskEntity>> getAll();
}
@@ -31,4 +31,24 @@ abstract class PrintHistoryDao {
(:status = '-1' OR status = :status)
''')
Future<List<PrintHistoryEntity>> getAll(String status);
/// 以服务端任务 ID 唯一定位本地记录,作为 MQTT/HTTP 双通道去重依据。
@Query(
'SELECT * FROM print_history WHERE servicePrintId = :servicePrintId LIMIT 1')
Future<PrintHistoryEntity?> findByServicePrintId(String servicePrintId);
/// 查询等待补报成功或失败结果的任务。
@Query(
'SELECT * FROM print_history WHERE resultPending = 1 ORDER BY createTime ASC')
Future<List<PrintHistoryEntity>> getPendingResults();
@Query("SELECT COUNT(*) FROM print_history WHERE status = 'NONE'")
Future<int?> countPending();
@Query("SELECT COUNT(*) FROM print_history WHERE status = 'ERROR'")
Future<int?> countFailed();
@Query(
"SELECT * FROM print_history WHERE lastError IS NOT NULL AND lastError != '' ORDER BY id DESC LIMIT 1")
Future<PrintHistoryEntity?> findLatestError();
}
@@ -0,0 +1,27 @@
import 'package:floor/floor.dart';
import 'package:pj_supermarket_cloud/common/index.dart';
/// `scheduled_task` 配置和最近执行快照的数据访问接口。
@dao
abstract class ScheduledTaskDao {
/// 按 taskCode 保存单条任务;主键冲突时以新配置和执行快照整体替换旧记录。
@Insert(onConflict: OnConflictStrategy.replace)
Future<void> save(ScheduledTaskEntity entity);
/// 批量补齐默认任务;每条记录均按 taskCode 使用替换冲突策略。
@Insert(onConflict: OnConflictStrategy.replace)
Future<void> batchSave(List<ScheduledTaskEntity> entities);
/// 按实体主键更新一条已经存在的任务记录。
@update
Future<void> edit(ScheduledTaskEntity entity);
/// 查询全部任务并按稳定 taskCode 升序返回,便于启动时构建确定性配置映射。
@Query('SELECT * FROM scheduled_task ORDER BY taskCode')
Future<List<ScheduledTaskEntity>> getAll();
/// 按稳定 taskCode 查询单条任务;不存在时返回空。
@Query('SELECT * FROM scheduled_task WHERE taskCode = :taskCode LIMIT 1')
Future<ScheduledTaskEntity?> getByCode(String taskCode);
}
@@ -0,0 +1,70 @@
import 'package:floor/floor.dart';
import 'package:pj_supermarket_cloud/common/index.dart';
/// `scheduled_task_log` 结构化执行日志的数据访问接口。
@dao
abstract class ScheduledTaskLogDao {
/// 新增任务开始日志并返回 SQLite 自增主键。
@insert
Future<int> save(ScheduledTaskLogEntity entity);
/// 按日志主键写回结束状态、耗时、错误和统计字段。
@update
Future<void> edit(ScheduledTaskLogEntity entity);
/// 按任务编码查询最近日志,可选依赖类型过滤,并按主键倒序限制返回数量。
@Query('''
SELECT * FROM scheduled_task_log
WHERE taskCode = :taskCode
AND (:sourceType = '' OR sourceType = :sourceType)
ORDER BY id DESC
LIMIT :limit
''')
Future<List<ScheduledTaskLogEntity>> getByTask(
String taskCode,
String sourceType,
int limit,
);
/// 按任务编码和可选来源分页查询日志,使用主键倒序保证翻页期间最新记录始终位于前页。
@Query('''
SELECT * FROM scheduled_task_log
WHERE taskCode = :taskCode
AND (:sourceType = '' OR sourceType = :sourceType)
ORDER BY id DESC
LIMIT :pageSize OFFSET :offset
''')
Future<List<ScheduledTaskLogEntity>> getPageByTask(
String taskCode,
String sourceType,
int pageSize,
int offset,
);
/// 统计指定任务和可选来源下的日志总数,为分页组件提供准确的总记录数。
@Query('''
SELECT COUNT(0) FROM scheduled_task_log
WHERE taskCode = :taskCode
AND (:sourceType = '' OR sourceType = :sourceType)
''')
Future<int?> countByTask(String taskCode, String sourceType);
/// 查询指定任务最近一条日志;从未执行时返回空。
@Query('''
SELECT * FROM scheduled_task_log
WHERE taskCode = :taskCode
ORDER BY id DESC
LIMIT 1
''')
Future<ScheduledTaskLogEntity?> getLatest(String taskCode);
/// 终端长期常驻,执行日志只保留最近 2000 条,避免数据库无限增长。
@Query('''
DELETE FROM scheduled_task_log
WHERE id NOT IN (
SELECT id FROM scheduled_task_log ORDER BY id DESC LIMIT 2000
)
''')
Future<void> trimHistory();
}
+9 -1
View File
@@ -22,6 +22,10 @@ const entities = <Type>[
UserEntity,
FaceEntity,
SettingEntity,
ScheduledTaskEntity,
ScheduledTaskLogEntity,
FileUploadRecordEntity,
OrderEvidenceUploadTaskEntity,
];
const typeConverters = <Type>[
@@ -40,7 +44,7 @@ const typeConverters = <Type>[
BoolConverter,
];
@Database(version: 9, entities: entities)
@Database(version: 10, entities: entities)
@TypeConverters(typeConverters)
abstract class AppDatabase extends FloorDatabase {
BroadcastDao get getBroadcastDao;
@@ -56,6 +60,10 @@ abstract class AppDatabase extends FloorDatabase {
UserDao get getUserDao;
FaceDao get getFaceDao;
SettingDao get getSettingDao;
ScheduledTaskDao get getScheduledTaskDao;
ScheduledTaskLogDao get getScheduledTaskLogDao;
FileUploadRecordDao get getFileUploadRecordDao;
OrderEvidenceUploadTaskDao get getOrderEvidenceUploadTaskDao;
static Future<AppDatabase> getDataBase() async {
final dbPath = await AppFileTool.getDatabasePath();
File diff suppressed because it is too large. Load diff
@@ -234,6 +234,195 @@ abstract class DatabaseMigration {
''');
});
/// 从版本9到版本10的迁移(迁移自 biz_xfj_cloud):新增定时任务/任务日志/文件上传
/// 记录/订单证据上传队列表,并为 face 表补充在库质量检测列。
static Migration migration9To10 = Migration(9, 10, (database) async {
await database.execute('''
CREATE TABLE IF NOT EXISTS scheduled_task (
taskCode TEXT NOT NULL PRIMARY KEY,
enabled INTEGER NOT NULL,
cronExpression TEXT NOT NULL,
idleOnly INTEGER NOT NULL,
lastRunAt TEXT,
lastSuccessAt TEXT,
lastStatus TEXT NOT NULL,
lastMessage TEXT NOT NULL,
createTime TEXT NOT NULL,
updateTime TEXT NOT NULL
)
''');
await database.execute('''
CREATE TABLE IF NOT EXISTS scheduled_task_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
taskCode TEXT NOT NULL,
taskName TEXT NOT NULL,
triggerType TEXT NOT NULL,
sourceType TEXT NOT NULL,
sourceName TEXT NOT NULL,
status TEXT NOT NULL,
message TEXT NOT NULL,
errorType TEXT,
errorMessage TEXT,
errorStack TEXT,
startedAt TEXT NOT NULL,
finishedAt TEXT,
durationMs INTEGER NOT NULL,
scannedCount INTEGER,
skippedCount INTEGER,
successCount INTEGER,
failureCount INTEGER
)
''');
await database.execute(
'CREATE INDEX IF NOT EXISTS index_scheduled_task_log_taskCode_startedAt '
'ON scheduled_task_log(taskCode, startedAt)',
);
await database.execute(
'CREATE INDEX IF NOT EXISTS index_scheduled_task_log_sourceType_status_startedAt '
'ON scheduled_task_log(sourceType, status, startedAt)',
);
await database.execute('''
CREATE TABLE IF NOT EXISTS file_upload_record (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hardId TEXT NOT NULL,
bizType TEXT NOT NULL,
relativePath TEXT NOT NULL,
originalName TEXT NOT NULL,
sourceSize INTEGER NOT NULL,
sourceModifiedTime TEXT NOT NULL,
sanitizedHash TEXT NOT NULL,
sanitizedSize INTEGER NOT NULL,
status TEXT NOT NULL,
serverFileId TEXT,
attemptCount INTEGER NOT NULL,
lastAttemptAt TEXT,
uploadedAt TEXT,
lastError TEXT,
createTime TEXT NOT NULL,
updateTime TEXT NOT NULL
)
''');
await database.execute(
'CREATE UNIQUE INDEX IF NOT EXISTS index_file_upload_record_hardId_bizType_relativePath '
'ON file_upload_record(hardId, bizType, relativePath)',
);
await database.execute(
'CREATE INDEX IF NOT EXISTS index_file_upload_record_bizType_status_updateTime '
'ON file_upload_record(bizType, status, updateTime)',
);
await database.execute('''
CREATE TABLE IF NOT EXISTS order_evidence_upload_task (
id INTEGER PRIMARY KEY AUTOINCREMENT,
androidOrderCode TEXT,
serverOrderId TEXT,
recognitionSessionId TEXT NOT NULL,
evidenceType TEXT NOT NULL,
evidenceRole TEXT NOT NULL,
sequenceNo INTEGER NOT NULL,
userId TEXT NOT NULL,
faceSampleId TEXT,
referenceImageUrl TEXT,
referenceImageVersion TEXT,
localFilePath TEXT,
imageHash TEXT,
imageSize INTEGER,
imageWidth INTEGER,
imageHeight INTEGER,
similarity REAL,
similarityThreshold REAL,
capturedAt TEXT NOT NULL,
appVersion TEXT NOT NULL,
status TEXT NOT NULL,
attemptCount INTEGER NOT NULL,
nextRetryAt TEXT,
serverEvidenceId TEXT,
serverImageUrl TEXT,
lastError TEXT,
unavailableReportedAt TEXT,
completedAt TEXT,
createTime TEXT NOT NULL,
updateTime TEXT NOT NULL,
expiresAt TEXT NOT NULL
)
''');
await database.execute(
'CREATE UNIQUE INDEX IF NOT EXISTS index_order_evidence_upload_task_position '
'ON order_evidence_upload_task(androidOrderCode, evidenceType, evidenceRole, sequenceNo)',
);
await database.execute(
'CREATE INDEX IF NOT EXISTS index_order_evidence_upload_task_status_nextRetryAt_createTime '
'ON order_evidence_upload_task(status, nextRetryAt, createTime)',
);
// face 表补充在库质量检测列(迁移自 biz_xfj_cloud 版本11到12迁移)。
final columns = await database.rawQuery('PRAGMA table_info(face)');
final names = columns.map((column) => column['name']).toSet();
if (!names.contains('qualityScore')) {
await database.execute('ALTER TABLE face ADD COLUMN qualityScore REAL');
}
if (!names.contains('qualityStatus')) {
await database.execute(
"ALTER TABLE face ADD COLUMN qualityStatus TEXT NOT NULL DEFAULT 'pending'",
);
}
if (!names.contains('qualityCheckedAt')) {
await database.execute(
'ALTER TABLE face ADD COLUMN qualityCheckedAt TEXT',
);
}
if (!names.contains('qualityErrorCode')) {
await database.execute(
'ALTER TABLE face ADD COLUMN qualityErrorCode INTEGER',
);
}
await database.execute(
"UPDATE face SET qualityStatus = 'pending' WHERE qualityStatus IS NULL OR qualityStatus NOT IN ('pending', 'detected', 'failed')",
);
// 项目现有 DateTimeEmptyConverter 以空字符串表示业务空值;迁移旧记录时同步规范化,
// 避免 Floor 生成代码把 SQLite NULL 强转为 String 后读取失败。
await database.execute(
"UPDATE face SET qualityCheckedAt = '' WHERE qualityCheckedAt IS NULL",
);
await database.execute(
'CREATE INDEX IF NOT EXISTS index_face_qualityStatus_qualityScore ON face(qualityStatus, qualityScore)',
);
// print_history 升级为幂等打印任务队列(迁移自 biz_xfj_cloud 版本12到13迁移)。
final printColumns = await database.rawQuery('PRAGMA table_info(print_history)');
final printColumnNames = printColumns.map((column) => column['name']).toSet();
if (!printColumnNames.contains('printType')) {
await database.execute(
"ALTER TABLE print_history ADD COLUMN printType TEXT NOT NULL DEFAULT '1'");
}
if (!printColumnNames.contains('printNo')) {
await database.execute(
'ALTER TABLE print_history ADD COLUMN printNo INTEGER NOT NULL DEFAULT 1');
}
if (!printColumnNames.contains('retryVersion')) {
await database.execute(
'ALTER TABLE print_history ADD COLUMN retryVersion INTEGER NOT NULL DEFAULT 0');
}
if (!printColumnNames.contains('attemptCount')) {
await database.execute(
'ALTER TABLE print_history ADD COLUMN attemptCount INTEGER NOT NULL DEFAULT 0');
}
if (!printColumnNames.contains('lastError')) {
await database.execute('ALTER TABLE print_history ADD COLUMN lastError TEXT');
}
if (!printColumnNames.contains('resultPending')) {
await database.execute(
'ALTER TABLE print_history ADD COLUMN resultPending INTEGER NOT NULL DEFAULT 0');
}
await database.execute('''
DELETE FROM print_history
WHERE id NOT IN (SELECT MAX(id) FROM print_history GROUP BY servicePrintId)
''');
await database.execute(
'CREATE UNIQUE INDEX IF NOT EXISTS index_print_history_servicePrintId ON print_history(servicePrintId)',
);
});
static List<Migration> getDatabaseMigrations() {
return [
migration1To2,
@@ -244,6 +433,7 @@ abstract class DatabaseMigration {
migration6To7,
migration7To8,
migration8To9,
migration9To10,
];
}
}
@@ -41,6 +41,18 @@ class FaceEntity extends Equatable {
/// 同步时间
DateTime? updateTime;
/// 最近一次在库图片质量分,范围为0到1;未检测或检测失败时为空。
double? qualityScore;
/// 质量检测状态,持久化值为pending、detected或failed。
String qualityStatus;
/// 最近一次质量检测完成时间;从未检测时为空。
DateTime? qualityCheckedAt;
/// 最近一次质量检测的虹软或插件错误码;成功时为空。
int? qualityErrorCode;
FaceEntity({
this.id,
required this.userId,
@@ -53,6 +65,10 @@ class FaceEntity extends Equatable {
this.faceModel,
this.createTime,
this.updateTime,
this.qualityScore,
this.qualityStatus = 'pending',
this.qualityCheckedAt,
this.qualityErrorCode,
});
factory FaceEntity.fromJson(Map<String, dynamic> json) =>
@@ -22,6 +22,12 @@ FaceEntity _$FaceEntityFromJson(Map<String, dynamic> json) => FaceEntity(
updateTime: json['updateTime'] == null
? null
: DateTime.parse(json['updateTime'] as String),
qualityScore: (json['qualityScore'] as num?)?.toDouble(),
qualityStatus: json['qualityStatus'] as String? ?? 'pending',
qualityCheckedAt: json['qualityCheckedAt'] == null
? null
: DateTime.parse(json['qualityCheckedAt'] as String),
qualityErrorCode: (json['qualityErrorCode'] as num?)?.toInt(),
);
Map<String, dynamic> _$FaceEntityToJson(FaceEntity instance) =>
@@ -37,4 +43,8 @@ Map<String, dynamic> _$FaceEntityToJson(FaceEntity instance) =>
'faceModel': instance.faceModel,
'createTime': instance.createTime?.toIso8601String(),
'updateTime': instance.updateTime?.toIso8601String(),
'qualityScore': instance.qualityScore,
'qualityStatus': instance.qualityStatus,
'qualityCheckedAt': instance.qualityCheckedAt?.toIso8601String(),
'qualityErrorCode': instance.qualityErrorCode,
};
@@ -0,0 +1,97 @@
import 'package:floor/floor.dart';
import 'package:json_annotation/json_annotation.dart';
part 'file_upload_record_entity.g.dart';
/// 本机文件上传记录。
///
/// 目前 [bizType] 使用 `terminal_log`,保留业务类型后可复用到数据库备份等文件任务。
@JsonSerializable()
@Entity(
tableName: 'file_upload_record',
indices: [
Index(value: ['hardId', 'bizType', 'relativePath'], unique: true),
Index(value: ['bizType', 'status', 'updateTime']),
],
)
class FileUploadRecordEntity {
/// SQLite 自增主键;首次保存前为空。
@PrimaryKey(autoGenerate: true)
final int? id;
/// 终端绑定的设备 ID,用于隔离设备重新绑定前后的文件状态。
final String hardId;
/// 文件业务类型;终端日志固定使用 `terminal_log`。
final String bizType;
/// 文件在业务根目录下使用正斜杠的相对路径。
final String relativePath;
/// 原始文件名,不包含目录。
final String originalName;
/// 生成脱敏副本时读取的原文件快照大小,单位为字节。
final int sourceSize;
/// 生成脱敏副本时原文件的最后修改时间。
final DateTime sourceModifiedTime;
/// 脱敏后文件内容的 MD5 哈希;首次尝试尚未完成脱敏时可为空字符串。
final String sanitizedHash;
/// 脱敏后文件大小,单位为字节;尚未完成脱敏时为零。
final int sanitizedSize;
/// 最近一次上传尝试状态,保存 [FileUploadStatus.name]。
final String status;
/// 后端 hard_terminal_log_file 记录 ID;预检判定无需上传且本机无旧记录时可为空。
final String? serverFileId;
/// 对该路径发起过的上传尝试次数,预检前即递增。
final int attemptCount;
/// 最近一次开始上传尝试的本地时间;从未尝试时为空。
final DateTime? lastAttemptAt;
/// 最近一次被服务端确认成功或无需上传的本地时间。
final DateTime? uploadedAt;
/// 最近一次失败的截断错误摘要;成功记录为空。
final String? lastError;
/// 当前设备与业务类型下首次创建该路径记录的本地时间。
final DateTime createTime;
/// 状态、快照或服务端 ID 最近一次更新的本地时间。
final DateTime updateTime;
/// 创建一条文件上传状态记录。
const FileUploadRecordEntity({
this.id,
required this.hardId,
required this.bizType,
required this.relativePath,
required this.originalName,
required this.sourceSize,
required this.sourceModifiedTime,
required this.sanitizedHash,
required this.sanitizedSize,
required this.status,
this.serverFileId,
this.attemptCount = 0,
this.lastAttemptAt,
this.uploadedAt,
this.lastError,
required this.createTime,
required this.updateTime,
});
/// 从 JSON 数据恢复上传记录,仅供调试序列化使用。
factory FileUploadRecordEntity.fromJson(Map<String, dynamic> json) =>
_$FileUploadRecordEntityFromJson(json);
/// 将上传记录转换为 JSON,仅供调试序列化使用。
Map<String, dynamic> toJson() => _$FileUploadRecordEntityToJson(this);
}
@@ -0,0 +1,55 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'file_upload_record_entity.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
FileUploadRecordEntity _$FileUploadRecordEntityFromJson(
Map<String, dynamic> json) =>
FileUploadRecordEntity(
id: (json['id'] as num?)?.toInt(),
hardId: json['hardId'] as String,
bizType: json['bizType'] as String,
relativePath: json['relativePath'] as String,
originalName: json['originalName'] as String,
sourceSize: (json['sourceSize'] as num).toInt(),
sourceModifiedTime: DateTime.parse(json['sourceModifiedTime'] as String),
sanitizedHash: json['sanitizedHash'] as String,
sanitizedSize: (json['sanitizedSize'] as num).toInt(),
status: json['status'] as String,
serverFileId: json['serverFileId'] as String?,
attemptCount: (json['attemptCount'] as num?)?.toInt() ?? 0,
lastAttemptAt: json['lastAttemptAt'] == null
? null
: DateTime.parse(json['lastAttemptAt'] as String),
uploadedAt: json['uploadedAt'] == null
? null
: DateTime.parse(json['uploadedAt'] as String),
lastError: json['lastError'] as String?,
createTime: DateTime.parse(json['createTime'] as String),
updateTime: DateTime.parse(json['updateTime'] as String),
);
Map<String, dynamic> _$FileUploadRecordEntityToJson(
FileUploadRecordEntity instance) =>
<String, dynamic>{
'id': instance.id,
'hardId': instance.hardId,
'bizType': instance.bizType,
'relativePath': instance.relativePath,
'originalName': instance.originalName,
'sourceSize': instance.sourceSize,
'sourceModifiedTime': instance.sourceModifiedTime.toIso8601String(),
'sanitizedHash': instance.sanitizedHash,
'sanitizedSize': instance.sanitizedSize,
'status': instance.status,
'serverFileId': instance.serverFileId,
'attemptCount': instance.attemptCount,
'lastAttemptAt': instance.lastAttemptAt?.toIso8601String(),
'uploadedAt': instance.uploadedAt?.toIso8601String(),
'lastError': instance.lastError,
'createTime': instance.createTime.toIso8601String(),
'updateTime': instance.updateTime.toIso8601String(),
};
@@ -82,6 +82,7 @@ const _$OrderPayTypeEnumMap = {
OrderPayType.WALLET_DEFAULT: 'WALLET_DEFAULT',
OrderPayType.WALLET_QR_CODE: 'WALLET_QR_CODE',
OrderPayType.WALLET_QR_CODE_SCAN: 'WALLET_QR_CODE_SCAN',
OrderPayType.AGGREGATE_ORDER_PAY: 'AGGREGATE_ORDER_PAY',
OrderPayType.WALLET_CARD: 'WALLET_CARD',
OrderPayType.FACE: 'FACE',
OrderPayType.STRIDE_DEPT_QR_CODE: 'STRIDE_DEPT_QR_CODE',
@@ -0,0 +1,211 @@
import 'package:floor/floor.dart';
/// 终端订单识别证据补传任务。
///
/// 本表是断网、崩溃和接口超时后的本地待办队列,不是最终证据库。服务端确认成功后立即删除
/// [localFilePath] 对应的现场图片;失败图片最多保留 30 天,任务终态元数据按业务规则保留。
@Entity(
tableName: 'order_evidence_upload_task',
indices: [
Index(
value: [
'androidOrderCode',
'evidenceType',
'evidenceRole',
'sequenceNo',
],
unique: true,
),
Index(value: ['status', 'nextRetryAt', 'createTime']),
],
)
class OrderEvidenceUploadTaskEntity {
/// SQLite 自增主键,首次保存前为空。
@PrimaryKey(autoGenerate: true)
final int? id;
/// 终端生成的稳定订单号,用于响应超时后与服务端对账。
final String? androidOrderCode;
/// 对账成功后的服务端订单主键;尚未落单或请求结果不确定时为空。
final String? serverOrderId;
/// 产生现场图片的人脸识别业务会话编号,用于诊断但不作为服务端幂等键。
final String recognitionSessionId;
/// 证据类型,保存 `OrderEvidenceType.code`。
final String evidenceType;
/// 图片角色,保存 `OrderEvidenceRole.code`。
final String evidenceRole;
/// 同一订单、类型和角色下的稳定序号,从 0 开始。
final int sequenceNo;
/// 识别用户主键快照,来源于最终用于下单的人员。
final String userId;
/// 虹软样本编号快照;非人脸证据允许为空。
final String? faceSampleId;
/// 识别时本地人脸记录保存的后台参考图片地址,仅由服务端校验和复制。
final String? referenceImageUrl;
/// 识别时参考图片的版本摘要,当前使用本地人脸记录更新时间 ISO 字符串。
final String? referenceImageVersion;
/// 终端私有目录中的现场 JPEG 路径;参考图、已成功或图片到期任务为空。
final String? localFilePath;
/// 现场图片或已知参考图内容的 SHA-256;参考图未知内容时允许为空。
final String? imageHash;
/// 现场图片大小,单位字节;参考图未下载时允许为空。
final int? imageSize;
/// 现场图片宽度,单位像素;参考图未下载时允许为空。
final int? imageWidth;
/// 现场图片高度,单位像素;参考图未下载时允许为空。
final int? imageHeight;
/// 人脸比对相似度快照,范围 0 到 1;菜谱识别允许为空。
final double? similarity;
/// 人脸比对通过时使用的阈值快照,范围 0 到 1;菜谱识别允许为空。
final double? similarityThreshold;
/// 真正比对成功帧的终端采集时间。
final DateTime capturedAt;
/// 产生证据时的终端应用展示版本与内部版本组合。
final String appVersion;
/// 当前任务状态,保存 `OrderEvidenceTaskStatus.code`。
final String status;
/// 已发起的服务端对账、提交或上传尝试次数,用于计算退避间隔。
final int attemptCount;
/// 下一次允许自动处理的本地时间;立即可处理时为空。
final DateTime? nextRetryAt;
/// 服务端证据记录主键;尚未创建或永久缺失上报未返回时为空。
final String? serverEvidenceId;
/// 服务端最终证据图片地址;非 AVAILABLE 或参考复制失败时为空。
final String? serverImageUrl;
/// 最近一次失败的单行截断摘要,不包含 Token、原始帧、完整路径或图片内容。
final String? lastError;
/// 终端已向服务端成功报告 UNAVAILABLE 的时间;未报告或无订单时为空。
final DateTime? unavailableReportedAt;
/// 服务端确认 AVAILABLE 的完成时间;其他状态为空。
final DateTime? completedAt;
/// 任务首次创建时间,用于 24 小时未绑定订单和终态保留策略。
final DateTime createTime;
/// 状态、重试时间或服务端结果最近一次更新时间。
final DateTime updateTime;
/// 当前图片或未绑定任务的本地到期时间;绑定订单后延长为现场图片 30 天。
final DateTime expiresAt;
/// 创建一条可持久化的订单证据补传任务。
const OrderEvidenceUploadTaskEntity({
this.id,
this.androidOrderCode,
this.serverOrderId,
required this.recognitionSessionId,
required this.evidenceType,
required this.evidenceRole,
required this.sequenceNo,
required this.userId,
this.faceSampleId,
this.referenceImageUrl,
this.referenceImageVersion,
this.localFilePath,
this.imageHash,
this.imageSize,
this.imageWidth,
this.imageHeight,
this.similarity,
this.similarityThreshold,
required this.capturedAt,
required this.appVersion,
required this.status,
this.attemptCount = 0,
this.nextRetryAt,
this.serverEvidenceId,
this.serverImageUrl,
this.lastError,
this.unavailableReportedAt,
this.completedAt,
required this.createTime,
required this.updateTime,
required this.expiresAt,
});
/// 保留证据位置和识别快照,生成包含指定状态变化的新任务。
OrderEvidenceUploadTaskEntity copyWith({
String? androidOrderCode,
String? serverOrderId,
String? localFilePath,
bool clearLocalFilePath = false,
String? imageHash,
int? imageSize,
int? imageWidth,
int? imageHeight,
String? status,
int? attemptCount,
DateTime? nextRetryAt,
bool clearNextRetryAt = false,
String? serverEvidenceId,
String? serverImageUrl,
String? lastError,
bool clearLastError = false,
DateTime? unavailableReportedAt,
DateTime? completedAt,
DateTime? updateTime,
DateTime? expiresAt,
}) {
return OrderEvidenceUploadTaskEntity(
id: id,
androidOrderCode: androidOrderCode ?? this.androidOrderCode,
serverOrderId: serverOrderId ?? this.serverOrderId,
recognitionSessionId: recognitionSessionId,
evidenceType: evidenceType,
evidenceRole: evidenceRole,
sequenceNo: sequenceNo,
userId: userId,
faceSampleId: faceSampleId,
referenceImageUrl: referenceImageUrl,
referenceImageVersion: referenceImageVersion,
localFilePath:
clearLocalFilePath ? null : localFilePath ?? this.localFilePath,
imageHash: imageHash ?? this.imageHash,
imageSize: imageSize ?? this.imageSize,
imageWidth: imageWidth ?? this.imageWidth,
imageHeight: imageHeight ?? this.imageHeight,
similarity: similarity,
similarityThreshold: similarityThreshold,
capturedAt: capturedAt,
appVersion: appVersion,
status: status ?? this.status,
attemptCount: attemptCount ?? this.attemptCount,
nextRetryAt: clearNextRetryAt ? null : nextRetryAt ?? this.nextRetryAt,
serverEvidenceId: serverEvidenceId ?? this.serverEvidenceId,
serverImageUrl: serverImageUrl ?? this.serverImageUrl,
lastError: clearLastError ? null : lastError ?? this.lastError,
unavailableReportedAt:
unavailableReportedAt ?? this.unavailableReportedAt,
completedAt: completedAt ?? this.completedAt,
createTime: createTime,
updateTime: updateTime ?? this.updateTime,
expiresAt: expiresAt ?? this.expiresAt,
);
}
}
@@ -6,7 +6,12 @@ import 'package:pj_supermarket_cloud/common/index.dart';
part 'print_history_entity.g.dart';
@JsonSerializable()
@Entity(tableName: "print_history")
@Entity(
tableName: "print_history",
indices: [
Index(value: ['servicePrintId'], unique: true)
],
)
class PrintHistoryEntity {
/// 自增ID
@PrimaryKey(autoGenerate: true)
@@ -24,6 +29,18 @@ class PrintHistoryEntity {
/// 发票图片地址
String invoiceUrl;
/// 服务端凭证类型、打印序号与重试版本。
String printType;
int printNo;
int retryVersion;
/// 当前重试版本实际执行的 USB 尝试次数和最近错误。
int attemptCount;
String? lastError;
/// 打印结果尚未可靠提交到服务端时保持为 true,网络恢复后继续补报。
bool resultPending;
/// 打印状态
PrintStatus status;
@@ -42,6 +59,12 @@ class PrintHistoryEntity {
required this.serviceOrderId,
required this.servicePrintId,
required this.invoiceUrl,
this.printType = '1',
this.printNo = 1,
this.retryVersion = 0,
this.attemptCount = 0,
this.lastError,
this.resultPending = false,
required this.status,
this.printTime,
this.syncTime,
@@ -13,6 +13,12 @@ PrintHistoryEntity _$PrintHistoryEntityFromJson(Map<String, dynamic> json) =>
serviceOrderId: json['serviceOrderId'] as String,
servicePrintId: json['servicePrintId'] as String,
invoiceUrl: json['invoiceUrl'] as String,
printType: json['printType'] as String? ?? '1',
printNo: (json['printNo'] as num?)?.toInt() ?? 1,
retryVersion: (json['retryVersion'] as num?)?.toInt() ?? 0,
attemptCount: (json['attemptCount'] as num?)?.toInt() ?? 0,
lastError: json['lastError'] as String?,
resultPending: json['resultPending'] as bool? ?? false,
status: $enumDecode(_$PrintStatusEnumMap, json['status']),
printTime: json['printTime'] as String?,
syncTime: json['syncTime'] == null
@@ -28,6 +34,12 @@ Map<String, dynamic> _$PrintHistoryEntityToJson(PrintHistoryEntity instance) =>
'serviceOrderId': instance.serviceOrderId,
'servicePrintId': instance.servicePrintId,
'invoiceUrl': instance.invoiceUrl,
'printType': instance.printType,
'printNo': instance.printNo,
'retryVersion': instance.retryVersion,
'attemptCount': instance.attemptCount,
'lastError': instance.lastError,
'resultPending': instance.resultPending,
'status': _$PrintStatusEnumMap[instance.status]!,
'printTime': instance.printTime,
'syncTime': instance.syncTime?.toIso8601String(),
@@ -0,0 +1,87 @@
import 'package:floor/floor.dart';
import 'package:json_annotation/json_annotation.dart';
part 'scheduled_task_entity.g.dart';
/// 终端定时任务配置及最近一次执行快照。
///
/// [taskCode] 使用代码枚举名作为稳定主键,界面文案调整时不会产生重复任务。
@JsonSerializable()
@Entity(tableName: 'scheduled_task')
class ScheduledTaskEntity {
/// 稳定任务编码,取 [ScheduledTaskType.name],作为表主键且不能随展示文案变化。
@PrimaryKey()
final String taskCode;
/// 是否启用 Cron 自动调度;关闭时仍可通过设置页手动触发。
final bool enabled;
/// cron 0.6.2 使用的六段表达式,字段顺序为秒、分、时、日、月、周。
final String cronExpression;
/// 自动任务是否必须等待连续 60 秒无人操作的完整空闲窗口。
final bool idleOnly;
/// 最近一次任务开始执行的本地时间;从未执行时为空。
final DateTime? lastRunAt;
/// 最近一次成功完成的本地时间;尚无成功记录时为空。
final DateTime? lastSuccessAt;
/// 最近一次运行状态,保存 [ScheduledTaskRunStatus.name]。
final String lastStatus;
/// 最近一次运行结果的简短中文摘要。
final String lastMessage;
/// 任务配置首次写入 SQLite 的本地时间。
final DateTime createTime;
/// 配置或执行快照最近一次更新的本地时间。
final DateTime updateTime;
/// 创建一条定时任务配置与最近执行快照。
const ScheduledTaskEntity({
required this.taskCode,
required this.enabled,
required this.cronExpression,
required this.idleOnly,
this.lastRunAt,
this.lastSuccessAt,
required this.lastStatus,
required this.lastMessage,
required this.createTime,
required this.updateTime,
});
/// 从 JSON 数据恢复实体,仅供兼容迁移和调试序列化使用。
factory ScheduledTaskEntity.fromJson(Map<String, dynamic> json) =>
_$ScheduledTaskEntityFromJson(json);
/// 将实体转换为 JSON,仅供兼容迁移和调试序列化使用。
Map<String, dynamic> toJson() => _$ScheduledTaskEntityToJson(this);
/// 保留主键与创建时间,生成包含配置或最近执行结果变更的新实体。
ScheduledTaskEntity copyWith({
bool? enabled,
String? cronExpression,
bool? idleOnly,
DateTime? lastRunAt,
DateTime? lastSuccessAt,
String? lastStatus,
String? lastMessage,
DateTime? updateTime,
}) =>
ScheduledTaskEntity(
taskCode: taskCode,
enabled: enabled ?? this.enabled,
cronExpression: cronExpression ?? this.cronExpression,
idleOnly: idleOnly ?? this.idleOnly,
lastRunAt: lastRunAt ?? this.lastRunAt,
lastSuccessAt: lastSuccessAt ?? this.lastSuccessAt,
lastStatus: lastStatus ?? this.lastStatus,
lastMessage: lastMessage ?? this.lastMessage,
createTime: createTime,
updateTime: updateTime ?? this.updateTime,
);
}
@@ -0,0 +1,40 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'scheduled_task_entity.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ScheduledTaskEntity _$ScheduledTaskEntityFromJson(Map<String, dynamic> json) =>
ScheduledTaskEntity(
taskCode: json['taskCode'] as String,
enabled: json['enabled'] as bool,
cronExpression: json['cronExpression'] as String,
idleOnly: json['idleOnly'] as bool,
lastRunAt: json['lastRunAt'] == null
? null
: DateTime.parse(json['lastRunAt'] as String),
lastSuccessAt: json['lastSuccessAt'] == null
? null
: DateTime.parse(json['lastSuccessAt'] as String),
lastStatus: json['lastStatus'] as String,
lastMessage: json['lastMessage'] as String,
createTime: DateTime.parse(json['createTime'] as String),
updateTime: DateTime.parse(json['updateTime'] as String),
);
Map<String, dynamic> _$ScheduledTaskEntityToJson(
ScheduledTaskEntity instance) =>
<String, dynamic>{
'taskCode': instance.taskCode,
'enabled': instance.enabled,
'cronExpression': instance.cronExpression,
'idleOnly': instance.idleOnly,
'lastRunAt': instance.lastRunAt?.toIso8601String(),
'lastSuccessAt': instance.lastSuccessAt?.toIso8601String(),
'lastStatus': instance.lastStatus,
'lastMessage': instance.lastMessage,
'createTime': instance.createTime.toIso8601String(),
'updateTime': instance.updateTime.toIso8601String(),
};
@@ -0,0 +1,135 @@
import 'package:floor/floor.dart';
import 'package:json_annotation/json_annotation.dart';
part 'scheduled_task_log_entity.g.dart';
/// 定时任务的结构化执行日志。
///
/// 来源、触发方式、状态和统计值使用独立列,日志页面无需解析拼接文本即可筛选。
@JsonSerializable()
@Entity(
tableName: 'scheduled_task_log',
indices: [
Index(value: ['taskCode', 'startedAt']),
Index(value: ['sourceType', 'status', 'startedAt']),
],
)
class ScheduledTaskLogEntity {
/// SQLite 自增主键;日志尚未插入数据库时为空。
@PrimaryKey(autoGenerate: true)
final int? id;
/// 任务稳定编码,关联 scheduled_task.taskCode,但不建立外键以兼容任务版本演进。
final String taskCode;
/// 执行发生时的任务展示名称快照。
final String taskName;
/// 触发来源,保存 [ScheduledTaskTriggerType.name]。
final String triggerType;
/// 主要依赖类型,保存 [ScheduledTaskSourceType.name]。
final String sourceType;
/// 执行发生时的依赖名称快照,例如 MQTT 消息服务或 HTTP 商品接口。
final String sourceName;
/// 本次执行状态,保存 [ScheduledTaskRunStatus.name]。
final String status;
/// 本次执行结果的简短中文摘要。
final String message;
/// 异常运行时的 Dart 异常类型;成功或跳过时为空。
final String? errorType;
/// 截断并脱敏后的异常摘要;成功或跳过时为空。
final String? errorMessage;
/// 截断后的异常堆栈,最多保存调度器允许的诊断长度。
final String? errorStack;
/// 任务开始执行的本地时间。
final DateTime startedAt;
/// 任务结束的本地时间;执行中日志为空。
final DateTime? finishedAt;
/// 从开始到结束的耗时,单位为毫秒;执行中默认为零。
final int durationMs;
/// 文件类任务扫描到的文件数量;不适用的任务为空。
final int? scannedCount;
/// 文件类任务因未变化或服务端已存在而跳过的数量;不适用时为空。
final int? skippedCount;
/// 文件类任务实际上传成功的数量;不适用时为空。
final int? successCount;
/// 文件类任务处理失败的数量;不适用时为空。
final int? failureCount;
/// 创建一条任务执行日志;开始记录尚未结束时不填写结束信息。
const ScheduledTaskLogEntity({
this.id,
required this.taskCode,
required this.taskName,
required this.triggerType,
required this.sourceType,
required this.sourceName,
required this.status,
required this.message,
this.errorType,
this.errorMessage,
this.errorStack,
required this.startedAt,
this.finishedAt,
this.durationMs = 0,
this.scannedCount,
this.skippedCount,
this.successCount,
this.failureCount,
});
/// 从 JSON 数据恢复任务日志,仅供调试序列化使用。
factory ScheduledTaskLogEntity.fromJson(Map<String, dynamic> json) =>
_$ScheduledTaskLogEntityFromJson(json);
/// 将任务日志转换为 JSON,仅供调试序列化使用。
Map<String, dynamic> toJson() => _$ScheduledTaskLogEntityToJson(this);
/// 使用最终状态、错误和统计值生成同一条日志的完成快照。
ScheduledTaskLogEntity finish({
required String nextStatus,
required String nextMessage,
required DateTime at,
String? nextErrorType,
String? nextErrorMessage,
String? nextErrorStack,
int? scanned,
int? skipped,
int? succeeded,
int? failed,
}) =>
ScheduledTaskLogEntity(
id: id,
taskCode: taskCode,
taskName: taskName,
triggerType: triggerType,
sourceType: sourceType,
sourceName: sourceName,
status: nextStatus,
message: nextMessage,
errorType: nextErrorType,
errorMessage: nextErrorMessage,
errorStack: nextErrorStack,
startedAt: startedAt,
finishedAt: at,
durationMs: at.difference(startedAt).inMilliseconds,
scannedCount: scanned,
skippedCount: skipped,
successCount: succeeded,
failureCount: failed,
);
}
@@ -0,0 +1,55 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'scheduled_task_log_entity.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ScheduledTaskLogEntity _$ScheduledTaskLogEntityFromJson(
Map<String, dynamic> json) =>
ScheduledTaskLogEntity(
id: (json['id'] as num?)?.toInt(),
taskCode: json['taskCode'] as String,
taskName: json['taskName'] as String,
triggerType: json['triggerType'] as String,
sourceType: json['sourceType'] as String,
sourceName: json['sourceName'] as String,
status: json['status'] as String,
message: json['message'] as String,
errorType: json['errorType'] as String?,
errorMessage: json['errorMessage'] as String?,
errorStack: json['errorStack'] as String?,
startedAt: DateTime.parse(json['startedAt'] as String),
finishedAt: json['finishedAt'] == null
? null
: DateTime.parse(json['finishedAt'] as String),
durationMs: (json['durationMs'] as num?)?.toInt() ?? 0,
scannedCount: (json['scannedCount'] as num?)?.toInt(),
skippedCount: (json['skippedCount'] as num?)?.toInt(),
successCount: (json['successCount'] as num?)?.toInt(),
failureCount: (json['failureCount'] as num?)?.toInt(),
);
Map<String, dynamic> _$ScheduledTaskLogEntityToJson(
ScheduledTaskLogEntity instance) =>
<String, dynamic>{
'id': instance.id,
'taskCode': instance.taskCode,
'taskName': instance.taskName,
'triggerType': instance.triggerType,
'sourceType': instance.sourceType,
'sourceName': instance.sourceName,
'status': instance.status,
'message': instance.message,
'errorType': instance.errorType,
'errorMessage': instance.errorMessage,
'errorStack': instance.errorStack,
'startedAt': instance.startedAt.toIso8601String(),
'finishedAt': instance.finishedAt?.toIso8601String(),
'durationMs': instance.durationMs,
'scannedCount': instance.scannedCount,
'skippedCount': instance.skippedCount,
'successCount': instance.successCount,
'failureCount': instance.failureCount,
};