解决特权时间段的Bug

This commit is contained in:
2026-05-14 23:53:37 +08:00
parent 5585f2c08a
commit 5958e8412d
4 changed files with 192 additions and 12 deletions

View File

@@ -50,7 +50,7 @@ public class LbPurchaseApplyController {
} }
@PostMapping("/add") @PostMapping("/add")
@Operation(summary = "新增") @Operation(summary = "新增或按申请人电话更新", description = "同一租户下若 applyPhone 已存在则更新该条,否则新增")
public ResponseEntity<Map<String, Object>> add( public ResponseEntity<Map<String, Object>> add(
@Parameter(description = "实体", required = true) @RequestBody LbPurchaseApply entity) { @Parameter(description = "实体", required = true) @RequestBody LbPurchaseApply entity) {
Map<String, Object> result = lbPurchaseApplyService.add(entity); Map<String, Object> result = lbPurchaseApplyService.add(entity);

View File

@@ -13,6 +13,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.rj.entity.LbDailyUserTradeReport; import com.rj.entity.LbDailyUserTradeReport;
import com.rj.entity.LbPurchaseApply; import com.rj.entity.LbPurchaseApply;
import com.rj.util.IsoWorkdayUtils;
import com.rj.mapper.LbDailyUserTradeReportMapper; import com.rj.mapper.LbDailyUserTradeReportMapper;
import com.rj.mapper.LbPurchaseApplyMapper; import com.rj.mapper.LbPurchaseApplyMapper;
import com.rj.service.ILbPurchaseApplyService; import com.rj.service.ILbPurchaseApplyService;
@@ -26,6 +27,7 @@ import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -46,21 +48,27 @@ public class LbPurchaseApplyServiceImpl
@Value("${langchain4j.open-ai.chat-model.model-name:qwen-plus}") @Value("${langchain4j.open-ai.chat-model.model-name:qwen-plus}")
private String dashScopeChatModel; private String dashScopeChatModel;
private static final DateTimeFormatter TRY_DATE_MM_DD = DateTimeFormatter.ofPattern("MM.dd");
/** 试用期包含的工作日天数(周一至周五),周六日不计入。 */
private static final int TRIAL_WEEKDAY_COUNT = 3;
private static final String PARSE_SYSTEM_PROMPT = """ private static final String PARSE_SYSTEM_PROMPT = """
你是信息抽取助手。用户会提供一段「进货申请」相关的自然语言订货信息。 你是信息抽取助手。用户会提供一段「进货申请」相关的自然语言订货信息。
请只输出一个 JSON 对象,不要 markdown 代码块,不要解释性文字。 请只输出一个 JSON 对象,不要 markdown 代码块,不要解释性文字。
字段均为可选;无法从原文推断的字段请省略或设为 null。 字段均为可选;无法从原文推断的字段请省略或设为 null。
tryDate 试用日期:若同时给出 applyDate服务端会按 3 个工作日规则重算并覆盖本字段;模型可省略 tryDate。
字段名与含义JSON 键名必须完全一致): 字段名与含义JSON 键名必须完全一致):
applyUser 申请人姓名; applyUser 申请人姓名;
applyPhone 申请人电话; applyPhone 申请人电话;
tryDate 试用日期(原文怎么说就怎么填字符串,如 yyyy-MM-dd 或中文描述起始日期是申请日期applyDate结束日期申请日期applyDate 加 2天跳过周六日 ,比如: 05.14 到 05.16 tryDate 试用日期(可省略;有 applyDate 时由服务端按 3 个工作日重算)
age 年龄(整数); age 年龄(整数);
workExperience 从业经历; workExperience 从业经历;
colleagueName 同事姓名; colleagueName 同事姓名;
colleaguePhone 同事电话; colleaguePhone 同事电话;
teamLeader 团队长; teamLeader 团队长;
teamBigLeader 双收益团队长; teamBigLeader 双收益团队长;
applyDate 申请日期,格式 yyyy-MM-dd若原文无明确日期可省略 applyDate 申请日期,格式 yyyy-MM-dd如果年的信息为空,请用系统的当前年替换
不要输出 id、tenantId、createTime、updateTime。 不要输出 id、tenantId、createTime、updateTime。
"""; """;
@@ -113,11 +121,18 @@ public class LbPurchaseApplyServiceImpl
String json = normalizeJson(raw); String json = normalizeJson(raw);
LbPurchaseApply entity = objectMapper.readValue(json, LbPurchaseApply.class); LbPurchaseApply entity = objectMapper.readValue(json, LbPurchaseApply.class);
log.debug(
"parseFromOrderInfoByLlmLLM JSON 解析后 applyDate={} tryDate={}",
entity.getApplyDate(),
entity.getTryDate());
entity.setTenantId(tenantId.trim()); entity.setTenantId(tenantId.trim());
entity.setId(null); entity.setId(null);
entity.setCreateTime(null); entity.setCreateTime(null);
entity.setUpdateTime(null); entity.setUpdateTime(null);
fillTryDateFromApplyDate(entity);
result.put("success", true); result.put("success", true);
result.put("message", "解析成功"); result.put("message", "解析成功");
result.put("data", entity); result.put("data", entity);
@@ -140,6 +155,95 @@ public class LbPurchaseApplyServiceImpl
} }
} }
/**
* 试用日期:只要有 {@code applyDate},一律按服务端规则计算(覆盖模型可能错填的 tryDate
* 试用期为连续 {@link #TRIAL_WEEKDAY_COUNT} 个工作日(周一至周五),周六日为无效日不计入。
* 展示起始为申请日 applyDate日历日结束日为从「申请日及之后首个工作日」起计的第 3 个工作日(含该首日)。
* 若申请日年份不是当年,先按 {@link LocalDate#withYear(int)} 校正为当年同月同日(闰年 2 月 29 日在平年变为 2 月 28 日)。
*/
private void fillTryDateFromApplyDate(LbPurchaseApply entity) {
if (entity == null) {
return;
}
LocalDate applyDate = entity.getApplyDate();
if (applyDate == null) {
log.info("试用日期applyDate 为空,不覆盖,保留 LLM tryDate={}", entity.getTryDate());
return;
}
int currentYear = LocalDate.now().getYear();
if (applyDate.getYear() != currentYear) {
applyDate = applyDate.withYear(currentYear);
entity.setApplyDate(applyDate);
}
String existingTryDate = entity.getTryDate();
LocalDate trialFirstWeekday = firstWeekdayOnOrAfter(applyDate);
LocalDate endDate = endOfInclusiveWeekdaySpan(trialFirstWeekday, TRIAL_WEEKDAY_COUNT);
String computed =
applyDate.format(TRY_DATE_MM_DD) + "" + endDate.format(TRY_DATE_MM_DD);
entity.setTryDate(computed);
}
/** 申请日及之后第一个周一至周五(申请日本身为工作日时即为当天)。 */
private static LocalDate firstWeekdayOnOrAfter(LocalDate d) {
LocalDate x = d;
while (IsoWorkdayUtils.isWeekend(x)) {
x = x.plusDays(1);
}
return x;
}
/**
* 从 {@code firstWeekday} 起连续 {@code inclusiveWeekdays} 个工作日(仅周一至周五,含起点当日),返回该段最后一天。
* 若起点落在周六日,会先顺延到下一工作日再计数,且返回值保证不为周六日。
*/
private LocalDate endOfInclusiveWeekdaySpan(LocalDate firstWeekday, int inclusiveWeekdays) {
if (inclusiveWeekdays < 1) {
throw new IllegalArgumentException("inclusiveWeekdays must be >= 1");
}
LocalDate d = firstWeekday;
while (IsoWorkdayUtils.isWeekend(d)) {
if (log.isDebugEnabled()) {
log.debug(
"trialSpan起点为周末顺延 cursor={} dow={} isoDow={}",
d,
d.getDayOfWeek(),
IsoWorkdayUtils.isoDayOfWeek(d));
}
d = d.plusDays(1);
}
int weekdaysRemaining = inclusiveWeekdays;
int guard = 0;
while (true) {
boolean weekend = IsoWorkdayUtils.isWeekend(d);
if (log.isDebugEnabled()) {
log.debug(
"trialSpancursor={} dow={} isoDow={} weekend={} weekdaysRemaining={}",
d,
d.getDayOfWeek(),
IsoWorkdayUtils.isoDayOfWeek(d),
weekend,
weekdaysRemaining);
}
if (!weekend) {
weekdaysRemaining--;
if (weekdaysRemaining == 0) {
return d;
}
}
d = d.plusDays(1);
guard++;
if (guard > 400) {
throw new IllegalStateException(
"trialSpan超过 400 步仍未结束firstWeekday="
+ firstWeekday
+ " inclusiveWeekdays="
+ inclusiveWeekdays);
}
}
}
private static String normalizeJson(String content) { private static String normalizeJson(String content) {
if (content == null) { if (content == null) {
return ""; return "";
@@ -180,17 +284,39 @@ public class LbPurchaseApplyServiceImpl
return result; return result;
} }
LocalDateTime now = LocalDateTime.now(); String tenantId = entity.getTenantId().trim();
if (entity.getCreateTime() == null) { String phone = entity.getApplyPhone().trim();
entity.setCreateTime(now); entity.setTenantId(tenantId);
} entity.setApplyPhone(phone);
entity.setUpdateTime(now);
boolean ok = this.save(entity); LocalDateTime now = LocalDateTime.now();
result.put("success", ok); LambdaQueryWrapper<LbPurchaseApply> phoneQuery = new LambdaQueryWrapper<>();
result.put("message", ok ? "新增成功" : "新增失败"); phoneQuery.eq(LbPurchaseApply::getTenantId, tenantId)
.eq(LbPurchaseApply::getApplyPhone, phone)
.orderByDesc(LbPurchaseApply::getId)
.last("LIMIT 1");
LbPurchaseApply existing = this.getOne(phoneQuery);
boolean ok;
if (existing != null) {
entity.setId(existing.getId());
entity.setCreateTime(existing.getCreateTime());
entity.setUpdateTime(now);
ok = this.updateById(entity);
result.put("success", ok);
result.put("message", ok ? "按申请人电话已更新" : "更新失败");
} else {
entity.setId(null);
if (entity.getCreateTime() == null) {
entity.setCreateTime(now);
}
entity.setUpdateTime(now);
ok = this.save(entity);
result.put("success", ok);
result.put("message", ok ? "新增成功" : "新增失败");
}
if (ok) { if (ok) {
result.put("data", entity); result.put("data", this.getById(entity.getId()));
} }
return result; return result;
} catch (Exception e) { } catch (Exception e) {

View File

@@ -0,0 +1,25 @@
package com.rj.util;
import java.time.LocalDate;
import java.time.temporal.WeekFields;
/**
* ISO-8601 工作日判断(与 {@link LocalDate} 默认历法一致)。
* <p>
* 周末判定使用 {@link WeekFields#ISO} 的 day-of-week 数值:周一=1 … 周日=7周六、周日为 6、7。
* 与 {@link java.time.LocalDate#getDayOfWeek()} 结论一致;调试时可打印 {@link #isoDayOfWeek(LocalDate)} 便于核对年份。
*/
public final class IsoWorkdayUtils {
private IsoWorkdayUtils() {}
/** ISO 星期字段数值1=周一 … 7=周日。 */
public static int isoDayOfWeek(LocalDate d) {
return d.get(WeekFields.ISO.dayOfWeek());
}
/** 周六、周日为 trueisoDow 为 6 或 7。 */
public static boolean isWeekend(LocalDate d) {
return isoDayOfWeek(d) >= 6;
}
}

View File

@@ -0,0 +1,29 @@
package com.rj.util;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class IsoWorkdayUtilsTest {
@Test
void may162026_isSaturday_weekend() {
LocalDate d = LocalDate.of(2026, 5, 16);
assertTrue(IsoWorkdayUtils.isWeekend(d), () -> "expected weekend, isoDow=" + IsoWorkdayUtils.isoDayOfWeek(d));
}
@Test
void may172026_isSunday_weekend() {
assertTrue(IsoWorkdayUtils.isWeekend(LocalDate.of(2026, 5, 17)));
}
/** 同年月日不同年份2024-05-16 为周四,勿与 2026-05-16周六混淆。 */
@Test
void may162024_isThursday_notWeekend() {
LocalDate d = LocalDate.of(2024, 5, 16);
assertFalse(IsoWorkdayUtils.isWeekend(d), () -> "expected weekday, isoDow=" + IsoWorkdayUtils.isoDayOfWeek(d));
}
}