diff --git a/bms-back/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java b/bms-back/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java index 16a4676d..bb88734d 100644 --- a/bms-back/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java +++ b/bms-back/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java @@ -4,6 +4,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; /** * 启动程序 @@ -11,6 +12,7 @@ import org.springframework.scheduling.annotation.EnableAsync; * @author ruoyi */ @EnableAsync +@EnableScheduling @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) public class RuoYiApplication { diff --git a/bms-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/price/PriceStatusController.java b/bms-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/price/PriceStatusController.java new file mode 100644 index 00000000..c3a05a8c --- /dev/null +++ b/bms-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/price/PriceStatusController.java @@ -0,0 +1,39 @@ +package com.ruoyi.web.controller.price; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.price.task.BmsStatusRefreshTask; + +/** + * 状态自动刷新 - 手动触发入口 + * + *

日常由 BmsStatusRefreshTask 每天 02:00 跑,特殊情况可手动触发。 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/price/status") +public class PriceStatusController extends BaseController +{ + @Autowired + private BmsStatusRefreshTask refreshTask; + + /** + * 立即刷新 bms_product / bms_product_country 的 status 字段 + */ + @PreAuthorize("@ss.hasPermi('price:product:edit')") + @PostMapping("/refresh") + public AjaxResult refresh() + { + int[] counts = refreshTask.refreshNow(); + String msg = MessageUtils.message("price.status.refresh.success", + String.valueOf(counts[0]), String.valueOf(counts[1])); + return success(msg); + } +} \ No newline at end of file diff --git a/bms-back/ruoyi-admin/src/main/resources/i18n/messages.properties b/bms-back/ruoyi-admin/src/main/resources/i18n/messages.properties index c4863965..3479f047 100644 --- a/bms-back/ruoyi-admin/src/main/resources/i18n/messages.properties +++ b/bms-back/ruoyi-admin/src/main/resources/i18n/messages.properties @@ -115,4 +115,7 @@ price.quote.bizDateInvalid=业务日期格式必须为yyyy-MM-dd price.quote.fixedPackageNoDetails=套餐固定价格没有明细,使用产品默认价格 price.quote.noCustomerPackageOverride=没有客户套餐覆盖,使用产品默认运费/挂号费 price.quote.quoteIdRequired=报价ID为空 -price.quote.hitCustomerFixedPackageDetail=命中客户套餐固定价格明细 \ No newline at end of file +price.quote.hitCustomerFixedPackageDetail=命中客户套餐固定价格明细 + +# ---- 状态自动刷新 ---- +price.status.refresh.success=状态刷新完成:bms_product 更新 {0} 行,bms_product_country 更新 {1} 行 \ No newline at end of file diff --git a/bms-back/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java b/bms-back/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java index abe6b2de..b9f7c20c 100644 --- a/bms-back/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java +++ b/bms-back/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java @@ -154,7 +154,15 @@ public class SysJobController extends BaseController @PutMapping("/changeStatus") public AjaxResult changeStatus(@RequestBody SysJob job) throws SchedulerException { + if (job == null || job.getJobId() == null) + { + return error("任务ID不能为空,请刷新列表后重试"); + } SysJob newJob = jobService.selectJobById(job.getJobId()); + if (newJob == null) + { + return error("任务不存在或已被删除,请刷新列表后重试"); + } newJob.setStatus(job.getStatus()); return toAjax(jobService.changeStatus(newJob)); } diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/task/BmsStatusRefreshTask.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/task/BmsStatusRefreshTask.java new file mode 100644 index 00000000..cebd0e94 --- /dev/null +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/task/BmsStatusRefreshTask.java @@ -0,0 +1,58 @@ +package com.ruoyi.price.task; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import com.ruoyi.price.mapper.BmsProductCountryMapper; +import com.ruoyi.price.mapper.BmsProductMapper; + +/** + * bms_product / bms_product_country 状态自动刷新 + * + *

每天 02:00 根据 effective_date / expire_date 与当天日期,回填 status: + *

+ * + *

对应字典 bms_product_effect_status 的 value=ACTIVE/PENDING/EXPIRED。 + * 实际存储使用 0/1/2 与列 comment 保持一致;前端 dict-tag 需做 value 映射或调整 dict_value。 + * + *

