修改定位信息 0323
parent
512f42dfd4
commit
13d3abeeee
|
|
@ -43,7 +43,6 @@ import org.springframework.web.multipart.MultipartFile;
|
||||||
@Service
|
@Service
|
||||||
public class WorkServiceImpl implements WorkService {
|
public class WorkServiceImpl implements WorkService {
|
||||||
|
|
||||||
private static final String NOMINATIM_BASE_URL = "https://nominatim.openstreetmap.org/reverse";
|
|
||||||
private static final String PHOTO_METADATA_PREFIX = "[[CHECKIN_PHOTOS]]";
|
private static final String PHOTO_METADATA_PREFIX = "[[CHECKIN_PHOTOS]]";
|
||||||
private static final String PHOTO_METADATA_SUFFIX = "[[/CHECKIN_PHOTOS]]";
|
private static final String PHOTO_METADATA_SUFFIX = "[[/CHECKIN_PHOTOS]]";
|
||||||
private static final Pattern PHOTO_METADATA_PATTERN = Pattern.compile("\\[\\[CHECKIN_PHOTOS]](.*?)\\[\\[/CHECKIN_PHOTOS]]", Pattern.DOTALL);
|
private static final Pattern PHOTO_METADATA_PATTERN = Pattern.compile("\\[\\[CHECKIN_PHOTOS]](.*?)\\[\\[/CHECKIN_PHOTOS]]", Pattern.DOTALL);
|
||||||
|
|
@ -129,49 +128,7 @@ public class WorkServiceImpl implements WorkService {
|
||||||
if (latitude == null || longitude == null) {
|
if (latitude == null || longitude == null) {
|
||||||
throw new BusinessException("定位坐标不能为空");
|
throw new BusinessException("定位坐标不能为空");
|
||||||
}
|
}
|
||||||
|
throw new BusinessException("当前环境未启用服务端逆地理解析");
|
||||||
try {
|
|
||||||
String requestUrl = NOMINATIM_BASE_URL
|
|
||||||
+ "?format=jsonv2&addressdetails=1&namedetails=1&extratags=1&zoom=19"
|
|
||||||
+ "&lat=" + URLEncoder.encode(latitude.stripTrailingZeros().toPlainString(), StandardCharsets.UTF_8)
|
|
||||||
+ "&lon=" + URLEncoder.encode(longitude.stripTrailingZeros().toPlainString(), StandardCharsets.UTF_8)
|
|
||||||
+ "&accept-language=" + URLEncoder.encode("zh-CN,zh;q=0.9,en;q=0.8", StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
|
||||||
.uri(URI.create(requestUrl))
|
|
||||||
.header("Accept", "application/json")
|
|
||||||
.header("User-Agent", "unis-crm-backend/1.0 (workbench reverse geocoding)")
|
|
||||||
.timeout(Duration.ofSeconds(10))
|
|
||||||
.GET()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
|
||||||
if (response.statusCode() < 200 || response.statusCode() >= 300) {
|
|
||||||
throw new BusinessException("地点解析失败,请稍后重试");
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonNode root = objectMapper.readTree(response.body());
|
|
||||||
JsonNode addressNode = root.path("address");
|
|
||||||
String orderedLocation = buildOrderedLocationName(root, addressNode);
|
|
||||||
if (orderedLocation != null) {
|
|
||||||
return orderedLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
String displayName = textValue(root, "display_name");
|
|
||||||
if (displayName != null) {
|
|
||||||
String normalizedDisplayName = normalizeDisplayName(displayName);
|
|
||||||
if (normalizedDisplayName != null) {
|
|
||||||
return normalizedDisplayName;
|
|
||||||
}
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
} catch (BusinessException exception) {
|
|
||||||
throw exception;
|
|
||||||
} catch (Exception exception) {
|
|
||||||
throw new BusinessException("地点解析失败,请检查网络后重试");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new BusinessException("未能解析出具体地点名称");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
server:
|
|
||||||
port: 8080
|
|
||||||
|
|
||||||
spring:
|
|
||||||
application:
|
|
||||||
name: unis-crm-backend
|
|
||||||
servlet:
|
|
||||||
multipart:
|
|
||||||
max-file-size: 20MB
|
|
||||||
max-request-size: 25MB
|
|
||||||
datasource:
|
|
||||||
url: jdbc:postgresql://127.0.0.1:5432/nex_auth
|
|
||||||
username: postgres
|
|
||||||
password: 199628
|
|
||||||
driver-class-name: org.postgresql.Driver
|
|
||||||
data:
|
|
||||||
redis:
|
|
||||||
host: 127.0.0.1
|
|
||||||
port: 6379
|
|
||||||
password: 199628@tlw
|
|
||||||
database: 14
|
|
||||||
|
|
||||||
mybatis-plus:
|
|
||||||
mapper-locations: classpath*:/mapper/**/*.xml
|
|
||||||
type-aliases-package: com.unis.crm.dto.dashboard
|
|
||||||
configuration:
|
|
||||||
map-underscore-to-camel-case: true
|
|
||||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
|
||||||
|
|
||||||
logging:
|
|
||||||
level:
|
|
||||||
com.unis.crm: info
|
|
||||||
|
|
||||||
unisbase:
|
|
||||||
tenant:
|
|
||||||
enabled: false
|
|
||||||
web:
|
|
||||||
auth-endpoints-enabled: true
|
|
||||||
management-endpoints-enabled: true
|
|
||||||
security:
|
|
||||||
enabled: true
|
|
||||||
mode: embedded
|
|
||||||
jwt-secret: change-me-please-change-me-32bytes
|
|
||||||
auth-header: Authorization
|
|
||||||
token-prefix: "Bearer "
|
|
||||||
permit-all-urls:
|
|
||||||
- /actuator/health
|
|
||||||
internal-auth:
|
|
||||||
enabled: true
|
|
||||||
secret: change-me-internal-secret
|
|
||||||
header-name: X-Internal-Secret
|
|
||||||
app:
|
|
||||||
upload-path: /Users/kangwenjing/Downloads/crm/uploads
|
|
||||||
resource-prefix: /sys/api/static/
|
|
||||||
captcha:
|
|
||||||
ttl-seconds: 120
|
|
||||||
max-attempts: 5
|
|
||||||
token:
|
|
||||||
access-default-minutes: 30
|
|
||||||
refresh-default-days: 7
|
|
||||||
|
|
@ -1,206 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<!DOCTYPE mapper
|
|
||||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|
||||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
|
||||||
<mapper namespace="com.unis.crm.mapper.DashboardMapper">
|
|
||||||
|
|
||||||
<select id="selectDefaultUserId" resultType="java.lang.Long">
|
|
||||||
select user_id
|
|
||||||
from sys_user
|
|
||||||
where status = 1
|
|
||||||
order by user_id asc
|
|
||||||
limit 1
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectUserWelcome" resultType="com.unis.crm.dto.dashboard.UserWelcomeDTO">
|
|
||||||
select
|
|
||||||
u.user_id as userId,
|
|
||||||
u.display_name as realName,
|
|
||||||
null as jobTitle,
|
|
||||||
null as deptName,
|
|
||||||
null as hireDate
|
|
||||||
from sys_user u
|
|
||||||
where u.user_id = #{userId}
|
|
||||||
and u.status = 1
|
|
||||||
limit 1
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectDashboardStats" resultType="com.unis.crm.dto.dashboard.DashboardStatDTO">
|
|
||||||
select '本月新增商机' as name,
|
|
||||||
count(1)::bigint as value,
|
|
||||||
'monthlyOpportunities' as metricKey
|
|
||||||
from crm_opportunity
|
|
||||||
where owner_user_id = #{userId}
|
|
||||||
and date_trunc('month', created_at) = date_trunc('month', now())
|
|
||||||
|
|
||||||
union all
|
|
||||||
|
|
||||||
select '跟进中客户' as name,
|
|
||||||
count(1)::bigint as value,
|
|
||||||
'followingCustomers' as metricKey
|
|
||||||
from crm_customer
|
|
||||||
where owner_user_id = #{userId}
|
|
||||||
and status = 'following'
|
|
||||||
|
|
||||||
union all
|
|
||||||
|
|
||||||
select '已成单项目' as name,
|
|
||||||
count(1)::bigint as value,
|
|
||||||
'wonProjects' as metricKey
|
|
||||||
from crm_opportunity
|
|
||||||
where owner_user_id = #{userId}
|
|
||||||
and stage = 'won'
|
|
||||||
|
|
||||||
union all
|
|
||||||
|
|
||||||
select '本月打卡天数' as name,
|
|
||||||
count(distinct checkin_date)::bigint as value,
|
|
||||||
'monthlyCheckins' as metricKey
|
|
||||||
from work_checkin
|
|
||||||
where user_id = #{userId}
|
|
||||||
and date_trunc('month', checkin_date::timestamp) = date_trunc('month', now())
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectPendingTodos" resultType="com.unis.crm.dto.dashboard.DashboardTodoDTO">
|
|
||||||
select
|
|
||||||
id,
|
|
||||||
title,
|
|
||||||
biz_type as bizType,
|
|
||||||
biz_id as bizId,
|
|
||||||
priority,
|
|
||||||
status,
|
|
||||||
due_date as dueDate,
|
|
||||||
created_at as createdAt
|
|
||||||
from work_todo
|
|
||||||
where user_id = #{userId}
|
|
||||||
and status = 'todo'
|
|
||||||
order by
|
|
||||||
case priority
|
|
||||||
when 'high' then 1
|
|
||||||
when 'medium' then 2
|
|
||||||
else 3
|
|
||||||
end,
|
|
||||||
coalesce(due_date, created_at) asc
|
|
||||||
limit 6
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectLatestActivities" resultType="com.unis.crm.dto.dashboard.DashboardActivityDTO">
|
|
||||||
with latest_report_comment as (
|
|
||||||
select distinct on (c.report_id)
|
|
||||||
c.report_id,
|
|
||||||
c.reviewer_user_id,
|
|
||||||
c.score,
|
|
||||||
c.comment_content,
|
|
||||||
c.reviewed_at
|
|
||||||
from work_daily_report_comment c
|
|
||||||
order by c.report_id, c.reviewed_at desc, c.id desc
|
|
||||||
),
|
|
||||||
activity_union as (
|
|
||||||
select
|
|
||||||
l.id,
|
|
||||||
l.biz_type as bizType,
|
|
||||||
l.biz_id as bizId,
|
|
||||||
l.action_type as actionType,
|
|
||||||
l.title,
|
|
||||||
l.content,
|
|
||||||
l.operator_user_id as operatorUserId,
|
|
||||||
l.created_at as createdAt
|
|
||||||
from sys_activity_log l
|
|
||||||
where l.operator_user_id = #{userId}
|
|
||||||
or l.operator_user_id is null
|
|
||||||
|
|
||||||
union all
|
|
||||||
|
|
||||||
select
|
|
||||||
(1000000000 + o.id) as id,
|
|
||||||
'opportunity' as bizType,
|
|
||||||
o.id as bizId,
|
|
||||||
'stage_update' as actionType,
|
|
||||||
'商机阶段更新' as title,
|
|
||||||
o.opportunity_name || ' 已推进至' ||
|
|
||||||
case o.stage
|
|
||||||
when 'initial_contact' then '初步沟通'
|
|
||||||
when 'solution_discussion' then '方案交流'
|
|
||||||
when 'bidding' then '招投标'
|
|
||||||
when 'business_negotiation' then '商务谈判'
|
|
||||||
when 'won' then '已成交'
|
|
||||||
when 'lost' then '已输单'
|
|
||||||
else o.stage
|
|
||||||
end || '阶段' as content,
|
|
||||||
o.owner_user_id as operatorUserId,
|
|
||||||
o.updated_at as createdAt
|
|
||||||
from crm_opportunity o
|
|
||||||
where o.owner_user_id = #{userId}
|
|
||||||
and o.updated_at > o.created_at
|
|
||||||
|
|
||||||
union all
|
|
||||||
|
|
||||||
select
|
|
||||||
(2000000000 + r.id) as id,
|
|
||||||
'report' as bizType,
|
|
||||||
r.id as bizId,
|
|
||||||
case
|
|
||||||
when r.status = 'reviewed' or lc.score is not null then 'report_reviewed'
|
|
||||||
else 'report_read'
|
|
||||||
end as actionType,
|
|
||||||
case
|
|
||||||
when r.status = 'reviewed' or lc.score is not null then '日报已点评'
|
|
||||||
else '日报已阅'
|
|
||||||
end as title,
|
|
||||||
case
|
|
||||||
when lc.score is not null then '主管对你' || to_char(r.report_date, 'MM-DD') || '的日报给出了 ' || lc.score || ' 分'
|
|
||||||
when r.status = 'reviewed' then '你的' || to_char(r.report_date, 'MM-DD') || '日报已完成主管点评'
|
|
||||||
else '你的' || to_char(r.report_date, 'MM-DD') || '日报已被查阅'
|
|
||||||
end as content,
|
|
||||||
coalesce(lc.reviewer_user_id, r.user_id) as operatorUserId,
|
|
||||||
coalesce(lc.reviewed_at, r.updated_at, r.created_at) as createdAt
|
|
||||||
from work_daily_report r
|
|
||||||
left join latest_report_comment lc on lc.report_id = r.id
|
|
||||||
where r.user_id = #{userId}
|
|
||||||
and r.status in ('read', 'reviewed')
|
|
||||||
|
|
||||||
union all
|
|
||||||
|
|
||||||
select
|
|
||||||
(3000000000 + c.id) as id,
|
|
||||||
'channel' as bizType,
|
|
||||||
c.id as bizId,
|
|
||||||
'channel_created' as actionType,
|
|
||||||
'新渠道录入' as title,
|
|
||||||
'成功录入 ' || c.channel_name || ' 渠道商信息' as content,
|
|
||||||
c.owner_user_id as operatorUserId,
|
|
||||||
c.created_at as createdAt
|
|
||||||
from crm_channel_expansion c
|
|
||||||
where c.owner_user_id = #{userId}
|
|
||||||
|
|
||||||
union all
|
|
||||||
|
|
||||||
select
|
|
||||||
(4000000000 + f.id) as id,
|
|
||||||
'opportunity_followup' as bizType,
|
|
||||||
f.opportunity_id as bizId,
|
|
||||||
'opportunity_followup' as actionType,
|
|
||||||
'商机跟进新增' as title,
|
|
||||||
o.opportunity_name || ' 新增了一条' || f.followup_type || '跟进记录' as content,
|
|
||||||
f.followup_user_id as operatorUserId,
|
|
||||||
f.followup_time as createdAt
|
|
||||||
from crm_opportunity_followup f
|
|
||||||
join crm_opportunity o on o.id = f.opportunity_id
|
|
||||||
where f.followup_user_id = #{userId}
|
|
||||||
)
|
|
||||||
select
|
|
||||||
a.id,
|
|
||||||
a.bizType,
|
|
||||||
a.bizId,
|
|
||||||
a.actionType,
|
|
||||||
a.title,
|
|
||||||
a.content,
|
|
||||||
a.operatorUserId,
|
|
||||||
u.display_name as operatorName,
|
|
||||||
a.createdAt
|
|
||||||
from activity_union a
|
|
||||||
left join sys_user u on u.user_id = a.operatorUserId
|
|
||||||
order by a.createdAt desc nulls last
|
|
||||||
limit 8
|
|
||||||
</select>
|
|
||||||
</mapper>
|
|
||||||
|
|
@ -1,279 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<!DOCTYPE mapper
|
|
||||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|
||||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
|
||||||
<mapper namespace="com.unis.crm.mapper.ExpansionMapper">
|
|
||||||
|
|
||||||
<select id="selectDepartments" resultType="com.unis.crm.dto.expansion.DepartmentOptionDTO">
|
|
||||||
select
|
|
||||||
id,
|
|
||||||
org_name as name
|
|
||||||
from sys_org
|
|
||||||
where status = 1
|
|
||||||
order by id asc
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectSalesExpansions" resultType="com.unis.crm.dto.expansion.SalesExpansionItemDTO">
|
|
||||||
select
|
|
||||||
s.id,
|
|
||||||
'sales' as type,
|
|
||||||
s.candidate_name as name,
|
|
||||||
coalesce(s.mobile, '无') as phone,
|
|
||||||
coalesce(s.email, '无') as email,
|
|
||||||
s.target_dept_id as targetDeptId,
|
|
||||||
'无' as dept,
|
|
||||||
coalesce(s.industry, '无') as industry,
|
|
||||||
coalesce(s.title, '无') as title,
|
|
||||||
s.intent_level as intentLevel,
|
|
||||||
case s.intent_level
|
|
||||||
when 'high' then '高'
|
|
||||||
when 'medium' then '中'
|
|
||||||
when 'low' then '低'
|
|
||||||
else '无'
|
|
||||||
end as intent,
|
|
||||||
s.stage as stageCode,
|
|
||||||
case s.stage
|
|
||||||
when 'initial_contact' then '初步沟通'
|
|
||||||
when 'solution_discussion' then '方案交流'
|
|
||||||
when 'bidding' then '招投标'
|
|
||||||
when 'business_negotiation' then '商务谈判'
|
|
||||||
when 'won' then '已成交'
|
|
||||||
when 'lost' then '已放弃'
|
|
||||||
else coalesce(s.stage, '无')
|
|
||||||
end as stage,
|
|
||||||
s.has_desktop_exp as hasExp,
|
|
||||||
s.in_progress as inProgress,
|
|
||||||
(s.employment_status = 'active') as active,
|
|
||||||
s.employment_status as employmentStatus,
|
|
||||||
coalesce(to_char(s.expected_join_date, 'YYYY-MM-DD'), '无') as expectedJoinDate,
|
|
||||||
coalesce(s.remark, '无') as notes
|
|
||||||
from crm_sales_expansion s
|
|
||||||
where s.owner_user_id = #{userId}
|
|
||||||
<if test="keyword != null and keyword != ''">
|
|
||||||
and (
|
|
||||||
s.candidate_name ilike concat('%', #{keyword}, '%')
|
|
||||||
or coalesce(s.industry, '') ilike concat('%', #{keyword}, '%')
|
|
||||||
)
|
|
||||||
</if>
|
|
||||||
order by s.updated_at desc, s.id desc
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectChannelExpansions" resultType="com.unis.crm.dto.expansion.ChannelExpansionItemDTO">
|
|
||||||
select
|
|
||||||
c.id,
|
|
||||||
'channel' as type,
|
|
||||||
c.channel_name as name,
|
|
||||||
coalesce(c.province, '无') as province,
|
|
||||||
coalesce(c.industry, '无') as industry,
|
|
||||||
coalesce(cast(c.annual_revenue as varchar), '') as annualRevenue,
|
|
||||||
case
|
|
||||||
when c.annual_revenue is null then '无'
|
|
||||||
when c.annual_revenue >= 10000 then trim(to_char(c.annual_revenue / 10000.0, 'FM999999990.##')) || '万'
|
|
||||||
else trim(to_char(c.annual_revenue, 'FM999999990.##'))
|
|
||||||
end as revenue,
|
|
||||||
coalesce(c.staff_size, 0) as size,
|
|
||||||
coalesce(c.contact_name, '无') as contact,
|
|
||||||
coalesce(c.contact_title, '无') as contactTitle,
|
|
||||||
coalesce(c.contact_mobile, '无') as phone,
|
|
||||||
c.stage as stageCode,
|
|
||||||
case c.stage
|
|
||||||
when 'initial_contact' then '初步接触'
|
|
||||||
when 'solution_discussion' then '方案交流'
|
|
||||||
when 'bidding' then '招投标'
|
|
||||||
when 'business_negotiation' then '合作洽谈'
|
|
||||||
when 'won' then '已合作'
|
|
||||||
when 'lost' then '已终止'
|
|
||||||
else coalesce(c.stage, '无')
|
|
||||||
end as stage,
|
|
||||||
c.landed_flag as landed,
|
|
||||||
coalesce(to_char(c.expected_sign_date, 'YYYY-MM-DD'), '无') as expectedSignDate,
|
|
||||||
coalesce(c.remark, '无') as notes
|
|
||||||
from crm_channel_expansion c
|
|
||||||
where c.owner_user_id = #{userId}
|
|
||||||
<if test="keyword != null and keyword != ''">
|
|
||||||
and (
|
|
||||||
c.channel_name ilike concat('%', #{keyword}, '%')
|
|
||||||
or coalesce(c.industry, '') ilike concat('%', #{keyword}, '%')
|
|
||||||
or coalesce(c.province, '') ilike concat('%', #{keyword}, '%')
|
|
||||||
)
|
|
||||||
</if>
|
|
||||||
order by c.updated_at desc, c.id desc
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectSalesFollowUps" resultType="com.unis.crm.dto.expansion.ExpansionFollowUpDTO">
|
|
||||||
select
|
|
||||||
f.id,
|
|
||||||
f.biz_id as bizId,
|
|
||||||
f.biz_type as bizType,
|
|
||||||
f.followup_time as followUpTime,
|
|
||||||
f.followup_type as type,
|
|
||||||
coalesce(f.content, '无') as content,
|
|
||||||
coalesce(u.display_name, '无') as user
|
|
||||||
from crm_expansion_followup f
|
|
||||||
join crm_sales_expansion s on s.id = f.biz_id and f.biz_type = 'sales'
|
|
||||||
left join sys_user u on u.user_id = f.followup_user_id
|
|
||||||
where s.owner_user_id = #{userId}
|
|
||||||
and f.biz_id in
|
|
||||||
<foreach collection="bizIds" item="id" open="(" separator="," close=")">
|
|
||||||
#{id}
|
|
||||||
</foreach>
|
|
||||||
order by f.followup_time desc, f.id desc
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectChannelFollowUps" resultType="com.unis.crm.dto.expansion.ExpansionFollowUpDTO">
|
|
||||||
select
|
|
||||||
f.id,
|
|
||||||
f.biz_id as bizId,
|
|
||||||
f.biz_type as bizType,
|
|
||||||
f.followup_time as followUpTime,
|
|
||||||
f.followup_type as type,
|
|
||||||
coalesce(f.content, '无') as content,
|
|
||||||
coalesce(u.display_name, '无') as user
|
|
||||||
from crm_expansion_followup f
|
|
||||||
join crm_channel_expansion c on c.id = f.biz_id and f.biz_type = 'channel'
|
|
||||||
left join sys_user u on u.user_id = f.followup_user_id
|
|
||||||
where c.owner_user_id = #{userId}
|
|
||||||
and f.biz_id in
|
|
||||||
<foreach collection="bizIds" item="id" open="(" separator="," close=")">
|
|
||||||
#{id}
|
|
||||||
</foreach>
|
|
||||||
order by f.followup_time desc, f.id desc
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<insert id="insertSalesExpansion" useGeneratedKeys="true" keyProperty="request.id">
|
|
||||||
insert into crm_sales_expansion (
|
|
||||||
candidate_name,
|
|
||||||
mobile,
|
|
||||||
email,
|
|
||||||
target_dept_id,
|
|
||||||
industry,
|
|
||||||
title,
|
|
||||||
intent_level,
|
|
||||||
stage,
|
|
||||||
has_desktop_exp,
|
|
||||||
in_progress,
|
|
||||||
employment_status,
|
|
||||||
expected_join_date,
|
|
||||||
owner_user_id,
|
|
||||||
remark
|
|
||||||
) values (
|
|
||||||
#{request.candidateName},
|
|
||||||
#{request.mobile},
|
|
||||||
#{request.email},
|
|
||||||
#{request.targetDeptId},
|
|
||||||
#{request.industry},
|
|
||||||
#{request.title},
|
|
||||||
#{request.intentLevel},
|
|
||||||
#{request.stage},
|
|
||||||
#{request.hasDesktopExp},
|
|
||||||
#{request.inProgress},
|
|
||||||
#{request.employmentStatus},
|
|
||||||
#{request.expectedJoinDate},
|
|
||||||
#{userId},
|
|
||||||
#{request.remark}
|
|
||||||
)
|
|
||||||
</insert>
|
|
||||||
|
|
||||||
<insert id="insertChannelExpansion" useGeneratedKeys="true" keyProperty="request.id">
|
|
||||||
insert into crm_channel_expansion (
|
|
||||||
channel_name,
|
|
||||||
province,
|
|
||||||
industry,
|
|
||||||
annual_revenue,
|
|
||||||
staff_size,
|
|
||||||
contact_name,
|
|
||||||
contact_title,
|
|
||||||
contact_mobile,
|
|
||||||
stage,
|
|
||||||
landed_flag,
|
|
||||||
expected_sign_date,
|
|
||||||
owner_user_id,
|
|
||||||
remark
|
|
||||||
) values (
|
|
||||||
#{request.channelName},
|
|
||||||
#{request.province},
|
|
||||||
#{request.industry},
|
|
||||||
#{request.annualRevenue},
|
|
||||||
#{request.staffSize},
|
|
||||||
#{request.contactName},
|
|
||||||
#{request.contactTitle},
|
|
||||||
#{request.contactMobile},
|
|
||||||
#{request.stage},
|
|
||||||
#{request.landedFlag},
|
|
||||||
#{request.expectedSignDate},
|
|
||||||
#{userId},
|
|
||||||
#{request.remark}
|
|
||||||
)
|
|
||||||
</insert>
|
|
||||||
|
|
||||||
<update id="updateSalesExpansion">
|
|
||||||
update crm_sales_expansion
|
|
||||||
set candidate_name = #{request.candidateName},
|
|
||||||
mobile = #{request.mobile},
|
|
||||||
email = #{request.email},
|
|
||||||
target_dept_id = #{request.targetDeptId},
|
|
||||||
industry = #{request.industry},
|
|
||||||
title = #{request.title},
|
|
||||||
intent_level = #{request.intentLevel},
|
|
||||||
stage = #{request.stage},
|
|
||||||
has_desktop_exp = #{request.hasDesktopExp},
|
|
||||||
in_progress = #{request.inProgress},
|
|
||||||
employment_status = #{request.employmentStatus},
|
|
||||||
expected_join_date = #{request.expectedJoinDate},
|
|
||||||
remark = #{request.remark}
|
|
||||||
where id = #{id}
|
|
||||||
and owner_user_id = #{userId}
|
|
||||||
</update>
|
|
||||||
|
|
||||||
<update id="updateChannelExpansion">
|
|
||||||
update crm_channel_expansion
|
|
||||||
set channel_name = #{request.channelName},
|
|
||||||
province = #{request.province},
|
|
||||||
industry = #{request.industry},
|
|
||||||
annual_revenue = #{request.annualRevenue},
|
|
||||||
staff_size = #{request.staffSize},
|
|
||||||
contact_name = #{request.contactName},
|
|
||||||
contact_title = #{request.contactTitle},
|
|
||||||
contact_mobile = #{request.contactMobile},
|
|
||||||
stage = #{request.stage},
|
|
||||||
landed_flag = #{request.landedFlag},
|
|
||||||
expected_sign_date = #{request.expectedSignDate},
|
|
||||||
remark = #{request.remark}
|
|
||||||
where id = #{id}
|
|
||||||
and owner_user_id = #{userId}
|
|
||||||
</update>
|
|
||||||
|
|
||||||
<select id="countOwnedSalesExpansion" resultType="int">
|
|
||||||
select count(1)
|
|
||||||
from crm_sales_expansion
|
|
||||||
where id = #{id}
|
|
||||||
and owner_user_id = #{userId}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="countOwnedChannelExpansion" resultType="int">
|
|
||||||
select count(1)
|
|
||||||
from crm_channel_expansion
|
|
||||||
where id = #{id}
|
|
||||||
and owner_user_id = #{userId}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<insert id="insertExpansionFollowUp">
|
|
||||||
insert into crm_expansion_followup (
|
|
||||||
biz_type,
|
|
||||||
biz_id,
|
|
||||||
followup_time,
|
|
||||||
followup_type,
|
|
||||||
content,
|
|
||||||
next_action,
|
|
||||||
followup_user_id
|
|
||||||
) values (
|
|
||||||
#{bizType},
|
|
||||||
#{bizId},
|
|
||||||
#{request.followUpTime},
|
|
||||||
#{request.followUpType},
|
|
||||||
#{request.content},
|
|
||||||
#{request.nextAction},
|
|
||||||
#{userId}
|
|
||||||
)
|
|
||||||
</insert>
|
|
||||||
</mapper>
|
|
||||||
|
|
@ -1,197 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<!DOCTYPE mapper
|
|
||||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|
||||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
|
||||||
<mapper namespace="com.unis.crm.mapper.OpportunityMapper">
|
|
||||||
|
|
||||||
<select id="selectOpportunities" resultType="com.unis.crm.dto.opportunity.OpportunityItemDTO">
|
|
||||||
select
|
|
||||||
o.id,
|
|
||||||
o.opportunity_code as code,
|
|
||||||
o.opportunity_name as name,
|
|
||||||
coalesce(c.customer_name, '未命名客户') as client,
|
|
||||||
coalesce(u.display_name, '当前用户') as owner,
|
|
||||||
o.amount,
|
|
||||||
to_char(o.expected_close_date, 'YYYY-MM-DD') as date,
|
|
||||||
o.confidence_pct as confidence,
|
|
||||||
case coalesce(o.stage, 'initial_contact')
|
|
||||||
when 'initial_contact' then '初步沟通'
|
|
||||||
when 'solution_discussion' then '方案交流'
|
|
||||||
when 'bidding' then '招投标'
|
|
||||||
when 'business_negotiation' then '商务谈判'
|
|
||||||
when 'won' then '已成交'
|
|
||||||
when 'lost' then '已放弃'
|
|
||||||
else coalesce(o.stage, '初步沟通')
|
|
||||||
end as stage,
|
|
||||||
coalesce(o.opportunity_type, '新建') as type,
|
|
||||||
coalesce(o.pushed_to_oms, false) as pushedToOms,
|
|
||||||
coalesce(o.product_type, 'VDI云桌面') as product,
|
|
||||||
coalesce(o.source, '主动开发') as source,
|
|
||||||
coalesce(o.description, '') as notes
|
|
||||||
from crm_opportunity o
|
|
||||||
left join crm_customer c on c.id = o.customer_id
|
|
||||||
left join sys_user u on u.user_id = o.owner_user_id
|
|
||||||
where o.owner_user_id = #{userId}
|
|
||||||
<if test="keyword != null and keyword != ''">
|
|
||||||
and (
|
|
||||||
o.opportunity_name ilike concat('%', #{keyword}, '%')
|
|
||||||
or o.opportunity_code ilike concat('%', #{keyword}, '%')
|
|
||||||
or coalesce(c.customer_name, '') ilike concat('%', #{keyword}, '%')
|
|
||||||
)
|
|
||||||
</if>
|
|
||||||
<if test="stage != null and stage != ''">
|
|
||||||
and o.stage = #{stage}
|
|
||||||
</if>
|
|
||||||
order by coalesce(o.updated_at, o.created_at) desc, o.id desc
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectOpportunityFollowUps" resultType="com.unis.crm.dto.opportunity.OpportunityFollowUpDTO">
|
|
||||||
select
|
|
||||||
f.id,
|
|
||||||
f.opportunity_id as opportunityId,
|
|
||||||
to_char(f.followup_time, 'YYYY-MM-DD HH24:MI') as date,
|
|
||||||
coalesce(f.followup_type, '无') as type,
|
|
||||||
coalesce(f.content, '无') as content,
|
|
||||||
coalesce(u.display_name, '无') as user
|
|
||||||
from crm_opportunity_followup f
|
|
||||||
join crm_opportunity o on o.id = f.opportunity_id
|
|
||||||
left join sys_user u on u.user_id = f.followup_user_id
|
|
||||||
where o.owner_user_id = #{userId}
|
|
||||||
and f.opportunity_id in
|
|
||||||
<foreach collection="opportunityIds" item="id" open="(" separator="," close=")">
|
|
||||||
#{id}
|
|
||||||
</foreach>
|
|
||||||
order by f.followup_time desc, f.id desc
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectOwnedCustomerIdByName" resultType="java.lang.Long">
|
|
||||||
select id
|
|
||||||
from crm_customer
|
|
||||||
where owner_user_id = #{userId}
|
|
||||||
and customer_name = #{customerName}
|
|
||||||
limit 1
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<insert id="insertCustomer">
|
|
||||||
insert into crm_customer (
|
|
||||||
id,
|
|
||||||
customer_code,
|
|
||||||
customer_name,
|
|
||||||
owner_user_id,
|
|
||||||
source,
|
|
||||||
status,
|
|
||||||
created_at,
|
|
||||||
updated_at
|
|
||||||
) values (
|
|
||||||
#{id},
|
|
||||||
'CUS-' || to_char(current_date, 'YYYYMMDD') || '-' || lpad((coalesce((select count(1) from crm_customer), 0) + 1)::text, 3, '0'),
|
|
||||||
#{customerName},
|
|
||||||
#{userId},
|
|
||||||
coalesce(#{source}, '主动开发'),
|
|
||||||
'following',
|
|
||||||
now(),
|
|
||||||
now()
|
|
||||||
)
|
|
||||||
</insert>
|
|
||||||
|
|
||||||
<insert id="insertOpportunity" useGeneratedKeys="true" keyProperty="request.id">
|
|
||||||
insert into crm_opportunity (
|
|
||||||
opportunity_code,
|
|
||||||
opportunity_name,
|
|
||||||
customer_id,
|
|
||||||
owner_user_id,
|
|
||||||
amount,
|
|
||||||
expected_close_date,
|
|
||||||
confidence_pct,
|
|
||||||
stage,
|
|
||||||
opportunity_type,
|
|
||||||
product_type,
|
|
||||||
source,
|
|
||||||
pushed_to_oms,
|
|
||||||
oms_push_time,
|
|
||||||
description,
|
|
||||||
status,
|
|
||||||
created_at,
|
|
||||||
updated_at
|
|
||||||
) values (
|
|
||||||
'OPP-' || to_char(current_date, 'YYYYMMDD') || '-' || lpad((coalesce((select count(1) from crm_opportunity), 0) + 1)::text, 3, '0'),
|
|
||||||
#{request.opportunityName},
|
|
||||||
#{customerId},
|
|
||||||
#{userId},
|
|
||||||
#{request.amount},
|
|
||||||
#{request.expectedCloseDate},
|
|
||||||
#{request.confidencePct},
|
|
||||||
#{request.stage},
|
|
||||||
#{request.opportunityType},
|
|
||||||
#{request.productType},
|
|
||||||
#{request.source},
|
|
||||||
#{request.pushedToOms},
|
|
||||||
case when #{request.pushedToOms} then now() else null end,
|
|
||||||
#{request.description},
|
|
||||||
case
|
|
||||||
when #{request.stage} = 'won' then 'won'
|
|
||||||
when #{request.stage} = 'lost' then 'lost'
|
|
||||||
else 'active'
|
|
||||||
end,
|
|
||||||
now(),
|
|
||||||
now()
|
|
||||||
)
|
|
||||||
</insert>
|
|
||||||
|
|
||||||
<select id="countOwnedOpportunity" resultType="int">
|
|
||||||
select count(1)
|
|
||||||
from crm_opportunity
|
|
||||||
where id = #{id}
|
|
||||||
and owner_user_id = #{userId}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<update id="updateOpportunity">
|
|
||||||
update crm_opportunity
|
|
||||||
set opportunity_name = #{request.opportunityName},
|
|
||||||
customer_id = #{customerId},
|
|
||||||
amount = #{request.amount},
|
|
||||||
expected_close_date = #{request.expectedCloseDate},
|
|
||||||
confidence_pct = #{request.confidencePct},
|
|
||||||
stage = #{request.stage},
|
|
||||||
opportunity_type = #{request.opportunityType},
|
|
||||||
product_type = #{request.productType},
|
|
||||||
source = #{request.source},
|
|
||||||
pushed_to_oms = #{request.pushedToOms},
|
|
||||||
oms_push_time = case
|
|
||||||
when #{request.pushedToOms} then coalesce(oms_push_time, now())
|
|
||||||
else null
|
|
||||||
end,
|
|
||||||
description = #{request.description},
|
|
||||||
status = case
|
|
||||||
when #{request.stage} = 'won' then 'won'
|
|
||||||
when #{request.stage} = 'lost' then 'lost'
|
|
||||||
else 'active'
|
|
||||||
end,
|
|
||||||
updated_at = now()
|
|
||||||
where id = #{opportunityId}
|
|
||||||
and owner_user_id = #{userId}
|
|
||||||
</update>
|
|
||||||
|
|
||||||
<insert id="insertOpportunityFollowUp">
|
|
||||||
insert into crm_opportunity_followup (
|
|
||||||
opportunity_id,
|
|
||||||
followup_time,
|
|
||||||
followup_type,
|
|
||||||
content,
|
|
||||||
next_action,
|
|
||||||
followup_user_id,
|
|
||||||
created_at,
|
|
||||||
updated_at
|
|
||||||
) values (
|
|
||||||
#{opportunityId},
|
|
||||||
#{request.followUpTime},
|
|
||||||
#{request.followUpType},
|
|
||||||
#{request.content},
|
|
||||||
#{request.nextAction},
|
|
||||||
#{userId},
|
|
||||||
now(),
|
|
||||||
now()
|
|
||||||
)
|
|
||||||
</insert>
|
|
||||||
|
|
||||||
</mapper>
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<!DOCTYPE mapper
|
|
||||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|
||||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
|
||||||
<mapper namespace="com.unis.crm.mapper.ProfileMapper">
|
|
||||||
|
|
||||||
<select id="selectProfileOverview" resultType="com.unis.crm.dto.profile.ProfileOverviewDTO">
|
|
||||||
select
|
|
||||||
u.user_id as userId,
|
|
||||||
u.display_name as realName,
|
|
||||||
case
|
|
||||||
when u.created_at is null then 0
|
|
||||||
else greatest((current_date - u.created_at::date)::bigint, 0)
|
|
||||||
end as onboardingDays,
|
|
||||||
case
|
|
||||||
when u.status = 1 then '正常'
|
|
||||||
else '停用'
|
|
||||||
end as accountStatus
|
|
||||||
from sys_user u
|
|
||||||
where u.user_id = #{userId}
|
|
||||||
and u.is_deleted = 0
|
|
||||||
limit 1
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectUserRoleNames" resultType="java.lang.String">
|
|
||||||
select r.role_name
|
|
||||||
from sys_user_role ur
|
|
||||||
join sys_role r on r.role_id = ur.role_id
|
|
||||||
where ur.user_id = #{userId}
|
|
||||||
and ur.is_deleted = 0
|
|
||||||
and r.is_deleted = 0
|
|
||||||
order by r.role_id asc
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectUserOrgNames" resultType="java.lang.String">
|
|
||||||
select o.org_name
|
|
||||||
from sys_tenant_user tu
|
|
||||||
join sys_org o on o.id = tu.org_id
|
|
||||||
where tu.user_id = #{userId}
|
|
||||||
and tu.is_deleted = 0
|
|
||||||
and o.is_deleted = 0
|
|
||||||
order by tu.id asc
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectMonthlyOpportunityCount" resultType="java.lang.Long">
|
|
||||||
select count(1)::bigint
|
|
||||||
from crm_opportunity
|
|
||||||
where owner_user_id = #{userId}
|
|
||||||
and date_trunc('month', created_at) = date_trunc('month', now())
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectMonthlyExpansionCount" resultType="java.lang.Long">
|
|
||||||
select (
|
|
||||||
coalesce((
|
|
||||||
select count(1)
|
|
||||||
from crm_sales_expansion
|
|
||||||
where owner_user_id = #{userId}
|
|
||||||
and date_trunc('month', created_at) = date_trunc('month', now())
|
|
||||||
), 0)
|
|
||||||
+
|
|
||||||
coalesce((
|
|
||||||
select count(1)
|
|
||||||
from crm_channel_expansion
|
|
||||||
where owner_user_id = #{userId}
|
|
||||||
and date_trunc('month', created_at) = date_trunc('month', now())
|
|
||||||
), 0)
|
|
||||||
)::bigint
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectAverageScore" resultType="java.lang.Integer">
|
|
||||||
with latest_comment as (
|
|
||||||
select distinct on (c.report_id)
|
|
||||||
c.report_id,
|
|
||||||
c.score
|
|
||||||
from work_daily_report_comment c
|
|
||||||
order by c.report_id, c.reviewed_at desc nulls last, c.id desc
|
|
||||||
)
|
|
||||||
select coalesce(round(avg(coalesce(lc.score, r.score))), 0)::int
|
|
||||||
from work_daily_report r
|
|
||||||
left join latest_comment lc on lc.report_id = r.id
|
|
||||||
where r.user_id = #{userId}
|
|
||||||
and date_trunc('month', r.report_date::timestamp) = date_trunc('month', now())
|
|
||||||
</select>
|
|
||||||
</mapper>
|
|
||||||
|
|
@ -1,374 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<!DOCTYPE mapper
|
|
||||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|
||||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
|
||||||
<mapper namespace="com.unis.crm.mapper.WorkMapper">
|
|
||||||
|
|
||||||
<select id="selectTodayCheckIn" resultType="com.unis.crm.dto.work.WorkCheckInDTO">
|
|
||||||
select
|
|
||||||
id,
|
|
||||||
to_char(checkin_date, 'YYYY-MM-DD') as date,
|
|
||||||
to_char(checkin_time, 'HH24:MI') as time,
|
|
||||||
coalesce(location_text, '') as locationText,
|
|
||||||
coalesce(remark, '') as remark,
|
|
||||||
coalesce(status, 'normal') as status,
|
|
||||||
longitude,
|
|
||||||
latitude
|
|
||||||
from work_checkin
|
|
||||||
where user_id = #{userId}
|
|
||||||
and checkin_date = current_date
|
|
||||||
order by checkin_time desc nulls last, id desc
|
|
||||||
limit 1
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectTodayReport" resultType="com.unis.crm.dto.work.WorkDailyReportDTO">
|
|
||||||
select
|
|
||||||
r.id,
|
|
||||||
to_char(r.report_date, 'YYYY-MM-DD') as date,
|
|
||||||
to_char(r.submit_time, 'YYYY-MM-DD HH24:MI') as submitTime,
|
|
||||||
coalesce(r.work_content, '') as workContent,
|
|
||||||
coalesce(r.tomorrow_plan, '') as tomorrowPlan,
|
|
||||||
coalesce(r.source_type, 'manual') as sourceType,
|
|
||||||
coalesce(r.status, 'submitted') as status,
|
|
||||||
c.score,
|
|
||||||
c.comment_content as comment
|
|
||||||
from work_daily_report r
|
|
||||||
left join (
|
|
||||||
select distinct on (report_id)
|
|
||||||
report_id,
|
|
||||||
score,
|
|
||||||
comment_content
|
|
||||||
from work_daily_report_comment
|
|
||||||
order by report_id, reviewed_at desc nulls last, id desc
|
|
||||||
) c on c.report_id = r.id
|
|
||||||
where r.user_id = #{userId}
|
|
||||||
and r.report_date = current_date
|
|
||||||
order by r.submit_time desc nulls last, r.id desc
|
|
||||||
limit 1
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectTodayWorkContentActions" resultType="com.unis.crm.dto.work.WorkSuggestedActionDTO">
|
|
||||||
select
|
|
||||||
group_name as groupName,
|
|
||||||
detail
|
|
||||||
from (
|
|
||||||
select
|
|
||||||
coalesce(s.created_at, now()) as action_time,
|
|
||||||
coalesce(s.candidate_name, '销售拓展') as group_name,
|
|
||||||
'新增销售拓展' ||
|
|
||||||
case
|
|
||||||
when s.title is not null and btrim(s.title) <> '' then ',岗位:' || s.title
|
|
||||||
else ''
|
|
||||||
end ||
|
|
||||||
case
|
|
||||||
when s.intent_level is not null and btrim(s.intent_level) <> '' then ',意向:' || s.intent_level
|
|
||||||
else ''
|
|
||||||
end as detail
|
|
||||||
from crm_sales_expansion s
|
|
||||||
where s.owner_user_id = #{userId}
|
|
||||||
and s.created_at::date = current_date
|
|
||||||
|
|
||||||
union all
|
|
||||||
|
|
||||||
select
|
|
||||||
coalesce(c.created_at, now()) as action_time,
|
|
||||||
coalesce(c.channel_name, '渠道拓展') as group_name,
|
|
||||||
'新增渠道拓展' ||
|
|
||||||
case
|
|
||||||
when c.province is not null and btrim(c.province) <> '' then ',地区:' || c.province
|
|
||||||
else ''
|
|
||||||
end ||
|
|
||||||
case
|
|
||||||
when c.industry is not null and btrim(c.industry) <> '' then ',行业:' || c.industry
|
|
||||||
else ''
|
|
||||||
end as detail
|
|
||||||
from crm_channel_expansion c
|
|
||||||
where c.owner_user_id = #{userId}
|
|
||||||
and c.created_at::date = current_date
|
|
||||||
|
|
||||||
union all
|
|
||||||
|
|
||||||
select
|
|
||||||
coalesce(o.created_at, now()) as action_time,
|
|
||||||
coalesce(nullif(btrim(cust.customer_name), ''), nullif(btrim(o.opportunity_name), ''), '商机客户') as group_name,
|
|
||||||
'新增商机:' ||
|
|
||||||
coalesce(o.opportunity_name, '未命名商机') ||
|
|
||||||
case
|
|
||||||
when o.amount is not null then ',金额:¥' || trim(to_char(o.amount, 'FM9999999999990.00'))
|
|
||||||
else ''
|
|
||||||
end as detail
|
|
||||||
from crm_opportunity o
|
|
||||||
left join crm_customer cust on cust.id = o.customer_id
|
|
||||||
where o.owner_user_id = #{userId}
|
|
||||||
and o.created_at::date = current_date
|
|
||||||
|
|
||||||
union all
|
|
||||||
|
|
||||||
select
|
|
||||||
coalesce(f.followup_time, now()) as action_time,
|
|
||||||
coalesce(s.candidate_name, '销售拓展') as group_name,
|
|
||||||
'销售拓展跟进' ||
|
|
||||||
case
|
|
||||||
when f.followup_type is not null and btrim(f.followup_type) <> '' then ',方式:' || f.followup_type
|
|
||||||
else ''
|
|
||||||
end ||
|
|
||||||
case
|
|
||||||
when f.content is not null and btrim(f.content) <> '' then ',内容:' || f.content
|
|
||||||
else ''
|
|
||||||
end as detail
|
|
||||||
from crm_expansion_followup f
|
|
||||||
join crm_sales_expansion s on s.id = f.biz_id and f.biz_type = 'sales'
|
|
||||||
where f.followup_user_id = #{userId}
|
|
||||||
and f.followup_time::date = current_date
|
|
||||||
|
|
||||||
union all
|
|
||||||
|
|
||||||
select
|
|
||||||
coalesce(f.followup_time, now()) as action_time,
|
|
||||||
coalesce(c.channel_name, '渠道拓展') as group_name,
|
|
||||||
'渠道拓展跟进' ||
|
|
||||||
case
|
|
||||||
when f.followup_type is not null and btrim(f.followup_type) <> '' then ',方式:' || f.followup_type
|
|
||||||
else ''
|
|
||||||
end ||
|
|
||||||
case
|
|
||||||
when f.content is not null and btrim(f.content) <> '' then ',内容:' || f.content
|
|
||||||
else ''
|
|
||||||
end as detail
|
|
||||||
from crm_expansion_followup f
|
|
||||||
join crm_channel_expansion c on c.id = f.biz_id and f.biz_type = 'channel'
|
|
||||||
where f.followup_user_id = #{userId}
|
|
||||||
and f.followup_time::date = current_date
|
|
||||||
|
|
||||||
union all
|
|
||||||
|
|
||||||
select
|
|
||||||
coalesce(f.followup_time, now()) as action_time,
|
|
||||||
coalesce(nullif(btrim(cust.customer_name), ''), nullif(btrim(o.opportunity_name), ''), '商机客户') as group_name,
|
|
||||||
'商机跟进' ||
|
|
||||||
case
|
|
||||||
when f.followup_type is not null and btrim(f.followup_type) <> '' then ',方式:' || f.followup_type
|
|
||||||
else ''
|
|
||||||
end ||
|
|
||||||
case
|
|
||||||
when f.content is not null and btrim(f.content) <> '' then ',内容:' || f.content
|
|
||||||
else ''
|
|
||||||
end as detail
|
|
||||||
from crm_opportunity_followup f
|
|
||||||
join crm_opportunity o on o.id = f.opportunity_id
|
|
||||||
left join crm_customer cust on cust.id = o.customer_id
|
|
||||||
where f.followup_user_id = #{userId}
|
|
||||||
and f.followup_time::date = current_date
|
|
||||||
) work_lines
|
|
||||||
order by action_time asc, group_name asc, detail asc
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectHistory" resultType="com.unis.crm.dto.work.WorkHistoryItemDTO">
|
|
||||||
select
|
|
||||||
id,
|
|
||||||
type,
|
|
||||||
date,
|
|
||||||
time,
|
|
||||||
content,
|
|
||||||
status,
|
|
||||||
score,
|
|
||||||
comment
|
|
||||||
from (
|
|
||||||
select
|
|
||||||
c.id,
|
|
||||||
'外勤打卡' as type,
|
|
||||||
to_char(c.checkin_date, 'YYYY-MM-DD') as date,
|
|
||||||
to_char(c.checkin_time, 'HH24:MI') as time,
|
|
||||||
coalesce(c.location_text, '') ||
|
|
||||||
case
|
|
||||||
when c.remark is not null and btrim(c.remark) <> '' then E'\n备注:' || c.remark
|
|
||||||
else ''
|
|
||||||
end as content,
|
|
||||||
case coalesce(c.status, 'normal')
|
|
||||||
when 'normal' then '正常'
|
|
||||||
when 'updated' then '已更新'
|
|
||||||
else coalesce(c.status, '正常')
|
|
||||||
end as status,
|
|
||||||
null::integer as score,
|
|
||||||
null::text as comment,
|
|
||||||
coalesce(c.checkin_date::timestamp + c.checkin_time::time, c.created_at) as sort_time
|
|
||||||
from work_checkin c
|
|
||||||
where c.user_id = #{userId}
|
|
||||||
|
|
||||||
union all
|
|
||||||
|
|
||||||
select
|
|
||||||
r.id,
|
|
||||||
'日报' as type,
|
|
||||||
to_char(r.report_date, 'YYYY-MM-DD') as date,
|
|
||||||
to_char(r.submit_time, 'HH24:MI') as time,
|
|
||||||
coalesce(r.work_content, '') ||
|
|
||||||
case
|
|
||||||
when r.tomorrow_plan is not null and btrim(r.tomorrow_plan) <> '' then E'\n明日计划:' || r.tomorrow_plan
|
|
||||||
else ''
|
|
||||||
end as content,
|
|
||||||
case coalesce(rc.comment_content, '')
|
|
||||||
when '' then
|
|
||||||
case coalesce(r.status, 'submitted')
|
|
||||||
when 'submitted' then '已提交'
|
|
||||||
when 'reviewed' then '已点评'
|
|
||||||
else coalesce(r.status, '已提交')
|
|
||||||
end
|
|
||||||
else '已点评'
|
|
||||||
end as status,
|
|
||||||
rc.score,
|
|
||||||
rc.comment_content as comment,
|
|
||||||
coalesce(r.report_date::timestamp + r.submit_time::time, r.created_at) as sort_time
|
|
||||||
from work_daily_report r
|
|
||||||
left join (
|
|
||||||
select distinct on (report_id)
|
|
||||||
report_id,
|
|
||||||
score,
|
|
||||||
comment_content
|
|
||||||
from work_daily_report_comment
|
|
||||||
order by report_id, reviewed_at desc nulls last, id desc
|
|
||||||
) rc on rc.report_id = r.id
|
|
||||||
where r.user_id = #{userId}
|
|
||||||
) history
|
|
||||||
order by sort_time desc nulls last, id desc
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectTodayCheckInId" resultType="java.lang.Long">
|
|
||||||
select id
|
|
||||||
from work_checkin
|
|
||||||
where user_id = #{userId}
|
|
||||||
and checkin_date = current_date
|
|
||||||
order by checkin_time desc nulls last, id desc
|
|
||||||
limit 1
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<insert id="insertCheckIn">
|
|
||||||
insert into work_checkin (
|
|
||||||
id,
|
|
||||||
user_id,
|
|
||||||
checkin_date,
|
|
||||||
checkin_time,
|
|
||||||
longitude,
|
|
||||||
latitude,
|
|
||||||
location_text,
|
|
||||||
remark,
|
|
||||||
status,
|
|
||||||
created_at,
|
|
||||||
updated_at
|
|
||||||
) values (
|
|
||||||
(select coalesce(max(id), 0) + 1 from work_checkin),
|
|
||||||
#{userId},
|
|
||||||
current_date,
|
|
||||||
now(),
|
|
||||||
#{request.longitude},
|
|
||||||
#{request.latitude},
|
|
||||||
#{request.locationText},
|
|
||||||
#{request.remark},
|
|
||||||
'normal',
|
|
||||||
now(),
|
|
||||||
now()
|
|
||||||
)
|
|
||||||
</insert>
|
|
||||||
|
|
||||||
<update id="updateCheckIn">
|
|
||||||
update work_checkin
|
|
||||||
set checkin_time = now(),
|
|
||||||
longitude = #{request.longitude},
|
|
||||||
latitude = #{request.latitude},
|
|
||||||
location_text = #{request.locationText},
|
|
||||||
remark = #{request.remark},
|
|
||||||
status = 'normal',
|
|
||||||
updated_at = now()
|
|
||||||
where id = #{checkInId}
|
|
||||||
</update>
|
|
||||||
|
|
||||||
<select id="selectTodayReportId" resultType="java.lang.Long">
|
|
||||||
select id
|
|
||||||
from work_daily_report
|
|
||||||
where user_id = #{userId}
|
|
||||||
and report_date = current_date
|
|
||||||
order by submit_time desc nulls last, id desc
|
|
||||||
limit 1
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<insert id="insertDailyReport">
|
|
||||||
insert into work_daily_report (
|
|
||||||
user_id,
|
|
||||||
report_date,
|
|
||||||
work_content,
|
|
||||||
tomorrow_plan,
|
|
||||||
source_type,
|
|
||||||
submit_time,
|
|
||||||
status,
|
|
||||||
created_at,
|
|
||||||
updated_at
|
|
||||||
) values (
|
|
||||||
#{userId},
|
|
||||||
current_date,
|
|
||||||
#{request.workContent},
|
|
||||||
#{request.tomorrowPlan},
|
|
||||||
#{request.sourceType},
|
|
||||||
now(),
|
|
||||||
'submitted',
|
|
||||||
now(),
|
|
||||||
now()
|
|
||||||
)
|
|
||||||
</insert>
|
|
||||||
|
|
||||||
<update id="updateDailyReport">
|
|
||||||
update work_daily_report
|
|
||||||
set work_content = #{request.workContent},
|
|
||||||
tomorrow_plan = #{request.tomorrowPlan},
|
|
||||||
source_type = #{request.sourceType},
|
|
||||||
submit_time = now(),
|
|
||||||
status = 'submitted',
|
|
||||||
updated_at = now()
|
|
||||||
where id = #{reportId}
|
|
||||||
</update>
|
|
||||||
|
|
||||||
<select id="selectTodoIdByBiz" resultType="java.lang.Long">
|
|
||||||
select id
|
|
||||||
from work_todo
|
|
||||||
where user_id = #{userId}
|
|
||||||
and biz_type = #{bizType}
|
|
||||||
and biz_id = #{bizId}
|
|
||||||
limit 1
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<insert id="insertTodo">
|
|
||||||
insert into work_todo (
|
|
||||||
id,
|
|
||||||
user_id,
|
|
||||||
title,
|
|
||||||
biz_type,
|
|
||||||
biz_id,
|
|
||||||
due_date,
|
|
||||||
status,
|
|
||||||
priority,
|
|
||||||
created_at,
|
|
||||||
updated_at
|
|
||||||
) values (
|
|
||||||
#{todoId},
|
|
||||||
#{userId},
|
|
||||||
#{title},
|
|
||||||
#{bizType},
|
|
||||||
#{bizId},
|
|
||||||
current_date::timestamp + interval '1 day' + time '09:00',
|
|
||||||
'todo',
|
|
||||||
'medium',
|
|
||||||
now(),
|
|
||||||
now()
|
|
||||||
)
|
|
||||||
</insert>
|
|
||||||
|
|
||||||
<update id="updateTodo">
|
|
||||||
update work_todo
|
|
||||||
set title = #{title},
|
|
||||||
due_date = current_date::timestamp + interval '1 day' + time '09:00',
|
|
||||||
status = 'todo',
|
|
||||||
priority = 'medium',
|
|
||||||
updated_at = now()
|
|
||||||
where id = #{todoId}
|
|
||||||
</update>
|
|
||||||
|
|
||||||
</mapper>
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
artifactId=unis-crm-backend
|
|
||||||
groupId=com.unis.crm
|
|
||||||
version=1.0.0-SNAPSHOT
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
com/unis/crm/service/impl/WorkServiceImpl$PhotoMetadata.class
|
|
||||||
com/unis/crm/service/impl/ExpansionServiceImpl.class
|
|
||||||
com/unis/crm/dto/dashboard/UserWelcomeDTO.class
|
|
||||||
com/unis/crm/dto/expansion/ExpansionOverviewDTO.class
|
|
||||||
com/unis/crm/service/impl/DashboardServiceImpl.class
|
|
||||||
com/unis/crm/controller/ExpansionController.class
|
|
||||||
com/unis/crm/dto/expansion/ExpansionFollowUpDTO.class
|
|
||||||
com/unis/crm/dto/expansion/CreateSalesExpansionRequest.class
|
|
||||||
com/unis/crm/common/ApiResponse.class
|
|
||||||
com/unis/crm/UnisCrmBackendApplication.class
|
|
||||||
com/unis/crm/common/CurrentUserUtils.class
|
|
||||||
com/unis/crm/service/DashboardService.class
|
|
||||||
com/unis/crm/dto/expansion/UpdateChannelExpansionRequest.class
|
|
||||||
com/unis/crm/common/CrmGlobalExceptionHandler.class
|
|
||||||
com/unis/crm/dto/dashboard/DashboardStatDTO.class
|
|
||||||
com/unis/crm/dto/work/WorkSuggestedActionDTO.class
|
|
||||||
com/unis/crm/dto/expansion/CreateChannelExpansionRequest.class
|
|
||||||
com/unis/crm/dto/expansion/DepartmentOptionDTO.class
|
|
||||||
com/unis/crm/dto/expansion/SalesExpansionItemDTO.class
|
|
||||||
com/unis/crm/dto/dashboard/DashboardActivityDTO.class
|
|
||||||
com/unis/crm/service/ExpansionService.class
|
|
||||||
com/unis/crm/dto/expansion/UpdateSalesExpansionRequest.class
|
|
||||||
com/unis/crm/dto/profile/ProfileOverviewDTO.class
|
|
||||||
com/unis/crm/dto/dashboard/DashboardTodoDTO.class
|
|
||||||
com/unis/crm/controller/DashboardController.class
|
|
||||||
com/unis/crm/controller/ProfileController.class
|
|
||||||
com/unis/crm/dto/expansion/ExpansionMetaDTO.class
|
|
||||||
com/unis/crm/service/impl/ProfileServiceImpl.class
|
|
||||||
com/unis/crm/mapper/ExpansionMapper.class
|
|
||||||
com/unis/crm/dto/dashboard/DashboardHomeDTO.class
|
|
||||||
com/unis/crm/mapper/ProfileMapper.class
|
|
||||||
com/unis/crm/service/ProfileService.class
|
|
||||||
com/unis/crm/common/BusinessException.class
|
|
||||||
com/unis/crm/mapper/DashboardMapper.class
|
|
||||||
com/unis/crm/dto/expansion/CreateExpansionFollowUpRequest.class
|
|
||||||
com/unis/crm/dto/expansion/ChannelExpansionItemDTO.class
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/mapper/ExpansionMapper.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/work/WorkDailyReportDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/dashboard/DashboardHomeDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/opportunity/OpportunityFollowUpDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/opportunity/CreateOpportunityFollowUpRequest.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/expansion/ExpansionOverviewDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/work/CreateWorkCheckInRequest.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/common/CurrentUserUtils.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/controller/DashboardController.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/common/ApiResponse.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/service/impl/WorkServiceImpl.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/UnisCrmBackendApplication.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/expansion/DepartmentOptionDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/expansion/ExpansionMetaDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/dashboard/DashboardTodoDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/work/WorkHistoryItemDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/work/CreateWorkDailyReportRequest.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/service/WorkService.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/profile/ProfileOverviewDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/work/WorkCheckInDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/controller/OpportunityController.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/work/WorkOverviewDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/work/WorkSuggestedActionDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/dashboard/UserWelcomeDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/opportunity/OpportunityItemDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/dashboard/DashboardActivityDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/service/ProfileService.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/mapper/DashboardMapper.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/dashboard/DashboardStatDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/service/DashboardService.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/service/OpportunityService.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/controller/WorkController.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/expansion/CreateSalesExpansionRequest.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/controller/ExpansionController.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/common/BusinessException.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/service/impl/DashboardServiceImpl.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/service/ExpansionService.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/service/impl/ExpansionServiceImpl.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/mapper/OpportunityMapper.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/expansion/UpdateChannelExpansionRequest.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/expansion/ExpansionFollowUpDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/opportunity/OpportunityOverviewDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/mapper/WorkMapper.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/expansion/UpdateSalesExpansionRequest.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/expansion/ChannelExpansionItemDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/opportunity/CreateOpportunityRequest.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/expansion/CreateChannelExpansionRequest.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/expansion/SalesExpansionItemDTO.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/service/impl/ProfileServiceImpl.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/service/impl/OpportunityServiceImpl.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/dto/expansion/CreateExpansionFollowUpRequest.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/controller/ProfileController.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/mapper/ProfileMapper.java
|
|
||||||
/Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/common/CrmGlobalExceptionHandler.java
|
|
||||||
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,22 +0,0 @@
|
||||||
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect width="64" height="64" rx="18" fill="url(#bg)"/>
|
|
||||||
<rect x="4" y="4" width="56" height="56" rx="14" stroke="rgba(255,255,255,0.22)"/>
|
|
||||||
<text
|
|
||||||
x="32"
|
|
||||||
y="38"
|
|
||||||
text-anchor="middle"
|
|
||||||
font-size="21"
|
|
||||||
font-weight="700"
|
|
||||||
font-family="Arial, PingFang SC, Microsoft YaHei, sans-serif"
|
|
||||||
fill="white"
|
|
||||||
letter-spacing="0.5"
|
|
||||||
>
|
|
||||||
CRM
|
|
||||||
</text>
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="bg" x1="10" y1="8" x2="56" y2="56" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop stop-color="#7C3AED"/>
|
|
||||||
<stop offset="1" stop-color="#4F46E5"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 684 B |
|
|
@ -1,14 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/crm-favicon.svg" />
|
|
||||||
<title>紫光汇智CRM</title>
|
|
||||||
<script type="module" crossorigin src="/assets/index-Ba78XVP4.js"></script>
|
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-D3WIva4A.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -524,11 +524,140 @@ export async function getWorkOverview() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function reverseWorkGeocode(latitude: number, longitude: number) {
|
export async function reverseWorkGeocode(latitude: number, longitude: number) {
|
||||||
const params = new URLSearchParams({
|
const nominatimParams = new URLSearchParams({
|
||||||
|
format: "jsonv2",
|
||||||
|
addressdetails: "1",
|
||||||
|
namedetails: "1",
|
||||||
|
extratags: "1",
|
||||||
|
zoom: "19",
|
||||||
lat: String(latitude),
|
lat: String(latitude),
|
||||||
lon: String(longitude),
|
lon: String(longitude),
|
||||||
|
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
|
||||||
});
|
});
|
||||||
return request<string>(`/api/work/reverse-geocode?${params.toString()}`, undefined, true);
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`https://nominatim.openstreetmap.org/reverse?${nominatimParams.toString()}`, {
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Nominatim reverse geocoding failed (${response.status})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = (await response.json()) as {
|
||||||
|
display_name?: string;
|
||||||
|
address?: Record<string, string>;
|
||||||
|
namedetails?: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const orderedLocation = buildNominatimLocationName(payload);
|
||||||
|
if (orderedLocation) {
|
||||||
|
return orderedLocation;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Fall through to the browser-side free fallback.
|
||||||
|
}
|
||||||
|
|
||||||
|
const bigDataCloudParams = new URLSearchParams({
|
||||||
|
latitude: String(latitude),
|
||||||
|
longitude: String(longitude),
|
||||||
|
localityLanguage: "zh",
|
||||||
|
});
|
||||||
|
|
||||||
|
const fallbackResponse = await fetch(`https://api.bigdatacloud.net/data/reverse-geocode-client?${bigDataCloudParams.toString()}`);
|
||||||
|
if (!fallbackResponse.ok) {
|
||||||
|
throw new Error("地点解析失败,请稍后重试");
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallbackPayload = (await fallbackResponse.json()) as {
|
||||||
|
locality?: string;
|
||||||
|
city?: string;
|
||||||
|
principalSubdivision?: string;
|
||||||
|
countryName?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fallbackLocation = joinLocationParts(
|
||||||
|
fallbackPayload.countryName,
|
||||||
|
fallbackPayload.principalSubdivision,
|
||||||
|
fallbackPayload.city,
|
||||||
|
fallbackPayload.locality,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (fallbackLocation) {
|
||||||
|
return fallbackLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("未能解析出具体地点名称");
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildNominatimLocationName(payload: {
|
||||||
|
display_name?: string;
|
||||||
|
address?: Record<string, string>;
|
||||||
|
namedetails?: Record<string, string>;
|
||||||
|
}) {
|
||||||
|
const address = payload.address ?? {};
|
||||||
|
const namedetails = payload.namedetails ?? {};
|
||||||
|
|
||||||
|
const regionPart = joinLocationParts(
|
||||||
|
firstDefined(address.state, address.province, address.region),
|
||||||
|
firstDefined(address.city, address.municipality, address.town, address.county),
|
||||||
|
firstDefined(address.district, address.city_district, address.borough),
|
||||||
|
);
|
||||||
|
|
||||||
|
const streetPart = joinLocationParts(
|
||||||
|
firstDefined(address.suburb, address.township, address.quarter, address.neighbourhood),
|
||||||
|
firstDefined(address.road, address.street, address.pedestrian),
|
||||||
|
joinLocationParts(address.house_number, address.house_name),
|
||||||
|
);
|
||||||
|
|
||||||
|
const buildingPart = firstDefined(
|
||||||
|
address.building,
|
||||||
|
address.city_block,
|
||||||
|
address.amenity,
|
||||||
|
address.office,
|
||||||
|
address.shop,
|
||||||
|
address.commercial,
|
||||||
|
address.residential,
|
||||||
|
address.industrial,
|
||||||
|
address.retail,
|
||||||
|
namedetails.name,
|
||||||
|
namedetails.official_name,
|
||||||
|
namedetails.short_name,
|
||||||
|
);
|
||||||
|
|
||||||
|
return firstDefined(
|
||||||
|
joinLocationParts(regionPart, streetPart, buildingPart),
|
||||||
|
joinLocationParts(regionPart, streetPart),
|
||||||
|
normalizeLocationText(payload.display_name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function firstDefined(...values: Array<string | undefined>) {
|
||||||
|
for (const value of values) {
|
||||||
|
const normalized = normalizeLocationText(value);
|
||||||
|
if (normalized) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function joinLocationParts(...values: Array<string | undefined>) {
|
||||||
|
const result: string[] = [];
|
||||||
|
for (const value of values) {
|
||||||
|
const normalized = normalizeLocationText(value);
|
||||||
|
if (!normalized || result.includes(normalized)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.push(normalized);
|
||||||
|
}
|
||||||
|
return result.length ? result.join("") : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLocationText(value?: string) {
|
||||||
|
const normalized = value?.trim();
|
||||||
|
return normalized ? normalized : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveWorkCheckIn(payload: CreateWorkCheckInPayload) {
|
export async function saveWorkCheckIn(payload: CreateWorkCheckInPayload) {
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ export default function Work() {
|
||||||
const displayName = await reverseWorkGeocode(latitude, longitude);
|
const displayName = await reverseWorkGeocode(latitude, longitude);
|
||||||
setCheckInForm((prev) => ({
|
setCheckInForm((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
locationText: displayName || `定位坐标:${latitude}, ${longitude}`,
|
locationText: displayName || formatLocationFallback(latitude, longitude),
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
}));
|
}));
|
||||||
|
|
@ -111,15 +111,15 @@ export default function Work() {
|
||||||
setLocationHint(displayName
|
setLocationHint(displayName
|
||||||
? "定位已刷新并锁定当前位置,如需变更请点击“刷新定位”。"
|
? "定位已刷新并锁定当前位置,如需变更请点击“刷新定位”。"
|
||||||
: "已获取定位坐标,如需更精确地址可再次刷新定位。");
|
: "已获取定位坐标,如需更精确地址可再次刷新定位。");
|
||||||
} catch {
|
} catch (error) {
|
||||||
setCheckInForm((prev) => ({
|
setCheckInForm((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
locationText: `定位坐标:${latitude}, ${longitude}`,
|
locationText: formatLocationFallback(latitude, longitude),
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
}));
|
}));
|
||||||
setLocationLocked(false);
|
setLocationLocked(false);
|
||||||
setLocationHint("已获取坐标,但地点名称解析失败,你也可以手动补充。");
|
setLocationHint(getReverseGeocodeHint(error));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setLocationLocked(false);
|
setLocationLocked(false);
|
||||||
|
|
@ -533,6 +533,18 @@ export default function Work() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatLocationFallback(latitude: number, longitude: number) {
|
||||||
|
return `当前位置待补充(坐标:${latitude}, ${longitude})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReverseGeocodeHint(error: unknown) {
|
||||||
|
const message = error instanceof Error ? error.message : "";
|
||||||
|
if (message.includes("地点解析失败")) {
|
||||||
|
return "已获取定位坐标,但地址解析服务暂时不可用,你也可以手动补充当前位置。";
|
||||||
|
}
|
||||||
|
return "已获取坐标,但地点名称解析失败,你也可以手动补充。";
|
||||||
|
}
|
||||||
|
|
||||||
function getGeoErrorMessage(error: GeolocationPositionError) {
|
function getGeoErrorMessage(error: GeolocationPositionError) {
|
||||||
if (!window.isSecureContext) {
|
if (!window.isSecureContext) {
|
||||||
return "手机端定位需要通过安全地址访问。请使用 HTTPS,或继续手动填写当前位置。";
|
return "手机端定位需要通过安全地址访问。请使用 HTTPS,或继续手动填写当前位置。";
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
-- sys_user: migrate dept_id -> org_id (idempotent)
|
||||||
|
-- Target state:
|
||||||
|
-- 1) sys_user has org_id column
|
||||||
|
-- 2) dept_id column removed
|
||||||
|
-- 3) idx_sys_user_org_id exists
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
do $$
|
||||||
|
begin
|
||||||
|
if to_regclass('public.sys_user') is not null then
|
||||||
|
-- If org_id is missing, add it.
|
||||||
|
if not exists (
|
||||||
|
select 1
|
||||||
|
from information_schema.columns
|
||||||
|
where table_schema = 'public'
|
||||||
|
and table_name = 'sys_user'
|
||||||
|
and column_name = 'org_id'
|
||||||
|
) then
|
||||||
|
execute 'alter table public.sys_user add column org_id bigint';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
-- If dept_id exists, backfill org_id and then drop dept_id.
|
||||||
|
if exists (
|
||||||
|
select 1
|
||||||
|
from information_schema.columns
|
||||||
|
where table_schema = 'public'
|
||||||
|
and table_name = 'sys_user'
|
||||||
|
and column_name = 'dept_id'
|
||||||
|
) then
|
||||||
|
execute 'update public.sys_user set org_id = coalesce(org_id, dept_id) where dept_id is not null';
|
||||||
|
execute 'alter table public.sys_user drop column dept_id';
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end $$;
|
||||||
|
|
||||||
|
do $$
|
||||||
|
begin
|
||||||
|
if to_regclass('public.sys_user') is not null then
|
||||||
|
execute 'drop index if exists public.idx_sys_user_dept_id';
|
||||||
|
execute 'create index if not exists idx_sys_user_org_id on public.sys_user(org_id)';
|
||||||
|
end if;
|
||||||
|
end $$;
|
||||||
|
|
||||||
|
commit;
|
||||||
|
|
@ -40,18 +40,6 @@ begin
|
||||||
end;
|
end;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
create table if not exists sys_department (
|
|
||||||
id bigint generated by default as identity primary key,
|
|
||||||
dept_code varchar(50),
|
|
||||||
dept_name varchar(100) not null,
|
|
||||||
parent_id bigint,
|
|
||||||
manager_user_id bigint,
|
|
||||||
status smallint not null default 1,
|
|
||||||
created_at timestamptz not null default now(),
|
|
||||||
updated_at timestamptz not null default now(),
|
|
||||||
constraint uk_sys_department_code unique (dept_code)
|
|
||||||
);
|
|
||||||
|
|
||||||
create table if not exists sys_user (
|
create table if not exists sys_user (
|
||||||
id bigint generated by default as identity primary key,
|
id bigint generated by default as identity primary key,
|
||||||
user_code varchar(50),
|
user_code varchar(50),
|
||||||
|
|
@ -59,7 +47,7 @@ create table if not exists sys_user (
|
||||||
real_name varchar(50) not null,
|
real_name varchar(50) not null,
|
||||||
mobile varchar(20),
|
mobile varchar(20),
|
||||||
email varchar(100),
|
email varchar(100),
|
||||||
dept_id bigint,
|
org_id bigint,
|
||||||
job_title varchar(100),
|
job_title varchar(100),
|
||||||
status smallint not null default 1,
|
status smallint not null default 1,
|
||||||
hire_date date,
|
hire_date date,
|
||||||
|
|
@ -68,8 +56,7 @@ create table if not exists sys_user (
|
||||||
created_at timestamptz not null default now(),
|
created_at timestamptz not null default now(),
|
||||||
updated_at timestamptz not null default now(),
|
updated_at timestamptz not null default now(),
|
||||||
constraint uk_sys_user_username unique (username),
|
constraint uk_sys_user_username unique (username),
|
||||||
constraint uk_sys_user_mobile unique (mobile),
|
constraint uk_sys_user_mobile unique (mobile)
|
||||||
constraint fk_sys_user_dept foreign key (dept_id) references sys_department(id)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
create table if not exists crm_customer (
|
create table if not exists crm_customer (
|
||||||
|
|
@ -206,18 +193,6 @@ create table if not exists work_checkin (
|
||||||
updated_at timestamptz not null default now()
|
updated_at timestamptz not null default now()
|
||||||
);
|
);
|
||||||
|
|
||||||
create table if not exists work_checkin_attachment (
|
|
||||||
id bigint generated by default as identity primary key,
|
|
||||||
checkin_id bigint not null,
|
|
||||||
file_url varchar(255) not null,
|
|
||||||
file_type varchar(30) not null check (file_type in ('image', 'audio', 'video')),
|
|
||||||
file_name varchar(255),
|
|
||||||
file_size bigint check (file_size is null or file_size >= 0),
|
|
||||||
created_at timestamptz not null default now(),
|
|
||||||
constraint fk_work_checkin_attachment_checkin
|
|
||||||
foreign key (checkin_id) references work_checkin(id) on delete cascade
|
|
||||||
);
|
|
||||||
|
|
||||||
create table if not exists work_daily_report (
|
create table if not exists work_daily_report (
|
||||||
id bigint generated by default as identity primary key,
|
id bigint generated by default as identity primary key,
|
||||||
user_id bigint not null,
|
user_id bigint not null,
|
||||||
|
|
@ -276,7 +251,7 @@ create table if not exists sys_activity_log (
|
||||||
|
|
||||||
create index if not exists idx_crm_customer_owner on crm_customer(owner_user_id);
|
create index if not exists idx_crm_customer_owner on crm_customer(owner_user_id);
|
||||||
create index if not exists idx_crm_customer_name on crm_customer(customer_name);
|
create index if not exists idx_crm_customer_name on crm_customer(customer_name);
|
||||||
create index if not exists idx_sys_user_dept_id on sys_user(dept_id);
|
create index if not exists idx_sys_user_org_id on sys_user(org_id);
|
||||||
create index if not exists idx_crm_opportunity_customer on crm_opportunity(customer_id);
|
create index if not exists idx_crm_opportunity_customer on crm_opportunity(customer_id);
|
||||||
create index if not exists idx_crm_opportunity_owner on crm_opportunity(owner_user_id);
|
create index if not exists idx_crm_opportunity_owner on crm_opportunity(owner_user_id);
|
||||||
create index if not exists idx_crm_opportunity_stage on crm_opportunity(stage);
|
create index if not exists idx_crm_opportunity_stage on crm_opportunity(stage);
|
||||||
|
|
@ -294,14 +269,12 @@ create index if not exists idx_crm_expansion_followup_biz_time
|
||||||
on crm_expansion_followup(biz_type, biz_id, followup_time desc);
|
on crm_expansion_followup(biz_type, biz_id, followup_time desc);
|
||||||
create index if not exists idx_crm_expansion_followup_user on crm_expansion_followup(followup_user_id);
|
create index if not exists idx_crm_expansion_followup_user on crm_expansion_followup(followup_user_id);
|
||||||
create index if not exists idx_work_checkin_user_date on work_checkin(user_id, checkin_date desc);
|
create index if not exists idx_work_checkin_user_date on work_checkin(user_id, checkin_date desc);
|
||||||
create index if not exists idx_work_checkin_attachment_checkin on work_checkin_attachment(checkin_id);
|
|
||||||
create index if not exists idx_work_daily_report_user_date on work_daily_report(user_id, report_date desc);
|
create index if not exists idx_work_daily_report_user_date on work_daily_report(user_id, report_date desc);
|
||||||
create index if not exists idx_work_daily_report_status on work_daily_report(status);
|
create index if not exists idx_work_daily_report_status on work_daily_report(status);
|
||||||
create index if not exists idx_work_daily_report_comment_report on work_daily_report_comment(report_id);
|
create index if not exists idx_work_daily_report_comment_report on work_daily_report_comment(report_id);
|
||||||
create index if not exists idx_sys_activity_log_created on sys_activity_log(created_at desc);
|
create index if not exists idx_sys_activity_log_created on sys_activity_log(created_at desc);
|
||||||
create index if not exists idx_sys_activity_log_biz on sys_activity_log(biz_type, biz_id);
|
create index if not exists idx_sys_activity_log_biz on sys_activity_log(biz_type, biz_id);
|
||||||
|
|
||||||
select create_trigger_if_not_exists('trg_sys_department_updated_at', 'sys_department');
|
|
||||||
select create_trigger_if_not_exists('trg_sys_user_updated_at', 'sys_user');
|
select create_trigger_if_not_exists('trg_sys_user_updated_at', 'sys_user');
|
||||||
select create_trigger_if_not_exists('trg_crm_customer_updated_at', 'crm_customer');
|
select create_trigger_if_not_exists('trg_crm_customer_updated_at', 'crm_customer');
|
||||||
select create_trigger_if_not_exists('trg_crm_opportunity_updated_at', 'crm_opportunity');
|
select create_trigger_if_not_exists('trg_crm_opportunity_updated_at', 'crm_opportunity');
|
||||||
|
|
@ -313,7 +286,6 @@ select create_trigger_if_not_exists('trg_work_checkin_updated_at', 'work_checkin
|
||||||
select create_trigger_if_not_exists('trg_work_daily_report_updated_at', 'work_daily_report');
|
select create_trigger_if_not_exists('trg_work_daily_report_updated_at', 'work_daily_report');
|
||||||
select create_trigger_if_not_exists('trg_work_todo_updated_at', 'work_todo');
|
select create_trigger_if_not_exists('trg_work_todo_updated_at', 'work_todo');
|
||||||
|
|
||||||
comment on table sys_department is '组织部门';
|
|
||||||
comment on table sys_user is '系统用户';
|
comment on table sys_user is '系统用户';
|
||||||
comment on table crm_customer is '客户主表';
|
comment on table crm_customer is '客户主表';
|
||||||
comment on table crm_opportunity is '商机主表';
|
comment on table crm_opportunity is '商机主表';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue