Browse Source

feature: 添加项目时,审核人作为必选字段,必须填写。
feature: 增加综合数量统计功能
feature: 流程引擎中添加对过程文档的归档功能

eyes4 6 tháng trước cách đây
mục cha
commit
74cfadf8df

+ 9 - 2
base-lib/src/core-models/AcsFunction.ts

@@ -131,7 +131,10 @@ export class AcsFunction extends Model {
             (1005, 201, 'stat', '获取客户统计数据', 'biz.customer.stat', true, 0),
             
             (1900, 281, 'count', '项目数量统计', 'report.prj.count', true, 0),
-            
+            (1901, 281, 'cat_stat', '项目分类统计', 'report.prj.cat_stat', true, 0),
+            (1902, 281, 'multi_stat', '综合数量统计', 'report.prj.multi_stat', true, 0),
+            (1903, 281, 'task_stat', '任务数量统计', 'report.prj.task_stat', true, 0),
+            (1904, 281, 'task_delay_rate', '任务延误率统计', 'report.prj.task_delay_rate', true, 0),
 
             (2020, 302, 'get_list', '获取项目列表', 'prj.info.get_list', true, 0),
             (2021, 302, 'add', '创建项目', 'prj.info.add', true, 1),
@@ -200,7 +203,11 @@ export class AcsFunction extends Model {
             (2201, 311, 'detail', '获取工作详情', 'prj.work.detail', true, 0),
             (2202, 311, 'submit', '提交工作', 'prj.work.submit', true, 1),
             (2203, 311, 'upload_file', '上传工作项附件', 'prj.work.upload_file', true, 1),
-            (2204, 311, 'stat', '获取工作项统计信息', 'prj.work.stat', true, 0)
+            (2204, 311, 'download_file', '下载工作项附件', 'prj.work.download_file', true, 0)
+            (2205, 311, 'undone_count', '获取未完成工作项总数', 'prj.work.undone_count', true, 0),
+            (2206, 311, 'done_stat', '获取已完成工作项统计信息', 'prj.work.done_stat', true, 0),
+            (2207, 311, 'undone_stat', '获取未完成工作项统计信息', 'prj.work.undone_stat', true, 0),
+            (2208, 311, 'stat', '获取工作项统计信息', 'prj.work.stat', true, 0)
 
          ON CONFLICT DO NOTHING `,
         `select setval('tb_acs_function_id_seq', 20000 ) where (select nextval('tb_acs_function_id_seq')) < 20000;`

+ 10 - 2
base-lib/src/core-models/AcsRoleFunction.ts

@@ -40,7 +40,7 @@ export class AcsRoleFunction extends Model {
         {fields: ['func_id', 'role_id']}
     ]
 
-    static associate(sequelize: Sequelize) {
+    static associate(_sequelize: Sequelize) {
         try {
             AcsFunction.belongsToMany(AcsRole, { through: AcsRoleFunction, foreignKey: 'id', otherKey: 'role_id' });
             AcsRole.belongsToMany(AcsFunction, { through: AcsRoleFunction, foreignKey: 'id', otherKey: 'func_id' });
@@ -110,6 +110,10 @@ export class AcsRoleFunction extends Model {
             ('admin', 1005),
             
             ('admin', 1900),
+            ('admin', 1901),
+            ('admin', 1902),
+            ('admin', 1903),
+            ('admin', 1904),
             
             ('admin', 2020),
             ('admin', 2021),
@@ -178,7 +182,11 @@ export class AcsRoleFunction extends Model {
             ('admin', 2201),
             ('admin', 2202),
             ('admin', 2203),
-            ('admin', 2204)
+            ('admin', 2204),
+            ('admin', 2205),
+            ('admin', 2206),
+            ('admin', 2207),
+            ('admin', 2208)
             
             ON CONFLICT DO NOTHING `
     ]

+ 2 - 2
pmr-api-gateway/deploy.sh

@@ -5,8 +5,8 @@ webpack --mode production
 
 # 上传到服务器
 scp -r ../__dist/pmr-api-gateway/index.js root@112.124.10.239:/home/pmr/pmr-api-gateway
-scp -r ../__dist/pmr-api-gateway/proxy.json root@112.124.10.239:/home/pmr/proxy.json
-scp -r ../__dist/pmr-api-gateway/config.json root@112.124.10.239:/home/pmr/config.json
+scp -r ../__dist/pmr-api-gateway/proxy.json root@112.124.10.239:/home/pmr/pmr-api-gateway
+scp -r ../__dist/pmr-api-gateway/config.json root@112.124.10.239:/home/pmr/pmr-api-gateway
 
 # 重启 Docker
 ssh root@112.124.10.239 "docker restart pmr-api-gateway && docker logs pmr-api-gateway --tail 100 -ft"

+ 1 - 1
pmr-biz-manager/bpmn/任务审核流程.bpmn

@@ -12,7 +12,7 @@
     <bpmn2:userTask id="task_request_task" name="提交审核" camunda:formKey="select_approve_type" camunda:assignee="owner">
       <bpmn2:extensionElements>
         <camunda:inputOutput>
-          <camunda:inputParameter name="show_in_my_works">${true}</camunda:inputParameter>
+          <camunda:inputParameter name="show_in_my_works">${false}</camunda:inputParameter>
           <camunda:inputParameter name="process_type">${1}</camunda:inputParameter>
           <camunda:inputParameter name="target_type">project-plan</camunda:inputParameter>
           <camunda:inputParameter name="draft">${true}</camunda:inputParameter>

+ 100 - 67
pmr-biz-manager/bpmn/立项流程.bpmn

@@ -9,7 +9,7 @@
       </bpmn2:extensionElements>
       <bpmn2:outgoing>Flow_15zn1tu</bpmn2:outgoing>
     </bpmn2:startEvent>
-    <bpmn2:userTask id="task_request" name="申请立项" camunda:assignee="owner">
+    <bpmn2:userTask id="task_request" name="申请立项" camunda:formKey="prj_create_req" camunda:assignee="owner">
       <bpmn2:extensionElements>
         <camunda:inputOutput>
           <camunda:inputParameter name="show_in_my_works">${false}</camunda:inputParameter>
@@ -34,6 +34,7 @@
           <camunda:inputParameter name="prj_id">${environment.variables.prj_id}</camunda:inputParameter>
           <camunda:inputParameter name="prj_phase">overview_create</camunda:inputParameter>
           <camunda:inputParameter name="prj_phase_name">立项审核中</camunda:inputParameter>
+          <camunda:inputParameter name="task_desc">{"memo": "{{}}"}</camunda:inputParameter>
         </camunda:inputOutput>
       </bpmn2:extensionElements>
       <bpmn2:incoming>Flow_0jpbzcr</bpmn2:incoming>
@@ -86,7 +87,7 @@
       <bpmn2:incoming>Flow_0tj9nd6</bpmn2:incoming>
       <bpmn2:signalEventDefinition id="SignalEventDefinition_1tt7p06" signalRef="Signal_0hhmd7l" />
     </bpmn2:intermediateThrowEvent>
-    <bpmn2:sequenceFlow id="Flow_02taytj" sourceRef="event_throw_approved" targetRef="Event_end">
+    <bpmn2:sequenceFlow id="Flow_02taytj" sourceRef="event_throw_approved" targetRef="Activity_10dmh6u">
       <bpmn2:extensionElements />
     </bpmn2:sequenceFlow>
     <bpmn2:intermediateThrowEvent id="event_throw_approved">
@@ -113,7 +114,7 @@
         </camunda:properties>
       </bpmn2:extensionElements>
     </bpmn2:sequenceFlow>
-    <bpmn2:sequenceFlow id="Flow_06cxzjx" sourceRef="event_throw_approved_back" targetRef="Activity_1dcslsv" />
+    <bpmn2:sequenceFlow id="Flow_06cxzjx" sourceRef="event_throw_approved_back" targetRef="task_place_on_file_reject" />
     <bpmn2:manualTask id="task_create" name="创建项目">
       <bpmn2:incoming>Flow_15zn1tu</bpmn2:incoming>
       <bpmn2:outgoing>Flow_0gyvyyo</bpmn2:outgoing>
@@ -130,7 +131,7 @@
     <bpmn2:sequenceFlow id="Flow_06kumr8" sourceRef="Activity_1dcslsv" targetRef="task_modify" />
     <bpmn2:scriptTask id="Activity_1dcslsv" name="提醒审核被驳回" scriptFormat="Javascript">
       <bpmn2:extensionElements />
-      <bpmn2:incoming>Flow_06cxzjx</bpmn2:incoming>
+      <bpmn2:incoming>Flow_0sb2avy</bpmn2:incoming>
       <bpmn2:outgoing>Flow_06kumr8</bpmn2:outgoing>
       <bpmn2:script>this.environment.services.get_handlers('owner').then((handlers) =&gt; {
 for (var i = 0; i &lt; handlers.length; i++) {
@@ -141,7 +142,7 @@ next();
     </bpmn2:scriptTask>
     <bpmn2:endEvent id="Event_end">
       <bpmn2:incoming>Flow_1ucg773</bpmn2:incoming>
-      <bpmn2:incoming>Flow_02taytj</bpmn2:incoming>
+      <bpmn2:incoming>Flow_0if8for</bpmn2:incoming>
     </bpmn2:endEvent>
     <bpmn2:sequenceFlow id="Flow_01ekshh" sourceRef="Activity_16s6waw" targetRef="Gateway_00icdjr" />
     <bpmn2:scriptTask id="Activity_16s6waw" name="检查审核人是否存在" scriptFormat="JavaScript">
@@ -161,7 +162,7 @@ this.environment.services.get_handlers('project_checker').then((handlers) =&gt;
     <bpmn2:sequenceFlow id="Flow_0a0l2t3" name="已设置审核组成员" sourceRef="Gateway_00icdjr" targetRef="Gateway_1hhlhsh">
       <bpmn2:extensionElements>
         <camunda:properties>
-          <camunda:property name="prh_phase" value="apply_create" />
+          <camunda:property name="prj_phase" value="apply_create" />
           <camunda:property name="prj_phase_name" value="申请立项中" />
         </camunda:properties>
       </bpmn2:extensionElements>
@@ -186,6 +187,22 @@ next();
     </bpmn2:inclusiveGateway>
     <bpmn2:sequenceFlow id="Flow_12nug8s" sourceRef="Gateway_1hhlhsh" targetRef="task_with_draw" />
     <bpmn2:sequenceFlow id="Flow_0jpbzcr" sourceRef="Gateway_1hhlhsh" targetRef="task_approve" />
+    <bpmn2:sequenceFlow id="Flow_0sb2avy" sourceRef="task_place_on_file_reject" targetRef="Activity_1dcslsv" />
+    <bpmn2:scriptTask id="task_place_on_file_reject" name="归档审核未通过的立项文件" scriptFormat="Javascript">
+      <bpmn2:incoming>Flow_06cxzjx</bpmn2:incoming>
+      <bpmn2:outgoing>Flow_0sb2avy</bpmn2:outgoing>
+      <bpmn2:script>let files = this.environment.output.task_request.files;
+
+this.environment.services.place_on_files(files, "立项驳回").then(()=&gt; next());</bpmn2:script>
+    </bpmn2:scriptTask>
+    <bpmn2:sequenceFlow id="Flow_0if8for" sourceRef="Activity_10dmh6u" targetRef="Event_end" />
+    <bpmn2:scriptTask id="Activity_10dmh6u" name="归档审核通过的立项文件" scriptFormat="JavaScript">
+      <bpmn2:incoming>Flow_02taytj</bpmn2:incoming>
+      <bpmn2:outgoing>Flow_0if8for</bpmn2:outgoing>
+      <bpmn2:script>let files = this.environment.output.task_request.files;
+
+this.environment.services.place_on_files(files, "立项通过", "project_set_up").then(()=&gt; next());</bpmn2:script>
+    </bpmn2:scriptTask>
   </bpmn2:process>
   <bpmn2:signal id="Signal_0hhmd7l" name="cancel_sign" />
   <bpmn2:message id="Message_0fh9t3e" name="msg_cancel" />
@@ -193,9 +210,30 @@ next();
   <bpmn2:signal id="Signal_0828f9d" name="Signal_2sf6vik" />
   <bpmndi:BPMNDiagram id="BPMNDiagram_1">
     <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="ProcessProjectApprove">
+      <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="event_start">
+        <dc:Bounds x="152" y="212" width="36" height="36" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="Activity_09wmdbt_di" bpmnElement="task_request">
+        <dc:Bounds x="380" y="190" width="100" height="80" />
+        <bpmndi:BPMNLabel />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="Activity_0ds6ym0_di" bpmnElement="task_approve">
+        <dc:Bounds x="860" y="280" width="100" height="80" />
+        <bpmndi:BPMNLabel />
+      </bpmndi:BPMNShape>
       <bpmndi:BPMNShape id="Gateway_06baw71_di" bpmnElement="gateway_approved" isMarkerVisible="true">
         <dc:Bounds x="1115" y="295" width="50" height="50" />
       </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="Activity_0fonc56_di" bpmnElement="task_with_draw">
+        <dc:Bounds x="870" y="80" width="100" height="80" />
+        <bpmndi:BPMNLabel />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="Event_0mceau4_di" bpmnElement="Event_throw_withdraw">
+        <dc:Bounds x="1032" y="102" width="36" height="36" />
+        <bpmndi:BPMNLabel>
+          <dc:Bounds x="1028" y="78" width="44" height="14" />
+        </bpmndi:BPMNLabel>
+      </bpmndi:BPMNShape>
       <bpmndi:BPMNShape id="Event_0tntlum_di" bpmnElement="event_throw_approved">
         <dc:Bounds x="1242" y="302" width="36" height="36" />
         <bpmndi:BPMNLabel>
@@ -208,50 +246,42 @@ next();
           <dc:Bounds x="1162" y="403" width="55" height="14" />
         </bpmndi:BPMNLabel>
       </bpmndi:BPMNShape>
-      <bpmndi:BPMNShape id="Activity_1fktaxz_di" bpmnElement="Activity_1dcslsv">
-        <dc:Bounds x="970" y="410" width="100" height="80" />
-      </bpmndi:BPMNShape>
-      <bpmndi:BPMNShape id="Event_1jljt88_di" bpmnElement="Event_end">
-        <dc:Bounds x="1442" y="302" width="36" height="36" />
-      </bpmndi:BPMNShape>
-      <bpmndi:BPMNShape id="Activity_0ds6ym0_di" bpmnElement="task_approve">
-        <dc:Bounds x="860" y="280" width="100" height="80" />
-        <bpmndi:BPMNLabel />
-      </bpmndi:BPMNShape>
-      <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="event_start">
-        <dc:Bounds x="152" y="212" width="36" height="36" />
-      </bpmndi:BPMNShape>
-      <bpmndi:BPMNShape id="Activity_09wmdbt_di" bpmnElement="task_request">
-        <dc:Bounds x="380" y="190" width="100" height="80" />
-        <bpmndi:BPMNLabel />
-      </bpmndi:BPMNShape>
       <bpmndi:BPMNShape id="Activity_0isolrc_di" bpmnElement="task_create">
         <dc:Bounds x="250" y="190" width="100" height="80" />
         <bpmndi:BPMNLabel />
       </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="Activity_1k7kq2t_di" bpmnElement="task_modify">
+        <dc:Bounds x="490" y="410" width="100" height="80" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="Event_1jljt88_di" bpmnElement="Event_end">
+        <dc:Bounds x="1442" y="302" width="36" height="36" />
+      </bpmndi:BPMNShape>
       <bpmndi:BPMNShape id="Activity_0kc4efu_di" bpmnElement="Activity_16s6waw">
         <dc:Bounds x="510" y="190" width="100" height="80" />
       </bpmndi:BPMNShape>
       <bpmndi:BPMNShape id="Gateway_00icdjr_di" bpmnElement="Gateway_00icdjr" isMarkerVisible="true">
         <dc:Bounds x="655" y="205" width="50" height="50" />
       </bpmndi:BPMNShape>
-      <bpmndi:BPMNShape id="Activity_1k7kq2t_di" bpmnElement="task_modify">
-        <dc:Bounds x="490" y="410" width="100" height="80" />
-      </bpmndi:BPMNShape>
       <bpmndi:BPMNShape id="Activity_0qvsyb4_di" bpmnElement="Activity_0zp61jx">
         <dc:Bounds x="630" y="280" width="100" height="80" />
       </bpmndi:BPMNShape>
       <bpmndi:BPMNShape id="Gateway_06flgld_di" bpmnElement="Gateway_1hhlhsh">
         <dc:Bounds x="765" y="155" width="50" height="50" />
       </bpmndi:BPMNShape>
-      <bpmndi:BPMNShape id="Activity_0fonc56_di" bpmnElement="task_with_draw">
-        <dc:Bounds x="870" y="80" width="100" height="80" />
+      <bpmndi:BPMNShape id="Activity_1fktaxz_di" bpmnElement="Activity_1dcslsv">
+        <dc:Bounds x="600" y="540" width="100" height="80" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="Activity_0hdmx5i_di" bpmnElement="task_place_on_file_reject">
+        <dc:Bounds x="860" y="540" width="100" height="80" />
         <bpmndi:BPMNLabel />
       </bpmndi:BPMNShape>
-      <bpmndi:BPMNShape id="Event_0mceau4_di" bpmnElement="Event_throw_withdraw">
-        <dc:Bounds x="1032" y="102" width="36" height="36" />
+      <bpmndi:BPMNShape id="Activity_04s4p12_di" bpmnElement="Activity_10dmh6u">
+        <dc:Bounds x="1310" y="280" width="100" height="80" />
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="Event_06bs6js_di" bpmnElement="event_catch_approved">
+        <dc:Bounds x="912" y="142" width="36" height="36" />
         <bpmndi:BPMNLabel>
-          <dc:Bounds x="1028" y="78" width="44" height="14" />
+          <dc:Bounds x="943" y="173" width="33" height="14" />
         </bpmndi:BPMNLabel>
       </bpmndi:BPMNShape>
       <bpmndi:BPMNShape id="Event_0s1xb2l_di" bpmnElement="Event_catch_withdraw">
@@ -260,12 +290,14 @@ next();
           <dc:Bounds x="949" y="378" width="22" height="14" />
         </bpmndi:BPMNLabel>
       </bpmndi:BPMNShape>
-      <bpmndi:BPMNShape id="Event_06bs6js_di" bpmnElement="event_catch_approved">
-        <dc:Bounds x="912" y="142" width="36" height="36" />
-        <bpmndi:BPMNLabel>
-          <dc:Bounds x="943" y="173" width="33" height="14" />
-        </bpmndi:BPMNLabel>
-      </bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge id="Flow_15zn1tu_di" bpmnElement="Flow_15zn1tu">
+        <di:waypoint x="188" y="230" />
+        <di:waypoint x="250" y="230" />
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge id="Flow_1rg4oob_di" bpmnElement="Flow_1rg4oob">
+        <di:waypoint x="480" y="230" />
+        <di:waypoint x="510" y="230" />
+      </bpmndi:BPMNEdge>
       <bpmndi:BPMNEdge id="Flow_193tusd_di" bpmnElement="Flow_193tusd">
         <di:waypoint x="960" y="320" />
         <di:waypoint x="1115" y="320" />
@@ -284,18 +316,13 @@ next();
           <dc:Bounds x="1144" y="353" width="22" height="14" />
         </bpmndi:BPMNLabel>
       </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge id="Flow_0tj9nd6_di" bpmnElement="Flow_0tj9nd6">
+        <di:waypoint x="970" y="120" />
+        <di:waypoint x="1032" y="120" />
+      </bpmndi:BPMNEdge>
       <bpmndi:BPMNEdge id="Flow_02taytj_di" bpmnElement="Flow_02taytj">
         <di:waypoint x="1278" y="320" />
-        <di:waypoint x="1442" y="320" />
-      </bpmndi:BPMNEdge>
-      <bpmndi:BPMNEdge id="Flow_06cxzjx_di" bpmnElement="Flow_06cxzjx">
-        <di:waypoint x="1140" y="428" />
-        <di:waypoint x="1140" y="450" />
-        <di:waypoint x="1070" y="450" />
-      </bpmndi:BPMNEdge>
-      <bpmndi:BPMNEdge id="Flow_06kumr8_di" bpmnElement="Flow_06kumr8">
-        <di:waypoint x="970" y="450" />
-        <di:waypoint x="590" y="450" />
+        <di:waypoint x="1310" y="320" />
       </bpmndi:BPMNEdge>
       <bpmndi:BPMNEdge id="Flow_1ucg773_di" bpmnElement="Flow_1ucg773">
         <di:waypoint x="930" y="178" />
@@ -303,28 +330,30 @@ next();
         <di:waypoint x="1460" y="240" />
         <di:waypoint x="1460" y="302" />
       </bpmndi:BPMNEdge>
-      <bpmndi:BPMNEdge id="Flow_0jpbzcr_di" bpmnElement="Flow_0jpbzcr">
-        <di:waypoint x="790" y="205" />
-        <di:waypoint x="790" y="320" />
-        <di:waypoint x="860" y="320" />
-      </bpmndi:BPMNEdge>
-      <bpmndi:BPMNEdge id="Flow_15zn1tu_di" bpmnElement="Flow_15zn1tu">
-        <di:waypoint x="188" y="230" />
-        <di:waypoint x="250" y="230" />
-      </bpmndi:BPMNEdge>
       <bpmndi:BPMNEdge id="Flow_1t45qnv_di" bpmnElement="Flow_1t45qnv">
         <di:waypoint x="540" y="410" />
         <di:waypoint x="540" y="385" />
         <di:waypoint x="450" y="385" />
         <di:waypoint x="450" y="270" />
       </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge id="Flow_0nzbvpg_di" bpmnElement="Flow_0nzbvpg">
+        <di:waypoint x="940" y="378" />
+        <di:waypoint x="940" y="440" />
+        <di:waypoint x="590" y="440" />
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge id="Flow_06cxzjx_di" bpmnElement="Flow_06cxzjx">
+        <di:waypoint x="1140" y="428" />
+        <di:waypoint x="1140" y="580" />
+        <di:waypoint x="960" y="580" />
+      </bpmndi:BPMNEdge>
       <bpmndi:BPMNEdge id="Flow_0gyvyyo_di" bpmnElement="Flow_0gyvyyo">
         <di:waypoint x="350" y="230" />
         <di:waypoint x="380" y="230" />
       </bpmndi:BPMNEdge>
-      <bpmndi:BPMNEdge id="Flow_1rg4oob_di" bpmnElement="Flow_1rg4oob">
-        <di:waypoint x="480" y="230" />
-        <di:waypoint x="510" y="230" />
+      <bpmndi:BPMNEdge id="Flow_06kumr8_di" bpmnElement="Flow_06kumr8">
+        <di:waypoint x="600" y="580" />
+        <di:waypoint x="530" y="580" />
+        <di:waypoint x="530" y="490" />
       </bpmndi:BPMNEdge>
       <bpmndi:BPMNEdge id="Flow_01ekshh_di" bpmnElement="Flow_01ekshh">
         <di:waypoint x="610" y="230" />
@@ -342,11 +371,6 @@ next();
         <di:waypoint x="680" y="255" />
         <di:waypoint x="680" y="280" />
       </bpmndi:BPMNEdge>
-      <bpmndi:BPMNEdge id="Flow_0nzbvpg_di" bpmnElement="Flow_0nzbvpg">
-        <di:waypoint x="940" y="378" />
-        <di:waypoint x="940" y="440" />
-        <di:waypoint x="590" y="440" />
-      </bpmndi:BPMNEdge>
       <bpmndi:BPMNEdge id="Flow_01n3edp_di" bpmnElement="Flow_01n3edp">
         <di:waypoint x="680" y="360" />
         <di:waypoint x="680" y="400" />
@@ -359,9 +383,18 @@ next();
         <di:waypoint x="790" y="110" />
         <di:waypoint x="870" y="110" />
       </bpmndi:BPMNEdge>
-      <bpmndi:BPMNEdge id="Flow_0tj9nd6_di" bpmnElement="Flow_0tj9nd6">
-        <di:waypoint x="970" y="120" />
-        <di:waypoint x="1032" y="120" />
+      <bpmndi:BPMNEdge id="Flow_0jpbzcr_di" bpmnElement="Flow_0jpbzcr">
+        <di:waypoint x="790" y="205" />
+        <di:waypoint x="790" y="320" />
+        <di:waypoint x="860" y="320" />
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge id="Flow_0sb2avy_di" bpmnElement="Flow_0sb2avy">
+        <di:waypoint x="860" y="580" />
+        <di:waypoint x="700" y="580" />
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge id="Flow_0if8for_di" bpmnElement="Flow_0if8for">
+        <di:waypoint x="1410" y="320" />
+        <di:waypoint x="1442" y="320" />
       </bpmndi:BPMNEdge>
     </bpmndi:BPMNPlane>
   </bpmndi:BPMNDiagram>

+ 1 - 0
pmr-biz-manager/package.json

@@ -40,6 +40,7 @@
     "@types/path-to-regexp": "^1.7.0",
     "@types/pg": "^7.14.8",
     "@types/redis": "^2.8.28",
+    "@types/sequelize": "^4.28.20",
     "@types/uuid": "^8.3.0",
     "ajv": "^7.0.3",
     "axios": "^0.21.1",

+ 53 - 9
pmr-biz-manager/src/app.ts

@@ -16,13 +16,57 @@ import {BpmnCase} from "@core-models/BpmnCase";
 import {bpmn_flow_on_end} from "@src/utils/bpmn_work_helper";
 import {OutcomeStatus} from "@src/utils/define";
 import {prj_stat_monthly, task_delay_rate_daily, task_stat_daily} from "@src/utils/prj_stat";
-const { report } = require('node:process');
+import {evaluate} from "@util/Executor";
 
 const schedule = require('node-schedule');
 
 dayjs.extend(utc);
 dayjs.extend(timezone);
 dayjs.tz.guess();
+
+let result = evaluate(JSON.stringify({
+    "type": "object",
+    "title": "条件",
+    "required": [
+        "pass"
+    ],
+    "ui:order": [
+        "pass",
+        "opinion"
+    ],
+    "properties": {
+        "memo": {
+            "type": "string",
+            "title": "申请理由",
+            "format": "textarea",
+            "default": "${environment.output.task_request.memo}",
+            "readonly": true
+        },
+        "pass": {
+            "type": "boolean",
+            "title": "是否通过审核?",
+            "default": false
+        },
+        "opinion": {
+            "type": "string",
+            "title": "审核意见",
+            "format": "textarea",
+            "default": "",
+            "minRows": 6,
+            "minLength": 10,
+            "ui:options": {
+                "rows": 10,
+                "type": "textarea"
+            }
+        }
+    }
+}), {global: {environment: {output: {
+    task_request: {
+        memo: "memo text"
+    }
+            }}}})
+console.log(result);
+
 // dayjs.tz("2014-06-01 12:00", "Asia/Shanghai")
 new ServiceApp().start(new MyRouter('@src/routes', guards), Models).then(async () => {
     // await bpmn_flow_on_end('63dcaf1fe8000000', 'plan_alter', '63daded44f000000', 'admin');
@@ -44,9 +88,9 @@ new ServiceApp().start(new MyRouter('@src/routes', guards), Models).then(async (
                                 case 'outcome':
                                     let outcome_id = path[3];
                                     if (outcome_id) {
-                                        let [count, result] = await PrjTaskOutcome.update({status: OutcomeStatus.Uploaded, uploaded: true}, {
+                                        await PrjTaskOutcome.update({status: OutcomeStatus.Uploaded, uploaded: true}, {
                                             where: {id: outcome_id},
-                                            returning: ['id', 'task_id'],
+                                            returning: false,
                                             transaction: t
                                         });
                                     }
@@ -58,7 +102,7 @@ new ServiceApp().start(new MyRouter('@src/routes', guards), Models).then(async (
                                 uploaded_at: dayjs().format('YYYY-MM-DD HH:mm:ss'),
                                 uploaded: true,
                                 size: record.size
-                            }, {where: {id: record.object.key}, transaction: t})
+                            }, {where: {id: record.object}, transaction: t, returning: false})
                             break;
                         case 'contract':
                             let contract_id = path[1];
@@ -90,11 +134,11 @@ new ServiceApp().start(new MyRouter('@src/routes', guards), Models).then(async (
 
                             break;
                     }
-                    await PrjFile.update({
-                        uploaded_at: dayjs(),
-                        uploaded: true,
-                        size: record.size
-                    }, {where: {id: record.object}})
+                    // await PrjFile.update({
+                    //     uploaded_at: dayjs(),
+                    //     uploaded: true,
+                    //     size: record.size
+                    // }, {where: {id: record.object}})
                     await t.commit();
                 } catch (e) {
                     await t.rollback();

+ 53 - 14
pmr-biz-manager/src/bpmn/flow_engine.ts

@@ -6,7 +6,7 @@ import {AcsUserInfo} from "@core-models/AcsUserInfo";
 import {PrjInfo} from "@core-models/PrjInfo";
 import {PrjMembers} from "@core-models/PrjMembers";
 import {AcsUserRole} from "@core-models/AcsUserRole";
-import {Op, QueryTypes} from "sequelize";
+import {QueryTypes} from "sequelize";
 import {AcsDomain} from "@core-models/AcsDomain";
 import {AcsUserDomain} from "@core-models/AcsUserDomain";
 import {BpmnCase} from "@core-models/BpmnCase";
@@ -16,11 +16,11 @@ import {BpmnForm} from "@core-models/BpmnForm";
 
 const camunda = require('camunda-bpmn-moddle/resources/camunda.json');
 import {resolveExpression} from '@aircall/expression-parser';
-import {PrjTaskOutcome} from "@core-models/PrjTaskOutcome";
 import {PrjPlanTask} from "@core-models/PrjPlanTask";
-import {Checker} from "@src/routes/api/prj/plan/set_check_flow";
 import {IChecker} from "@src/utils/define";
 import {PrjLogger} from "@src/utils/prj_logger";
+import {PrjFile} from "@core-models/PrjFile";
+import {evaluate} from "@util/Executor";
 
 export interface IHandler {
     id: string;         // 处理人账号id
@@ -73,13 +73,13 @@ export class FlowEngine extends EventEmitter {
         listener.on('flow.take', this.on_flow_take);
         if (state) {
             this.engine = this.engine.recover(state);
-            this.execution = await this.engine.resume({listener}, async (err, execution) => {
+            this.execution = await this.engine.resume({listener}, async (err, _execution) => {
                 if (err) console.log(err);
                 console.log('Execution completed with id', this.id);
                 await this.complete();
             });
         } else {
-            this.execution = await this.engine.execute({listener}, async (err, execution) => {
+            this.execution = await this.engine.execute({listener}, async (err, _execution) => {
                 if (err) console.log(err);
                 console.log('Execution completed with id', this.id);
                 await this.complete();
@@ -159,6 +159,29 @@ export class FlowEngine extends EventEmitter {
                         where: {id: this._prj_id},
                         returning: false
                     })
+                },
+                // 将流程中的文件进行归档
+                place_on_files: async (files: string | undefined, suffix: string, category_id?: string, _memo?: string) => {
+                    Logger.trace(files);
+                    if (!files) return;
+                    for (let file_id of files) {
+                        let file = await PrjFile.findOne({
+                            where: {id: file_id}
+                        });
+                        if (!file || !file.uploaded) return;
+                        let filename = file.filename;
+                        // 分离文件的文件名和后缀到一个数组中
+                        let file_parts = filename.split('.');
+                        // 将文档时间和附加的suffix后缀插入的文件名后
+                        let date = dayjs(file.uploaded_at? file.uploaded_at : new Date()).format('YYYY-MM-DD');
+                        file_parts.splice(file_parts.length > 1 ? file_parts.length - 1 : file_parts.length, 0, `${suffix}(${date})`);
+                        filename = file_parts.join('.');
+                        await PrjFile.update({
+                            filename: filename,
+                            category_id:  category_id
+                        }, {where: {id: file_id}});
+                    }
+
                 }
             },
             variables: {
@@ -171,7 +194,7 @@ export class FlowEngine extends EventEmitter {
                 // @ts-ignore
                 // resolveExpression,
                 // @ts-ignore
-                bpmnActivityHandler: (bpmnActivity: any, context) => {
+                bpmnActivityHandler: (bpmnActivity: any, _context) => {
                     // if (bpmnActivity.type !== 'bpmn:UserTask' && bpmnActivity.type !== 'bpmn:ManualTask') return;
                     if (bpmnActivity.type === 'bpmn:Process') return;
                     bpmnActivity.on('enter', this.on_enter);
@@ -199,7 +222,7 @@ export class FlowEngine extends EventEmitter {
                             //             Logger.trace(`activity on execute.iteration.completed ${elementApi.id}`);
                             //         },{consumerTag: `format-on-execute.iteration.completed_${IdGen.id()}`});
                             // @ts-ignore
-                            bpmnActivity.on('enter', async (elementApi, engineApi) => {
+                            bpmnActivity.on('enter', async (_elementApi, _engineApi) => {
                                 bpmnActivity.broker.publish('format', 'run.format.start', {endRoutingKey: 'run.format.complete'});
                                 try {
                                     // await FlowActivityHandler.onStart(elementApi, engineApi);
@@ -264,7 +287,7 @@ export class FlowEngine extends EventEmitter {
         });
     }
 
-    on_loop_task_completed = async (routingKey, msg, consumerCount) => {
+    on_loop_task_completed = async (routingKey, msg, _consumerCount) => {
         let activity = this.execution.getActivityById(msg.content.id);
         activity.broker.subscribeOnce('execution', 'execute.iteration.completed', this.on_loop_task_completed);
         // Logger.info(`execute.iteration.completed, count: `);
@@ -308,9 +331,11 @@ export class FlowEngine extends EventEmitter {
         }
         // 非用户任务,不作处理
         if (activity.type !== 'bpmn:UserTask') return;
+
+        // 用户任务,获取任务执行人
         let handler: IHandler | undefined;
         if (activity.content?.handlers) {
-            if (activity.content.isMultiInstance === true) {
+            if (activity.content.isMultiInstance) {
                 handler = activity.content.handlers[activity.content.index ? activity.content.index : 0];
 
             } else {
@@ -322,17 +347,19 @@ export class FlowEngine extends EventEmitter {
             return;
         }
 
+        // 任务类型,0=待阅,1=待办
         let process_type = 0;
+        // 是否在我的工作项中显示
         let show_in_my_works = false;
+        // 工作项跳转类型,表示处理工作项时,要跳转到哪个页面
         let target_type;
+        // 与此工作项相关的其它参数
         let params = {};
         if (activity.content.input) {
             process_type = activity.content.input.process_type ? activity.content.input.process_type : 0;
             show_in_my_works = activity.content.input.show_in_my_works ? activity.content.input.show_in_my_works : false;
             target_type = activity.content.input.target_type;
-            // delete activity.content.input.process_type;
-            // delete activity.content.input.show_in_my_works;
-            // delete activity.content.input.target_type;
+            // 过滤掉通用参数
             let filter = ['process_type', 'show_in_my_works', 'target_type']
             params = Object.keys(activity.content.input).reduce((obj, key) => {
                 if (!filter.includes(key)) {
@@ -341,6 +368,8 @@ export class FlowEngine extends EventEmitter {
                 return obj;
             }, {});
         }
+        console.log(activity.content.form);
+        console.log({global: {environment: activity.environment}});
         await BpmnWork.findOrCreate({
             where: {id: activity.executionId},
             defaults: {
@@ -358,7 +387,13 @@ export class FlowEngine extends EventEmitter {
                 show_in_my_works: show_in_my_works,
                 target_type: target_type,
                 params: params,
-                form: activity.content.form ? activity.content.form : null
+                form: activity.content.form ?
+                    evaluate(JSON.stringify(activity.content.form), {
+                        global: {
+                            ...activity
+                        }
+                    })
+                    : null
             }
         });
         FlowEngine.execution_engine_map.set(activity.executionId, this);
@@ -367,6 +402,8 @@ export class FlowEngine extends EventEmitter {
 
     on_enter = async (activity) => {
         console.log(`##### log state immediately in enter ${activity.id} ${activity.executionId}`);
+        console.log(activity.environment.output);
+        // console.log(activity.environment.output.task_request);
     }
 
     on_end = async (activity) => {
@@ -376,7 +413,7 @@ export class FlowEngine extends EventEmitter {
         if (activity.content.output) {
             activity.environment.output[activity.id] = activity.content.output;
 
-            if (activity.content.isMultiInstance === true) {
+            if (activity.content.isMultiInstance) {
                 for (let key in activity.content.output[activity.content.index]) {
                     if (activity.content.output[activity.content.index].hasOwnProperty(key)) { // 确保属性是对象自身的而不是从原型链继承的
                         if (key === 'id' || key === 'executionId' || key === 'user_id') continue;
@@ -453,6 +490,8 @@ export class FlowEngine extends EventEmitter {
                                 })
                                 break;
                             case 'task_status':
+                                console.log(this);
+                                Logger.trace(`flow_take task_id ${this._task_id}`)
                                 await PrjPlanTask.update({status: value.value}, {
                                     where: {id: this._task_id}, returning: false
                                 });

+ 3 - 8
pmr-biz-manager/src/routes/api/prj/doc/get_list.ts

@@ -1,15 +1,10 @@
 import {IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined";
 import {Resp} from "@util/Resp";
-import {Op, QueryTypes, WhereOptions} from "sequelize";
+import {WhereOptions} from "sequelize";
 import DataCURD, {ISQLReplacements} from "@core/DataCURD";
 import {BizCustomer} from "@core-models/BizCustomer";
-import {BizCustomerLevel} from "@core-models/BizCustomerLevel";
-import {BizCustomerIndustry} from "@core-models/BizCustomerIndustry";
 import {AcsUserInfo} from "@core-models/AcsUserInfo";
-import {AcsDomain} from "@core-models/AcsDomain";
 import {PrjInfo} from "@core-models/PrjInfo";
-import {BizContractInfo} from "@core-models/BizContractInfo";
-import {PrjPhaseDefine} from "@core-models/PrjPhaseDefine";
 import {PrjFile} from "@core-models/PrjFile";
 import {PrjFileCategory} from "@core-models/PrjFileCategory";
 
@@ -53,7 +48,7 @@ interface IData {
 }
 
 
-function get_list(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<WhereOptions> {
+function get_list(json: IRequest, _params: IMethodParams, _cached_data: ICachedData): Promise<WhereOptions> {
     return new Promise<WhereOptions>(async (resolve, reject) => {
         try {
             let data = <IData>json.data;
@@ -108,7 +103,7 @@ function get_list(json: IRequest, params: IMethodParams, cached_data: ICachedDat
 
             count_sql += condition;
             select_sql += condition;
-            select_sql += ' order by prj_file.filename ';
+            select_sql += ' order by prj_file.uploaded_at desc, prj_file.filename ';
             let result = await DataCURD.get_page_list({
                 sequelize: BizCustomer.sequelize!,
                 page_no: data.page_no,

+ 51 - 4
pmr-biz-manager/src/routes/api/prj/info/add.ts

@@ -7,7 +7,7 @@ import dayjs from "dayjs";
 import {PrjInfo} from "@core-models/PrjInfo";
 import {PrjMembers} from "@core-models/PrjMembers";
 import {BpmnModel} from "@core-models/BpmnModel";
-import {FlowEngine} from "@src/bpmn/flow_engine";
+import {FlowEngine, IHandler} from "@src/bpmn/flow_engine";
 import {BpmnCase} from "@core-models/BpmnCase";
 import {bpmn_flow_on_end} from "@src/utils/bpmn_work_helper";
 import {PrjLogger} from "@src/utils/prj_logger";
@@ -17,6 +17,10 @@ interface IData {
      * 商务负责人,商务负责人id
      */
     bizman_id: string;
+    /**
+     * 审核组成员列表,必须有至少一个
+     */
+    checker_ids: string[];
     /**
      * 合同id,合同id
      */
@@ -28,7 +32,7 @@ interface IData {
     /**
      * 预计交付时间,预计交付时间(YYYY-MM-DD)
      */
-    deliver_at?: string;
+    deliver_at: string;
     /**
      * 项目简介,项目简介
      */
@@ -57,11 +61,22 @@ interface IData {
 
 
 
+
 function add(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<WhereOptions> {
     return new Promise<WhereOptions>(async (resolve, reject) => {
         let t = await PrjInfo.sequelize!.transaction();
         try {
             let data = <IData>json.data;
+            if (data.checker_ids.length === 0)
+                throw Resp.gen_err(Resp.ParamsError, '审核组成员列表不能为空');
+            let checkers: IHandler[] = [];
+            for (let checker of data.checker_ids) {
+                checkers.push({
+                    id: checker,
+                    due_day: 1
+                })
+            }
+
             let id = IdGen.id();
             let flow_case_id = IdGen.id();
             if (data.deliver_at) {
@@ -72,7 +87,9 @@ function add(json: IRequest, params: IMethodParams, cached_data: ICachedData): P
                 phase_id: 'new',
                 created_at: dayjs().format('YYYY-MM-DD HH:mm:ss'),
                 flow_case_id: flow_case_id,
-                ...data
+                ...data,
+                checker_ids: undefined,
+                checkers: checkers
             }
             value = DataCURD.filter_null(value);
             // 创建项目
@@ -172,15 +189,38 @@ const v1_0: IApiProcessor = {
                         "type": "string",
                         "title": "合同id",
                         "description": "合同id"
+                    },
+                    "checker_ids": {
+                        "type": "array",
+                        "items": {
+                            "type": "string"
+                        },
+                        "title": "审核组成员列表",
+                        "description": "必须有至少一个"
                     }
                 },
+                "x-apifox-orders": [
+                    "name",
+                    "customer_id",
+                    "region_id",
+                    "type_id",
+                    "contract_id",
+                    "template_id",
+                    "deliver_at",
+                    "leader_id",
+                    "bizman_id",
+                    "checker_ids",
+                    "intro"
+                ],
                 "title": "请求参数内容",
                 "required": [
                     "name",
                     "region_id",
                     "leader_id",
                     "bizman_id",
-                    "type_id"
+                    "type_id",
+                    "deliver_at",
+                    "checker_ids"
                 ]
             },
             "ver": {
@@ -205,6 +245,13 @@ const v1_0: IApiProcessor = {
                 "description": "平台登录后分配的token"
             }
         },
+        "x-apifox-orders": [
+            "ver",
+            "app_id",
+            "timestamp",
+            "token",
+            "data"
+        ],
         "required": [
             "data",
             "ver",

+ 4 - 4
pmr-biz-manager/src/routes/api/prj/info/overview.ts

@@ -9,7 +9,7 @@ import {BizCustomerIndustry} from "@core-models/BizCustomerIndustry";
 import {BizCustomerLevel} from "@core-models/BizCustomerLevel";
 import {Resp} from "@util/Resp";
 import {PrjMembers} from "@core-models/PrjMembers";
-import DataCURD, {ISQLReplacements} from "@core/DataCURD";
+import DataCURD from "@core/DataCURD";
 import {AcsRole} from "@core-models/AcsRole";
 import {BpmnWork} from "@core-models/BpmnWork";
 import {BpmnCase} from "@core-models/BpmnCase";
@@ -52,10 +52,10 @@ async function overview(json: IRequest, params: IMethodParams, cached_data: ICac
             customer_region.id as customer_region_id, customer_region.name as customer_region_name,
             customer_bizman.id as customer_bizman_id, customer_bizman.name as customer_bizman_name,
             
-            TO_CHAR(prj.created_at, 'yyyy-MM-dd HH24' || ':' || 'MI' || ':' || 'ss') as created_at,
+            TO_CHAR(prj.created_at, 'yyyy-MM-dd') as created_at,
             TO_CHAR(prj.deliver_at, 'yyyy-MM-dd') as deliver_at,
             TO_CHAR(prj.updated_at, 'yyyy-MM-dd HH24' || ':' || 'MI' || ':' || 'ss') as updated_at,
-            TO_CHAR(prj.started_at, 'yyyy-MM-dd HH24' || ':' || 'MI' || ':' || 'ss') as started_at,
+            TO_CHAR(prj.started_at, 'yyyy-MM-dd') as started_at,
             -- case when NULLIF(array_agg(work.id), '{NULL}') is null then '{}' else
   
            array_agg(
@@ -139,7 +139,7 @@ async function overview(json: IRequest, params: IMethodParams, cached_data: ICac
                 owner: result.leader_id
             }, bpmn_flow_on_end);
             await flow.run();
-            await PrjInfo.update({flow_case_id: flow.id}, {where: {id: data.id}, transaction: t});
+            await PrjInfo.update({flow_case_id: flow.id}, {where: {id: data.id}, transaction: t, returning: false});
             await BpmnCase.create({
                 id: flow.id,
                 model_id: bpmn.id,

+ 3 - 2
pmr-biz-manager/src/routes/api/prj/info/remove.ts

@@ -6,8 +6,8 @@ import {PrjPhaseDefine} from "@core-models/PrjPhaseDefine";
 import {BpmnCase} from "@core-models/BpmnCase";
 import {BpmnWork} from "@core-models/BpmnWork";
 import {FlowEngine} from "@src/bpmn/flow_engine";
-import {PrjLogger} from "@src/utils/prj_logger";
 import {is_project_modifiable} from "@src/utils/prj_premission_helper";
+import {PrjFile} from "@core-models/PrjFile";
 
 interface IData {
     id: string;//项目id
@@ -33,7 +33,7 @@ export function statusGuard(json: IRequest, cached_data: ICachedData): Promise<v
 
 }
 
-async function remove(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<any> {
+async function remove(json: IRequest, _params: IMethodParams, _cached_data: ICachedData): Promise<any> {
     let data = <IData>json.data;
     let t = await PrjInfo.sequelize!.transaction();
     try {
@@ -45,6 +45,7 @@ async function remove(json: IRequest, params: IMethodParams, cached_data: ICache
         await PrjMembers.destroy({where: {prj_id: data.id}, transaction: t});
         await BpmnWork.destroy({where: {prj_id: data.id}, transaction: t});
         await BpmnCase.destroy({where: {prj_id: data.id}, transaction: t});
+        await PrjFile.destroy({where: {prj_id: data.id}, transaction: t});
         await PrjInfo.destroy({where: {id: data.id}, transaction: t});
         await t.commit();
     } catch (e) {

+ 12 - 2
pmr-biz-manager/src/routes/api/prj/info/set_checkers.ts

@@ -5,6 +5,7 @@ import {PrjPhaseDefine} from "@core-models/PrjPhaseDefine";
 import {IHandler} from "@src/bpmn/flow_engine";
 import {PrjLogger} from "../../../../utils/prj_logger";
 import {is_project_modifiable, is_project_privileged_account} from "../../../../utils/prj_premission_helper";
+import {Transaction} from "sequelize";
 
 interface IData {
     /**
@@ -24,6 +25,8 @@ function statusGuard(json: IRequest, cached_data: ICachedData): Promise<void> {
         let data = <IData>json.data;
         if (!user) return reject(Resp.gen_err(Resp.Forbidden));
         if (user === ADMINISTRATOR) return resolve();
+        if (data.checkers.length === 0)
+            return reject(Resp.gen_err(Resp.ParamsError, '审核人不能为空'));
 
         let prj_info = await PrjInfo.findOne({where: {id: data.id}, raw: true});
         if (!prj_info) return reject(Resp.gen_err(Resp.ResourceNotFound));
@@ -34,6 +37,13 @@ function statusGuard(json: IRequest, cached_data: ICachedData): Promise<void> {
             return reject(Resp.gen_err(Resp.InvalidFlow, '当前项目阶段不允许修改审核人,请确认您是特权人员。。'));
         if (!await is_project_modifiable(cached_data.user_id, prj_info.id))
             return reject(Resp.gen_err(Resp.Forbidden, '您没有权限修改该项目,请确认您是项目负责人或特权人员。'));
+        if (phase.id === 'apply_create' || phase.id === 'overview_create')
+            return reject(Resp.gen_err(Resp.InvalidFlow, '项目立项审核阶段不允许修改审核人。'));
+        if (phase.id === 'apply_plan' || phase.id === 'overview_plan')
+            return reject(Resp.gen_err(Resp.InvalidFlow, '项目计划审核阶段不允许修改审核人。'));
+        if (phase.id === 'apply_plan_alt' || phase.id === 'review_plan_alt')
+            return reject(Resp.gen_err(Resp.InvalidFlow, '项目计划变更审核阶段不允许修改审核人。'));
+
         resolve();
     });
 }
@@ -45,13 +55,13 @@ function set_checkers(json: IRequest, params: IMethodParams, cached_data: ICache
         try {
 
             await PrjInfo.update({checkers: data.checkers},
-                {where: {id: data.id}, transaction: t});
+                {where: {id: data.id}, transaction: t, returning: false});
             await PrjLogger.updated({
                 creator_id: cached_data.user_id,
                 creator_name: cached_data.user_name,
                 content: '项目审核组成员',
                 prj_id: data.id,
-                t
+                t: t as Transaction
             });
             await t.commit();
             resolve({});

+ 16 - 17
pmr-biz-manager/src/routes/api/prj/info/stat.ts

@@ -1,18 +1,17 @@
 import {IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined";
 import {Resp} from "@util/Resp";
-import {Op, QueryTypes, WhereOptions} from "sequelize";
-import DataCURD from "@core/DataCURD";
-import {BizCustomer} from "@core-models/BizCustomer";
-import {BizCustomerLevel} from "@core-models/BizCustomerLevel";
+import {Op} from "sequelize";
 import {IStatInfo} from "@src/utils/define";
 import {PrjInfo} from "@core-models/PrjInfo";
 
-function stat(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<any> {
+function stat(_json: IRequest, _params: IMethodParams, _cached_data: ICachedData): Promise<any> {
     return new Promise<any>(async (resolve, reject) => {
         try {
             let result = new Array<IStatInfo>;
             /// 获取项目总数
-            let count = await PrjInfo.count({});
+            let count = await PrjInfo.count({
+
+            });
             result.push({
                 name: '项目总数',
                 value: count,
@@ -20,12 +19,12 @@ function stat(json: IRequest, params: IMethodParams, cached_data: ICachedData):
             })
             /// 获取本年度项目总数
             let year_count = await PrjInfo.count({
-               where: {
-                   created_at: {
-                       [Op.gte]: new Date(new Date().getFullYear() + '-01-01 00:00:00'),
-                       [Op.lte]: new Date(new Date().getFullYear() + '-12-31 23:59:59')
-                   }
-               }
+                where: {
+                    created_at: {
+                        [Op.gte]: new Date(new Date().getFullYear() + '-01-01 00:00:00'),
+                        [Op.lte]: new Date(new Date().getFullYear() + '-12-31 23:59:59')
+                    }
+                }
             });
             result.push({
                 name: '当年累计',
@@ -34,11 +33,11 @@ function stat(json: IRequest, params: IMethodParams, cached_data: ICachedData):
             })
             /// 获取正在进行中的项目总数,除去状态为'done'和状态为'deprecated'这两种
             let doing_count = await PrjInfo.count({
-               where: {
-                   phase_id: {
-                       [Op.notIn]: ['done', 'deprecated']
-                   }
-               }
+                where: {
+                    phase_id: {
+                        [Op.notIn]: ['done', 'deprecated']
+                    }
+                }
             });
             result.push({
                 name: '进行中',

+ 4 - 3
pmr-biz-manager/src/routes/api/prj/plan/set_check_flow.ts

@@ -7,6 +7,7 @@ import {PrjPlanTask} from "@core-models/PrjPlanTask";
 import {ChangeMarker, TaskStatus} from "@src/utils/define";
 import {start_plan_alt_flow} from "@src/utils/plan_task_helper";
 import {is_project_task_modifiable} from "@src/utils/prj_premission_helper";
+import {Transaction} from "sequelize";
 
 interface IData {
     /**
@@ -83,13 +84,13 @@ function set_check_flow(json: IRequest, params: IMethodParams, cached_data: ICac
                 await PrjPlanTaskDraft.update({
                     checkers: data.checkers,
                     change_marker: ChangeMarker.Changed
-                }, {where: {id: data.task_id}, transaction: t});
+                }, {where: {id: data.task_id}, transaction: t, returning: false});
                 let task = await PrjPlanTaskDraft.findOne({where: {id: data.task_id}, transaction: t, raw: true});
                 if (task)
-                    await start_plan_alt_flow(task.prj_id, cached_data.user_id, t);
+                    await start_plan_alt_flow(task.prj_id, cached_data.user_id, t as Transaction);
             } else {
                 await PrjPlanTask.update({checkers: data.checkers},
-                    {where: {id: data.task_id}});
+                    {where: {id: data.task_id}, returning: false});
             }
             await t.commit();
             resolve({});

+ 2 - 1
pmr-biz-manager/src/routes/api/prj/plan/set_progress.ts

@@ -7,6 +7,7 @@ import {update_parent_task_info} from "@src/utils/plan_task_helper";
 import {TaskStatus} from "@src/utils/define";
 import {create_task_approve_flow_if_not_exists} from "@src/utils/bpmn_work_helper";
 import {is_project_task_progress_modifiable} from "@src/utils/prj_premission_helper";
+import {Transaction} from "sequelize";
 
 interface IData {
     progress: number;
@@ -73,7 +74,7 @@ function set_progress(json: IRequest, params: IMethodParams, cached_data: ICache
             }
 
             // 如果条件正确的话,开启任务审核流程,第一个工作项是"提交审核"
-            await create_task_approve_flow_if_not_exists(task.prj_id, cached_data.user_id, data.task_id, t);
+            await create_task_approve_flow_if_not_exists(task.prj_id, cached_data.user_id, data.task_id, t as Transaction);
 
             let result = await PrjPlanTask.update({progress: data.progress, status: status}, {
                 where: {id: data.task_id},

+ 3 - 1
pmr-biz-manager/src/routes/api/prj/work/done_stat.ts

@@ -1,7 +1,7 @@
 /// 本人已完成工作项统计信息接口
 import {IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined";
 import {Resp} from "@util/Resp";
-import {Op, QueryTypes, WhereOptions} from "sequelize";
+import {Op} from "sequelize";
 import {IStatInfo} from "@src/utils/define";
 import {BpmnWork} from "@core-models/BpmnWork";
 
@@ -14,6 +14,7 @@ function stat(json: IRequest, params: IMethodParams, cached_data: ICachedData):
             let done_count = await BpmnWork.count({
                 where: {
                     assigned_to: cached_data.user_id,
+                    show_in_my_works: true,
                     status: 2
                 }
             });
@@ -28,6 +29,7 @@ function stat(json: IRequest, params: IMethodParams, cached_data: ICachedData):
                 where: {
                     assigned_to: cached_data.user_id,
                     status: 2,
+                    show_in_my_works: true,
                     started_at: {
                         [Op.gte]: new Date(new Date().getFullYear() + '-01-01 00:00:00'),
                         [Op.lte]: new Date(new Date().getFullYear() + '-12-31 23:59:59')

+ 94 - 0
pmr-biz-manager/src/routes/api/prj/work/download_file.ts

@@ -0,0 +1,94 @@
+import {IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined";
+import {Resp} from "@util/Resp";
+import {Oss} from "@util/Oss";
+import {PrjFile} from "@core-models/PrjFile";
+
+interface IData {
+    /**
+     * 文件object name
+     */
+    id: string;
+}
+
+function download(json: IRequest, _params: IMethodParams, _cached_data: ICachedData): Promise<any> {
+    return new Promise(async (resolve, reject) => {
+        try {
+            let data = <IData>json.data;
+
+            let prj_file = await PrjFile.findOne({where: {id: data.id}, raw: true});
+            if (!prj_file) throw Resp.gen_err(Resp.ResourceNotFound, `附件(id:${data.id})不存在。`);
+            let oss = Oss.get_instance('pmr-doc');
+            let result = await oss.presigned_url(oss.bucket, data.id, 300, prj_file.filename);
+            resolve({url: result, filename: prj_file.filename});
+        } catch (e) {
+            reject(Resp.throw_err(e));
+        }
+    });
+}
+
+const v1_0: IApiProcessor = {
+    schema: {
+        "type": "object",
+        "properties": {
+            "data": {
+                "type": "object",
+                "properties": {
+                    "id": {
+                        "type": "string",
+                        "title": "文件object name"
+                    }
+                },
+                "x-apifox-orders": [
+                    "id"
+                ],
+                "title": "请求参数内容",
+                "required": [
+                    "id"
+                ]
+            },
+            "ver": {
+                "type": "string",
+                "title": "版本号",
+                "description": "文档中没有说明时,默认为1.0",
+                "default": "1.0"
+            },
+            "app_id": {
+                "type": "string",
+                "title": "应用id",
+                "description": "平台分配给客户端的app id。一个应用一个id。"
+            },
+            "timestamp": {
+                "type": "integer",
+                "title": "UTC时间戳",
+                "description": "UTC时间戳,精确到毫秒。平台对超过五分钟的消息,将做忽略处理"
+            },
+            "token": {
+                "type": "string",
+                "title": "身份认证token",
+                "description": "平台登录后分配的token"
+            }
+        },
+        "x-apifox-orders": [
+            "ver",
+            "app_id",
+            "timestamp",
+            "token",
+            "data"
+        ],
+        "required": [
+            "data",
+            "ver",
+            "app_id",
+            "timestamp",
+            "token"
+        ]
+    },
+    method: download,
+    method_params: {},
+    guards: []
+};
+
+module.exports = {
+    default: v1_0,
+    v1_0: v1_0
+};

+ 6 - 6
pmr-biz-manager/src/routes/api/prj/work/stat.ts

@@ -1,11 +1,7 @@
 import {IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined";
 import {Resp} from "@util/Resp";
-import {Op, QueryTypes, WhereOptions} from "sequelize";
-import DataCURD from "@core/DataCURD";
-import {BizCustomer} from "@core-models/BizCustomer";
-import {BizCustomerLevel} from "@core-models/BizCustomerLevel";
+import {Op} from "sequelize";
 import {IStatInfo} from "@src/utils/define";
-import {PrjInfo} from "@core-models/PrjInfo";
 import {BpmnWork} from "@core-models/BpmnWork";
 
 function stat(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<any> {
@@ -15,7 +11,8 @@ function stat(json: IRequest, params: IMethodParams, cached_data: ICachedData):
             /// 获取归属于自己的总工作项总数
             let my_count = await BpmnWork.count({
                 where: {
-                    assigned_to: cached_data.user_id
+                    assigned_to: cached_data.user_id,
+                    show_in_my_works: true,
                 }
             });
             result.push({
@@ -27,6 +24,7 @@ function stat(json: IRequest, params: IMethodParams, cached_data: ICachedData):
             let done_count = await BpmnWork.count({
                 where: {
                     assigned_to: cached_data.user_id,
+                    show_in_my_works: true,
                     status: 2
                 }
             });
@@ -45,6 +43,7 @@ function stat(json: IRequest, params: IMethodParams, cached_data: ICachedData):
             let this_year_count = await BpmnWork.count({
                 where: {
                     assigned_to: cached_data.user_id,
+                    show_in_my_works: true,
                     started_at: {
                         [Op.gte]: new Date(new Date().getFullYear() + '-01-01 00:00:00'),
                         [Op.lte]: new Date(new Date().getFullYear() + '-12-31 23:59:59')
@@ -61,6 +60,7 @@ function stat(json: IRequest, params: IMethodParams, cached_data: ICachedData):
                 where: {
                     assigned_to: cached_data.user_id,
                     status: 2,
+                    show_in_my_works: true,
                     started_at: {
                         [Op.gte]: new Date(new Date().getFullYear() + '-01-01 00:00:00'),
                         [Op.lte]: new Date(new Date().getFullYear() + '-12-31 23:59:59')

+ 91 - 0
pmr-biz-manager/src/routes/api/prj/work/undone_count.ts

@@ -0,0 +1,91 @@
+import {IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined";
+import {Resp} from "@util/Resp";
+import {Op} from "sequelize";
+
+import {BpmnWork} from "@core-models/BpmnWork";
+
+function stat(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<any> {
+    return new Promise<any>(async (resolve, reject) => {
+        try {
+            /// 获取归属于自己的未完成工作项总数,status不等于2
+            let my_undone_count = await BpmnWork.count({
+                where: {
+                    assigned_to: cached_data.user_id,
+                    show_in_my_works: true,
+                    status: {
+                        [Op.ne]: 2
+                    }
+                }
+            });
+
+            resolve({undone_count: my_undone_count});
+        } catch (e) {
+            reject(Resp.throw_err(e));
+        }
+    })
+}
+
+const v1_0: IApiProcessor = {
+    schema: {
+        "type": "object",
+        "properties": {
+            "data": {
+                "type": "object",
+                "properties": {},
+                "x-apifox-orders": [],
+                "title": "请求参数内容"
+            },
+            "ver": {
+                "type": "string",
+                "title": "版本号",
+                "description": "文档中没有说明时,默认为1.0",
+                "examples": [
+                    "1.0"
+                ]
+            },
+            "app_id": {
+                "type": "string",
+                "title": "应用id",
+                "description": "平台分配给客户端的app id。一个应用一个id。"
+            },
+            "timestamp": {
+                "type": "integer",
+                "description": "UTC时间戳,精确到毫秒。平台对超过五分钟的消息,将做忽略处理",
+                "title": "UTC时间戳"
+            },
+            "sign": {
+                "type": "string",
+                "title": "签名"
+            },
+            "token": {
+                "type": "string",
+                "title": "身份认证token",
+                "description": "平台登录后分配的token"
+            }
+        },
+        "x-apifox-orders": [
+            "ver",
+            "app_id",
+            "timestamp",
+            "token",
+            "sign",
+            "data"
+        ],
+        "required": [
+            "data",
+            "ver",
+            "app_id",
+            "timestamp",
+            "token"
+        ]
+    },
+    method: stat,
+    method_params: {},
+    guards: []
+}
+
+
+module.exports = {
+    default: v1_0,
+    v1_0: v1_0
+}

+ 6 - 8
pmr-biz-manager/src/routes/api/prj/work/undone_stat.ts

@@ -1,11 +1,7 @@
 import {IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined";
 import {Resp} from "@util/Resp";
-import {Op, QueryTypes, WhereOptions} from "sequelize";
-import DataCURD from "@core/DataCURD";
-import {BizCustomer} from "@core-models/BizCustomer";
-import {BizCustomerLevel} from "@core-models/BizCustomerLevel";
+import {Op} from "sequelize";
 import {IStatInfo} from "@src/utils/define";
-import {PrjInfo} from "@core-models/PrjInfo";
 import {BpmnWork} from "@core-models/BpmnWork";
 
 function stat(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<any> {
@@ -16,6 +12,7 @@ function stat(json: IRequest, params: IMethodParams, cached_data: ICachedData):
             let my_count = await BpmnWork.count({
                 where: {
                     assigned_to: cached_data.user_id,
+                    show_in_my_works: true,
                     status: {
                         [Op.ne]: 2
                     }
@@ -27,17 +24,18 @@ function stat(json: IRequest, params: IMethodParams, cached_data: ICachedData):
                 unit: '个'
             });
             /// 获取归属于自己的超期工作项数量,status=1表示超期
-            let doing = await BpmnWork.count({
+            let due = await BpmnWork.count({
                 where: {
                     assigned_to: cached_data.user_id,
+                    show_in_my_works: true,
                     status: 1
                 }
             });
             result.push({
                 name: '已超期',
-                value: doing,
+                value: due,
                 unit: '个',
-                color: 'red'
+                color: due > 0 ? 'red': undefined
             });
 
             resolve({list: result});

+ 91 - 0
pmr-biz-manager/src/routes/api/report/prj/multi_stat.ts

@@ -0,0 +1,91 @@
+
+import {IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined";
+import {Resp} from "@util/Resp";
+import {BpmnWork} from "@core-models/BpmnWork";
+import {PrjInfo} from "@core-models/PrjInfo";
+import {BizCustomer} from "@core-models/BizCustomer";
+import {BizContractInfo} from "@core-models/BizContractInfo";
+
+function stat(_json: IRequest, _params: IMethodParams, _cached_data: ICachedData): Promise<any> {
+    return new Promise<any>(async (resolve, reject) => {
+        try {
+            let prj_count = await PrjInfo.count({});
+            let customer_count = await BizCustomer.count({where: {}});
+            let work_count = await BpmnWork.count({
+                where: {
+                    show_in_my_works: true
+                }
+            });
+            let contract_count = await BizContractInfo.count({});
+
+            resolve({prj_count: prj_count, customer_count: customer_count, work_count: work_count, contract_count: contract_count});
+        } catch (e) {
+            reject(Resp.throw_err(e));
+        }
+    })
+}
+
+const v1_0: IApiProcessor = {
+    schema: {
+        "type": "object",
+        "properties": {
+            "data": {
+                "type": "object",
+                "properties": {},
+                "x-apifox-orders": [],
+                "title": "请求参数内容"
+            },
+            "ver": {
+                "type": "string",
+                "title": "版本号",
+                "description": "文档中没有说明时,默认为1.0",
+                "examples": [
+                    "1.0"
+                ]
+            },
+            "app_id": {
+                "type": "string",
+                "title": "应用id",
+                "description": "平台分配给客户端的app id。一个应用一个id。"
+            },
+            "timestamp": {
+                "type": "integer",
+                "description": "UTC时间戳,精确到毫秒。平台对超过五分钟的消息,将做忽略处理",
+                "title": "UTC时间戳"
+            },
+            "sign": {
+                "type": "string",
+                "title": "签名"
+            },
+            "token": {
+                "type": "string",
+                "title": "身份认证token",
+                "description": "平台登录后分配的token"
+            }
+        },
+        "x-apifox-orders": [
+            "ver",
+            "app_id",
+            "timestamp",
+            "token",
+            "sign",
+            "data"
+        ],
+        "required": [
+            "data",
+            "ver",
+            "app_id",
+            "timestamp",
+            "token"
+        ]
+    },
+    method: stat,
+    method_params: {},
+    guards: []
+}
+
+
+module.exports = {
+    default: v1_0,
+    v1_0: v1_0
+}