手动触发:调用 refreshNow()(运维/测试用,详见 StatusRefreshController)。 + * + * @author ruoyi + */ +@Component +public class BmsStatusRefreshTask +{ + private static final Logger log = LoggerFactory.getLogger(BmsStatusRefreshTask.class); + + @Autowired + private BmsProductMapper productMapper; + + @Autowired + private BmsProductCountryMapper productCountryMapper; + + /** 每天 02:00 跑(cron: sec min hour DOM month DOW) */ + @Scheduled(cron = "0 0 2 * * ?") + public void scheduled() + { + refreshNow(); + } + + /** + * 立即刷新两表状态。返回 [product 影响行数, country 影响行数]。 + * 异常时打印 ERROR 并原样抛出,便于调用方(手动触发接口)回滚事务。 + */ + public int[] refreshNow() + { + int productCount = productMapper.refreshStatusByDates(); + int countryCount = productCountryMapper.refreshStatusByDates(); + log.info("BmsStatusRefreshTask done: bms_product={}, bms_product_country={}", + productCount, countryCount); + return new int[] { productCount, countryCount }; + } +} \ No newline at end of file diff --git a/bms-back/sql/price/product_status_alter.sql b/bms-back/sql/price/product_status_alter.sql new file mode 100644 index 00000000..bee4e381 --- /dev/null +++ b/bms-back/sql/price/product_status_alter.sql @@ -0,0 +1,40 @@ +-- ============================================================ +-- bms_product / bms_product_country 自动状态回填 +-- ============================================================ +-- 背景: +-- bms_product.status char(1) DEFAULT '0' COMMENT '0待生效 1已生效 2已失效' +-- bms_product_country.status char(1) DEFAULT '0' COMMENT '0待生效 1已生效 2已失效' +-- 字典 bms_product_effect_status value=ACTIVE/PENDING/EXPIRED +-- 自动刷新: +-- BmsStatusRefreshTask 每天 02:00 调用两表 Mapper.refreshStatusByDates() +-- 即本文件末尾的回填 SQL(每日定时跑同样效果) +-- 现状验证: +-- bms_product.status 当前值仅 '0' / '1'('2' = EXPIRED 尚未触发,因今天 2026-09-09 +-- 还没到任一 expire_date);bms_product_country.status 同上 +-- ============================================================ + +SET NAMES utf8mb4; + +-- ---------- 一次性回填(幂等:重跑结果一致) ---------- +-- bms_product: +-- expire_date 非空且 <= 今天 → '2' 已失效 +-- effective_date 为空 / 未来 → '0' 待生效 +-- 其余(已生效且仍在有效期) → '1' 已生效 +UPDATE bms_product p +SET p.status = ( + CASE + WHEN p.expire_date IS NOT NULL AND p.expire_date <= CURDATE() THEN '2' + WHEN p.effective_date IS NULL OR p.effective_date > CURDATE() THEN '0' + ELSE '1' + END +); + +-- bms_product_country:同上规则(多了 detail 级过期判断) +UPDATE bms_product_country c +SET c.status = ( + CASE + WHEN c.expire_date IS NOT NULL AND c.expire_date <= CURDATE() THEN '2' + WHEN c.effective_date IS NULL OR c.effective_date > CURDATE() THEN '0' + ELSE '1' + END +); \ No newline at end of file diff --git a/bms-front/src/api/price/status.js b/bms-front/src/api/price/status.js new file mode 100644 index 00000000..a83a3423 --- /dev/null +++ b/bms-front/src/api/price/status.js @@ -0,0 +1,15 @@ +import request from '@/utils/request' + +/** + * 立即刷新 bms_product / bms_product_country 的 status 字段 + * + * 日常由后端定时任务 BmsStatusRefreshTask 每天 02:00 自动跑, + * 此接口用于运维/测试手动触发。返回 success msg 形如: + * 「状态刷新完成:bms_product 更新 X 行,bms_product_country 更新 Y 行」 + */ +export function refreshStatus() { + return request({ + url: '/price/status/refresh', + method: 'post' + }) +} \ No newline at end of file diff --git a/bms-front/src/views/monitor/job/index.vue b/bms-front/src/views/monitor/job/index.vue index 229236bb..0a05d309 100644 --- a/bms-front/src/views/monitor/job/index.vue +++ b/bms-front/src/views/monitor/job/index.vue @@ -63,6 +63,7 @@