Przeglądaj źródła

feature: 添加了仪表盘中所需的多个统计数据

eyes4 6 miesięcy temu
rodzic
commit
6432eaecb9

+ 1 - 1
base-lib/src/core-models/ReportPrjState.ts → base-lib/src/core-models/ReportPrjStat.ts

@@ -6,7 +6,7 @@ import { Sequelize, DataTypes }  from 'sequelize';
  * 项目数量统计报表(每月底统计一次)
  */
 
-export class ReportPrjState extends Model {
+export class ReportPrjStat extends Model {
     declare ts: string;
     declare amount: number;
     declare undone: number;

+ 50 - 0
base-lib/src/core-models/ReportTaskDelayRate.ts

@@ -0,0 +1,50 @@
+import {Model} from "sequelize";
+import { Sequelize, DataTypes }  from 'sequelize';
+
+/**
+ * 项目累计延误率报表(每日23:50统计一次),针对已完成和未完成的项目
+ */
+
+export class ReportTaskDelayRate extends Model {
+    declare ts: string;
+    declare total_count: number;
+    declare delayed_count: number;
+    declare delay_rate: number;
+
+    static table_name = 'tb_report_task_delay_rate';
+
+    static attributes = {
+        ts: {
+            type: DataTypes.DATEONLY,
+            allowNull: false,
+            comment: '日期',
+            primaryKey: true,
+        },
+        total_count: {
+            type: DataTypes.INTEGER,
+            comment: '项目数量',
+        },
+        delayed_count: {
+            type: DataTypes.INTEGER,
+            comment: '延期数量',
+        },
+        delay_rate: {
+            type: DataTypes.FLOAT,
+            comment: '延期率',
+        }
+    }
+
+    static indexes = [
+        {fields: ['ts']},
+    ]
+
+    static associate(sequelize: Sequelize) {
+        try {
+        } catch (e) {
+            console.log(e);
+        }
+    }
+
+    static init_sqls = [
+    ]
+}

+ 57 - 0
base-lib/src/core-models/ReportTaskStat.ts

@@ -0,0 +1,57 @@
+
+import {Model} from "sequelize";
+import { Sequelize, DataTypes }  from 'sequelize';
+
+/**
+ * 项目任务数量统计报表(每日23:50统计一次)
+ */
+
+export class ReportTaskStat extends Model {
+    declare ts: string;
+    declare total_count: number;
+    declare incomplete_count: number;
+    declare delayed_count: number;
+    declare delay_rate: number;
+
+    static table_name = 'tb_report_task_stat';
+
+    static attributes = {
+        ts: {
+            type: DataTypes.DATEONLY,
+            allowNull: false,
+            comment: '日期',
+            primaryKey: true,
+        },
+        total_count: {
+            type: DataTypes.INTEGER,
+            comment: '项目数量',
+        },
+        incomplete_count: {
+            type: DataTypes.INTEGER,
+            comment: '未完成数量',
+        },
+        delayed_count: {
+            type: DataTypes.INTEGER,
+            comment: '延期数量',
+        },
+        delay_rate: {
+            type: DataTypes.FLOAT,
+            comment: '延期率',
+        }
+
+    }
+
+    static indexes = [
+        {fields: ['ts']},
+    ]
+
+    static associate(sequelize: Sequelize) {
+        try {
+        } catch (e) {
+            console.log(e);
+        }
+    }
+
+    static init_sqls = [
+    ]
+}

+ 0 - 40
base-lib/src/core-models/UploadedFiles.ts

@@ -1,40 +0,0 @@
-import {Model, DataTypes} from "sequelize";
-
-export class UploadedFiles extends Model {
-    declare id: string;
-    declare time: string;
-    declare status: number;
-    declare filename: string;
-
-    static tableName = 'tb_uploaded_file';
-
-    static attributes = {
-        id: {
-            type: DataTypes.STRING,
-            primaryKey: true,
-            comment: "对象id"
-        },
-        time: {
-            type: DataTypes.DATE,
-            comment: '上传时间'
-        },
-        filename: {
-            type: DataTypes.STRING,
-            comment: '文件名'
-        },
-        size: {
-            type: DataTypes.INTEGER,
-            comment: '长度(字节)'
-        },
-        status: {
-            type: DataTypes.INTEGER,
-            comment: '状态:0=尚未上传,1=上传完成,2=需要保留'
-        }
-    }
-
-    static indexes = [
-        {fields: ['time']},
-        {fields: ['status']},
-    ]
-    static initSqls = [];
-}

+ 10 - 0
pmr-access-control/src/routes/api/acs/user/add.ts

@@ -43,6 +43,16 @@ function add(json: IRequest, _params: IMethodParams, _cached_data: ICachedData):
                 domain_id: data.domain_id,
             }, {transaction: t});
 
