导出功能调整
parent
b3f85504f6
commit
ab711362c7
|
|
@ -24,30 +24,30 @@
|
|||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"RequestMappingsPanelOrder0": "0",
|
||||
"RequestMappingsPanelOrder1": "1",
|
||||
"RequestMappingsPanelWidth0": "75",
|
||||
"RequestMappingsPanelWidth1": "75",
|
||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"WebServerToolWindowFactoryState": "false",
|
||||
"git-widget-placeholder": "main",
|
||||
"last_opened_file_path": "/Users/kangwenjing/Downloads/crm/unis_crm",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"project.structure.last.edited": "项目",
|
||||
"project.structure.proportion": "0.14450866",
|
||||
"project.structure.side.proportion": "0.18800648",
|
||||
"settings.editor.selected.configurable": "configurable.group.appearance",
|
||||
"ts.external.directory.path": "/Users/kangwenjing/Downloads/crm/unis_crm/frontend/node_modules/typescript/lib",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"RequestMappingsPanelOrder0": "0",
|
||||
"RequestMappingsPanelOrder1": "1",
|
||||
"RequestMappingsPanelWidth0": "75",
|
||||
"RequestMappingsPanelWidth1": "75",
|
||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"WebServerToolWindowFactoryState": "false",
|
||||
"git-widget-placeholder": "main",
|
||||
"last_opened_file_path": "/Users/kangwenjing/Downloads/crm/unis_crm",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"project.structure.last.edited": "项目",
|
||||
"project.structure.proportion": "0.14450866",
|
||||
"project.structure.side.proportion": "0.18800648",
|
||||
"settings.editor.selected.configurable": "configurable.group.appearance",
|
||||
"ts.external.directory.path": "/Users/kangwenjing/Downloads/crm/unis_crm/frontend/node_modules/typescript/lib",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}]]></component>
|
||||
}</component>
|
||||
<component name="RunManager" selected="Spring Boot.UnisCrmBackendApplication">
|
||||
<configuration name="unis-crm-backend中的所有" type="JUnit" factoryName="JUnit" temporary="true" nameIsGenerated="true">
|
||||
<module name="unis-crm-backend" />
|
||||
|
|
@ -94,6 +94,8 @@
|
|||
<workItem from="1775541962012" duration="805000" />
|
||||
<workItem from="1775611219527" duration="1273000" />
|
||||
<workItem from="1775721940000" duration="8576000" />
|
||||
<workItem from="1776219416113" duration="650000" />
|
||||
<workItem from="1776238420843" duration="21000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="修改定位信息 0323">
|
||||
<option name="closed" value="true" />
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ public class ChannelExpansionItemDTO {
|
|||
private String stage;
|
||||
private Boolean landed;
|
||||
private String expectedSignDate;
|
||||
private String updatedAt;
|
||||
private String notes;
|
||||
private List<ChannelExpansionContactDTO> contacts = new ArrayList<>();
|
||||
private List<ChannelRelatedProjectSummaryDTO> relatedProjects = new ArrayList<>();
|
||||
|
|
@ -298,6 +299,14 @@ public class ChannelExpansionItemDTO {
|
|||
this.expectedSignDate = expectedSignDate;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(String updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public String getNotes() {
|
||||
return notes;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ public class SalesExpansionItemDTO {
|
|||
private Boolean active;
|
||||
private String employmentStatus;
|
||||
private String expectedJoinDate;
|
||||
private String updatedAt;
|
||||
private String notes;
|
||||
private java.util.List<RelatedProjectSummaryDTO> relatedProjects = new java.util.ArrayList<>();
|
||||
private List<ExpansionFollowUpDTO> followUps = new ArrayList<>();
|
||||
|
|
@ -225,6 +226,14 @@ public class SalesExpansionItemDTO {
|
|||
this.expectedJoinDate = expectedJoinDate;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(String updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public String getNotes() {
|
||||
return notes;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ public class OpportunityItemDTO {
|
|||
private String name;
|
||||
private String client;
|
||||
private String owner;
|
||||
private String updatedAt;
|
||||
private String projectLocation;
|
||||
private String operatorCode;
|
||||
private String operatorName;
|
||||
|
|
@ -85,6 +86,14 @@ public class OpportunityItemDTO {
|
|||
this.owner = owner;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(String updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public String getProjectLocation() {
|
||||
return projectLocation;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public class OpportunitySearchToolProvider extends PermissionedMcpToolProvider {
|
|||
properties.put("keyword", stringProperty("关键词,匹配商机名称、编号、客户名称、项目地点、产品类型。"));
|
||||
properties.put("stage", stringProperty("商机阶段编码,例如 initial_contact/solution_discussion/bidding/business_negotiation/won/lost。"));
|
||||
properties.put("ownerUserId", integerProperty("商机负责人用户 ID,实际可见范围仍按系统数据权限裁剪。"));
|
||||
properties.put("includeArchived", booleanProperty("是否包含已归档商机,默认 false。"));
|
||||
properties.put("includeArchived", booleanProperty("是否包含已签单商机,默认 false。"));
|
||||
properties.put("page", integerProperty("页码,默认 1。"));
|
||||
properties.put("pageSize", integerProperty("每页条数,默认 10,最大 50。"));
|
||||
properties.put("limit", integerProperty("返回条数,默认 10,最大 50。"));
|
||||
|
|
|
|||
|
|
@ -89,8 +89,26 @@
|
|||
(s.employment_status = 'active') as active,
|
||||
s.employment_status as employmentStatus,
|
||||
coalesce(to_char(s.expected_join_date, 'YYYY-MM-DD'), '无') as expectedJoinDate,
|
||||
coalesce(
|
||||
to_char(
|
||||
case
|
||||
when sales_followup.latest_followup_time is null then s.updated_at
|
||||
else greatest(s.updated_at, sales_followup.latest_followup_time)
|
||||
end,
|
||||
'YYYY-MM-DD HH24:MI'
|
||||
),
|
||||
'无'
|
||||
) as updatedAt,
|
||||
coalesce(s.remark, '无') as notes
|
||||
from crm_sales_expansion s
|
||||
left join (
|
||||
select
|
||||
f.biz_id,
|
||||
max(f.followup_time) as latest_followup_time
|
||||
from crm_expansion_followup f
|
||||
where f.biz_type = 'sales'
|
||||
group by f.biz_id
|
||||
) sales_followup on sales_followup.biz_id = s.id
|
||||
left join sys_user u
|
||||
on u.user_id = s.owner_user_id
|
||||
and u.is_deleted = 0
|
||||
|
|
@ -168,8 +186,26 @@
|
|||
end as stage,
|
||||
c.landed_flag as landed,
|
||||
coalesce(to_char(c.expected_sign_date, 'YYYY-MM-DD'), '无') as expectedSignDate,
|
||||
coalesce(
|
||||
to_char(
|
||||
case
|
||||
when channel_followup.latest_followup_time is null then c.updated_at
|
||||
else greatest(c.updated_at, channel_followup.latest_followup_time)
|
||||
end,
|
||||
'YYYY-MM-DD HH24:MI'
|
||||
),
|
||||
'无'
|
||||
) as updatedAt,
|
||||
coalesce(c.remark, '无') as notes
|
||||
from crm_channel_expansion c
|
||||
left join (
|
||||
select
|
||||
f.biz_id,
|
||||
max(f.followup_time) as latest_followup_time
|
||||
from crm_expansion_followup f
|
||||
where f.biz_type = 'channel'
|
||||
group by f.biz_id
|
||||
) channel_followup on channel_followup.biz_id = c.id
|
||||
left join sys_user u
|
||||
on u.user_id = c.owner_user_id
|
||||
and u.is_deleted = 0
|
||||
|
|
|
|||
|
|
@ -54,6 +54,16 @@
|
|||
o.opportunity_name as name,
|
||||
coalesce(c.customer_name, '未填写最终客户') as client,
|
||||
coalesce(u.display_name, '当前用户') as owner,
|
||||
coalesce(
|
||||
to_char(
|
||||
case
|
||||
when opportunity_followup.latest_followup_time is null then o.updated_at
|
||||
else greatest(o.updated_at, opportunity_followup.latest_followup_time)
|
||||
end,
|
||||
'YYYY-MM-DD HH24:MI'
|
||||
),
|
||||
'无'
|
||||
) as updatedAt,
|
||||
coalesce(o.project_location, '') as projectLocation,
|
||||
coalesce(operator_dict.item_value, o.operator_name, '') as operatorCode,
|
||||
coalesce(operator_dict.item_label, nullif(o.operator_name, ''), '') as operatorName,
|
||||
|
|
@ -117,6 +127,13 @@
|
|||
), '') as nextPlan,
|
||||
coalesce(o.description, '') as notes
|
||||
from crm_opportunity o
|
||||
left join (
|
||||
select
|
||||
f.opportunity_id,
|
||||
max(f.followup_time) as latest_followup_time
|
||||
from crm_opportunity_followup f
|
||||
group by f.opportunity_id
|
||||
) opportunity_followup on opportunity_followup.opportunity_id = o.id
|
||||
left join crm_customer c on c.id = o.customer_id
|
||||
left join sys_user u on u.user_id = o.owner_user_id
|
||||
left join crm_sales_expansion se on se.id = o.sales_expansion_id
|
||||
|
|
|
|||
|
|
@ -386,7 +386,7 @@ flowchart TD
|
|||
|
||||
建议对以下数据建立归档策略:
|
||||
|
||||
- 已归档商机
|
||||
- 已签单商机
|
||||
- 早期历史动态日志
|
||||
- 历史打卡照片
|
||||
- 大体量日报附件或冗余快照
|
||||
|
|
@ -638,4 +638,3 @@ flowchart TD
|
|||
- `docs/opportunity-integration-api.md`
|
||||
- `backend/README.md`
|
||||
- `frontend/README.md`
|
||||
|
||||
|
|
|
|||
|
|
@ -291,8 +291,8 @@ const opportunityOverview = {
|
|||
amount: 530000,
|
||||
date: "2026-03-18",
|
||||
confidence: "C",
|
||||
stageCode: "已归档",
|
||||
stage: "已归档",
|
||||
stageCode: "已签单",
|
||||
stage: "已签单",
|
||||
type: "替换",
|
||||
archived: true,
|
||||
pushedToOms: false,
|
||||
|
|
|
|||
|
|
@ -299,6 +299,7 @@ export interface OpportunityItem {
|
|||
name?: string;
|
||||
client?: string;
|
||||
owner?: string;
|
||||
updatedAt?: string;
|
||||
projectLocation?: string;
|
||||
operatorCode?: string;
|
||||
operatorName?: string;
|
||||
|
|
@ -339,6 +340,7 @@ export interface OpportunityMeta {
|
|||
operatorOptions?: OpportunityDictOption[];
|
||||
projectLocationOptions?: OpportunityDictOption[];
|
||||
opportunityTypeOptions?: OpportunityDictOption[];
|
||||
confidenceOptions?: OpportunityDictOption[];
|
||||
}
|
||||
|
||||
export interface OmsPreSalesOption {
|
||||
|
|
@ -416,6 +418,7 @@ export interface SalesExpansionItem {
|
|||
active?: boolean;
|
||||
employmentStatus?: string;
|
||||
expectedJoinDate?: string;
|
||||
updatedAt?: string;
|
||||
notes?: string;
|
||||
relatedProjects?: RelatedProjectSummary[];
|
||||
followUps?: ExpansionFollowUp[];
|
||||
|
|
@ -461,6 +464,7 @@ export interface ChannelExpansionItem {
|
|||
stage?: string;
|
||||
landed?: boolean;
|
||||
expectedSignDate?: string;
|
||||
updatedAt?: string;
|
||||
notes?: string;
|
||||
contacts?: ChannelExpansionContact[];
|
||||
relatedProjects?: ChannelRelatedProjectSummary[];
|
||||
|
|
|
|||
|
|
@ -330,6 +330,7 @@ function buildSalesExportHeaders(items: SalesExpansionItem[]) {
|
|||
"工号",
|
||||
"姓名",
|
||||
"创建人",
|
||||
"更新修改时间",
|
||||
"联系方式",
|
||||
"代表处 / 办事处",
|
||||
"所属部门",
|
||||
|
|
@ -357,6 +358,7 @@ function buildSalesExportData(items: SalesExpansionItem[]) {
|
|||
normalizeExportText(item.employeeNo),
|
||||
normalizeExportText(item.name),
|
||||
normalizeExportText(item.owner),
|
||||
normalizeExportText(item.updatedAt),
|
||||
normalizeExportText(item.phone),
|
||||
normalizeExportText(item.officeName),
|
||||
normalizeExportText(item.dept),
|
||||
|
|
@ -389,6 +391,7 @@ function buildChannelExportHeaders(items: ChannelExpansionItem[]) {
|
|||
"编码",
|
||||
"渠道名称",
|
||||
"创建人",
|
||||
"更新修改时间",
|
||||
"省份",
|
||||
"市",
|
||||
"办公地址",
|
||||
|
|
@ -426,6 +429,7 @@ function buildChannelExportData(items: ChannelExpansionItem[]) {
|
|||
normalizeExportText(item.channelCode),
|
||||
normalizeExportText(item.name),
|
||||
normalizeExportText(item.owner),
|
||||
normalizeExportText(item.updatedAt),
|
||||
normalizeExportText(item.province),
|
||||
normalizeExportText(item.city),
|
||||
normalizeExportText(item.officeAddress),
|
||||
|
|
|
|||
|
|
@ -83,6 +83,13 @@ function formatAmount(value?: number) {
|
|||
return new Intl.NumberFormat("zh-CN").format(Number(value));
|
||||
}
|
||||
|
||||
function formatOpportunityBoolean(value?: boolean, trueLabel = "是", falseLabel = "否") {
|
||||
if (value === null || value === undefined) {
|
||||
return "";
|
||||
}
|
||||
return value ? trueLabel : falseLabel;
|
||||
}
|
||||
|
||||
function normalizeOpportunityExportText(value?: string | number | boolean | null) {
|
||||
if (value === null || value === undefined) {
|
||||
return "";
|
||||
|
|
@ -648,8 +655,8 @@ function OpportunityExportFilterModal({
|
|||
|
||||
return (
|
||||
<ModalShell
|
||||
title={`导出${archiveTab === "active" ? "未归档" : "已归档"}商机`}
|
||||
subtitle="选择条件后导出 Excel;不填条件则导出当前归档页签下的全部可见商机。"
|
||||
title={`导出${archiveTab === "active" ? "未签单" : "已签单"}商机`}
|
||||
subtitle="选择条件后导出 Excel;不填条件则导出当前签单页签下的全部可见商机。"
|
||||
onClose={onClose}
|
||||
footer={(
|
||||
<div className="flex flex-col-reverse gap-3 sm:flex-row sm:justify-between">
|
||||
|
|
@ -1603,7 +1610,7 @@ export default function Opportunities() {
|
|||
.filter((item) => (archiveTab === "active" ? !item.archived : Boolean(item.archived)))
|
||||
.filter((item) => matchesOpportunityExportFilters(item, filters, effectiveConfidenceOptions));
|
||||
if (exportItems.length <= 0) {
|
||||
throw new Error(`当前筛选条件下暂无可导出的${archiveTab === "active" ? "未归档" : "已归档"}商机`);
|
||||
throw new Error(`当前筛选条件下暂无可导出的${archiveTab === "active" ? "未签单" : "已签单"}商机`);
|
||||
}
|
||||
|
||||
const ExcelJS = await import("exceljs");
|
||||
|
|
@ -1613,6 +1620,9 @@ export default function Opportunities() {
|
|||
"项目编号",
|
||||
"项目名称",
|
||||
"创建人",
|
||||
"更新修改时间",
|
||||
"是否签单",
|
||||
"是否推送OMS",
|
||||
"项目地",
|
||||
"最终用户",
|
||||
"建设类型",
|
||||
|
|
@ -1661,6 +1671,9 @@ export default function Opportunities() {
|
|||
normalizeOpportunityExportText(item.code),
|
||||
normalizeOpportunityExportText(item.name),
|
||||
normalizeOpportunityExportText(item.owner),
|
||||
normalizeOpportunityExportText(item.updatedAt),
|
||||
formatOpportunityBoolean(item.archived, "已签单", "未签单"),
|
||||
formatOpportunityBoolean(item.pushedToOms, "已推送", "未推送"),
|
||||
normalizeOpportunityExportText(item.projectLocation),
|
||||
normalizeOpportunityExportText(item.client),
|
||||
normalizeOpportunityExportText(item.type || "新建"),
|
||||
|
|
@ -1726,7 +1739,7 @@ export default function Opportunities() {
|
|||
});
|
||||
|
||||
const buffer = await workbook.xlsx.writeBuffer();
|
||||
const filename = `商机储备_${archiveTab === "active" ? "未归档" : "已归档"}_${formatOpportunityExportFilenameTime()}.xlsx`;
|
||||
const filename = `商机储备_${archiveTab === "active" ? "未签单" : "已签单"}_${formatOpportunityExportFilenameTime()}.xlsx`;
|
||||
downloadOpportunityExcelFile(filename, buffer);
|
||||
setExportFilterOpen(false);
|
||||
} catch (exportErr) {
|
||||
|
|
@ -1943,7 +1956,7 @@ export default function Opportunities() {
|
|||
|
||||
const renderEmpty = () => (
|
||||
<div className="crm-empty-panel">
|
||||
{archiveTab === "active" ? "暂无未归档商机,先新增一条试试。" : "暂无已归档商机。"}
|
||||
{archiveTab === "active" ? "暂无未签单商机,先新增一条试试。" : "暂无已签单商机。"}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
@ -1987,7 +2000,7 @@ export default function Opportunities() {
|
|||
: "text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-white"
|
||||
}`}
|
||||
>
|
||||
未归档
|
||||
未签单
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
|
|
@ -2000,7 +2013,7 @@ export default function Opportunities() {
|
|||
: "text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-white"
|
||||
}`}
|
||||
>
|
||||
已归档
|
||||
已签单
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue