修改定位信息 0323
parent
512f42dfd4
commit
13d3abeeee
|
|
@ -43,7 +43,6 @@ import org.springframework.web.multipart.MultipartFile;
|
|||
@Service
|
||||
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_SUFFIX = "[[/CHECKIN_PHOTOS]]";
|
||||
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) {
|
||||
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("未能解析出具体地点名称");
|
||||
throw new BusinessException("当前环境未启用服务端逆地理解析");
|
||||
}
|
||||
|
||||
@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) {
|
||||
const params = new URLSearchParams({
|
||||
const nominatimParams = new URLSearchParams({
|
||||
format: "jsonv2",
|
||||
addressdetails: "1",
|
||||
namedetails: "1",
|
||||
extratags: "1",
|
||||
zoom: "19",
|
||||
lat: String(latitude),
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ export default function Work() {
|
|||
const displayName = await reverseWorkGeocode(latitude, longitude);
|
||||
setCheckInForm((prev) => ({
|
||||
...prev,
|
||||
locationText: displayName || `定位坐标:${latitude}, ${longitude}`,
|
||||
locationText: displayName || formatLocationFallback(latitude, longitude),
|
||||
latitude,
|
||||
longitude,
|
||||
}));
|
||||
|
|
@ -111,15 +111,15 @@ export default function Work() {
|
|||
setLocationHint(displayName
|
||||
? "定位已刷新并锁定当前位置,如需变更请点击“刷新定位”。"
|
||||
: "已获取定位坐标,如需更精确地址可再次刷新定位。");
|
||||
} catch {
|
||||
} catch (error) {
|
||||
setCheckInForm((prev) => ({
|
||||
...prev,
|
||||
locationText: `定位坐标:${latitude}, ${longitude}`,
|
||||
locationText: formatLocationFallback(latitude, longitude),
|
||||
latitude,
|
||||
longitude,
|
||||
}));
|
||||
setLocationLocked(false);
|
||||
setLocationHint("已获取坐标,但地点名称解析失败,你也可以手动补充。");
|
||||
setLocationHint(getReverseGeocodeHint(error));
|
||||
}
|
||||
} catch (error) {
|
||||
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) {
|
||||
if (!window.isSecureContext) {
|
||||
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;
|
||||
$$;
|
||||
|
||||
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 (
|
||||
id bigint generated by default as identity primary key,
|
||||
user_code varchar(50),
|
||||
|
|
@ -59,7 +47,7 @@ create table if not exists sys_user (
|
|||
real_name varchar(50) not null,
|
||||
mobile varchar(20),
|
||||
email varchar(100),
|
||||
dept_id bigint,
|
||||
org_id bigint,
|
||||
job_title varchar(100),
|
||||
status smallint not null default 1,
|
||||
hire_date date,
|
||||
|
|
@ -68,8 +56,7 @@ create table if not exists sys_user (
|
|||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint uk_sys_user_username unique (username),
|
||||
constraint uk_sys_user_mobile unique (mobile),
|
||||
constraint fk_sys_user_dept foreign key (dept_id) references sys_department(id)
|
||||
constraint uk_sys_user_mobile unique (mobile)
|
||||
);
|
||||
|
||||
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()
|
||||
);
|
||||
|
||||
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 (
|
||||
id bigint generated by default as identity primary key,
|
||||
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_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_owner on crm_opportunity(owner_user_id);
|
||||
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);
|
||||
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_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_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_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);
|
||||
|
||||
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_crm_customer_updated_at', 'crm_customer');
|
||||
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_todo_updated_at', 'work_todo');
|
||||
|
||||
comment on table sys_department is '组织部门';
|
||||
comment on table sys_user is '系统用户';
|
||||
comment on table crm_customer is '客户主表';
|
||||
comment on table crm_opportunity is '商机主表';
|
||||
|
|
|
|||
Loading…
Reference in New Issue