fix:设备查询菜品频繁问题
fix:退看流水对账问题
This commit is contained in:
1 parent
917e7b4e83
commit
4d7bc5ed33
12 files changed
+137
-32
No files matched your search
@@ -7,7 +7,7 @@ flutter pub run build_runner build --delete-conflicting-outputs~
|
||||
## flutter打包
|
||||
|
||||
```
|
||||
flutter build apk --build-name 1.4.5 --build-number 145
|
||||
flutter build apk --build-name 1.4.6 --build-number 146
|
||||
--build-name为版本号(展示)
|
||||
--build-number为版本号(内部比较)
|
||||
```
|
||||
|
||||
@@ -15,7 +15,7 @@ abstract class ProductApi {
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// 菜品商品
|
||||
/// 查询指定单日的食堂菜谱商品。
|
||||
static Future<List<ServiceProduct>?> canteenProduct(
|
||||
dynamic pa,
|
||||
) async {
|
||||
@@ -29,6 +29,20 @@ abstract class ProductApi {
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// 按闭区间查询多日食堂菜谱商品,响应结构与单日接口保持一致。
|
||||
static Future<List<ServiceProduct>?> canteenProductsByDateRange(
|
||||
Map<String, dynamic> requestBody,
|
||||
) async {
|
||||
final res = await HttpManage().dio.post(
|
||||
"/canteen/canteenDailyMenu/ad/getCanteenListByDateRange",
|
||||
data: requestBody,
|
||||
);
|
||||
if (res.data == null) return null;
|
||||
return res.data
|
||||
?.map<ServiceProduct>((e) => ServiceProduct.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// 超市分类
|
||||
static Future<List<ServiceClassify>?> supermarketClassify(
|
||||
dynamic pa,
|
||||
|
||||
@@ -77,7 +77,8 @@ class ServiceProduct {
|
||||
/// 分类附带的餐段类型;为空时不参与本地餐段筛选。
|
||||
String? categoryType;
|
||||
|
||||
/// 本次同步写入的供应日期,接口未返回时由同步任务补齐。
|
||||
/// 菜谱供应日期,来源于单日和范围接口共同返回的 `startTime` 字段。
|
||||
@JsonKey(name: 'startTime')
|
||||
DateTime? saleDate;
|
||||
|
||||
/// 是否涨价
|
||||
|
||||
@@ -40,9 +40,9 @@ ServiceProduct _$ServiceProductFromJson(Map<String, dynamic> json) =>
|
||||
stock: JsonTool.anyToNullableInt(json['stock']),
|
||||
sort: (json['sort'] as num?)?.toInt(),
|
||||
categoryType: json['categoryType'] as String?,
|
||||
saleDate: json['saleDate'] == null
|
||||
saleDate: json['startTime'] == null
|
||||
? null
|
||||
: DateTime.parse(json['saleDate'] as String),
|
||||
: DateTime.parse(json['startTime'] as String),
|
||||
isPriceIncreased:
|
||||
JsonTool.stringToBoolean(json['isPriceIncreased'] as String?),
|
||||
spuType: (json['spuType'] as num?)?.toInt(),
|
||||
@@ -64,7 +64,7 @@ Map<String, dynamic> _$ServiceProductToJson(ServiceProduct instance) =>
|
||||
'stock': instance.stock,
|
||||
'sort': instance.sort,
|
||||
'categoryType': instance.categoryType,
|
||||
'saleDate': instance.saleDate?.toIso8601String(),
|
||||
'startTime': instance.saleDate?.toIso8601String(),
|
||||
'isPriceIncreased': JsonTool.booleanToString(instance.isPriceIncreased),
|
||||
'spuType': instance.spuType,
|
||||
'comboItems': instance.comboItems,
|
||||
|
||||
@@ -4,8 +4,11 @@ import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
/// 定时任务结构化存储入口,隔离调度器与 Floor DAO 的实体转换细节。
|
||||
abstract final class ScheduledTaskStorageTool {
|
||||
/// 旧版本商品同步任务的默认 Cron,仅用于识别尚未升级的默认配置。
|
||||
static const String _legacyProductSyncCron = '0 25 0 * * *';
|
||||
/// 最早版本商品同步任务的默认 Cron,仅用于识别尚未升级的关闭配置。
|
||||
static const String _legacyDisabledProductSyncCron = '0 25 0 * * *';
|
||||
|
||||
/// 上一版本商品同步任务的默认 Cron,仅用于把未自定义配置升级为每 3 小时。
|
||||
static const String _legacyFiveMinuteProductSyncCron = '0 */5 * * * *';
|
||||
|
||||
/// 当前应用数据库中的任务配置 DAO;数据库由 AppControl 在调度器启动前完成初始化。
|
||||
static ScheduledTaskDao get _taskDao =>
|
||||
@@ -89,9 +92,9 @@ abstract final class ScheduledTaskStorageTool {
|
||||
required bool idleOnly,
|
||||
}) =>
|
||||
type == ScheduledTaskType.productSync &&
|
||||
!enabled &&
|
||||
cron == _legacyProductSyncCron &&
|
||||
idleOnly;
|
||||
idleOnly &&
|
||||
((!enabled && cron == _legacyDisabledProductSyncCron) ||
|
||||
(enabled && cron == _legacyFiveMinuteProductSyncCron));
|
||||
|
||||
/// 从 scheduled_task 读取有效任务,并转换为调度器使用的配置和最近执行快照。
|
||||
///
|
||||
|
||||
@@ -304,7 +304,7 @@ class SyncCheckTool {
|
||||
ScheduledTaskConfig(
|
||||
type: ScheduledTaskType.productSync,
|
||||
enabled: true,
|
||||
cron: '0 */5 * * * *',
|
||||
cron: '0 0 */3 * * *',
|
||||
idleOnly: true,
|
||||
),
|
||||
ScheduledTaskConfig(
|
||||
|
||||
@@ -209,22 +209,20 @@ abstract class SyncProductTool {
|
||||
|
||||
/// 按终端业态执行常规商品同步。
|
||||
///
|
||||
/// 食堂常规同步保留今天起连续七天的缓存范围;MQTT 食堂变更必须改用 [syncTodayCanteenProduct]。
|
||||
/// 食堂常规同步保留今天起连续七天的缓存范围。
|
||||
/// MQTT 食堂变更和进入食堂收银页只刷新当天,使用 [syncTodayCanteenProduct]。
|
||||
static Future<void> syncProduct() async {
|
||||
final sysSetting = await AppSettingTool.getSysSetting();
|
||||
final sysType = sysSetting.sysType;
|
||||
try {
|
||||
if (sysType == SysType.CANTEEN) {
|
||||
// 获取从今天开始的所有日期时间
|
||||
final DateTime now = DateTime.now();
|
||||
for (int i = 0; i < 7; i++) {
|
||||
final DateTime date = now.add(Duration(days: i));
|
||||
final success = await _syncCanteenProductForDay(
|
||||
date: date,
|
||||
merchantId: sysSetting.serviceMerchantId,
|
||||
);
|
||||
if (!success) return;
|
||||
}
|
||||
final startDate = DateTime.now();
|
||||
final success = await _syncCanteenProductForDateRange(
|
||||
startDate: startDate,
|
||||
endDate: startDate.add(const Duration(days: 6)),
|
||||
merchantId: sysSetting.serviceMerchantId,
|
||||
);
|
||||
if (!success) return;
|
||||
} else if (sysType == SysType.SUPERMARKET) {
|
||||
// 超市
|
||||
final app = Get.find<AppControl>();
|
||||
@@ -275,7 +273,7 @@ abstract class SyncProductTool {
|
||||
|
||||
/// 仅同步终端本地时区的当天食堂菜品,并在成功后记录食堂商品同步时间。
|
||||
///
|
||||
/// MQTT 菜品变更使用此入口,确保请求和本地替换都不会扩展到未来一周。
|
||||
/// MQTT 菜品变更和进入食堂收银页使用此入口,避免刷新当天时额外拉取未来六天。
|
||||
static Future<bool> syncTodayCanteenProduct() async {
|
||||
final today = DateTime.now();
|
||||
final sysSetting = await AppSettingTool.getSysSetting();
|
||||
@@ -329,6 +327,75 @@ abstract class SyncProductTool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 一次请求指定闭区间的食堂菜谱,并按接口返回的供应日期逐日原子替换本地快照。
|
||||
///
|
||||
/// 返回 `false` 表示接口无有效响应或任一记录缺少合法供应日期,此时不会修改任何日期的本地数据。
|
||||
static Future<bool> _syncCanteenProductForDateRange({
|
||||
required DateTime startDate,
|
||||
required DateTime endDate,
|
||||
required String? merchantId,
|
||||
}) async {
|
||||
final startDay = DateUtil.formatDate(
|
||||
startDate,
|
||||
format: DateFormats.y_mo_d,
|
||||
);
|
||||
final endDay = DateUtil.formatDate(
|
||||
endDate,
|
||||
format: DateFormats.y_mo_d,
|
||||
);
|
||||
final requestMerchantId = int.tryParse(merchantId ?? '');
|
||||
if (requestMerchantId == null) {
|
||||
logger.w('|$_tag|范围菜谱同步缺少有效商户 ID,保留本地旧快照');
|
||||
return false;
|
||||
}
|
||||
final canteenList = await ProductApi.canteenProductsByDateRange({
|
||||
"startDate": startDay,
|
||||
"endDate": endDay,
|
||||
"hourType": null,
|
||||
"categoryId": null,
|
||||
"merchantId": requestMerchantId,
|
||||
});
|
||||
if (canteenList == null) return false;
|
||||
|
||||
final productsByDay = <String, List<ServiceProduct>>{};
|
||||
var currentDate = DateTime.parse(startDay);
|
||||
final lastDate = DateTime.parse(endDay);
|
||||
while (!currentDate.isAfter(lastDate)) {
|
||||
productsByDay[DateUtil.formatDate(
|
||||
currentDate,
|
||||
format: DateFormats.y_mo_d,
|
||||
)] = <ServiceProduct>[];
|
||||
currentDate = currentDate.add(const Duration(days: 1));
|
||||
}
|
||||
|
||||
for (final product in canteenList) {
|
||||
final saleDate = product.saleDate;
|
||||
if (saleDate == null) {
|
||||
logger.w('|$_tag|范围菜谱响应缺少供应日期,保留本地旧快照:${product.toJson()}');
|
||||
return false;
|
||||
}
|
||||
final saleDay = DateUtil.formatDate(
|
||||
saleDate,
|
||||
format: DateFormats.y_mo_d,
|
||||
);
|
||||
final dayProducts = productsByDay[saleDay];
|
||||
if (dayProducts == null) {
|
||||
logger
|
||||
.w('|$_tag|范围菜谱响应日期超出请求区间,保留本地旧快照:请求$startDay至$endDay,响应$saleDay');
|
||||
return false;
|
||||
}
|
||||
dayProducts.add(product);
|
||||
}
|
||||
|
||||
for (final entry in productsByDay.entries) {
|
||||
await _batchUpdateCaneenProduct(
|
||||
list: entry.value,
|
||||
dayStr: entry.key,
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// 同步食堂商品 spu start
|
||||
// **************************************************************************
|
||||
|
||||
@@ -15,8 +15,8 @@ class Constants {
|
||||
static const pwdEncKey = 'pigxpigxpigxpigx';
|
||||
|
||||
/// 服务 url 线上
|
||||
// static const apiUrl = 'https://dzc-test-1.fy.takin.cc';
|
||||
static const apiUrl = 'https://www.zianxin.com/api';
|
||||
static const apiUrl = 'https://dzc-test-1.fy.takin.cc';
|
||||
// static const apiUrl = 'https://www.zianxin.com/api';
|
||||
|
||||
// static const apiUrl = 'https://dzc-test-1.fy.takin.cc';
|
||||
|
||||
|
||||
@@ -181,7 +181,10 @@ class CanteenProductController extends GetxController with OrderMixin {
|
||||
}
|
||||
sysType = SysType.CANTEEN;
|
||||
_listenPayTypeSelectCallback();
|
||||
// 收银页只读取本地商品快照;定时和 MQTT 商品更新统一由 productSync 在安全窗口执行。
|
||||
// 进入收银页时在线刷新当天菜谱;接口失败或离线仍读取本地快照,不能阻断收银。
|
||||
if (AppNetworkTool.isAutoNetwork()) {
|
||||
await SyncProductTool.syncTodayCanteenProduct();
|
||||
}
|
||||
try {
|
||||
await _getMchRule();
|
||||
} catch (e, stack) {
|
||||
@@ -260,7 +263,7 @@ class CanteenProductController extends GetxController with OrderMixin {
|
||||
/// 在结算弹框中按商品编码修改真实购物车数量,并返回包含最新金额与库存的展示快照。
|
||||
///
|
||||
/// 弹框只负责触发操作,购物车、餐费和副屏仍由当前页面 Controller 统一维护;本地库存
|
||||
/// 可能因五分钟后台同步而滞后,因此这里不按库存上限拦截,最终以服务端下单校验为准。
|
||||
/// 仍可能在后台同步间隔内变化,因此这里不按库存上限拦截,最终以服务端下单校验为准。
|
||||
Future<CashierCheckoutSnapshot?> _changeCheckoutQuantity(
|
||||
String productServiceUniqueCode,
|
||||
int quantity,
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
name: pj_supermarket_cloud
|
||||
description: "资安信智慧后勤-测试"
|
||||
publish_to: "none"
|
||||
version: 1.4.5+145
|
||||
version: 1.4.6+146
|
||||
|
||||
environment:
|
||||
sdk: ^3.6.0
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
/// 验证每日菜谱单日与范围接口共用的日期字段能够稳定映射到终端商品模型。
|
||||
void main() {
|
||||
test('每日菜谱 startTime 映射为本地供应日期', () {
|
||||
final product = ServiceProduct.fromJson({
|
||||
'spuId': '1001',
|
||||
'startTime': '2026-09-02',
|
||||
});
|
||||
|
||||
expect(product.saleDate, DateTime(2026, 9, 2));
|
||||
expect(product.toJson()['startTime'], '2026-09-02T00:00:00.000');
|
||||
expect(product.toJson().containsKey('saleDate'), isFalse);
|
||||
});
|
||||
}
|
||||
@@ -2,15 +2,15 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
/// 验证商品同步任务使用既有调度器,并按五分钟周期等待终端空闲后自动执行。
|
||||
/// 验证商品同步任务使用既有调度器,并按三小时周期等待终端空闲后自动执行。
|
||||
void main() {
|
||||
test('商品同步默认开启且每五分钟仅空闲执行', () {
|
||||
test('商品同步默认开启且每三小时仅空闲执行', () {
|
||||
final config = SyncCheckTool.defaultConfigs.singleWhere(
|
||||
(item) => item.type == ScheduledTaskType.productSync,
|
||||
);
|
||||
|
||||
expect(config.enabled, isTrue);
|
||||
expect(config.cron, '0 */5 * * * *');
|
||||
expect(config.cron, '0 0 */3 * * *');
|
||||
expect(config.idleOnly, isTrue);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user