解决特权时间段的Bug
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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(
|
||||||
|
"parseFromOrderInfoByLlm:LLM 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(
|
||||||
|
"trialSpan:cursor={} 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) {
|
||||||
|
|||||||
25
src/main/java/com/rj/util/IsoWorkdayUtils.java
Normal file
25
src/main/java/com/rj/util/IsoWorkdayUtils.java
Normal 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 周六、周日为 true(isoDow 为 6 或 7)。 */
|
||||||
|
public static boolean isWeekend(LocalDate d) {
|
||||||
|
return isoDayOfWeek(d) >= 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/test/java/com/rj/util/IsoWorkdayUtilsTest.java
Normal file
29
src/test/java/com/rj/util/IsoWorkdayUtilsTest.java
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user