+            await AcsUserDomain.create({
+                user_id: data.user_id,
+                domain_id: 100,
+            }, {transaction: t});
+
+            await AcsUserDomain.create({
+                user_id: data.user_id,
+                domain_id: 4,
+            }, {transaction: t});
+
             await t.commit();
             resolve({});
         } catch (e) {

+ 3 - 1
pmr-biz-manager/src/app.ts

@@ -15,7 +15,7 @@ import {FlowEngine} from "@src/bpmn/flow_engine";
 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} from "@src/utils/prj_stat";
+import {prj_stat_monthly, task_delay_rate_daily, task_stat_daily} from "@src/utils/prj_stat";
 const { report } = require('node:process');
 
 const schedule = require('node-schedule');
@@ -125,6 +125,8 @@ new ServiceApp().start(new MyRouter('@src/routes', guards), Models).then(async (
     // 项目数统计
     schedule.scheduleJob('50 23 * * *', async function () {
         await prj_stat_monthly();
+        await task_stat_daily();
+        await task_delay_rate_daily();
     });
 
     // let bpmn = await BpmnModel.findOne({where: {id: 'task'}, raw: true});

+ 4 - 7
pmr-biz-manager/src/models/Models.ts

@@ -12,23 +12,21 @@ import {AcsRole} from "@core-models/AcsRole";
 import {PrjPlanTask} from "@core-models/PrjPlanTask";
 import {PrjTaskOutcome} from "@core-models/PrjTaskOutcome";
 import {PrjWeekReport} from "@core-models/PrjWeekReport";
-import {PrjWeekReportAttachment} from "@core-models/PrjWeekReportAttachment";
 import {PrjFile} from "@core-models/PrjFile";
 import {PrjFileCategory} from "@core-models/PrjFileCategory";
 import {AcsUserDomain} from "@core-models/AcsUserDomain";
 import {AcsUserRole} from "@core-models/AcsUserRole";
 import {BpmnModel} from "@core-models/BpmnModel";
-import {UploadedFiles} from "@core-models/UploadedFiles";
 import {BpmnCase} from "@core-models/BpmnCase";
 import {BpmnWork} from "@core-models/BpmnWork";
 import {BpmnForm} from "@core-models/BpmnForm";
 import {PrjPlanTaskDraft} from "@core-models/PrjPlanTaskDraft";
 import {PrjTaskOutcomeDraft} from "@core-models/PrjTaskOutcomeDraft";
 import {PrjLogs} from "@core-models/PrjLogs";
-import {ReportPrjState} from "@core-models/ReportPrjState";
+import {ReportPrjStat} from "@core-models/ReportPrjStat";
+import {ReportTaskStat} from "@core-models/ReportTaskStat";
 
 export const Models: Array<IDataModelInfo> = [
-
     {tag: 'acs', model: AcsDomain, timestamps: false},
     {tag: 'acs', model: AcsUserInfo, timestamps: false},
     {tag: 'acs', model: AcsUserDomain, timestamps: false},
@@ -53,7 +51,6 @@ export const Models: Array<IDataModelInfo> = [
     {tag: 'pmr', model: BpmnWork, timestamps: false},
     {tag: 'pmr', model: BpmnForm, timestamps: false},
     {tag: 'pmr', model: PrjLogs, timestamps: false},
-    {tag: 'pmr', model: ReportPrjState, timestamps: false}
-    // {tag: 'pmr', model: UploadedFiles, timestamps: false}
-
+    {tag: 'pmr', model: ReportPrjStat, timestamps: false},
+    {tag: 'pmr', model: ReportTaskStat, timestamps: false}
 ]

+ 4 - 0
pmr-biz-manager/src/routes/api/cfg/staff/add.ts

@@ -76,6 +76,10 @@ function add(json: IRequest, _params: IMethodParams, cached_data: ICachedData):
                 user_id: data.mobile,
                 domain_id: 100,
             }, {transaction: t});
+            await AcsUserDomain.create({
+                user_id: data.mobile,
+                domain_id: 4,
+            }, {transaction: t});
 
             await AcsUserRole.upsert({
                 user_id: data.mobile,

+ 6 - 2
pmr-biz-manager/src/routes/api/prj/info/get_list.ts

@@ -77,7 +77,11 @@ function get_list(json: IRequest, params: IMethodParams, cached_data: ICachedDat
             let select_sql = `
                 select prj.id, prj.name, prj.phase_id, phase.name as phase_name, 
                     prj.region_id, region.name as region_name, 
-                    prj.type_id, CONCAT_WS('/', pidtype.name, ptype.name) as type_name,
+                    prj.type_id, 
+                    case when pidtype.id = 300 then 
+                        ptype.name
+                        else CONCAT_WS('/', pidtype.name, ptype.name)
+                    end as type_name,
                     prj.customer_id, customer.name as customer_name, 
                     customer.industry_id, industry.name as industry_name,
                     cast(contract.amount as float) as contract_amount,
@@ -223,7 +227,7 @@ function get_list(json: IRequest, params: IMethodParams, cached_data: ICachedDat
             select_sql += condition;
             select_sql += `group by prj.id, phase.name, region.name, pidtype.name, ptype.name,customer.name, 
                 customer.industry_id, industry.name, contract_amount, prj.leader_id, leader.name,
-                    prj.bizman_id, bizman.name
+                    prj.bizman_id, bizman.name, pidtype.id
                 order by prj.created_at desc, prj.name `;
             let result = await DataCURD.get_page_list({
                 sequelize: BizCustomer.sequelize!,

+ 1 - 1
pmr-biz-manager/src/routes/api/prj/info/modify.ts

@@ -61,7 +61,7 @@ interface IData {
 function permission_guard(content: IRequest, cached_data: ICachedData): Promise<void> {
     return new Promise<void>(async (resolve, reject) => {
         let data = <IData>content.data;
-        let prj = await PrjInfo.findOne({where: {id: data.id, raw: true}});
+        let prj = await PrjInfo.findOne({where: {id: data.id},  raw: true});
         if (!prj)
             throw Resp.gen_err(Resp.ResourceNotFound, '项目不存在。');
 

+ 125 - 0
pmr-biz-manager/src/routes/api/report/prj/cat_stat.ts

@@ -0,0 +1,125 @@
+/// 按分类统计数量
+
+import {IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined";
+import {Resp} from "@util/Resp";
+import {QueryTypes} from "sequelize";
+import DataCURD from "@core/DataCURD";
+import {PrjMembers} from "@core-models/PrjMembers";
+
+interface IData {
+
+}
+
+function get_list(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<any> {
+    return new Promise<any>(async (resolve, reject) => {
+        try {
+            let data = <IData>json.data;
+            let sql = `
+                WITH ProjectHours AS (
+                    SELECT
+                        type_id,
+                        started_at,
+                        deliver_at,
+                        -- 计算工作日数(不包括周六周日)
+                        (
+                            SELECT COUNT(*) FILTER (WHERE EXTRACT(DOW FROM gs) NOT IN (0, 6))
+                            FROM generate_series(started_at, deliver_at, '1 day') gs(day)
+                        ) AS work_days
+                    FROM
+                        tb_prj_info
+                ),
+                ProjectSummaries AS (
+                    SELECT
+                        type_id,
+                        COUNT(*) AS project_count,
+                        SUM(work_days) AS total_work_days
+                    FROM
+                        ProjectHours
+                    GROUP BY
+                        type_id
+                )
+                SELECT
+                    type_id,
+                    case when pidtype.id = 300 then
+                        ptype.name
+                        else CONCAT_WS('/', pidtype.name, ptype.name)
+                    end
+                     as type_name,
+                    project_count,
+                    total_work_days
+                FROM
+                    ProjectSummaries, tb_acs_domain pidtype, tb_acs_domain ptype
+                WHERE ptype.id = type_id and ptype.pid = pidtype.id;
+            `;
+            let result = await PrjMembers.sequelize!.query(sql, {type: QueryTypes.SELECT,  raw: true});
+            let list = DataCURD.filter_null(result);
+            resolve({list: list});
+        } 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: get_list,
+    method_params: {}
+}
+
+
+module.exports = {
+    default: v1_0,
+    v1_0: v1_0
+}

+ 2 - 2
pmr-biz-manager/src/routes/api/report/prj/count.ts

@@ -3,7 +3,7 @@ import {Resp} from "@util/Resp";
 import {QueryTypes} from "sequelize";
 import DataCURD, {ISQLReplacements} from "@core/DataCURD";
 import {PrjMembers} from "@core-models/PrjMembers";
-import {ReportPrjState} from "@core-models/ReportPrjState";
+import {ReportPrjStat} from "@core-models/ReportPrjStat";
 import dayjs from "dayjs";
 
 interface IData {
@@ -25,7 +25,7 @@ function get_list(json: IRequest, params: IMethodParams, cached_data: ICachedDat
                 select 
                     TO_CHAR(ts, 'yyyy-MM') as ts,
                     amount, undone
-                from ${ReportPrjState.table_name} 
+                from ${ReportPrjStat.table_name} 
                 where ts >= :begin_at and ts <= :end_at
                 order by ts asc
             `

+ 129 - 0
pmr-biz-manager/src/routes/api/report/prj/task_delay_rate.ts

@@ -0,0 +1,129 @@
+/// 统计任务数和延期率
+
+import {IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined";
+import {Resp} from "@util/Resp";
+import {QueryTypes} from "sequelize";
+import DataCURD, {ISQLReplacements} from "@core/DataCURD";
+import dayjs from "dayjs";
+import {PrjMembers} from "@core-models/PrjMembers";
+import {ReportTaskStat} from "@core-models/ReportTaskStat";
+
+interface IData {
+    /**
+     * 起始月份,YYYY-MM-DD格式
+     */
+    begin_at: string;
+    /**
+     * 结束月份,YYYY-MM-DD格式
+     */
+    end_at: string;
+}
+
+function get_list(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<any> {
+    return new Promise<any>(async (resolve, reject) => {
+        try {
+            let data = <IData>json.data;
+            let sql = `
+                select 
+                    TO_CHAR(ts, 'yyyy-MM-DD') as ts,
+                    total_count, incomplete_count, delayed_count, delay_rate
+                from ${ReportTaskStat.table_name} 
+                where ts >= :begin_at and ts <= :end_at
+                order by ts asc
+            `;
+            let replacements: ISQLReplacements = {
+                begin_at: dayjs(data.begin_at).startOf('day').format('YYYY-MM-DD HH:mm:ss'),
+                end_at: dayjs(data.end_at).endOf('day').format('YYYY-MM-DD HH:mm:ss'),
+            };
+            let result = await PrjMembers.sequelize!.query(sql, {type: QueryTypes.SELECT, replacements: replacements, raw: true});
+            let list = DataCURD.filter_null(result);
+            resolve({list: list});
+        } catch (e) {
+            reject(Resp.throw_err(e));
+        }
+    })
+}
+
+const v1_0: IApiProcessor = {
+    schema: {
+        "type": "object",
+        "properties": {
+            "data": {
+                "type": "object",
+                "properties": {
+                    "begin_at": {
+                        "type": "string",
+                        "title": "起始月份",
+                        "description": "YYYY-MM-DD格式",
+                        "format": "YYYY-MM-DD"
+                    },
+                    "end_at": {
+                        "type": "string",
+                        "title": "结束月份",
+                        "description": "YYYY-MM-DD格式",
+                        "format": "YYYY-MM-DD"
+                    }
+                },
+                "x-apifox-orders": [
+                    "begin_at",
+                    "end_at"
+                ],
+                "title": "请求参数内容",
+                "required": [
+                    "begin_at",
+                    "end_at"
+                ]
+            },
+            "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: get_list,
+    method_params: {}
+}
+
+
+module.exports = {
+    default: v1_0,
+    v1_0: v1_0
+}

+ 131 - 0
pmr-biz-manager/src/routes/api/report/prj/task_stat.ts

@@ -0,0 +1,131 @@
+/// 统计任务数和延期率
+
+import {IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined";
+import {Resp} from "@util/Resp";
+import {QueryTypes} from "sequelize";
+import DataCURD, {ISQLReplacements} from "@core/DataCURD";
+import {PrjPlanTask} from "@core-models/PrjPlanTask";
+import {ReportPrjStat} from "@core-models/ReportPrjStat";
+import dayjs from "dayjs";
+import {PrjMembers} from "@core-models/PrjMembers";
+import {ReportTaskStat} from "@core-models/ReportTaskStat";
+
+interface IData {
+    /**
+     * 起始月份,YYYY-MM-DD格式
+     */
+    begin_at: string;
+    /**
+     * 结束月份,YYYY-MM-DD格式
+     */
+    end_at: string;
+}
+
+function get_list(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<any> {
+    return new Promise<any>(async (resolve, reject) => {
+        try {
+            let data = <IData>json.data;
+            let sql = `
+                select 
+                    TO_CHAR(ts, 'yyyy-MM-DD') as ts,
+                    total_count, incomplete_count, delayed_count, delay_rate
+                from ${ReportTaskStat.table_name} 
+                where ts >= :begin_at and ts <= :end_at
+                order by ts asc
+            `;
+            let replacements: ISQLReplacements = {
+                begin_at: dayjs(data.begin_at).startOf('day').format('YYYY-MM-DD HH:mm:ss'),
+                end_at: dayjs(data.end_at).endOf('day').format('YYYY-MM-DD HH:mm:ss'),
+            };
+            let result = await PrjMembers.sequelize!.query(sql, {type: QueryTypes.SELECT, replacements: replacements, raw: true});
+            let list = DataCURD.filter_null(result);
+            resolve({list: list});
+        } catch (e) {
+            reject(Resp.throw_err(e));
+        }
+    })
+}
+
+const v1_0: IApiProcessor = {
+    schema: {
+        "type": "object",
+        "properties": {
+            "data": {
+                "type": "object",
+                "properties": {
+                    "begin_at": {
+                        "type": "string",
+                        "title": "起始月份",
+                        "description": "YYYY-MM-DD格式",
+                        "format": "YYYY-MM-DD"
+                    },
+                    "end_at": {
+                        "type": "string",
+                        "title": "结束月份",
+                        "description": "YYYY-MM-DD格式",
+                        "format": "YYYY-MM-DD"
+                    }
+                },
+                "x-apifox-orders": [
+                    "begin_at",
+                    "end_at"
+                ],
+                "title": "请求参数内容",
+                "required": [
+                    "begin_at",
+                    "end_at"
+                ]
+            },
+            "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: get_list,
+    method_params: {}
+}
+
+
+module.exports = {
+    default: v1_0,
+    v1_0: v1_0
+}

+ 67 - 18
pmr-biz-manager/src/utils/prj_stat.ts

@@ -1,36 +1,85 @@
 import {PrjInfo} from "@core-models/PrjInfo";
-import {Op} from "sequelize";
-import {PrjPhaseDefine} from "@core-models/PrjPhaseDefine";
+import {Op, QueryTypes} from "sequelize";
 import dayjs from "dayjs";
-import {ReportPrjState} from "@core-models/ReportPrjState";
+import {ReportPrjStat} from "@core-models/ReportPrjStat";
+import {PrjPlanTask} from "@core-models/PrjPlanTask";
+import {Logger} from "@util/Logger";
+import {ReportTaskDelayRate} from "@core-models/ReportTaskDelayRate";
 
 export  function prj_stat_monthly(): Promise<void> {
     return new Promise(async (resolve, reject) => {
         try {
-            // 使用dayjs获取今天是否是月末这一天
-            // let is_last_day = dayjs().isSame(dayjs().endOf('month'), 'day');
-            //if (!is_last_day) return resolve();
             let count = await PrjInfo.count({where: {phase_id: {[Op.ne]: 'deprecated'}}});
             let undone = await PrjInfo.count({where: {phase_id: {[Op.ne]: 'done'}}});
             let ts = dayjs().endOf("month").format('YYYY-MM-DD');
-            // await PrjStatReport.findOrCreate({
-            //     where: {
-            //         ts: ts
-            //     },
-            //     defaults: {
-            //         ts: ts,
-            //         amount: count,
-            //         undone: undone
-            //     }
-            // })
-            await ReportPrjState.upsert({
+            await ReportPrjStat.upsert({
                 ts: ts,
                 amount: count,
                 undone: undone
             }, {returning: false})
             resolve();
         } catch (error) {
-            reject(error);
+            Logger.error(error);
+            resolve();
+        }
+    });
+}
+
+export  function task_stat_daily(): Promise<void> {
+    return new Promise(async (resolve, reject) => {
+        try {
+            let sql = `
+                INSERT INTO tb_report_task_stat
+                    (ts, total_count, incomplete_count, delayed_count, delay_rate)
+                SELECT
+                    now() as ts,
+                    COUNT(CASE WHEN status <> 90 THEN 1 ELSE 0 END) AS total_count,
+                    SUM(CASE WHEN status NOT IN (100, 90, 80) THEN 1 ELSE 0 END) AS incomplete_count,
+                    SUM(CASE WHEN status = 20 THEN 1 ELSE 0 END) AS delayed_count,
+                    CASE
+                        WHEN COUNT(CASE WHEN status <> 90 THEN 1 ELSE 0 END) = 0 THEN 0
+                        ELSE (SUM(CASE WHEN status in (20, 80) THEN 1 ELSE 0 END) * 100.0 / COUNT(CASE WHEN status <> 90 THEN 1 ELSE 0 END))
+                    END AS delay_rate
+                FROM
+                    tb_prj_plan_task, tb_prj_info
+                WHERE tb_prj_plan_task.prj_id = tb_prj_info.id and
+                      tb_prj_info.phase_id <> 'deprecated' and tb_prj_info.phase_id <> 'done' --去除已完成和取消的项目
+                ON CONFLICT DO NOTHING
+            `;
+            await PrjPlanTask.sequelize!.query(sql, {type: QueryTypes.UPDATE,  raw: true});
+            resolve();
+        } catch (error) {
+            Logger.error(error);
+            resolve();
+        }
+    });
+}
+
+export  function task_delay_rate_daily(): Promise<void> {
+    return new Promise(async (resolve, reject) => {
+        try {
+            let sql = `
+                INSERT INTO ${ReportTaskDelayRate.table_name}
+                    (ts, total_count, delayed_count, delay_rate)
+                SELECT
+                    now() as ts,
+                    COUNT(CASE WHEN status <> 90 THEN 1 ELSE 0 END) AS total_count,
+                    SUM(CASE WHEN status = 20 THEN 1 ELSE 0 END) AS delayed_count,
+                    CASE
+                        WHEN COUNT(CASE WHEN status <> 90 THEN 1 ELSE 0 END) = 0 THEN 0
+                        ELSE (SUM(CASE WHEN status in (20, 80) THEN 1 ELSE 0 END) * 100.0 / COUNT(CASE WHEN status <> 90 THEN 1 ELSE 0 END))
+                    END AS delay_rate
+                FROM
+                    tb_prj_plan_task, tb_prj_info
+                WHERE tb_prj_plan_task.prj_id = tb_prj_info.id and
+                      tb_prj_info.phase_id <> 'deprecated' --去除已取消的项目
+                ON CONFLICT DO NOTHING
+            `;
+            await PrjPlanTask.sequelize!.query(sql, {type: QueryTypes.UPDATE,  raw: true});
+            resolve();
+        } catch (error) {
+            Logger.error(error);
+            resolve();
         }
     });
 }