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

+14 -3
View File
@@ -1,13 +1,24 @@
import 'package:dio/dio.dart' as dio;
import 'package:pj_supermarket_cloud/common/index.dart';
/// 终端 App 配置与版本接口入口,统一复用 HttpManage 的鉴权、租户和错误处理。
abstract class AppApi {
/// 获取所有版本列表
///
/// 分页获取匹配 App 编码和平台的已发布版本。
///
/// [pa] 包含 appUniqueCode、platform、current 和 size;[silent] 为 true 时后台任务
/// 只记录异常,不弹业务 Toast。
static Future<ServiceAppVersionPage?> openCardList(
dynamic pa,
) async {
dynamic pa, {
bool silent = false,
}) async {
final res = await HttpManage().dio.get(
"/integrated/app-version/ad/page",
queryParameters: pa,
options: dio.Options(
extra: {if (silent) HttpManage.silentRequestKey: true},
),
);
if (res.data == null) return null;
return ServiceAppVersionPage.fromJson(res.data);
+339
View File
@@ -1,6 +1,88 @@
import 'dart:io';
import 'package:dio/dio.dart' as dio;
import 'package:pj_supermarket_cloud/common/index.dart';
abstract class HardApi {
/// 按终端订单号或服务端订单 ID 对账识别证据状态;后台任务失败不弹业务 Toast。
static Future<ServiceOrderEvidenceResolve?> resolveOrderEvidence({
required String hardId,
String? androidOrderCode,
String? orderId,
}) async {
final res = await HttpManage().dio.get(
'/hard/hard-order-evidence/ad/resolve',
queryParameters: {
if (androidOrderCode != null) 'androidOrderCode': androidOrderCode,
if (orderId != null) 'orderId': orderId,
},
options: dio.Options(
headers: {'DEVICE-ID': hardId},
extra: {HttpManage.silentRequestKey: true},
),
);
if (res.data == null) return null;
return ServiceOrderEvidenceResolve.fromJson(res.data);
}
/// 提交识别元数据、触发参考图复制或报告本地图片永久缺失。
static Future<ServiceOrderEvidenceUploadResult?> submitOrderEvidence({
required String hardId,
required OrderEvidenceRequest request,
}) async {
final res = await HttpManage().dio.post(
'/hard/hard-order-evidence/ad/submit',
data: request.toJson(),
options: dio.Options(
headers: {'DEVICE-ID': hardId},
extra: {HttpManage.silentRequestKey: true},
),
);
if (res.data == null) return null;
return ServiceOrderEvidenceUploadResult.fromJson(res.data);
}
/// 使用统一 Dio 鉴权和租户头上传现场图片,multipart 文件名不包含终端完整路径。
static Future<ServiceOrderEvidenceUploadResult?> uploadOrderEvidence({
required String hardId,
required OrderEvidenceRequest request,
required String filePath,
}) async {
final file = File(filePath);
final fileName = file.uri.pathSegments.last;
final form = dio.FormData.fromMap({
...request.toJson(),
'file': await dio.MultipartFile.fromFile(filePath, filename: fileName),
});
final res = await HttpManage().dio.post(
'/hard/hard-order-evidence/ad/upload',
data: form,
options: dio.Options(
headers: {'DEVICE-ID': hardId},
contentType: 'multipart/form-data',
extra: {HttpManage.silentRequestKey: true},
),
);
if (res.data == null) return null;
return ServiceOrderEvidenceUploadResult.fromJson(res.data);
}
/// 静默上报食堂、超市 Android 终端最新运行状态;后台失败不得弹出业务 Toast。
static Future<void> terminalHeartbeat({
required String hardId,
required Map<String, dynamic> data,
}) async {
await HttpManage().dio.post(
'/hard/hard-api/ad/terminal-heartbeat',
data: data,
options: dio.Options(
headers: {'DEVICE-ID': hardId},
extra: {HttpManage.silentRequestKey: true},
),
);
}
/// 获取硬件配置信息
static Future<ServiceHardware?> bind(dynamic pa) async {
final res = await HttpManage().dio.post(
@@ -45,11 +127,92 @@ abstract class HardApi {
return ServiceUser.fromJson(res.data);
}
/// 仅按当前终端和识别身份查询本人预约,调用方不得补充商户或其他用户参数。
static Future<ServiceReservationMenuQuery?> queryReservationMenu(
ReservationMenuQueryRequest request,
) async {
final res = await HttpManage().dio.post(
"/hard/hard-api/ad/reservation-menu/query",
data: request.toJson(),
);
if (res.data == null) return null;
return ServiceReservationMenuQuery.fromJson(res.data);
}
/// 只按查询结果中的订单 ID 核销一笔预约订单。
static Future<bool> cancelReservationOrder({
required int orderId,
}) async {
final setting = await AppSettingTool.getSysSetting();
final res = await HttpManage().dio.post(
'/hard/hard-api/ad/reservation-menu/cancel',
data: {'orderId': orderId},
options: dio.Options(
headers: {
if (setting.serviceId != null) 'DEVICE-ID': setting.serviceId,
},
),
);
return res.data == true;
}
/// 一次提交多笔同一用户预约订单,服务端在同一事务内核销并合并生成一张兑换券。
static Future<bool> cancelReservationOrders({
required List<int> orderIds,
}) async {
final setting = await AppSettingTool.getSysSetting();
final hardId = setting.serviceId;
if (hardId == null || hardId.isEmpty) {
throw StateError('当前业务终端尚未绑定');
}
final res = await HttpManage().dio.post(
'/hard/hard-api/ad/reservation-menu/cancel-batch',
data: {'orderIds': orderIds},
options: dio.Options(headers: {'DEVICE-ID': hardId}),
);
return res.data == true;
}
/// 分页查询当前设备的预约核销记录,设备范围由服务端再次校验。
static Future<ServiceVerificationRecordPage?> verificationRecordPage({
required int current,
required int size,
required VerificationRecordQueryRequest request,
}) async {
final res = await HttpManage().dio.post(
'/hard/hard-api/ad/verification-record/page',
queryParameters: {'current': current, 'size': size},
data: request.toJson(),
options: dio.Options(headers: {'DEVICE-ID': request.hardId}),
);
if (res.data == null) return null;
return ServiceVerificationRecordPage.fromJson(res.data);
}
/// 分页查询当前设备的交易记录;scope=MERCHANT 时按终端所属商户查询全部设备。
static Future<ServiceTerminalOrderRecordPage?> terminalOrderRecordPage({
required int current,
required int size,
required TerminalOrderRecordQueryRequest request,
}) async {
final res = await HttpManage().dio.post(
'/hard/hard-api/ad/order-record/page',
queryParameters: {'current': current, 'size': size},
data: request.toJson(),
options: dio.Options(headers: {'DEVICE-ID': '${request.hardId}'}),
);
if (res.data == null) return null;
return ServiceTerminalOrderRecordPage.fromJson(res.data);
}
/// 创建硬件订单
static Future<String?> createHardwareOrder(dynamic formData) async {
final res = await HttpManage().dio.post(
"/hard/hard-api/ad/create-order",
data: formData,
options: dio.Options(
extra: {HttpManage.rejectBusinessErrorKey: true},
),
);
if (res.data == null) return null;
return res.data;
@@ -61,11 +224,45 @@ abstract class HardApi {
final res = await HttpManage().dio.post(
"/hard/hard-api/ad/create-hard-qr-code-order",
data: formData,
options: dio.Options(
extra: {HttpManage.rejectBusinessErrorKey: true},
),
);
if (res.data == null) return null;
return ServiceHardCreateHardQrCodeScanOrder.fromJson(res.data);
}
/// 创建与 Hardware 业务订单绑定的支付宝、微信聚合订单码。
static Future<ServiceHardAggregateOrderCodePay?> createAggregateOrderCodePay(
dynamic formData) async {
final res = await HttpManage().dio.post(
"/hard/hard-api/ad/create-aggregate-order-code-pay",
data: formData,
options: dio.Options(
extra: {HttpManage.rejectBusinessErrorKey: true},
),
);
if (res.data == null) return null;
return ServiceHardAggregateOrderCodePay.fromJson(res.data);
}
/// 查询聚合支付状态;成功响应表示 Hardware 业务订单已同步完成结算。
static Future<ServiceHardAggregateOrderCodePayStatus?>
getAggregateOrderCodePayStatus({
required String orderId,
required String aggregateNo,
}) async {
final res = await HttpManage().dio.get(
"/hard/hard-api/ad/aggregate-order-code-pay/status",
queryParameters: {
"orderId": orderId,
"aggregateNo": aggregateNo,
},
);
if (res.data == null) return null;
return ServiceHardAggregateOrderCodePayStatus.fromJson(res.data);
}
/// 根据订单id,获取订单详情
static Future<ServiceHardOrder?> getOrder(dynamic formData) async {
final res = await HttpManage().dio.get(
@@ -120,6 +317,69 @@ abstract class HardApi {
return ServiceGenerateInvoice.fromJson(res.data);
}
/// 业务终端按 DEVICE-ID 创建打印任务,目标打印机只由服务端绑定关系决定。
static Future<bool> createPrinterTask({
required String orderId,
String printType = '1',
}) async {
final setting = await AppSettingTool.getSysSetting();
final hardId = setting.serviceId;
if (hardId == null || hardId.isEmpty) throw StateError('当前业务终端尚未绑定');
final res = await HttpManage().dio.post(
'/hard/hard-api/ad/printer/task',
data: {
'orderId': int.parse(orderId),
'printType': printType,
},
options: dio.Options(headers: {'DEVICE-ID': hardId}),
);
return res.data != null;
}
/// 在线查询当前业务终端为指定订单生成的打印凭证,不读取终端本地打印记录。
static Future<List<ServicePrintRecord>> getPrinterRecords({
required String orderId,
}) async {
final setting = await AppSettingTool.getSysSetting();
final hardId = setting.serviceId;
if (hardId == null || hardId.isEmpty) throw StateError('当前业务终端尚未绑定');
final res = await HttpManage().dio.get(
'/hard/hard-api/ad/printer/records',
queryParameters: {'orderId': int.parse(orderId)},
options: dio.Options(headers: {'DEVICE-ID': hardId}),
);
if (res.data == null) return const [];
if (res.data is! List) throw const FormatException('打印凭证记录格式错误');
return (res.data as List)
.map((item) => ServicePrintRecord.fromJson(
Map<String, dynamic>.from(item as Map),
))
.toList(growable: false);
}
/// 提交当前业务终端已打印兑换券的人工重打原因并取得新任务。
static Future<ServicePrintRecord?> reprintVoucher({
required String historyId,
required String reason,
}) async {
final setting = await AppSettingTool.getSysSetting();
final hardId = setting.serviceId;
if (hardId == null || hardId.isEmpty) throw StateError('当前业务终端尚未绑定');
final res = await HttpManage().dio.post(
'/hard/hard-api/ad/printer/reprint',
data: {
'historyId': int.parse(historyId),
'reprintReason': reason,
},
options: dio.Options(headers: {'DEVICE-ID': hardId}),
);
if (res.data == null) return null;
if (res.data is! Map) throw const FormatException('兑换券重打任务格式错误');
return ServicePrintRecord.fromJson(
Map<String, dynamic>.from(res.data as Map),
);
}
/// 打印成功
static Future<bool> printInvoice(dynamic formData) async {
final res = await HttpManage().dio.get(
@@ -130,6 +390,85 @@ abstract class HardApi {
return true;
}
/// 读取当前食堂或超市终端绑定的逻辑打印机及 USB/ESC-POS 参数。
static Future<Map<String, dynamic>?> getPrinterBinding() async {
final setting = await AppSettingTool.getSysSetting();
final hardId = setting.serviceId;
if (hardId == null || hardId.isEmpty) throw StateError('当前业务终端尚未绑定设备 ID');
final res = await HttpManage().dio.get(
'/hard/hard-api/ad/printer/bootstrap',
options: dio.Options(headers: {'DEVICE-ID': hardId}),
);
if (res.data == null) return null;
if (res.data is! Map) throw const FormatException('打印机绑定配置格式错误');
return Map<String, dynamic>.from(res.data as Map);
}
/// 由当前业务终端把实际枚举到的 USB 打印机型号和 VID/PID 上报到绑定的 EY_PRINT 配置。
static Future<Map<String, dynamic>> reportPrinterInfo({
required String hardId,
required PrinterInfoReportRequest request,
}) async {
final res = await HttpManage().dio.post(
'/hard/hard-api/ad/printer/info',
data: request.toJson(),
options: dio.Options(headers: {'DEVICE-ID': hardId}),
);
if (res.data is! Map) throw const FormatException('打印机信息上报响应格式错误');
return Map<String, dynamic>.from(res.data as Map);
}
/// 拉取当前业务终端所绑定打印机的未完成任务,补偿 MQTT 离线窗口。
static Future<List<MqttInvoiceMsg>> getPrinterTasks() async {
final setting = await AppSettingTool.getSysSetting();
final hardId = setting.serviceId;
if (hardId == null || hardId.isEmpty) return const <MqttInvoiceMsg>[];
final res = await HttpManage().dio.get(
'/hard/hard-api/ad/printer/tasks',
options: dio.Options(
headers: {'DEVICE-ID': hardId},
extra: {HttpManage.silentRequestKey: true},
),
);
if (res.data is! List) return const <MqttInvoiceMsg>[];
return (res.data as List).whereType<Map>().map((item) {
final json = Map<String, dynamic>.from(item);
json['historyId'] ??= json['id'];
return MqttInvoiceMsg.fromJson(json);
}).toList(growable: false);
}
/// 由当前业务终端上报完整 USB 写入结果;请求失败由本地队列保留并在网络恢复后补报。
static Future<bool> reportPrinterResult({
required String taskId,
required String orderId,
required int retryVersion,
required bool success,
required int attemptCount,
String? errorMessage,
}) async {
final setting = await AppSettingTool.getSysSetting();
final hardId = setting.serviceId;
if (hardId == null || hardId.isEmpty) throw StateError('业务终端设备 ID 缺失');
final res = await HttpManage().dio.post(
'/hard/hard-api/ad/printer/result',
data: {
'taskId': int.parse(taskId),
'orderId': int.parse(orderId),
'retryVersion': retryVersion,
'success': success,
'attemptCount': attemptCount,
if (errorMessage != null && errorMessage.isNotEmpty)
'errorMessage': errorMessage,
},
options: dio.Options(
headers: {'DEVICE-ID': hardId},
extra: {HttpManage.silentRequestKey: true},
),
);
return res.data == true;
}
/// 离线同步
static Future<List<String>?> syncOfflineOrder(dynamic formData) async {
final res = await HttpManage().dio.post(
+98
View File
@@ -75,6 +75,104 @@ abstract class UserApi {
.toList();
}
/// 按固定主键上界游标读取本部门及子部门的 V3 人脸权威快照。
static Future<ServiceFaceSyncSnapshot> getFaceSyncSnapshot(
Map<String, dynamic> requestBody,
) async {
final res = await HttpManage().dio.post(
"/admin/user-face/ad/v3/sync-snapshot",
data: requestBody,
);
if (res.data is! Map) {
throw const FormatException('服务端未返回有效的人脸快照');
}
return ServiceFaceSyncSnapshot.fromJson(
Map<String, dynamic>.from(res.data as Map),
);
}
/// 由当前设备请求删除服务端部门范围内的人脸,单批最多 500 条。
///
/// 服务端会以 DEVICE-ID 解析真实绑定部门;业务错误必须抛给调用方,确保终端
/// 在未得到 deleted 或 alreadyAbsent 确认前不清理本地数据。
static Future<ServiceTerminalFaceDeleteResult> deleteTerminalFaces(
List<String> sampleIds,
) async {
final setting = await AppSettingTool.getSysSetting();
final hardId = setting.serviceId;
if (hardId == null) throw StateError('终端尚未绑定设备');
final res = await HttpManage().dio.post(
'/hard/hard-api/ad/face/delete',
data: {'sampleIds': sampleIds},
options: dio.Options(
headers: {'DEVICE-ID': hardId},
extra: {HttpManage.rejectBusinessErrorKey: true},
),
);
if (res.data is! Map) {
throw const FormatException('服务端未返回有效的人脸删除结果');
}
return ServiceTerminalFaceDeleteResult.fromJson(
Map<String, dynamic>.from(res.data as Map),
);
}
/// 由当前设备批量上报在库人脸静态图片质量,单批最多 500 条。
///
/// 服务端通过 DEVICE-ID 解析可信设备和部门;业务失败必须抛出,但调用方应将
/// 质量上报与既有人脸同步、注册和识别结果隔离,网络失败不得回滚本地质量。
static Future<ServiceFaceQualityReportResult> reportTerminalFaceQuality(
List<ServiceFaceQualityReportItem> items,
) async {
if (items.isEmpty) throw ArgumentError.value(items, 'items', '质量结果不能为空');
if (items.length > 500) {
throw ArgumentError.value(items.length, 'items', '单批最多上报500条质量结果');
}
final setting = await AppSettingTool.getSysSetting();
final hardId = setting.serviceId;
if (hardId == null) throw StateError('终端尚未绑定设备');
final res = await HttpManage().dio.post(
'/hard/hard-api/ad/face/quality/report',
data: {
'items': items.map((item) => item.toJson()).toList(growable: false),
},
options: dio.Options(
headers: {'DEVICE-ID': hardId},
extra: {HttpManage.rejectBusinessErrorKey: true},
),
);
if (res.data is! Map) {
throw const FormatException('服务端未返回有效的人脸质量上报结果');
}
return ServiceFaceQualityReportResult.fromJson(
Map<String, dynamic>.from(res.data as Map),
);
}
/// 竞争当前商户部门的人脸质量上报租约。
///
/// Hardware 仍会在实际上报时二次验证租约;未取得租约不是业务失败,调用方应
/// 直接跳过本轮质量批次,避免同一单位的多台终端重复发送相同在库结果。
static Future<ServiceFaceQualityLease>
acquireTerminalFaceQualityLease() async {
final setting = await AppSettingTool.getSysSetting();
final hardId = setting.serviceId;
if (hardId == null) throw StateError('终端尚未绑定设备');
final res = await HttpManage().dio.post(
'/hard/hard-api/ad/face/quality/lease',
options: dio.Options(
headers: {'DEVICE-ID': hardId},
extra: {HttpManage.rejectBusinessErrorKey: true},
),
);
if (res.data is! Map) {
throw const FormatException('服务端未返回有效的人脸质量租约');
}
return ServiceFaceQualityLease.fromJson(
Map<String, dynamic>.from(res.data as Map),
);
}
/// 获取租户列表
static Future<List<ServiceTenant>?> getTenantList() async {
final res = await HttpManage().dio.get("/admin/tenant/list");