日报填报日期范围修改
parent
f3966a45ed
commit
4eed47cc70
|
|
@ -103,6 +103,7 @@ public class WorkServiceImpl implements WorkService {
|
||||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
||||||
private static final DateTimeFormatter REPORT_DATE_TIME_TEXT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm");
|
private static final DateTimeFormatter REPORT_DATE_TIME_TEXT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm");
|
||||||
private static final ZoneId BUSINESS_ZONE_ID = ZoneId.of("Asia/Shanghai");
|
private static final ZoneId BUSINESS_ZONE_ID = ZoneId.of("Asia/Shanghai");
|
||||||
|
private static final LocalTime DAILY_REPORT_CUTOFF_TIME = LocalTime.of(10, 0);
|
||||||
|
|
||||||
private final WorkMapper workMapper;
|
private final WorkMapper workMapper;
|
||||||
private final OpportunityMapper opportunityMapper;
|
private final OpportunityMapper opportunityMapper;
|
||||||
|
|
@ -2465,7 +2466,12 @@ public class WorkServiceImpl implements WorkService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private LocalDate currentBusinessDate() {
|
private LocalDate currentBusinessDate() {
|
||||||
return LocalDate.now(BUSINESS_ZONE_ID);
|
return resolveBusinessDate(LocalDateTime.now(BUSINESS_ZONE_ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
static LocalDate resolveBusinessDate(LocalDateTime businessDateTime) {
|
||||||
|
LocalDate date = businessDateTime.toLocalDate();
|
||||||
|
return businessDateTime.toLocalTime().isBefore(DAILY_REPORT_CUTOFF_TIME) ? date.minusDays(1) : date;
|
||||||
}
|
}
|
||||||
|
|
||||||
private record PhotoMetadata(String cleanText, List<String> photoUrls) {}
|
private record PhotoMetadata(String cleanText, List<String> photoUrls) {}
|
||||||
|
|
|
||||||
|
|
@ -12,25 +12,21 @@ minio:
|
||||||
spring:
|
spring:
|
||||||
application:
|
application:
|
||||||
name: unis-crm-backend
|
name: unis-crm-backend
|
||||||
jackson:
|
|
||||||
time-zone: Asia/Shanghai
|
|
||||||
servlet:
|
servlet:
|
||||||
multipart:
|
multipart:
|
||||||
max-file-size: 500MB
|
max-file-size: 500MB
|
||||||
max-request-size: 600MB
|
max-request-size: 600MB
|
||||||
datasource:
|
datasource:
|
||||||
url: jdbc:postgresql://127.0.0.1:5432/unis_crm
|
url: jdbc:postgresql://192.168.124.202:5432/unis_crm
|
||||||
username: postgres
|
username: postgres
|
||||||
password: 199628
|
password: unis@123
|
||||||
driver-class-name: org.postgresql.Driver
|
driver-class-name: org.postgresql.Driver
|
||||||
hikari:
|
|
||||||
connection-init-sql: SET TIME ZONE 'Asia/Shanghai'
|
|
||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
host: 127.0.0.1
|
host: 192.168.124.202
|
||||||
port: 6379
|
port: 6379
|
||||||
password: 199628@tlw
|
password: zghz@123
|
||||||
database: 14
|
database: 12
|
||||||
|
|
||||||
mybatis-plus:
|
mybatis-plus:
|
||||||
mapper-locations: classpath*:/mapper/**/*.xml
|
mapper-locations: classpath*:/mapper/**/*.xml
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,14 @@
|
||||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="com.unis.crm.mapper.WorkMapper">
|
<mapper namespace="com.unis.crm.mapper.WorkMapper">
|
||||||
|
|
||||||
|
<sql id="dailyReportBusinessDateExpression">
|
||||||
|
case
|
||||||
|
when r.submit_time is not null and (r.submit_time at time zone 'Asia/Shanghai')::time < time '10:00'
|
||||||
|
then (r.submit_time at time zone 'Asia/Shanghai')::date - 1
|
||||||
|
else coalesce((r.submit_time at time zone 'Asia/Shanghai')::date, r.report_date)
|
||||||
|
end
|
||||||
|
</sql>
|
||||||
|
|
||||||
<sql id="checkInExportFilters">
|
<sql id="checkInExportFilters">
|
||||||
<if test="startDate != null">
|
<if test="startDate != null">
|
||||||
and c.checkin_date >= #{startDate}
|
and c.checkin_date >= #{startDate}
|
||||||
|
|
@ -34,10 +42,22 @@
|
||||||
|
|
||||||
<sql id="dailyReportExportFilters">
|
<sql id="dailyReportExportFilters">
|
||||||
<if test="startDate != null">
|
<if test="startDate != null">
|
||||||
and r.report_date >= #{startDate}
|
and (
|
||||||
|
case
|
||||||
|
when r.submit_time is not null and (r.submit_time at time zone 'Asia/Shanghai')::time < time '10:00'
|
||||||
|
then (r.submit_time at time zone 'Asia/Shanghai')::date - 1
|
||||||
|
else coalesce((r.submit_time at time zone 'Asia/Shanghai')::date, r.report_date)
|
||||||
|
end
|
||||||
|
) >= #{startDate}
|
||||||
</if>
|
</if>
|
||||||
<if test="endDate != null">
|
<if test="endDate != null">
|
||||||
and r.report_date <= #{endDate}
|
and (
|
||||||
|
case
|
||||||
|
when r.submit_time is not null and (r.submit_time at time zone 'Asia/Shanghai')::time < time '10:00'
|
||||||
|
then (r.submit_time at time zone 'Asia/Shanghai')::date - 1
|
||||||
|
else coalesce((r.submit_time at time zone 'Asia/Shanghai')::date, r.report_date)
|
||||||
|
end
|
||||||
|
) <= #{endDate}
|
||||||
</if>
|
</if>
|
||||||
<if test="keyword != null and keyword != ''">
|
<if test="keyword != null and keyword != ''">
|
||||||
and (
|
and (
|
||||||
|
|
@ -320,7 +340,7 @@
|
||||||
select
|
select
|
||||||
r.id,
|
r.id,
|
||||||
'日报' as type,
|
'日报' as type,
|
||||||
to_char(r.report_date, 'YYYY-MM-DD') as date,
|
to_char(<include refid="dailyReportBusinessDateExpression"/>, 'YYYY-MM-DD') as date,
|
||||||
to_char(r.submit_time, 'HH24:MI') as time,
|
to_char(r.submit_time, 'HH24:MI') as time,
|
||||||
case
|
case
|
||||||
when coalesce(nullif(btrim(u.display_name), ''), nullif(btrim(u.username), '')) is not null
|
when coalesce(nullif(btrim(u.display_name), ''), nullif(btrim(u.username), '')) is not null
|
||||||
|
|
@ -343,7 +363,11 @@
|
||||||
end as status,
|
end as status,
|
||||||
rc.score,
|
rc.score,
|
||||||
rc.comment_content as comment,
|
rc.comment_content as comment,
|
||||||
coalesce(r.report_date::timestamp + r.submit_time::time, r.created_at) as sort_time
|
coalesce(
|
||||||
|
(<include refid="dailyReportBusinessDateExpression"/>)::timestamp
|
||||||
|
+ (r.submit_time at time zone 'Asia/Shanghai')::time,
|
||||||
|
r.created_at
|
||||||
|
) as sort_time
|
||||||
from work_daily_report r
|
from work_daily_report r
|
||||||
left join sys_user u on u.user_id = r.user_id and u.is_deleted = 0
|
left join sys_user u on u.user_id = r.user_id and u.is_deleted = 0
|
||||||
left join (
|
left join (
|
||||||
|
|
@ -362,7 +386,7 @@
|
||||||
select
|
select
|
||||||
r.id,
|
r.id,
|
||||||
'日报' as type,
|
'日报' as type,
|
||||||
to_char(r.report_date, 'YYYY-MM-DD') as date,
|
to_char(<include refid="dailyReportBusinessDateExpression"/>, 'YYYY-MM-DD') as date,
|
||||||
to_char(r.submit_time, 'HH24:MI') as time,
|
to_char(r.submit_time, 'HH24:MI') as time,
|
||||||
case
|
case
|
||||||
when coalesce(nullif(btrim(u.display_name), ''), nullif(btrim(u.username), '')) is not null
|
when coalesce(nullif(btrim(u.display_name), ''), nullif(btrim(u.username), '')) is not null
|
||||||
|
|
@ -385,7 +409,11 @@
|
||||||
end as status,
|
end as status,
|
||||||
rc.score,
|
rc.score,
|
||||||
rc.comment_content as comment,
|
rc.comment_content as comment,
|
||||||
coalesce(r.report_date::timestamp + r.submit_time::time, r.created_at) as sort_time
|
coalesce(
|
||||||
|
(<include refid="dailyReportBusinessDateExpression"/>)::timestamp
|
||||||
|
+ (r.submit_time at time zone 'Asia/Shanghai')::time,
|
||||||
|
r.created_at
|
||||||
|
) as sort_time
|
||||||
from work_daily_report r
|
from work_daily_report r
|
||||||
left join sys_user u on u.user_id = r.user_id and u.is_deleted = 0
|
left join sys_user u on u.user_id = r.user_id and u.is_deleted = 0
|
||||||
left join (
|
left join (
|
||||||
|
|
@ -408,7 +436,7 @@
|
||||||
select
|
select
|
||||||
r.id,
|
r.id,
|
||||||
'日报' as type,
|
'日报' as type,
|
||||||
to_char(r.report_date, 'YYYY-MM-DD') as date,
|
to_char(<include refid="dailyReportBusinessDateExpression"/>, 'YYYY-MM-DD') as date,
|
||||||
to_char(r.submit_time, 'HH24:MI') as time,
|
to_char(r.submit_time, 'HH24:MI') as time,
|
||||||
case
|
case
|
||||||
when coalesce(nullif(btrim(u.display_name), ''), nullif(btrim(u.username), '')) is not null
|
when coalesce(nullif(btrim(u.display_name), ''), nullif(btrim(u.username), '')) is not null
|
||||||
|
|
@ -431,7 +459,11 @@
|
||||||
end as status,
|
end as status,
|
||||||
rc.score,
|
rc.score,
|
||||||
rc.comment_content as comment,
|
rc.comment_content as comment,
|
||||||
coalesce(r.report_date::timestamp + r.submit_time::time, r.created_at) as sort_time
|
coalesce(
|
||||||
|
(<include refid="dailyReportBusinessDateExpression"/>)::timestamp
|
||||||
|
+ (r.submit_time at time zone 'Asia/Shanghai')::time,
|
||||||
|
r.created_at
|
||||||
|
) as sort_time
|
||||||
from work_daily_report r
|
from work_daily_report r
|
||||||
left join sys_user u on u.user_id = r.user_id and u.is_deleted = 0
|
left join sys_user u on u.user_id = r.user_id and u.is_deleted = 0
|
||||||
left join (
|
left join (
|
||||||
|
|
@ -459,7 +491,7 @@
|
||||||
select
|
select
|
||||||
r.id,
|
r.id,
|
||||||
'日报' as type,
|
'日报' as type,
|
||||||
to_char(r.report_date, 'YYYY-MM-DD') as date,
|
to_char(<include refid="dailyReportBusinessDateExpression"/>, 'YYYY-MM-DD') as date,
|
||||||
to_char(r.submit_time, 'HH24:MI') as time,
|
to_char(r.submit_time, 'HH24:MI') as time,
|
||||||
case
|
case
|
||||||
when coalesce(nullif(btrim(u.display_name), ''), nullif(btrim(u.username), '')) is not null
|
when coalesce(nullif(btrim(u.display_name), ''), nullif(btrim(u.username), '')) is not null
|
||||||
|
|
@ -482,7 +514,11 @@
|
||||||
end as status,
|
end as status,
|
||||||
rc.score,
|
rc.score,
|
||||||
rc.comment_content as comment,
|
rc.comment_content as comment,
|
||||||
coalesce(r.report_date::timestamp + r.submit_time::time, r.created_at) as sort_time
|
coalesce(
|
||||||
|
(<include refid="dailyReportBusinessDateExpression"/>)::timestamp
|
||||||
|
+ (r.submit_time at time zone 'Asia/Shanghai')::time,
|
||||||
|
r.created_at
|
||||||
|
) as sort_time
|
||||||
from work_daily_report r
|
from work_daily_report r
|
||||||
left join sys_user u on u.user_id = r.user_id and u.is_deleted = 0
|
left join sys_user u on u.user_id = r.user_id and u.is_deleted = 0
|
||||||
left join (
|
left join (
|
||||||
|
|
@ -573,7 +609,14 @@
|
||||||
<select id="selectDailyReportExportRows" resultType="com.unis.crm.dto.work.WorkDailyReportExportDTO">
|
<select id="selectDailyReportExportRows" resultType="com.unis.crm.dto.work.WorkDailyReportExportDTO">
|
||||||
select
|
select
|
||||||
r.id,
|
r.id,
|
||||||
to_char(r.report_date, 'YYYY-MM-DD') as reportDate,
|
to_char(
|
||||||
|
case
|
||||||
|
when r.submit_time is not null and (r.submit_time at time zone 'Asia/Shanghai')::time < time '10:00'
|
||||||
|
then (r.submit_time at time zone 'Asia/Shanghai')::date - 1
|
||||||
|
else coalesce((r.submit_time at time zone 'Asia/Shanghai')::date, r.report_date)
|
||||||
|
end,
|
||||||
|
'YYYY-MM-DD'
|
||||||
|
) as reportDate,
|
||||||
to_char(r.submit_time, 'YYYY-MM-DD HH24:MI') as submitTime,
|
to_char(r.submit_time, 'YYYY-MM-DD HH24:MI') as submitTime,
|
||||||
coalesce(nullif(btrim(u.display_name), ''), nullif(btrim(u.username), ''), '') as userName,
|
coalesce(nullif(btrim(u.display_name), ''), nullif(btrim(u.username), ''), '') as userName,
|
||||||
coalesce(nullif(btrim(org_info.org_names), ''), '') as deptName,
|
coalesce(nullif(btrim(org_info.org_names), ''), '') as deptName,
|
||||||
|
|
@ -618,7 +661,14 @@
|
||||||
<select id="selectDailyReportExportRowsByUserIds" resultType="com.unis.crm.dto.work.WorkDailyReportExportDTO">
|
<select id="selectDailyReportExportRowsByUserIds" resultType="com.unis.crm.dto.work.WorkDailyReportExportDTO">
|
||||||
select
|
select
|
||||||
r.id,
|
r.id,
|
||||||
to_char(r.report_date, 'YYYY-MM-DD') as reportDate,
|
to_char(
|
||||||
|
case
|
||||||
|
when r.submit_time is not null and (r.submit_time at time zone 'Asia/Shanghai')::time < time '10:00'
|
||||||
|
then (r.submit_time at time zone 'Asia/Shanghai')::date - 1
|
||||||
|
else coalesce((r.submit_time at time zone 'Asia/Shanghai')::date, r.report_date)
|
||||||
|
end,
|
||||||
|
'YYYY-MM-DD'
|
||||||
|
) as reportDate,
|
||||||
to_char(r.submit_time, 'YYYY-MM-DD HH24:MI') as submitTime,
|
to_char(r.submit_time, 'YYYY-MM-DD HH24:MI') as submitTime,
|
||||||
coalesce(nullif(btrim(u.display_name), ''), nullif(btrim(u.username), ''), '') as userName,
|
coalesce(nullif(btrim(u.display_name), ''), nullif(btrim(u.username), ''), '') as userName,
|
||||||
coalesce(nullif(btrim(org_info.org_names), ''), '') as deptName,
|
coalesce(nullif(btrim(org_info.org_names), ''), '') as deptName,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import com.unis.crm.dto.work.WorkReportLineItemRequest;
|
||||||
import com.unis.crm.dto.work.WorkTomorrowPlanItemRequest;
|
import com.unis.crm.dto.work.WorkTomorrowPlanItemRequest;
|
||||||
import com.unis.crm.mapper.OpportunityMapper;
|
import com.unis.crm.mapper.OpportunityMapper;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import com.unis.crm.mapper.ProfileMapper;
|
import com.unis.crm.mapper.ProfileMapper;
|
||||||
import com.unis.crm.mapper.WorkMapper;
|
import com.unis.crm.mapper.WorkMapper;
|
||||||
import com.unis.crm.service.CrmDataVisibilityService;
|
import com.unis.crm.service.CrmDataVisibilityService;
|
||||||
|
|
@ -718,6 +719,20 @@ class WorkServiceImplTest {
|
||||||
eq(17L));
|
eq(17L));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveBusinessDate_shouldUsePreviousDateBeforeTenOClock() {
|
||||||
|
assertEquals(
|
||||||
|
LocalDate.of(2026, 7, 21),
|
||||||
|
WorkServiceImpl.resolveBusinessDate(LocalDateTime.of(2026, 7, 22, 9, 59, 59)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveBusinessDate_shouldUseCurrentDateFromTenOClock() {
|
||||||
|
assertEquals(
|
||||||
|
LocalDate.of(2026, 7, 22),
|
||||||
|
WorkServiceImpl.resolveBusinessDate(LocalDateTime.of(2026, 7, 22, 10, 0)));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void saveDailyReport_shouldNotSyncOpportunitySnapshotForNonSalesRole() {
|
void saveDailyReport_shouldNotSyncOpportunitySnapshotForNonSalesRole() {
|
||||||
CreateWorkDailyReportRequest request = new CreateWorkDailyReportRequest();
|
CreateWorkDailyReportRequest request = new CreateWorkDailyReportRequest();
|
||||||
|
|
|
||||||
|
|
@ -219,9 +219,30 @@ const FALLBACK_OPPORTUNITY_TYPE_OPTIONS = [
|
||||||
{ value: "替换", label: "替换" },
|
{ value: "替换", label: "替换" },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
function getCurrentReportDate(now = new Date()): string {
|
||||||
|
const parts = new Intl.DateTimeFormat("en-CA", {
|
||||||
|
timeZone: "Asia/Shanghai",
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
hour: "2-digit",
|
||||||
|
hourCycle: "h23",
|
||||||
|
}).formatToParts(now);
|
||||||
|
const values = Object.fromEntries(parts.map((part) => [part.type, part.value]));
|
||||||
|
const reportDate = new Date(Date.UTC(Number(values.year), Number(values.month) - 1, Number(values.day)));
|
||||||
|
if (Number(values.hour) < 10) {
|
||||||
|
reportDate.setUTCDate(reportDate.getUTCDate() - 1);
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
reportDate.getUTCFullYear(),
|
||||||
|
String(reportDate.getUTCMonth() + 1).padStart(2, "0"),
|
||||||
|
String(reportDate.getUTCDate()).padStart(2, "0"),
|
||||||
|
].join("-");
|
||||||
|
}
|
||||||
|
|
||||||
function createEmptyReportLine(): WorkReportLineItem {
|
function createEmptyReportLine(): WorkReportLineItem {
|
||||||
return {
|
return {
|
||||||
workDate: format(new Date(), "yyyy-MM-dd"),
|
workDate: getCurrentReportDate(),
|
||||||
bizType: "sales",
|
bizType: "sales",
|
||||||
bizId: "",
|
bizId: "",
|
||||||
bizName: "",
|
bizName: "",
|
||||||
|
|
@ -790,7 +811,7 @@ export default function Work() {
|
||||||
const isMobileViewport = useIsMobileViewport();
|
const isMobileViewport = useIsMobileViewport();
|
||||||
const isWecomInternalBrowser = useIsWecomBrowser();
|
const isWecomInternalBrowser = useIsWecomBrowser();
|
||||||
const disableMobileMotion = isMobileViewport || isWecomInternalBrowser;
|
const disableMobileMotion = isMobileViewport || isWecomInternalBrowser;
|
||||||
const currentWorkDate = format(new Date(), "yyyy-MM-dd");
|
const currentWorkDate = getCurrentReportDate();
|
||||||
const hasAutoRefreshedLocation = useRef(false);
|
const hasAutoRefreshedLocation = useRef(false);
|
||||||
const photoInputRef = useRef<HTMLInputElement | null>(null);
|
const photoInputRef = useRef<HTMLInputElement | null>(null);
|
||||||
const historyLoadMoreRef = useRef<HTMLDivElement | null>(null);
|
const historyLoadMoreRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
@ -3767,7 +3788,11 @@ function HistoryCard({
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-semibold text-slate-900 dark:text-white">{item.type}</h3>
|
<h3 className="text-sm font-semibold text-slate-900 dark:text-white">{item.type}</h3>
|
||||||
<p className="text-[10px] text-slate-500 dark:text-slate-400">{[item.date, item.time].filter(Boolean).join(" ")}</p>
|
<p className="text-[10px] text-slate-500 dark:text-slate-400">
|
||||||
|
{item.type === "日报"
|
||||||
|
? `日报日期:${item.date || "未标注日期"}`
|
||||||
|
: [item.date, item.time].filter(Boolean).join(" ")}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-end gap-1">
|
<div className="flex flex-col items-end gap-1">
|
||||||
|
|
@ -4330,7 +4355,7 @@ function ReportPanel({
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full items-center justify-between gap-2 lg:w-auto lg:justify-end">
|
<div className="flex w-full items-center justify-between gap-2 lg:w-auto lg:justify-end">
|
||||||
<span className="crm-pill crm-pill-neutral text-[11px]">
|
<span className="crm-pill crm-pill-neutral text-[11px]">
|
||||||
日期 {currentWorkDate}
|
日报日期 {currentWorkDate}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -4715,7 +4740,9 @@ function HistoryDetailModal({
|
||||||
<div className="flex-1 overflow-y-auto px-4 py-4 pb-[calc(env(safe-area-inset-bottom)+20px)] sm:px-6 sm:py-5">
|
<div className="flex-1 overflow-y-auto px-4 py-4 pb-[calc(env(safe-area-inset-bottom)+20px)] sm:px-6 sm:py-5">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-slate-400 dark:text-slate-500">
|
<p className="text-xs font-medium text-slate-400 dark:text-slate-500">
|
||||||
{[item.date, item.time].filter(Boolean).join(" ") || "无时间信息"}
|
{item.type === "日报"
|
||||||
|
? `日报日期:${item.date || "未标注日期"}`
|
||||||
|
: [item.date, item.time].filter(Boolean).join(" ") || "无时间信息"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue