diff --git a/src/components/DataPreview/assign/ShowState.tsx b/src/components/DataPreview/assign/ShowState.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d91a05c2073781724e25745f6453314f858293e9 --- /dev/null +++ b/src/components/DataPreview/assign/ShowState.tsx @@ -0,0 +1,112 @@ +import React from 'react'; +import cls from './index.module.less'; + +interface StatusItem { + label: string; + key: string; // 对应状态标识 + color: string; + value?: number; +} + +interface Props { + nodesState?: Record; + onSelect?: (key: string) => void; + selectedKey?: string; +} + +export const ShoeStaues: React.FC = ({ nodesState, onSelect, selectedKey }) => { + const items: StatusItem[] = [ + { + label: '合计', + key: 'all', + color: '#6c757d', + value: + (nodesState?.empty ?? 0) + + (nodesState?.received ?? 0) + + (nodesState?.submitted ?? 0) + + (nodesState?.finished ?? 0), + }, + { label: '未接收', key: 'empty', color: '#fd7e14', value: nodesState?.empty ?? 0 }, + { + label: '已接收', + key: 'received', + color: '#0d6efd', + value: nodesState?.received ?? 0, + }, + { + label: '已上报', + key: 'submitted', + color: '#0d6efd', + value: nodesState?.submitted ?? 0, + }, + { + label: '已完结', + key: 'finished', + color: '#198754', + value: nodesState?.finished ?? 0, + }, + ]; + + return ( +
+ {items.map((item) => { + const isSelected = selectedKey === item.key; + return ( +
onSelect && onSelect(item.key)} + style={{ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + padding: '12px 16px', + backgroundColor: isSelected ? `${item.color}10` : 'white', + border: `2px solid ${item.color}20`, + borderRadius: '12px', + boxShadow: isSelected + ? '0 6px 18px rgba(0,0,0,0.18)' + : '0 2px 8px rgba(0,0,0,0.1)', + minWidth: '40px', + height: '120px', + transition: 'all 0.2s ease', + cursor: 'pointer', + }} + onMouseEnter={(e) => { + e.currentTarget.style.transform = 'translateY(-2px)'; + }} + onMouseLeave={(e) => { + e.currentTarget.style.transform = 'translateY(0)'; + }}> +
+ {item.label} +
+ + {item.value} + +
+ ); + })} +
+ ); +}; diff --git a/src/components/DataPreview/assign/assignDataView.tsx b/src/components/DataPreview/assign/assignDataView.tsx index 0f6a9762cf3f1a58061e5ed559f0f85c10663ab1..60873119c5c21739895114e6e66737f00933eafe 100644 --- a/src/components/DataPreview/assign/assignDataView.tsx +++ b/src/components/DataPreview/assign/assignDataView.tsx @@ -29,27 +29,27 @@ export function AssignDataView({ assignTask }: IProps) { let data: Dictionary = {}; - for (let [formId, thingId] of Object.entries(assignTask.metadata.thingId || {})) { - let form = work.forms.find((f) => f.id == formId); + if (assignTask.metadata.assignContent?.formId) { + let formId = assignTask.metadata.assignContent.formId; + let form = work.forms.find((f) => f.id === formId); + if (!form) { console.warn(`报表 ${formId} 不存在`); form = work.forms[0]; formId = form.id; } data[formId] ||= []; + const thingId = assignTask.metadata.id; const things = await form.thingColl.loadSpace({ options: { match: { - id: { - _in_: thingId, + assignId: { + _in_: [thingId], }, }, }, }); - if (things.length != thingId.length) { - message.warning(`找不到报表 ${form.name} 的数据`); - } for (const d of things) { for (const attr of form.attributes) { diff --git a/src/components/DataPreview/assign/assignFillData.tsx b/src/components/DataPreview/assign/assignFillData.tsx index ffc847ec3bd06e12028f346ed008aab84d59a524..3abe0adac79202ba1d72889d4200548537bf86d5 100644 --- a/src/components/DataPreview/assign/assignFillData.tsx +++ b/src/components/DataPreview/assign/assignFillData.tsx @@ -7,10 +7,10 @@ import { formatDate } from '@/utils'; import { Button, message, Modal, Progress, Switch, Typography } from 'antd'; import React, { useState } from 'react'; import { AssignTask, IAssignTask } from '@/ts/core/work/assign/assign'; -import { IAssignTaskTree } from '@/ts/core/work/assign/assignTree'; import { deepClone } from '@/ts/base/common'; import orgCtrl from '@/ts/controller'; import { Instance } from '@/ts/core/work/instance'; +import { ICompany, IGroup, ITarget } from '..'; interface Props { assignTask: IAssignTask; @@ -19,10 +19,7 @@ interface Props { } function isNeedFill(task: XAssignTask) { - return !( - task.thingId && - (task.isAutoFill || task.taskStatus == AssignStatusType.FINISHED) - ); + return !(task.isAutoFill || task.taskStatus == AssignStatusType.FINISHED); } export function AssignFillData(props: Props) { @@ -32,17 +29,26 @@ export function AssignFillData(props: Props) { const [errors, setErrors] = useState([]); const [isStart, setIsStart] = useState(false); - async function startSync() { + async function startSync(group: IGroup, period: string | undefined) { try { - setMsg('加载完整树形及任务接收'); - const taskTree = await props.assignTask.loadTree(); - if (!taskTree) { - setErrors(['加载树形失败']); - return; - } - const tasks = await taskTree.loadSubNodes(props.assignTask.id, true); + setMsg('加载组织群任务'); + const tasks = await group.resource.assignTaskPublicColl.loadSpace({ + options: { + match: { + isDeleted: false, + taskStatus: { _exists_: true }, + 'assignContent.period': period, + }, + project: { + children: 0, + }, + sort: { + createTime: -1, + }, + }, + }); if (!tasks.length) { - setErrors(['加载任务节点失败']); + setErrors(['加载任务失败']); return; } @@ -68,14 +74,14 @@ export function AssignFillData(props: Props) { }, {}); await nextTick(); for (const task of tasks) { - if (!task.period) { + if (!task.assignContent.period) { task.period = props.assignTask.period; task.assignContent = deepClone(props.assignTask.distContent); task.periodType = props.assignTask.metadata.periodType; task.assignType = props.assignTask.metadata.assignType; task.assignName = props.assignTask.metadata.assignName; } - await taskNodeFill(task, taskTree, err, formMap, work); + await taskNodeFill(task, err, formMap, work); current++; setProgress(parseFloat(((current / total) * 100).toFixed(2))); await nextTick(); @@ -92,7 +98,6 @@ export function AssignFillData(props: Props) { async function taskNodeFill( node: XAssignTask, - taskTree: IAssignTaskTree, err: string[], formMap: Dictionary, work: IWork, @@ -100,13 +105,13 @@ export function AssignFillData(props: Props) { if (!isNeedFill(node)) { return [node.id, true]; } - if (node.nodeType == NodeType.Summary) { + if (node.targetType == '组织群') { return [node.id, true]; } - const nodeInfo = `${node.name} ${node.nodeTypeName}:`; - const assignTask = new AssignTask(node, taskTree.directory.target); - if (node.instanceId && !skipSubmitted) { - setMsg(`正在保存 ${node.name} ${node.nodeTypeName}`); + const nodeInfo = `${node.name} ${node.targetType}:`; + const assignTask = new AssignTask(node, work.target); + if (node.instanceId && !skipSubmitted && node.taskStatus == AssignStatusType.SUBMITTED) { + setMsg(`正在保存 ${node.name} ${node.targetType}`); const detail = await orgCtrl.work.loadInstanceDetail( node.instanceId, assignTask.target.id, @@ -119,10 +124,8 @@ export function AssignFillData(props: Props) { } var ins = new Instance(detail, assignTask.target); const data: InstanceDataModel = await ins.loadData(); - const thingId: Dictionary = {}; for (const [formId, value] of Object.entries(data.data)) { const rows = value.at(-1)!.after; - thingId[formId] = rows.map((d) => d.id); const form = formMap[formId]; if (!form) { @@ -139,7 +142,7 @@ export function AssignFillData(props: Props) { } delete row[attr.id]; } - row.remark = `自动保存`; + row.remark = `自动审批`; row.archives ||= {}; row.assignId = node.id; }); @@ -148,8 +151,6 @@ export function AssignFillData(props: Props) { err.push(nodeInfo + `保存表单 ${form.name} 数据失败`); } } - node.thingId = thingId; - node.isAutoFill = true; node.taskStatus = AssignStatusType.FINISHED; const res = await assignTask.target.resource.assignTaskPublicColl!.replace(node); if (!res) { @@ -158,16 +159,16 @@ export function AssignFillData(props: Props) { } } else { node.thingId ||= {}; - setMsg(`正在通过上期数据补全 ${node.name} ${node.nodeTypeName}`); + setMsg(`正在通过上期数据补全 ${node.name} ${node.targetType}`); const lastPeriod = assignTask.getHistoryPeriod(-1); const [lastTask] = await assignTask.target.resource.assignTaskPublicColl!.loadSpace( { options: { match: { - period: lastPeriod, + 'assignContent.period': lastPeriod, assignName: node.assignName, belongId: node.belongId, - nodeType: node.nodeType, + targetType: node.targetType, }, }, }, @@ -177,19 +178,13 @@ export function AssignFillData(props: Props) { return [node.id, false]; } - if (!lastTask.thingId) { - err.push(nodeInfo + `上期报表未完结`); - return [node.id, false]; - } - - const thingId: Dictionary = {}; for (const form of work!.forms) { - const ids = lastTask.thingId[form.id]; + const ids = lastTask.id; const data = await form.thingColl.loadSpace({ options: { match: { - id: { - _in_: ids, + assignId: { + _in_: [ids], }, }, }, @@ -201,20 +196,18 @@ export function AssignFillData(props: Props) { d.createUser = node.receiveUserId; d.createTime = formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss.S'); d.remark = '自动取上月数'; - d.periodType = node.periodType; + d.periodType = node.assignContent?.periodType; d.assignId = node.id; - d.nodeType = node.nodeType; + d.targetType = node.targetType; d.belongId = node.belongId; d.targetId = node.targetId; }); const newData = await form.thingColl.replaceMany(data); - thingId[form.id] = newData.map((d: { id: any }) => d.id); if (newData.length != data.length) { err.push(nodeInfo + `保存 ${form.name} 表单数据失败`); } } - node.thingId = thingId; node.isAutoFill = true; node.taskStatus = AssignStatusType.FINISHED; const res = await assignTask.target.resource.assignTaskPublicColl!.replace(node); @@ -281,7 +274,7 @@ export function AssignFillData(props: Props) { message.warning('当前节点未接收'); return; } - startSync(); + startSync(props.assignTask.target, props.assignTask.metadata.assignContent?.period); }}> 开始补全 diff --git a/src/components/DataPreview/assign/assignStart.tsx b/src/components/DataPreview/assign/assignStart.tsx index 8a9290680c4ff0ae0f069c912816f176f2f8d804..dec9705cffad74f28f5c80e3463881ca8d95793e 100644 --- a/src/components/DataPreview/assign/assignStart.tsx +++ b/src/components/DataPreview/assign/assignStart.tsx @@ -87,7 +87,7 @@ const AssignStart: React.FC = ({ assignTask, finished }) => { key={key} apply={combine.apply} work={combine.work} - content={assignTask.period + '-' + assignTask.metadata.name} + content={assignTask.period || assignTask.assignContent?.period + '-' + assignTask.metadata.name} onStagging={async (instanceId) => { await assignTask.draft(instanceId); setKey(generateUuid()); diff --git a/src/components/DataPreview/assign/assignTree.tsx b/src/components/DataPreview/assign/assignTree.tsx index 950bc2446b79e63f9dd6ec064b084d411dd97653..89acc67e2bee8f91dac207f65bdc019ab266b456 100644 --- a/src/components/DataPreview/assign/assignTree.tsx +++ b/src/components/DataPreview/assign/assignTree.tsx @@ -105,133 +105,9 @@ export function AssignTree(props: { } }, [props.assignTask]); - function selectNode(e: XAssignTask) { - setNodeId(e.id); - props.onNodeSelect(e); - } - function renderNode(node: XAssignTask) { - const taskStatus = (node.taskStatus ?? 'empty') as taskStatus; - let color = getColorName(statusMap[taskStatus]?.color); - return ( - <> - {node.name} - - ); - } - - async function updateValue(v: model.ReportContent) { - let vClone = Object.assign(value, v); - setValue(vClone); - } - - const workValidate = async (file: IFile[]): Promise => { - let work = file[0] as IWork; - await work.loadNode(); - let flag = work.primaryForms.some((value) => { - return value.typeName === '报表' || '表格'; - }); - if (!flag) { - message.warning('办事主表中未存在报表类型数据'); - } - return true; - }; - - const taskModal = () => { - const columns: ProFormColumnsType[] = [ - { - title: '任务详情', - dataIndex: 'content', - colProps: { span: 24 }, - renderFormItem: () => { - if (!props.assignTask || !props.assignTask.distContent) { - return <>; - } - - switch (props.assignTask.distContent?.type) { - case TaskContentType.Report: - return ( -
-
{ - updateValue(v); - }}> - - { - if (file) { - setDirectory(file); - } - }} - /> - - - - - - - - - - - - - -
-
- ); - default: - return <>; - } - }, - }, - ]; - return ( - - open={open} - title={'任务分发'} - width={640} - columns={columns} - initialValues={{}} - rowProps={{ - gutter: [24, 0], - }} - layoutType="ModalForm" - onOpenChange={(open: boolean) => { - if (!open) { - setOpen(open); - } - }} - onFinish={async () => { - let values = Object.assign({}, {}, value); - values.typeName = '任务模板'; - await props.assignTask.distAssignTask(values); - setOpen(false); - }} - /> - ); - }; - const renderTag = (info: SummaryReceptionStatusInfo, child = false) => { const sum = props.summary!; - let tagValue = sum[info.status as taskStatus] || 0; + let tagValue = 0; const e = () => { return ( @@ -249,85 +125,6 @@ export function AssignTree(props: { }; return e(); }; - - const exportTreeStructure = async ( - exportData: any[] | undefined, - assignTask: IAssignTask, - ) => { - if (!exportData || exportData.length <= 0) { - message.warning('当前任务无树形'); - return; - } - - let isMessageShown = false; - try { - const workbook = new Workbook(); - const sheet = workbook.addWorksheet('当前树形结构'); - sheet.addRow([ - '节点编码', - '上级节点编码', - '节点名称', - '节点类型', - '归属组织信用代码', - ]); - const stack = exportData.map((node) => ({ node, parentId: null, level: 0 })); - const rows = []; - // 分批处理节点,每批处理 50 个 - const batchSize = 50; - while (stack.length > 0) { - const batch = stack.splice(0, batchSize); - const belongIds = batch.map(({ node }) => node.belongId); - // 批量查询目标对象 - const targetsResponse = await kernel.queryTargetById({ - ids: belongIds, - } as IdArrayModel); - if (targetsResponse.success && targetsResponse.data) { - const targetsMap = new Map( - targetsResponse.data.result.map((target) => [target.id, target.code]), - ); - for (const { node, parentId, level } of batch) { - const indent = level > 0 ? _.repeat(' ', level) : ''; - const code = targetsMap.get(node.belongId) || ''; - let row = [node.id, parentId, indent + node.name, node.nodeTypeName, code]; - rows.push(row); - - for (const child of node.children.reverse()) { - stack.push({ node: child, parentId: node.id, level: level + 1 }); - } - } - } - if (!isMessageShown) { - message.loading('正在处理节点数据,请稍后', 0); - isMessageShown = true; - } - } - sheet.addRows(rows); - // 异步生成并下载文件 - const buffer = await workbook.xlsx.writeBuffer(); - const fileName = `${assignTask.name}树形结构.xlsx`; - const file = new File([buffer], fileName, { type: 'application/octet-stream' }); - const url = URL.createObjectURL(file); - const link = document.createElement('a'); - link.href = url; - link.download = fileName; - link.click(); - URL.revokeObjectURL(url); - message.destroy(); - } catch (error) { - message.error(error instanceof Error ? error.message : String(error)); - } - }; - - const sendInfo = async () => { - Modal.confirm({ - icon: <>, - title: `确认要向直属下级节点发送任务催报吗?`, - onOk: async () => { - await props.assignTask.sendUrgeInfo(); - }, - }); - }; - return ( <> )} -
- { - if (!props.exportData || props.exportData.length <= 0) { - message.warning('当前任务无树形'); - return; - } - try { - const workbook = new Workbook(); - const sheet = workbook.addWorksheet('上报状态'); - sheet.addRow(['节点名称', '节点类型', '上报状态']); - const stack = props.exportData.map((node) => ({ node, level: 0 })); - const rows = []; - while (stack.length > 0) { - const { node, level } = stack.pop()!; - const indent = level > 0 ? _.repeat(' ', level) : ''; - let row = [indent + node.name, node.nodeTypeName, '未接收']; - row[2] = statusMap[node.taskStatus].label; - rows.push(row); - if (node.children) { - for (const child of node.children.reverse()) { - stack.push({ node: child as XAssignTask, level: level + 1 }); - } - } - } - sheet.addRows(rows); - const buffer = await workbook.xlsx.writeBuffer(); - const fileName = `${props.assignTask.name}上报状态.xlsx`; - const file = new File([buffer], fileName, { - type: 'application/octet-stream', - }); - const url = URL.createObjectURL(file); - const link = document.createElement('a'); - link.href = url; - link.download = fileName; - link.click(); - URL.revokeObjectURL(url); - } catch (error) { - message.error(error instanceof Error ? error.message : String(error)); - } - }} - /> -
-
- { - exportTreeStructure(props.exportData, props.assignTask); - }} - /> -
-
- -
{props.assignTask.metadata.nodeType === NodeType.Summary && ( <> @@ -444,27 +174,8 @@ export function AssignTree(props: { )} -
- - nodes={props.tree} - current={props.assignTree} - type={'assign'} - selectedKeys={nodeId ? [nodeId] : []} - renderNode={renderNode} - onSelect={(_, n) => { - selectNode(n.node); - }} - checkable={true} - currentTag={currentTag} - searchText={searchText} - externalField={assignFields} - curRootNodeId={props.assignTask.id} - curRootNodeName={props.assignTask.metadata.name} - /> -
- {taskModal()} ); } diff --git a/src/components/DataPreview/assign/index.tsx b/src/components/DataPreview/assign/index.tsx index 2ffd7131a89f6d870707cfaf8e267b98fb0452f5..409d85bbfe1f80241ef57d45551af0aff9e66dcf 100644 --- a/src/components/DataPreview/assign/index.tsx +++ b/src/components/DataPreview/assign/index.tsx @@ -2,12 +2,9 @@ import { AssignTask, IAssignTask } from '@/ts/core/work/assign/assign'; import React, { createContext, useEffect, useMemo, useState } from 'react'; import { Button, Empty, message, Result, Spin } from 'antd'; import cls from './index.module.less'; -import { AssignTree } from './assignTree'; import { XAssignTask } from '@/ts/base/schema'; import { IAssignTaskTree } from '@/ts/core/work/assign/assignTree'; -import { getEmptySummary } from '@/ts/core/work/assign/status'; -import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'; -import { AssignStatusType, NodeType } from '@/ts/base/enum'; +import { AssignStatusType } from '@/ts/base/enum'; import { AssignDataView } from '@/components/DataPreview/assign/assignDataView'; import AssignStart from '@/components/DataPreview/assign/assignStart'; import { AssignView } from '@/components/DataPreview/assign/assignView'; @@ -15,7 +12,7 @@ import orgCtrl from '@/ts/controller'; import EntityIcon from '@/components/Common/GlobalComps/entityIcon'; import ApprovalHistory from './approvalHistory'; import { command } from '@/ts/base'; -import useAsyncLoad from '@/hooks/useAsyncLoad'; +import { ViewTree } from './viewTree' export interface IProps { assignTask: IAssignTask; @@ -24,19 +21,11 @@ export interface IProps { export const AssignTaskContext = createContext(null as IAssignTask | null); const Assign: React.FC = (props) => { - const [tree, setTree] = useState([]); - const [expand, setExpand] = useState(true); const [showType, setShowType] = useState('workdetails'); - const [treeNodes, setTreeNodes] = useState([]); const [assignTree, setAssignTree] = useState(undefined); const [currentTask, setCurrentTask] = useState( props.assignTask, ); - const [loaded] = useAsyncLoad(async () => { - if (props.assignTask.metadata.nodeType === NodeType.Summary) { - return await loadTree(); - } - }); useEffect(() => { const id = props.assignTask?.subscribe((_, cmd, typeName, showText) => { if (cmd === 'actionChange' && typeName === '任务' && showText) { @@ -45,82 +34,6 @@ const Assign: React.FC = (props) => { }); return () => props.assignTask?.unsubscribe(id); }, []); - function buildTree(data: XAssignTask[]) { - const map: any = {}; - if (!data.find((value) => value.id === props.assignTask.id)) { - data.push(props.assignTask.metadata); - } - data.forEach((item) => { - map[item.id] = { ...item, children: [] }; - }); - - const tree: XAssignTask[] = []; - data.forEach((item) => { - if (item.parentId && map[item.parentId]) { - const parent = map[item.parentId]; - if (parent) { - parent.children.push(map[item.id]); - } - } else { - tree.push(map[item.id]); - } - }); - return tree; - } - - async function loadTree() { - const assignTree = await props.assignTask.loadTree(); - setAssignTree(assignTree); - if (!assignTree) { - message.error('找不到当前任务树'); - } - const loadNodes: XAssignTask[] | undefined = await assignTree?.loadSimpleSubNodes( - props.assignTask.id, - ); - if (!loadNodes) { - return; - } - const treeNodes = loadNodes.filter((value) => value.id !== props.assignTask.id); - treeNodes.push(props.assignTask.metadata); - const builtTree = buildTree(loadNodes ?? []); - setTreeNodes(treeNodes ?? []); - setTree(builtTree); - } - - const treeSummary = useMemo(() => { - let summary = getEmptySummary(); - treeNodes.forEach((node) => { - if (node.taskStatus) { - switch (node.taskStatus) { - case 'received': - summary.received++; - break; - case 'submitted': - summary.submitted++; - break; - case 'finished': - summary.finished++; - break; - case 'rejected': - summary.submitted++; - break; - case 'changed': - summary.submitted++; - break; - case 'assigned': - summary.received++; - break; - default: - summary.empty++; - break; - } - } else { - summary.empty++; - } - summary.total++; - }); - return summary; - }, [treeNodes]); async function handleNodeSelect(_node: XAssignTask) { setCurrentTask(undefined); @@ -141,7 +54,7 @@ const Assign: React.FC = (props) => { } const { metadata } = currentTask; - const { assignContent, thingId, taskStatus, instanceId, receiveUserId, isAutoFill } = + const { assignContent, taskStatus, instanceId, receiveUserId, isAutoFill } = metadata; if (showType === 'flowdetails') { return ; @@ -154,11 +67,11 @@ const Assign: React.FC = (props) => { return ; } // 自动补全的任务 - if (thingId && isAutoFill) { + if (isAutoFill) { return ; } // 完成的任务 - if (thingId && taskStatus === AssignStatusType.FINISHED) { + if (taskStatus === AssignStatusType.FINISHED) { return ; } // 有实例ID且任务状态不为 "RECEIVED", "ASSIGNED", "REJECTED" @@ -197,42 +110,7 @@ const Assign: React.FC = (props) => { return (
- {props.assignTask.metadata.nodeType === NodeType.Summary && ( -
- - {tree.length > 0 && ( -
- - -
- )} -
-
- )} + {props.assignTask.metadata.targetType === '组织群' && }
{renderContent}
diff --git a/src/components/DataPreview/assign/viewTree.tsx b/src/components/DataPreview/assign/viewTree.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cf396a2dd9b3372dc1766f321e2a472d00f4f892 --- /dev/null +++ b/src/components/DataPreview/assign/viewTree.tsx @@ -0,0 +1,279 @@ +import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import cls from './index.module.less'; +import { XAssignTask } from '@/ts/base/schema'; +import { IAssignTask } from '@/ts/core/work/assign/assign'; +import { AssignStatusType } from '@/ts/base/enum'; +import { + MenuFoldOutlined, + MenuUnfoldOutlined, + RightOutlined, + DownOutlined, +} from '@ant-design/icons'; +import { Button, Spin } from 'antd'; +import { ShoeStaues } from './ShowState'; + +export interface IProps { + assignTask: IAssignTask; +} + +export const ViewTree: React.FC = (props) => { + const [group, setGroup] = useState([]); + const [nodesState, setNodesState] = useState>(); + const [loading, setLoading] = useState(false); + const [expand, setExpand] = useState(true); + + const [selectedKey, setSelectedKey] = useState('all'); + const [tasks, setTasks] = useState([]); + const [loadingTasks, setLoadingTasks] = useState(false); + + const [expandedKeys, setExpandedKeys] = useState>(new Set()); + + const loadAndFilterNodes = useCallback(async () => { + setLoading(true); + try { + const groupNodes: XAssignTask[] = await props.assignTask.loadTask('组织群'); + const empty = await props.assignTask.loadTsakState('empty'); + const received = await props.assignTask.loadTsakState('received'); + const submitted = await props.assignTask.loadTsakState('submitted'); + const finished = await props.assignTask.loadTsakState('finished'); + + const statusCount: Record = { + [AssignStatusType.EMPTY]: empty ?? 0, + [AssignStatusType.RECEIVED]: received ?? 0, + [AssignStatusType.SUBMITTED]: submitted ?? 0, + [AssignStatusType.FINISHED]: finished ?? 0, + [AssignStatusType.ASSIGNED]: 0, + [AssignStatusType.REJECTED]: 0, + [AssignStatusType.CHANGED]: 0, + }; + + setGroup(groupNodes); + setNodesState(statusCount); + } catch (error) { + console.error('加载节点失败:', error); + } finally { + setLoading(false); + } + }, [props.assignTask]); + + useEffect(() => { + loadAndFilterNodes(); + }, [loadAndFilterNodes]); + + const loadTasksByStatus = useCallback( + async (statusKey: string) => { + setLoadingTasks(true); + try { + const stateParam = statusKey === 'all' ? undefined : statusKey; + const list = await props.assignTask.loadTasksByState?.(stateParam, 0, 1000); + if (Array.isArray(list)) { + setTasks(list); + } else { + const extracted = + list?.data || list?.result || list?.list || list?.rows || list?.items || []; + setTasks(Array.isArray(extracted) ? extracted : []); + } + } catch (error) { + console.warn('加载任务列表失败:', error); + setTasks([]); + } finally { + setLoadingTasks(false); + } + }, + [props.assignTask], + ); + + useEffect(() => { + loadTasksByStatus(selectedKey); + setExpandedKeys(new Set()); + }, [selectedKey, loadTasksByStatus]); + + const handleStatusSelect = (key: string) => { + setSelectedKey(key); + }; + + const buildTree = useCallback((flatList: any[]) => { + const map = new Map(); + const roots: any[] = []; + + flatList.forEach((item) => { + const id = item.id ?? item._id ?? item.instanceId; + if (!id) return; + map.set(String(id), { ...item, id: String(id), children: [] }); + }); + + const findAncestorInMap = (node: any) => { + if (!node.nodePath) return undefined; + const segments = String(node.nodePath) + .split('/') + .filter((s) => s !== ''); + if (segments.length === 0) return undefined; + if (segments[segments.length - 1] === node.id) { + segments.pop(); + } + for (let i = segments.length - 1; i >= 0; i--) { + const candidate = String(segments[i]); + if (map.has(candidate)) return candidate; + } + return undefined; + }; + + map.forEach((node) => { + const parentId = node.parentId ? String(node.parentId) : undefined; + if (parentId && parentId !== node.id && map.has(parentId)) { + map.get(parentId).children.push(node); + } else { + const ancestorId = findAncestorInMap(node); + if (ancestorId && ancestorId !== node.id) { + map.get(ancestorId).children.push(node); + } else { + roots.push(node); + } + } + }); + + const sortFn = (a: any, b: any) => { + const aDepth = a.nodePath + ? String(a.nodePath).split('/').filter(Boolean).length + : 1; + const bDepth = b.nodePath + ? String(b.nodePath).split('/').filter(Boolean).length + : 1; + if (aDepth !== bDepth) return aDepth - bDepth; + const an = a.name || ''; + const bn = b.name || ''; + return an.localeCompare(bn); + }; + + const sortRecursive = (list: any[]) => { + list.sort(sortFn); + list.forEach((n) => { + if (n.children && n.children.length > 0) sortRecursive(n.children); + }); + }; + + sortRecursive(roots); + return roots; + }, []); + + const treeRoots = useMemo(() => buildTree(tasks), [tasks, buildTree]); + + const toggleExpand = (id: string) => { + setExpandedKeys((prev) => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); + else next.add(id); + return next; + }); + }; + + const collectAllParentIds = useCallback((nodes: any[], acc: Set) => { + for (const n of nodes) { + if (n.children && n.children.length > 0) { + acc.add(String(n.id)); + collectAllParentIds(n.children, acc); + } + } + }, []); + + const statusColorMap: Record = { + empty: '#fd7e14', + received: '#0d6efd', + submitted: '#0d6efd', + finished: '#198754', + }; + + const renderTree = (nodes: any[], level = 0) => { + return nodes.map((node) => { + const key = String(node.id); + const hasChildren = node.children && node.children.length > 0; + const isOpen = expandedKeys.has(key); + + const status = String(node.taskStatus ?? '').toLowerCase(); + const nameColor = statusColorMap[status] || '#333'; + + return ( +
+
+ {hasChildren ? ( +
toggleExpand(key)} + style={{ + width: 20, + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + marginRight: 8, + cursor: 'pointer', + }}> + {isOpen ? ( + + ) : ( + + )} +
+ ) : ( +
+ )} + + {node.name || '(无名称)'} + +
+ {hasChildren && isOpen && renderTree(node.children, level + 1)} +
+ ); + }); + }; + + return ( +
+ {expand && ( + <> + + +
+ {loadingTasks ? ( +
+ +
+ ) : tasks.length === 0 ? ( +
暂无任务
+ ) : ( +
{renderTree(treeRoots)}
+ )} +
+ + )} + +
+ ); +}; diff --git a/src/components/DataStandard/NewReportForm/Viewer/hotTable.tsx b/src/components/DataStandard/NewReportForm/Viewer/hotTable.tsx index 0f87740a287ef8e52528150efbde2408bb72deb4..1044ba7673f6868f08fcb76a6c8c1cd8a1c0b339 100644 --- a/src/components/DataStandard/NewReportForm/Viewer/hotTable.tsx +++ b/src/components/DataStandard/NewReportForm/Viewer/hotTable.tsx @@ -580,7 +580,6 @@ const HotTableView: React.FC<{ for (const key of ruleKeys) { if (Array.isArray(acc)) { - debugger; const field = fieldMap[key]; acc = acc.map((item: any) => { if (info.valueType !== '分类型') { diff --git a/src/components/DataStandard/NewReportForm/Viewer/index.tsx b/src/components/DataStandard/NewReportForm/Viewer/index.tsx index 50f87e17a91e4796bc4bba5b6896fcb1d3d7fa2a..8704e0adc8642c8511f2c8887acdeda2fba882f3 100644 --- a/src/components/DataStandard/NewReportForm/Viewer/index.tsx +++ b/src/components/DataStandard/NewReportForm/Viewer/index.tsx @@ -1,6 +1,6 @@ import React, { lazy, Suspense, useContext, useEffect, useMemo, useState } from 'react'; import { IBelong } from '@/ts/core'; -import { Badge, Button, Dropdown, message, Space, Tabs } from 'antd'; +import { Badge, Button, Dropdown, message, Space, Tabs, Modal, Spin } from 'antd'; import { XSheet, XValidation } from '@/ts/base/schema'; import { command, model, schema } from '@/ts/base'; import WorkFormService from '@/ts/scripting/core/services/WorkFormService'; @@ -51,6 +51,7 @@ const WorkReportViewer: React.FC = (props) => { const [errorCount, setErrorCount] = useState(0); const [validates, setValidates] = useState([]); const [validateVisible, setValidateVisible] = useState(false); + const [summaryLoading, setSummaryLoading] = useState(false); useEffect(() => { const sheetListData: XSheet[] = Object.values(props.form?.sheets || {}); @@ -152,7 +153,7 @@ const WorkReportViewer: React.FC = (props) => { }, [sheetList, variablesData, props, btnType, fillDataVisible]); const handleOneKeyReport = async () => { - const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); message.loading('正在自动上报,请稍后', 0); await sleep(1000); @@ -184,6 +185,17 @@ const WorkReportViewer: React.FC = (props) => { if (!loaded) return null; return (
+ +
+ +
正在汇总数据,请稍候...
+
+
= (props) => { placement={'bottomRight'} menu={{ items: [ - { - key: 'summaryDirectChildren', - label: '直属下级汇总', - onClick: async () => { - try { - if (assignTask) { - const changes = await props.service.summary( - assignTask, - false, - ); - if (changes.length > 0) { - message.success('汇总成功'); - } else { - message.success('没有可汇总的数据'); - } - } else { - message.error('汇总失败'); - } - } catch (error) { - console.error(error); - message.error('汇总失败'); - } - }, - }, { key: 'summaryLeafChildren', label: '叶子节点汇总', onClick: async () => { try { if (assignTask) { - const changes = await props.service.summary( - assignTask, - true, - ); + setSummaryLoading(true); + const changes = await props.service.newSummary(assignTask); if (changes.length > 0) { message.success('汇总成功'); } else { @@ -248,6 +234,8 @@ const WorkReportViewer: React.FC = (props) => { } catch (error) { console.error(error); message.error('汇总失败'); + } finally { + setSummaryLoading(false); } }, }, @@ -349,8 +337,11 @@ const WorkReportViewer: React.FC = (props) => { { key: 'fill', label: '数据补全', - disabled: assignTask?.metadata.name !== '浙江全省汇总', - onClick: () => { + onClick: async () => { + await $confirm({ + content: + '此功能会将下级单位的上月数据补充到未上报单位中,请谨慎使用!', + }); setFillDataVisible(true); }, }, @@ -366,29 +357,27 @@ const WorkReportViewer: React.FC = (props) => { {orgCtrl.user.companys .find((com) => com.id === assignTask.metadata.belongId) - ?.hasSuperAuth() && ( - - )} + ?.hasSuperAuth() && + assignTask?.metadata.taskStatus !== 'finished' && ( + + )} {validates.length > 0 && (
{renderSpecialTags(item)} {!isDynamic && - item.groupTags - .filter((i) => i.length > 0) - .map((label) => { - return ( - - {label} - - ); - })} + (item.groupTags ?? []) + .filter((i) => i && i.length > 0) + .map((label) => ( + + {label} + + ))} + {isNav ? ( <> diff --git a/src/executor/operate/entityForm/ReportContentForm.tsx b/src/executor/operate/entityForm/ReportContentForm.tsx index 7e4cce59f538b1bfd0911f199538ad31d2b8cb8a..b4c60cafd62864752a8f0d8223888ea69a41e036 100644 --- a/src/executor/operate/entityForm/ReportContentForm.tsx +++ b/src/executor/operate/entityForm/ReportContentForm.tsx @@ -76,13 +76,6 @@ export function ReportContentForm(props: Props) { validate={workValidate} /> - - -
); diff --git a/src/executor/operate/entityForm/assignTaskModelForm.tsx b/src/executor/operate/entityForm/assignTaskModelForm.tsx index c8a0b5ea31c8aefacb80734265bd9d2dd1b26201..876b854abc546567abadba6f1d26c3192ffe0cd6 100644 --- a/src/executor/operate/entityForm/assignTaskModelForm.tsx +++ b/src/executor/operate/entityForm/assignTaskModelForm.tsx @@ -11,6 +11,7 @@ import { ReportContentForm } from './ReportContentForm'; import { Button } from 'antd'; import { PeriodType } from '@/ts/base/enum'; import { IAssignTaskModel } from '@/ts/core/thing/standard/assignTaskModel'; +import {DatePicker} from "@/components/Common/StringDatePickers/DatePicker"; const periodTypeNames: PeriodType[] = [ PeriodType.Year, @@ -97,6 +98,26 @@ const AssignTaskModelForm = (props: Iprops) => { rules: [{ required: true, message: '任务代码为必填项' }], }, }, + { + title: '填报开始时间', + dataIndex: 'startDate', + renderFormItem: () => { + return ; + }, + formItemProps: { + rules: [{ required: true, message: '填报开始时间为必填项' }], + }, + }, + { + title: '填报结束时间', + dataIndex: 'endDate', + renderFormItem: () => { + return ; + }, + formItemProps: { + rules: [{ required: true, message: '填报结束时间为必填项' }], + }, + }, { title: '任务类型', dataIndex: 'periodType', diff --git a/src/executor/operate/entityForm/reportTaskForm.tsx b/src/executor/operate/entityForm/reportTaskForm.tsx index b299e68ecf3a51631f95e063233667611c3cf3a9..fc8578c86987966100298110db0ab3708b5e376b 100644 --- a/src/executor/operate/entityForm/reportTaskForm.tsx +++ b/src/executor/operate/entityForm/reportTaskForm.tsx @@ -1,5 +1,5 @@ import SchemaForm from '@/components/SchemaForm'; -import { command, schema } from '@/ts/base'; +import { command, schema, kernel } from '@/ts/base'; import { ProFormColumnsType } from '@ant-design/pro-components'; import React, { useMemo, useRef, useState } from 'react'; import { ProFormInstance } from '@ant-design/pro-form'; @@ -7,13 +7,15 @@ import { EntityInput as _EntityInput } from '@/components/Common/EntityInput'; import OrgCtrl from '@/ts/controller'; import { AssignTaskModel } from '@/ts/core/thing/standard/assignTaskModel'; import { XAssignTask } from '@/ts/base/schema'; -import { ReportTree } from '@/ts/core/thing/standard/reporttree/ReportTree'; import { DatePicker, DatePickerProps, } from '@/components/Common/StringDatePickers/DatePicker'; import { PeriodType } from '@/ts/base/enum'; import message from '@/utils/message'; +import { IWork, Work } from '@/ts/core/work'; +import { IApplication } from '@/ts/core'; +import { Modal, Button } from 'antd'; interface Iprops { formType: string; @@ -27,6 +29,9 @@ const EntityInput: any = _EntityInput; const ReportTaskForm = (props: Iprops) => { const [taskModel, setTaskModel] = useState({}); const [modelPeriodType, setModelPeriodType] = useState(PeriodType.Any); + const [progressMessage, setProgressMessage] = useState(''); + const [isModalVisible, setIsModalVisible] = useState(false); + let title = '新建任务'; let initialValue: any = {}; const formRef = useRef(); @@ -65,6 +70,25 @@ const ReportTaskForm = (props: Iprops) => { return ; }, [modelPeriodType]); + const queryTaskProgress = async (flag: string) => { + try { + const res = await kernel.QueryWorkProgress({ flag }); + if (res && res.data) { + const message = ` + 任务进度: ${res.data.message} + 发布任务总数: ${res.data.totalCount} + 已发布数量: ${res.data.complatecount} + `; + setProgressMessage(message); + } else { + setProgressMessage('无法获取任务进度'); + } + } catch (error) { + console.error('查询进度失败:', error); + setProgressMessage('查询进度失败'); + } + }; + const columns: ProFormColumnsType[] = [ { title: '任务模板', @@ -125,64 +149,118 @@ const ReportTaskForm = (props: Iprops) => { return createPeriodType; }, }, + { + title: '指定分发集群id', + dataIndex: 'destId', + valueType: 'text', + }, ]; + const handleCancel = () => { + setIsModalVisible(false); + props.finished(); + }; return ( - - formRef={formRef} - open - title={title} - width={640} - columns={columns} - initialValues={initialValue} - rowProps={{ - gutter: [24, 0], - }} - layoutType="ModalForm" - onOpenChange={(open: boolean) => { - if (!open) { - props.finished(); - } - }} - onFinish={async (values) => { - const assignTaskModel = taskModel as AssignTaskModel; - const space = assignTaskModel.target; - if (!space) { - message.error('未获取到模板对应组织!'); - return; - } - if (!space.hasSuperAuth()) { - message.error('您不是模板所在空间管理员!'); - return; - } - // 根据任务模板查询报表树 - const xReportTree = await assignTaskModel.loadReportTree(); - if (!xReportTree) { - message.error('未获取到任务模板绑定的报表树!'); - return; - } - const reportTree = new ReportTree(xReportTree, space.directory); - // 报表树根节点信息 + 表单信息创建任务 - const nodes = await reportTree.loadNodeById([xReportTree.rootNodeId]); - if (!nodes || nodes.length < 1) { - message.error('未获取到任务模板绑定的报表树!'); - return; - } - const assignTask = { - ...nodes[0], - period: values.period, - assignType: values.assignType, - } as XAssignTask; - const task = await assignTaskModel.create(assignTask); - if (!task) { - message.error('创建任务失败!'); - return; - } - // 根据任务生成任务树,任务树根节点为传入的任务 - await reportTree.createAssignTree(task, space.directory); - command.emitter('assign', 'refresh'); - props.finished(); - }} - /> + <> + + formRef={formRef} + open + title={title} + width={640} + columns={columns} + initialValues={initialValue} + rowProps={{ + gutter: [24, 0], + }} + layoutType="ModalForm" + onOpenChange={(open: boolean) => { + if (!open) { + props.finished(); + } + }} + onFinish={async (values) => { + const assignTaskModel = taskModel as AssignTaskModel; + const space = assignTaskModel.target; + async function getWork( + assignTaskModel: any, + ): Promise<{ work?: IWork; formId?: string }> { + const res = await assignTaskModel.target.resource.workDefineColl.loadResult({ + options: { + match: { + primaryId: assignTaskModel.metadata.content.workId, + applicationId: { + _gt_: '0', + }, + isDeleted: false, + }, + }, + }); + + if (res.success && res.data.length === 1) { + const work = new Work(res.data[0], { + directory: assignTaskModel.target.directory, + } as IApplication); + + const workNode = await work.loadNode(); + if (workNode) { + const formId = + workNode.forms && workNode.forms.length > 0 + ? workNode.forms[0].id + : undefined; + return { work, formId }; + } + } + return {}; + } + if (!space) { + message.error('未获取到模板对应组织!'); + return; + } + if (!space.hasSuperAuth()) { + message.error('您不是模板所在空间管理员!'); + return; + } + const { formId } = await getWork(assignTaskModel); + if (!formId) { + message.error('未能获取到有效的表单 ID!'); + return; + } + const assignTask = { + period: values.period, + assignType: values.assignType, + periodType: assignTaskModel.metadata.periodType, + startDate: assignTaskModel.metadata.startDate, + endDate: assignTaskModel.metadata.endDate, + shareId: assignTaskModel.target.id, + formId: formId, + destId: values.destId, + } as XAssignTask; + const task = await assignTaskModel.createCompany(assignTask); + if (!task) { + message.error('创建任务失败!'); + return; + } + setIsModalVisible(true); + await queryTaskProgress(task.flag); + }} + /> + setIsModalVisible(false)} + footer={[ + , + , + ]}> +

{progressMessage}

+
+ ); }; diff --git a/src/pages/Home/components/TaskViewer/index.tsx b/src/pages/Home/components/TaskViewer/index.tsx index d4dbb7039f1f49d14d0753ac9757226a828cee87..cf0c103d58a67bf9ce55c8edc0eff4a69f4983c8 100755 --- a/src/pages/Home/components/TaskViewer/index.tsx +++ b/src/pages/Home/components/TaskViewer/index.tsx @@ -10,10 +10,11 @@ import { useFixedCallback } from '@/hooks/useFixedCallback'; import { useEffectOnce } from 'react-use'; import { cleanMenus } from '@/utils/tools'; import { loadFileMenus } from '@/executor/fileOperate'; -import { Spin } from 'antd'; +import { Spin, message } from 'antd'; import { statusMap } from '@/ts/core/work/assign/status'; import orgCtrl from '@/ts/controller'; import { IAssignTask } from '@/ts/core/work/assign/assign'; +import { operatesToMenus } from '@/executor/fileOperate'; /** * 办事-事项清单组件 @@ -106,14 +107,35 @@ const WorkContent: React.FC<{ space: ICompany }> = ({ space }) => { }; // 生成右键菜单 - const contextMenu = (file?: IFile) => { - return { - items: cleanMenus(loadFileMenus(file)) || [], - onClick: ({ key }: { key: string }) => { - command.emitter('executor', key, file); - }, - }; - }; + // const contextMenu = (file?: IFile) => { + // return { + // items: cleanMenus(loadFileMenus(file)) || [], + // onClick: ({ key }: { key: string }) => { + // command.emitter('executor', key, file); + // }, + // }; + // }; + const contextMenu = (file?: IFile) => ({ + items: operatesToMenus( + [ + { + sort: 1, + cmd: 'cancelReceive', + label: '取消接收', + iconType: 'restore', + }, + ], + file as IAssignTask, + ), + onClick: async ({ key }: { key: string }) => { + if (key === 'cancelReceive' && file) { + const ret = await (file as IAssignTask).cancelReceive(); + if (ret) { + message.info('取消接收任务成功'); + } + } + }, + }); // 渲染额外操作按钮 const renderMore = () => { @@ -181,7 +203,8 @@ const WorkContent: React.FC<{ space: ICompany }> = ({ space }) => { tagChanged={(t) => setCurrentTag(t)} rightBars={renderMore()} fileOpen={(entity) => clickHanlder(entity as IAssignTask, false)} - contextMenu={(entity) => contextMenu(entity as IAssignTask)} + // contextMenu={(entity) => contextMenu(entity as IAssignTask)} + contextMenu={(f) => (f ? contextMenu(f as IAssignTask) : {})} /> diff --git a/src/ts/base/common/uuid.ts b/src/ts/base/common/uuid.ts index 1581bdc6b14d2e304521f69d0138f57dc62b4d88..26dbc72e3593d5a489bb7738b11ee5dc0b39f54f 100644 --- a/src/ts/base/common/uuid.ts +++ b/src/ts/base/common/uuid.ts @@ -1,88 +1,96 @@ -/* eslint-disable no-unused-vars */ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -const _UUIDPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; - -export function isUUID(value: string): boolean { - return _UUIDPattern.test(value); -} - -declare const crypto: - | undefined - | { - //https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#browser_compatibility - getRandomValues?(data: Uint8Array): Uint8Array; - //https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID#browser_compatibility - randomUUID?(): string; - }; - -export const generateUuid = (function (): () => string { - // use `randomUUID` if possible - if (typeof crypto === 'object' && typeof crypto.randomUUID === 'function') { - return crypto.randomUUID.bind(crypto); - } - - // use `randomValues` if possible - let getRandomValues: (bucket: Uint8Array) => Uint8Array; - if (typeof crypto === 'object' && typeof crypto.getRandomValues === 'function') { - getRandomValues = crypto.getRandomValues.bind(crypto); - } else { - getRandomValues = function (bucket: Uint8Array): Uint8Array { - for (let i = 0; i < bucket.length; i++) { - bucket[i] = Math.floor(Math.random() * 256); - } - return bucket; - }; - } - - // prep-work - const _data = new Uint8Array(16); - const _hex: string[] = []; - for (let i = 0; i < 256; i++) { - _hex.push(i.toString(16).padStart(2, '0')); - } - - return function generateUuid(): string { - // get data - getRandomValues(_data); - - // set version bits - _data[6] = (_data[6] & 0x0f) | 0x40; - _data[8] = (_data[8] & 0x3f) | 0x80; - - // print as string - let i = 0; - let result = ''; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += '-'; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += '-'; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += '-'; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += '-'; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - return result; - }; -})(); - -export const generateShortUuid = (function (): () => string { - return function generateShortUuid(): string { - const uuid = generateUuid(); - return uuid.replace(/-/g, '').slice(0, 18); - }; -})(); +/* eslint-disable no-unused-vars */ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const _UUIDPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + +export function isUUID(value: string): boolean { + return _UUIDPattern.test(value); +} + +declare const crypto: + | undefined + | { + //https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#browser_compatibility + getRandomValues?(data: Uint8Array): Uint8Array; + //https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID#browser_compatibility + randomUUID?(): string; + }; + +export const generateUuid = (function (): () => string { + // use `randomUUID` if possible + if (typeof crypto === 'object' && typeof crypto.randomUUID === 'function') { + return crypto.randomUUID.bind(crypto); + } + + // use `randomValues` if possible + let getRandomValues: (bucket: Uint8Array) => Uint8Array; + if (typeof crypto === 'object' && typeof crypto.getRandomValues === 'function') { + getRandomValues = crypto.getRandomValues.bind(crypto); + } else { + getRandomValues = function (bucket: Uint8Array): Uint8Array { + for (let i = 0; i < bucket.length; i++) { + bucket[i] = Math.floor(Math.random() * 256); + } + return bucket; + }; + } + + // prep-work + const _data = new Uint8Array(16); + const _hex: string[] = []; + for (let i = 0; i < 256; i++) { + _hex.push(i.toString(16).padStart(2, '0')); + } + + return function generateUuid(): string { + // get data + getRandomValues(_data); + + // set version bits + _data[6] = (_data[6] & 0x0f) | 0x40; + _data[8] = (_data[8] & 0x3f) | 0x80; + + // print as string + let i = 0; + let result = ''; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + return result; + }; +})(); + +export const generateShortUuid = (function (): () => string { + return function generateShortUuid(): string { + const uuid = generateUuid(); + return uuid.replace(/-/g, '').slice(0, 18); + }; +})(); +export const generateNumtUuid = (function (): () => string { + return function generateShortUuid(): string { + const hex128 = generateUuid().replace(/-/g, ''); + const hex64 = hex128.slice(-16); + const numStr = BigInt('0x' + hex64).toString(); + return numStr; + }; +})(); diff --git a/src/ts/base/model.ts b/src/ts/base/model.ts index c7b27a0cb2ab55bf950dfe9bc612edbb6fd19cb0..0479a099084276b494dff4826851fe485142db37 100644 --- a/src/ts/base/model.ts +++ b/src/ts/base/model.ts @@ -2019,6 +2019,7 @@ export interface QueryOptions { unwind?: string; set?: any; extra?: any; + requireTotalCount?:boolean; } type SortOptions = { @@ -2444,6 +2445,9 @@ export interface ReportContent extends TaskContentBase { startDate?: string; /** 结束时间 */ endDate?: string; + period: string; + assignName: string; + periodType: string; } export interface NoticeContent extends TaskContentBase { @@ -2654,7 +2658,7 @@ export interface TaskAssignmentReq { content: TaskAssignmentModel; // 分发完成后是否需要通知任务分发发起人 needNotice: boolean; - // 通知方式 + // 通知方式--目前暂时先不传 noticeType?: string; // 允许节点重复 allowDuplicateNode: boolean; @@ -2675,7 +2679,7 @@ export interface TaskAssignmentModel { assignType: string; // 任务模板ID templateId: string; - // 任务发布组织 + // 任务发布组织-chuan shareId: string; // 任务状态 taskStatus: string; diff --git a/src/ts/base/schema.ts b/src/ts/base/schema.ts index a4fa670d0e10476c671dc0808a10381dd1a9c405..28c720ef533d44458e00346af7b193236f4a8b44 100644 --- a/src/ts/base/schema.ts +++ b/src/ts/base/schema.ts @@ -1537,6 +1537,8 @@ export interface XReportTree extends XStandard { // 任务 export interface XAssignTaskModel extends XStandard { // 任务类型 + startDate: string; + endDate: string; typeName: '任务模板'; // 任务周期 periodType: PeriodType; @@ -1545,6 +1547,7 @@ export interface XAssignTaskModel extends XStandard { } export interface XAssignTask extends XReportTreeNode, ReceptionStatusModel { + targetType: string; /** 任务模板ID */ modelId?: string; /** 任务周期 */ @@ -1555,6 +1558,7 @@ export interface XAssignTask extends XReportTreeNode, ReceptionStatusModel { assignType: AssignType; /** 上报内容 */ assignContent?: model.TaskContent; + content?: model.TaskContent; /** 分发内容 */ distContent?: model.TaskContent; /** 任务状态 */ @@ -1565,6 +1569,16 @@ export interface XAssignTask extends XReportTreeNode, ReceptionStatusModel { assignName: string; /** 集群id */ groupId?: string; + /** 业务标识 */ + flag: string; + /** 填报开始时间 */ + startDate: string; + /** 填报截止时间 */ + endDate: string; + /** 报表id */ + formId: string; + /** 指定任务分发集群id */ + destId?: string; } export interface XAssignTaskTree extends XReportTree { diff --git a/src/ts/core/public/collection.ts b/src/ts/core/public/collection.ts index 8caf4476adc12995d827950e49d9f265c4966e3a..9322f9245958b75b4e482d19aae841b564907182 100644 --- a/src/ts/core/public/collection.ts +++ b/src/ts/core/public/collection.ts @@ -156,6 +156,17 @@ export class XCollection { return []; } + + /** + * 加载空间数据 + * @param options 加载选项 + * @returns 返回数据数组 + */ + async loadSpace_(options: LoadOptions): Promise> { + const res = await this.loadResult(options); + return (res as any ) || []; + } + /** * 加载聚合数据 * @param options 聚合选项 @@ -286,7 +297,19 @@ export class XCollection { data.id = data.id || 'snowId()'; data.shareId = this._target.id; data.belongId = data.belongId || this._target.belongId; - const res = await kernel.collectionReplace( + const isTask = (data as any).taskStatus !== undefined; + let res; + if (isTask) { + await kernel.collectionReplace( + data.belongId, + data.belongId, + [data.belongId], + this._collName, + data, + copyId, + ); + } + res = await kernel.collectionReplace( this._target.id, this._target.belongId, this._relations, diff --git a/src/ts/core/thing/resource.ts b/src/ts/core/thing/resource.ts index 826128f9384f57283d1ce0b692ba64f6ef0879a7..8a680789c805a75fbabb7c3935f1997122c5388a 100644 --- a/src/ts/core/thing/resource.ts +++ b/src/ts/core/thing/resource.ts @@ -81,7 +81,7 @@ export class DataResource { this.assignTaskModelColl = this.genTargetColl('assign-task-model'); this.assignTaskColl = this.genTargetColl('assign-task'); this.assignTaskTreeColl = this.genTargetColl('assign-task-tree'); - this.assignTaskPublicColl = this.genTargetColl('-assign-task'); + this.assignTaskPublicColl = this.genTargetColl('assign-task'); this.orderColl = this.genTargetColl('mall-order'); this.assignTaskTreePublicColl = this.genTargetColl('-assign-task-tree'); diff --git a/src/ts/core/thing/standard/assignTaskModel.ts b/src/ts/core/thing/standard/assignTaskModel.ts index e05e42e15cde46b20f4c2b426ab546fe945a6fce..c46b66b6c1b52045441da313fdbb65b7e68297e4 100644 --- a/src/ts/core/thing/standard/assignTaskModel.ts +++ b/src/ts/core/thing/standard/assignTaskModel.ts @@ -1,10 +1,10 @@ import { IStandardFileInfo, StandardFileInfo } from '@/ts/core/thing/fileinfo'; -import { schema } from '@/ts/base'; +import { schema, kernel } from '@/ts/base'; import { TaskContentType } from '@/ts/base/model'; import { XAssignTask, XReportTree } from '@/ts/base/schema'; import { IDirectory, XCollection } from '@/ts/core'; import { AssignStatusType } from '@/ts/base/enum'; -import { generateShortUuid } from '@/ts/base/common/uuid'; +import { generateShortUuid, generateNumtUuid } from '@/ts/base/common/uuid'; export interface IAssignTaskModel extends IStandardFileInfo { /** 关联任务 */ @@ -20,6 +20,7 @@ export interface IAssignTaskModel extends IStandardFileInfo; + createCompany(task: schema.XAssignTask): Promise; /** 删除下发任务 */ hardDeleteTask(task: schema.XAssignTask): Promise; @@ -72,9 +73,10 @@ export class AssignTaskModel } async create(task: XAssignTask): Promise { - if (task.belongId !== this.belongId) { - throw new Error('任务树根节点与当前任务节点归属不一致'); - } + // if (task.belongId !== this.belongId) { + // throw new Error('任务树根节点与当前任务节点归属不一致'); + // } + //检查任务是否已经存在 let tasks = await this.assignTaskPublicColl.loadSpace({ options: { match: { @@ -88,9 +90,7 @@ export class AssignTaskModel throw new Error(`任务 ${task.period} 已存在`); } task.id = generateShortUuid(); - task.treeId = generateShortUuid(); task.nodePath = task.id; - this.metadata.content.treeId = task.treeId; this.metadata.content.sessionId = this.target.id; return await this.assignTaskPublicColl.insert({ ...task, @@ -105,6 +105,69 @@ export class AssignTaskModel assignName: this.metadata.name, }); } + async createCompany(task: XAssignTask): Promise { + task.id = generateNumtUuid(); + this.generateFlag(task); + this.metadata.content.sessionId = this.target.id; + this.metadata.content.startDate = task.startDate; + this.metadata.content.endDate = task.endDate; + this.metadata.content.period = task.period; + this.metadata.content.assignName = this.metadata.name; + this.metadata.content.periodType = this.metadata.periodType; + this.metadata.content.formId = task.formId; + this.metadata.content.flag = task.flag; + await kernel.TaskAssignment({ + flag: task.flag, + assignType: task.assignType, + content: { + id: task.id, + assignContent: this.metadata.content, + assignName: this.metadata.name, + assignType: task.assignType, + shareId: task.shareId, + needDataCopy: true, + }, + assignName: this.metadata.name, + period: task.period, + shareId: task.shareId, + allowDuplicateNode: true, + destId: task.destId, + }); + await kernel.QueryWorkProgress({ + flag: task.flag, + }); + return task; + } + + private generateFlag(task: XAssignTask): void { + let prefix = ''; + switch (this.metadata.periodType) { + case '年': + prefix = 'Y'; + break; + case '月': + prefix = 'M'; + break; + case '季度': + prefix = 'Q'; + break; + default: + prefix = 'U'; + break; + } + const MAX_FLAG_LEN = 20; + const now = new Date(); + const currentDate = + now.getFullYear().toString() + + (now.getMonth() + 1).toString().padStart(2, '0') + + now.getDate().toString().padStart(2, '0'); + const remain = MAX_FLAG_LEN - (prefix.length + currentDate.length); + let idPart = task.id; + if (idPart.length > remain) { + idPart = idPart.slice(-remain); + } + task.flag = `${prefix}${currentDate}${idPart}`; + } async loadTasks(): Promise { let tasks = await this.assignTaskPublicColl.loadSpace({ diff --git a/src/ts/core/thing/standard/report.ts b/src/ts/core/thing/standard/report.ts index 4287cf84c888549b694b0231c27832235ed2c398..4df0960bc8c05d71f55c927554d75275b7264892 100644 --- a/src/ts/core/thing/standard/report.ts +++ b/src/ts/core/thing/standard/report.ts @@ -61,6 +61,10 @@ export interface IReport extends IStandardFileInfo { loadOptions: LoadOptions & { ids?: string[] }, ): Promise<{ [key: string]: number } | undefined>; /** 查询表格sheet页汇总数据 */ + newLoadSheetSummary( + loadOptions: LoadOptions & { ids?: any[] }, + ): Promise<{ [key: string]: object } | undefined>; + /** 查询表格sheet页汇总数据 */ loadSheetSummary( loadOptions: LoadOptions & { ids?: string[] }, ): Promise<{ [key: string]: object } | undefined>; @@ -326,8 +330,8 @@ export class Report extends StandardFileInfo implements IReport code: attr.options?.isNative ? attr.propId : attr.valueType === '对象型' - ? `R${attr.propId}` - : `T${attr.propId}`, + ? `R${attr.propId}` + : `T${attr.propId}`, info: attr.code, remark: attr.remark, speciesId: attr.property?.speciesId, @@ -702,6 +706,114 @@ export class Report extends StandardFileInfo implements IReport return loadOptions; } + async newLoadSheetSummary( + loadOptions: any, + ): Promise<{ [key: string]: object } | undefined> { + + try { + const filters = this.objectFields; + if (filters.length == 0) { + return {}; + } + + const sheets = this.metadata.sheets; + const group: { [key: string]: any } = { key: [] }; + for (let sheetsKey in sheets) { + for (let cellsKey in sheets[sheetsKey].cells) { + if (sheets[sheetsKey].cells[cellsKey].rule.isSummary) { + group['R' + sheets[sheetsKey].attributeId + '_' + cellsKey] = { + _sum_: '$' + 'R' + sheets[sheetsKey].attributeId + '.' + cellsKey, + }; + } + } + } + const match = { + ...loadOptions.options?.match, + ...common.filterConvert(JSON.stringify(loadOptions.filter ?? '[]')), + }; + const options: any = { + match, + group, + collName: this.thingColl.collName, + limit: 1, + }; + if (loadOptions.userData?.length > 0) { + if (match.labels) { + match._and_ = match._and_ || []; + match._and_.push({ labels: { _in_: loadOptions.userData } }); + match._and_.push({ labels: { ...match.labels } }); + delete match.labels; + } else { + match.labels = { _in_: loadOptions.userData }; + } + } + let _targetId = this.target.id; + let _relations = [...this.target.relations]; + if (loadOptions?.extraReations && loadOptions.extraReations.length > 5) { + _relations.push(loadOptions.extraReations); + _targetId = loadOptions.extraReations; + delete loadOptions.extraReations; + } + // 批量查询 + const batchSize = 400; + const batchResults: any[] = []; + const batchPromises = []; + for (let i = 0; i < loadOptions.ids.length; i += batchSize) { + const batchIds = loadOptions.ids.slice(i, i + batchSize); + if (Array.isArray(batchIds) && batchIds.length > 0) { + match['assignId'] = { + _in_: batchIds, + }; + } + if (!this.thingColl?.collName) { + console.error('newLoadSheetSummary: 缺少thingColl.collName'); + continue; + } + + const batchPromise = kernel + .collectionAggregate( + _targetId, + this.target.spaceId, + _relations, + this.thingColl.collName, + options, + ) + .then((batchResult) => { + batchResults.push(...batchResult.data); + }); + + batchPromises.push(batchPromise); + } + await Promise.all(batchPromises); + // 处理结果 + function processResult(batchResults: any[]) { + const objectResult: { [key: string]: { [field: string]: number } } = {}; + batchResults.forEach((item) => { + Object.keys(item).forEach((key) => { + if (key === 'id') return; + const [attrId, field] = key.split('_'); + let newAttrId = attrId.replace('T', 'R'); + if (!objectResult[newAttrId]) { + objectResult[newAttrId] = {}; + } + if (item[key] !== undefined) { + if (objectResult[newAttrId][field]) { + objectResult[newAttrId][field] += item[key]; + } else { + objectResult[newAttrId][field] = item[key]; + } + } + }); + }); + return objectResult; + } + return processResult(batchResults); + } catch (err) { + console.error('newLoadSheetSummary: 执行失败', err); + return {}; + } + } + async loadSheetSummary( loadOptions: any, ): Promise<{ [key: string]: object } | undefined> { @@ -826,8 +938,8 @@ export class Report extends StandardFileInfo implements IReport tableIdIndex >= 0 ? filterExpData[tableIdIndex] : filterExpData.filter( - (a: string | string[]) => Array.isArray(a) && a.indexOf('person') >= 0, - )[0]?.[0]; + (a: string | string[]) => Array.isArray(a) && a.indexOf('person') >= 0, + )[0]?.[0]; if (value && this.fields) { const lookups = this.fields.find((a) => a.code === value)?.lookups; const dictId = lookups?.find((a) => a.code === orgCtrl.user.code)?.id; diff --git a/src/ts/core/work/apply.ts b/src/ts/core/work/apply.ts index 501b6329a3f55084ef787dc70303e5df20e437a8..8ce7ba8262a71eeca4558909fab7a71eacb21021 100644 --- a/src/ts/core/work/apply.ts +++ b/src/ts/core/work/apply.ts @@ -115,7 +115,7 @@ export class WorkApply implements IWorkApply { for (const form of forms) { for (const item of form.after) { item.periodType = this.assignTask.periodType; - item.period = this.assignTask.period; + item.period = this.assignTask.assignContent?.periodperiod; item.assignId = this.assignTask.id; item.nodeType = this.assignTask.nodeType; item.targetId = this.assignTask.targetId; diff --git a/src/ts/core/work/assign/assign.ts b/src/ts/core/work/assign/assign.ts index d36acd9883d0417102676f14e1a0ea17e34e983f..d295174b3a0bbd9497eb74964de006156e0d8d1a 100644 --- a/src/ts/core/work/assign/assign.ts +++ b/src/ts/core/work/assign/assign.ts @@ -93,8 +93,14 @@ export interface IAssignTask extends IFileInfo { summary(isLeafSummary?: boolean): Promise>>; /** 计算任务接收对应的往期任务时期 */ getHistoryPeriod(timeSpan: number): string; - /** 任务催报 */ - sendUrgeInfo(message?: string): Promise; + /** 加载单位任务并汇总 */ + taskSummary(): Promise>>; + /**加载单位与组织群任务 */ + loadTask(targetType?: string): Promise; + /**获取指定状态的单位与组织群数量 */ + loadTsakState(state: string): Promise; + /**根据状态加载任务列表*/ + loadTasksByState?(state?: string, skip?: number, take?: number): Promise; } export class AssignTask extends FileInfo implements IAssignTask { @@ -139,7 +145,7 @@ export class AssignTask extends FileInfo implements IAssignT } get name() { - return '[' + this.period + ']' + this.metadata.assignName; + return '[' + this.assignContent?.period + ']' + this.metadata.assignName; } get cacheFlag() { @@ -154,12 +160,20 @@ export class AssignTask extends FileInfo implements IAssignT return this.metadata.taskStatus; } + /** + * 任务标签添加 + */ get groupTags() { const tags: string[] = [ this.metadata.nodeTypeName, this.metadata.name, this.metadata.periodType, + this.metadata.typeName, + this.metadata.assignContent?.period, ]; + if (this.metadata.targetType) { + tags.push(this.metadata.targetType === '单位' ? '单户表' : '汇总表') + } if (this.metadata.assignType) { tags.push(this.metadata.assignType === AssignType.ALL ? '全部分发' : '逐级分发'); } @@ -230,7 +244,7 @@ export class AssignTask extends FileInfo implements IAssignT // 初始化节点信息 node.assignType = AssignType.ALL; node.periodType = this.metadata.periodType; - node.period = this.period; + node.period = this.assignContent?.period; node.assignContent = deepClone(this.distContent); node.taskStatus = AssignStatusType.EMPTY; node.typeName = '任务'; @@ -324,26 +338,19 @@ export class AssignTask extends FileInfo implements IAssignT message.error('当前任务已过期'); return; } - if (!this.assignGroup || this.assignGroup.id !== this.target.id) { - message.error('当前任务空间不支持接收'); - return; - } const company = orgCtrl.user.companys.find((c) => c.id == this.metadata.belongId); if (!company) { message.error('找不到当前节点的接收单位'); return; } - if (!this.assignTaskTree) { - const tree = await this.loadAssignTree(true); - if (!tree) { - message.error('找不到分发树形'); - return; - } - } // 更新任务状态 - this.metadata.taskStatus = AssignStatusType.RECEIVED; this.metadata.receiveUserId = orgCtrl.user.id; await this.target.resource.assignTaskPublicColl.replace(this.metadata); + await kernel.ApprovalAssignmentTask({ + id: this.metadata.id, + shareId: this.metadata.shareId, + status: AssignStatusType.RECEIVED, + }); return this.metadata; } @@ -395,10 +402,88 @@ export class AssignTask extends FileInfo implements IAssignT return true; } + async loadTsakState(state: string): Promise { + const match: any = { + isDeleted: false, + taskStatus: state, + }; + + if (this.id) { + match.nodePath = { _regex_: this.id }; + } + try { + const nodes = await this.target.resource.assignTaskPublicColl.loadSpace_({ + options: { + match: match, + project: { + children: 0, + }, + sort: { + createTime: -1, + }, + }, + requireTotalCount: true, + skip: 0, + take: 1, + }); + if (nodes.success) { + return nodes.totalCount; + } + } catch (error) { + console.warn('获取状态失败'); + } + return 0; + } + async loadTasksByState(state?: string, skip = 0, take = 1000): Promise { + const match: any = { + isDeleted: false, + }; + if (state && state !== 'all') { + match.taskStatus = state; + } + if (this.id) { + match.nodePath = { _regex_: this.id }; + } + try { + let current = skip; + let nums = take; + const allNodes: XAssignTask[] = []; + let hasMore = true; + while (hasMore) { + const nodes = await this.target.resource.assignTaskPublicColl.loadSpace({ + options: { + match: match, + project: { + id: 1, + name: 1, + parentId: 1, + nodePath: 1, + taskStatus: 1, + }, + sort: { + createTime: -1, + }, + }, + requireTotalCount: true, + skip: current, + take: nums, + }); + if (nodes && nodes.length > 0) { + allNodes.push(...nodes); + current += nums; + } else { + hasMore = false; + } + } + return allNodes; + } catch (err) { + console.warn('加载状态任务列表失败', err); + return []; + } + } + async change(): Promise { this.metadata.receiveUserId = this.userId; - this.metadata.previousInstanceId = this.metadata.instanceId; - this.metadata.isReject = false; this.metadata.taskStatus = AssignStatusType.RECEIVED; delete this.metadata.instanceId; const data = await this.target.resource.assignTaskPublicColl.replace(this.metadata); @@ -417,9 +502,6 @@ export class AssignTask extends FileInfo implements IAssignT } this.metadata.receiveUserId = ''; this.metadata.taskStatus = AssignStatusType.EMPTY; - this.metadata.draftId = ''; - this.metadata.instanceId = ''; - this.metadata.previousInstanceId = ''; await this.target.resource.assignTaskPublicColl.replace(this.metadata); return true; } @@ -470,6 +552,54 @@ export class AssignTask extends FileInfo implements IAssignT return true; } + /** + * 加载组织群与单位任务 + */ + async loadTask(targetType?: string): Promise { + const match: any = { + isDeleted: false, + taskStatus: { _exists_: true }, + }; + if (targetType && (targetType == '单位' || targetType == '组织群')) { + match.targetType = targetType; + } + if (this.id) { + match.nodePath = { _regex_: this.id }; + } + try { + let current = 0; + let nums = 1000; + const allNodes = []; + let hasMore = true; + while (hasMore) { + const nodes = await this.target.resource.assignTaskPublicColl.loadSpace({ + options: { + match: match, + project: { + children: 0, + }, + sort: { + createTime: -1, + }, + }, + requireTotalCount: true, + skip: current, + take: nums, + }); + if (nodes && nodes.length > 0) { + allNodes.push(...nodes); + current += nums; + } else { + hasMore = false; + } + } + return allNodes; + } catch (err) { + console.warn('加载组织群与单位任务时出现错误', err); + return []; + } + } + async loadWork(reload?: boolean, isDistWork?: boolean): Promise { if (reload || !this._workLoaded) { this._workLoaded = true; @@ -716,6 +846,48 @@ export class AssignTask extends FileInfo implements IAssignT result.value = sumValues(result.children); return result; } + async taskSummary() { + if (!this.work) { + const work = await this.loadWork(true); + if (!work) { + throw new Error('找不到办事'); + } + this.work = work; + } + + const children = await this.loadTask('单位'); + + const result: Dictionary> = {}; + if (children.length === 0) { + return result; + } + + const forms = [...this.work.primaryForms, ...this.work.detailForms]; + for (const form of forms) { + const ids = children.map((item) => { + return item.id; + }); + if (ids.length <= 0) { + return result; + } + let summary: { [p: string]: number | object } | undefined = {}; + const item: any = {}; + if (form.metadata.typeName == '表格') { + const reportForm = form as IReport; + summary = await reportForm.newLoadSheetSummary({ ids }); + for (const field of reportForm.objectFields) { + if (summary) { + item[field.id] = summary[field.code]; + } + } + } + if (!summary) { + throw new Error(`汇总 ${form.name} 失败`); + } + result[form.id] = item; + } + return result; + } async summary(isLeafSummary?: boolean): Promise>> { if (this.metadata.nodeType != NodeType.Summary) { @@ -737,9 +909,9 @@ export class AssignTask extends FileInfo implements IAssignT this.work = work; } const children = isLeafSummary - ? ((await this.currentTree.loadSubNodes(this.id, true))?.filter( - (v) => v.nodeType === NodeType.Normal, - ) as XAssignTask[]) + ? ((await this.currentTree.loadSubNodes(this.id, true))?.filter((v) => { + v.nodeType === NodeType.Normal; + }) as XAssignTask[]) : ((await this.currentTree.loadChildren(this.id)) as XAssignTask[]); if (children.length == 0) { return result; @@ -772,13 +944,13 @@ export class AssignTask extends FileInfo implements IAssignT } getHistoryPeriod(timeSpan = -1) { - const period = this.metadata.period; + const period = this.metadata.assignContent?.period; const date = new Date(period); const year = date.getFullYear(); const month = date.getMonth(); const day = date.getDate(); - switch (this.metadata.periodType) { + switch (this.metadata.assignContent?.periodType) { case PeriodType.Year: return year + timeSpan + ''; case PeriodType.Quarter: { @@ -803,64 +975,4 @@ export class AssignTask extends FileInfo implements IAssignT return period; } } - - async sendUrgeInfo(_message?: string): Promise { - if (this.metadata.nodeType != NodeType.Summary) { - throw new Error('当前节点不可催报'); - } - if (!this.currentTree) { - const distTree = await this.loadTree(); - if (!distTree) { - throw new Error('找不到树形'); - } - this.currentTree = distTree; - } - if (!this.work) { - const work = await this.loadWork(true); - if (!work) { - throw new Error('找不到办事'); - } - this.work = work; - } - let treeNodes = (await this.currentTree.loadChildren(this.id)) as XAssignTask[]; - if (treeNodes.length == 0) { - throw new Error('不存在直属下级节点'); - } - // 获取authId - const workNode = await this.work.loadNode(); - let authId: string; - if ( - workNode?.children?.destType === '角色' && - workNode?.children?.destName.includes('权限') - ) { - authId = workNode?.children?.destId; - } else { - throw new Error('当前办事不允许催报'); - } - for (const node of treeNodes) { - // 筛选直属下级节点任务状态为未接收和已接收的节点 - if ( - node.taskStatus !== AssignStatusType.FINISHED && - node.taskStatus !== AssignStatusType.CHANGED && - node.taskStatus !== AssignStatusType.SUBMITTED - ) { - // 发送短信 - try { - await orgCtrl.auth.sendNotice({ - shareId: node.belongId, - authId: authId, - data: - _message ?? - `【催办】${node.assignName} 中的 "${node.name}"还未完成,请及时处理。`, - type: 'sms', - platName: getPlatFormName(), - }); - } catch (error) { - throw new Error(`任务催办失败`); - } - } - } - - return true; - } } diff --git a/src/ts/core/work/index.ts b/src/ts/core/work/index.ts index 26aef3a3a00161b156c79f19f8d8a4ec55819493..8050c670327ec44d6e3cc83e2423bffba5baa260 100644 --- a/src/ts/core/work/index.ts +++ b/src/ts/core/work/index.ts @@ -18,8 +18,10 @@ import { IReport, Report } from '../thing/standard/report'; import { XForm, XReport } from '@/ts/base/schema'; import { message } from 'antd'; import { IPerson } from '../target/person'; +import { ITarget } from '../target/base/target'; export interface IWork extends IVersion { + target: ITarget; /** 我的办事 */ isAuth: boolean; /** 是否允许发起 */ diff --git a/src/ts/core/work/provider.ts b/src/ts/core/work/provider.ts index b102b529a90d8b10de32cfbdcc2b1073a379178b..2d0a28b85ccbbe410ea3fee07c245ddc39e75801 100644 --- a/src/ts/core/work/provider.ts +++ b/src/ts/core/work/provider.ts @@ -337,31 +337,67 @@ export class WorkProvider implements IWorkProvider { } return {}; } + // async loadAssignTasks(company: ICompany): Promise { + // const assignTasks: IAssignTask[] = []; + // const groups = company.topTargets.filter((g) => g.id !== '587865178075443200'); + // debugger; + // await Promise.all( + // groups.map(async (group) => { + // console.log(group); + // const res = await group.resource.assignTaskPublicColl.loadSpace({ + // options: { + // match: { + // isDeleted: false, + // belongId: company.id, + // 'assignContent.sessionId': group.id, + // taskStatus: { _exists_: true }, + // typeName: '任务', + // }, + // project: { + // children: 0, + // }, + // sort: { + // createTime: -1, + // }, + // }, + // }); + // assignTasks.push(...res.map((i) => new AssignTask(i, group))); + // }), + // ); + // return assignTasks; + // } + /** + * 任务查询 + * */ async loadAssignTasks(company: ICompany): Promise { const assignTasks: IAssignTask[] = []; - const groups = company.topTargets.filter((g) => g.id !== '587865178075443200'); - await Promise.all( - groups.map(async (group) => { - const res = await group.resource.assignTaskPublicColl.loadSpace({ - options: { - match: { - isDeleted: false, - belongId: company.id, - 'assignContent.sessionId': group.id, - taskStatus: { _exists_: true }, - typeName: '任务', - }, - project: { - children: 0, - }, - sort: { - createTime: -1, - }, - }, - }); + const groups = company.topTargets; + if (groups.length === 0) { + return assignTasks; + } + const res = await company.resource.assignTaskPublicColl.loadSpace({ + options: { + match: { + isDeleted: false, + belongId: company.id, + taskStatus: { _exists_: true }, + }, + project: { + children: 0, + }, + sort: { + createTime: -1, + }, + }, + }); + if (res.length > 0) { + const i = res[0]; + const sessionId = i.shareId; + const group = groups.find((g) => g.id === sessionId); + if (group) { assignTasks.push(...res.map((i) => new AssignTask(i, group))); - }), - ); + } + } return assignTasks; } async loadGroupAssignTasks( diff --git a/src/ts/scripting/core/services/WorkFormService.ts b/src/ts/scripting/core/services/WorkFormService.ts index d85d3e72fe98f3bbfb2619dcbd1718b0418b2876..7ebbc535c6ee7655ec3ad79ae25ffe95e9f40466 100644 --- a/src/ts/scripting/core/services/WorkFormService.ts +++ b/src/ts/scripting/core/services/WorkFormService.ts @@ -59,8 +59,8 @@ export default class WorkFormService extends mixins( data: data.length > 0 ? { - [form.id]: data, - } + [form.id]: data, + } : {}, primary: {}, rules: [], @@ -313,7 +313,7 @@ export default class WorkFormService extends mixins( // 合并的目标资产 const mergePrimaryAsset = this.formData.primary[mergeRule.combination!.assetSource.formId][ - mergeRule.combination!.assetSource.id + mergeRule.combination!.assetSource.id ]; if (mergePrimaryAsset) { // 合并的目标资产的id @@ -429,6 +429,57 @@ export default class WorkFormService extends mixins( return fieldUpdates; } + + /** + * 汇总 + * @param report 报表实例 + * @returns 变更对象 + */ + + async newSummary(report: IAssignTask) { + const changes: FormChangeEvent[] = []; + const sum = await report.taskSummary(); + if (!sum || typeof sum !== 'object') { + console.warn('未找到可汇总数据'); + return []; + } + for (const [formId, data] of Object.entries(sum)) { + const formInfo = this.formInfo[formId]; + if (!formInfo) { + console.warn(`找不到表单 ${formId},汇总数据将被忽略`); + continue; + } + if (formInfo.typeName == '工作表' || formInfo.typeName == '浮动行') { + continue; + } + + const form = formInfo.form; + const attrs = form.attributes.map((a) => a.id); + for (const [attrId, value] of Object.entries(data)) { + if (value == null) { + continue; + } + if (!attrs.includes(attrId)) { + continue; + } + const find: any = Object.values(form.sheets).find( + (item: any) => item.attributeId == attrId, + ); + for (const [cell, cellValue] of Object.entries(value)) { + changes.push({ + formId, + sheet: find.code, + destId: cell, + value: cellValue, + }); + } + } + } + this.dispatchEvent('batchUpdate', changes); + return changes; + } + + /** * 汇总 * @param report 报表实例