Browse Source

部分质量管理代码,为了提交补差计算

MaiXinRong 3 weeks ago
parent
commit
7ffaf584e5

+ 407 - 0
app/controller/quality_controller.js

@@ -0,0 +1,407 @@
+'use strict';
+
+/**
+ * 标段管理控制器
+ *
+ * @author Mai
+ * @date 2025/7/17
+ * @version
+ */
+
+const auditConst = require('../const/audit');
+const contractConst = require('../const/contract');
+const moment = require('moment');
+const sendToWormhole = require('stream-wormhole');
+const fs = require('fs');
+const path = require('path');
+
+module.exports = app => {
+    class QualityController extends app.BaseController {
+        constructor(ctx) {
+            super(ctx);
+            ctx.showProject = true;
+            // ctx.showTitle = true;
+        }
+
+        async tender(ctx) {
+            try {
+                if (!ctx.session.sessionProject.page_show.quality) throw '该功能已关闭';
+
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.quality.tender),
+                };
+                const accountList = await ctx.service.projectAccount.getAllDataByCondition({
+                    where: { project_id: ctx.session.sessionProject.id, enable: 1 },
+                    columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile'],
+                });
+                renderData.accountList = accountList;
+                const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
+                renderData.accountGroup = unitList.map(item => {
+                    const groupList = accountList.filter(item1 => item1.company === item.name);
+                    return { groupName: item.name, groupList };
+                });
+                renderData.accountInfo = await this.ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                renderData.tenderList = await ctx.service.tender.getSpecList(ctx.service.tenderPermission, 'quality', ctx.session.sessionUser.is_admin ? 'all' : '');
+                renderData.categoryData = await this.ctx.service.category.getAllCategory(this.ctx.session.sessionProject.id);
+                // renderData.selfCategoryLevel = this.ctx.subProject.permission.self_category_level;
+                renderData.permissionConst = ctx.service.tenderPermission.partPermissionConst('quality');
+                renderData.permissionBlock = ctx.service.tenderPermission.partPermissionBlock('quality');
+                await this.layout('quality/tender.ejs', renderData, 'quality/tender_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '无法查看质量管理数据');
+                ctx.redirect('/dashboard');
+            }
+        }
+
+        async member(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const result = await ctx.service.tenderPermission.getPartsPermission(data.tid, data.parts);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '查询标段权限错误');
+            }
+        }
+
+        async memberSave(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                await ctx.service.tenderPermission.savePermission(data.tid, data.member, data.permissionBlock);
+                ctx.body = { err: 0, msg: '', data: '' };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '保存标段权限错误');
+            }
+        }
+
+        async info(ctx) {
+            try {
+                if (!ctx.session.sessionProject.page_show.quality) throw '该功能已关闭';
+                const renderData = {
+                    thirdParty: {
+                        gxby: ctx.session.sessionProject.gxby_status,
+                        dagl: ctx.session.sessionProject.dagl_status,
+                    },
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.quality.info),
+                };
+                await this.layout('quality/info.ejs', renderData, 'quality/info_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '无法查看质量管理数据');
+                ctx.redirect('/quality');
+            }
+        }
+
+        async flaw(ctx) {
+            try {
+                if (!ctx.session.sessionProject.page_show.quality) throw '该功能已关闭';
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.quality.flaw),
+                };
+                await this.layout('quality/flaw.ejs', renderData, 'quality/flaw_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '无法查看质量管理数据');
+                ctx.redirect('/quality');
+            }
+        }
+
+        async lab(ctx) {
+            try {
+                if (!ctx.session.sessionProject.page_show.quality) throw '该功能已关闭';
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.quality.lab),
+                };
+                await this.layout('quality/lab.ejs', renderData, 'quality/lab_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '无法查看质量管理数据');
+                ctx.redirect('/quality');
+            }
+        }
+
+        async _loadXmjData(spec) {
+            const xmj = await this.ctx.service.ledger.getAllDataByCondition({
+                where: { tender_id: this.ctx.tender.id },
+                columns: ['id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf', 'code', 'name'],
+            });
+            const quality = spec && spec.loadStatus ? await this.ctx.service.quality.getAllDataByCondition({
+                where: { tid: this.ctx.tender.id, rela_type: 'xmj' },
+                columns: ['rela_id', 'gxby_status', 'gxby_date', 'dagl_status'],
+            }) : [];
+            this.ctx.helper.assignRelaData(xmj, [
+                { data: quality, fields: ['gxby_status', 'gxby_date', 'dagl_status'], prefix: '', relaId: 'rela_id' },
+            ]);
+            return xmj;
+        }
+        async _loadPosData(condition, spec) {
+            const pos = await this.ctx.service.pos.getAllDataByCondition({
+                where: condition,
+                columns: ['id', 'tid', 'lid', 'tree_id', 'tree_pid', 'level', 'order', 'full_path', 'is_leaf', 'code', 'b_code', 'name'],
+            });
+            const quality = spec && spec.loadStatus ? await this.ctx.service.quality.getAllDataByCondition({
+                where: { tid: this.ctx.tender.id, rela_type: 'pos' },
+                columns: ['rela_id', 'gxby_status', 'gxby_date', 'dagl_status'],
+            }) : [];
+            this.ctx.helper.assignRelaData(pos, [
+                { data: quality, fields: ['gxby_status', 'gxby_date', 'dagl_status'], prefix: '', relaId: 'rela_id' },
+            ]);
+            return pos;
+        }
+        async _loadDetailData(id) {
+            const result = await this.ctx.service.quality.getDataByCondition({ tid: this.ctx.tender.id, rela_id: id });
+            if (!result) return result;
+            await this.ctx.service.quality.loadQualityDetail(result);
+            await this.ctx.service.quality.checkQualityStatusAndSave(result);
+            return result;
+        }
+
+        async _loadData(type, data, result) {
+            if (result[type]) return result[type];
+            switch (type) {
+                case 'xmj':
+                    return this._loadXmjData(data.spec);
+                case 'pos':
+                    return this._loadPosData({ tid: this.ctx.tender.id }, data.spec);
+                case 'pos1':
+                    if (result.pos) return result.pos.filter(p => { return p.level === 1; });
+                    return this._loadPosData({ tid: this.ctx.tender.id, level: 1 }, data.spec);
+                case 'detail':
+                    if (!data.id) throw '缺少参数';
+                    return this._loadDetailData(data.id);
+                case 'quality':
+                    return await this.ctx.service.quality.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
+                case 'lab':
+                    return await this.ctx.service.qualityLab.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
+                default: throw '未知数据类型';
+            }
+        }
+        async load(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.filter) throw '参数错误';
+                const filter = data.filter.split(';');
+                const result = {};
+                for (const f of filter) {
+                    result[f] = await this._loadData(f, data, result);
+                }
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '获取数据错误');
+            }
+        }
+
+        async _saveKaigong(ctx, data) {
+            if (data.add) {
+                if (!ctx.permission.quality.add) throw '您无权新增开工';
+                await ctx.service.quality.kaigong(data, data.add.name, data.add.date);
+            } else if (data.update) {
+                await ctx.service.quality.kaigong(data, data.update.name, data.update.date);
+            } else if (data.del) {
+                await ctx.service.quality.delKaigong(data);
+            }
+        }
+        async _saveGongxu(ctx, data) {
+            if (data.add) {
+                if (!ctx.permission.quality.add) throw '您无权新增工序';
+                await ctx.service.qualityGongxu.add(data, data.add.name);
+            } else if (data.update) {
+                await ctx.service.qualityGongxu.update(data.update.id, data.update.name);
+            } else if (data.del) {
+                await ctx.service.qualityGongxu.del(data.del);
+            }
+        }
+        async _saveYinbi(ctx, data) {
+            if (data.add) {
+                if (!ctx.permission.quality.add) throw '您无权新增隐蔽工程';
+                await ctx.service.qualityYinbi.add(data, data.add);
+            } else if (data.update) {
+                await ctx.service.qualityYinbi.update(data.update.id, data.update);
+            } else if (data.del) {
+                await ctx.service.qualityYinbi.del(data.del);
+            }
+        }
+        async save(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.quality_id && (!data.rela_type && !data.rela_id)) throw '参数错误';
+
+                switch (ctx.params.type) {
+                    case 'kaigong':
+                        await this._saveKaigong(ctx, data);
+                        break;
+                    case 'gongxu':
+                        await this._saveGongxu(ctx, data);
+                        break;
+                    case 'yinbi':
+                        await this._saveYinbi(ctx, data);
+                        break;
+                    default: throw '请求错误';
+                }
+                const quality = await this._loadDetailData(data.rela_id);
+                ctx.body = { err: 0, msg: '', data: quality };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '保存数据错误');
+            }
+        }
+
+        async checkCanUpload(ctx) {
+            if (!ctx.permission.quality.upload) {
+                throw '您无权上传、删除文件';
+            }
+        }
+        async uploadFile(ctx) {
+            let stream;
+            try {
+                await this.checkCanUpload(ctx);
+
+                const parts = ctx.multipart({ autoFields: true });
+
+                let index = 0;
+                const create_time = Date.parse(new Date()) / 1000;
+                let stream = await parts();
+                const user = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                const quality = await ctx.service.quality.getDataById(parts.field.quality_id);
+                if (!quality) throw '工程未开工';
+                const blockType = parts.field.block_type;
+                const blockId = parts.field.block_id || '';
+                const specType = parts.field.spec_type || '';
+                if (!blockType) throw '参数错误';
+
+                const uploadfiles = [];
+                while (stream !== undefined) {
+                    if (!stream.filename) throw '未发现上传文件!';
+
+                    const fileInfo = path.parse(stream.filename);
+                    const filepath = `${ctx.session.sessionProject.id}/${ctx.tender.id}/quality/${blockType}/${ctx.moment().format('YYYYMMDD')}/${create_time + '_' + index + fileInfo.ext}`;
+
+                    // 保存文件
+                    await ctx.app.oss.put(filepath, stream);
+                    await sendToWormhole(stream);
+
+                    // 插入到stage_pay对应的附件列表中
+                    uploadfiles.push({
+                        filename: fileInfo.name,
+                        fileext: fileInfo.ext,
+                        filesize: Array.isArray(parts.field.size) ? parts.field.size[index] : parts.field.size,
+                        filepath,
+                    });
+                    ++index;
+                    if (Array.isArray(parts.field.size) && index < parts.field.size.length) {
+                        stream = await parts();
+                    } else {
+                        stream = undefined;
+                    }
+                }
+
+                const result = await ctx.service.qualityFile.addFiles(user, quality, uploadfiles, blockType, blockId, specType);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (error) {
+                ctx.log(error);
+                // 失败需要消耗掉stream 以防卡死
+                if (stream) await sendToWormhole(stream);
+                ctx.body = this.ajaxErrorBody(error, '上传附件失败,请重试');
+            }
+        }
+        async delFile(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.del) throw '缺少参数';
+                const result = await ctx.service.qualityFile.delFiles(data.del);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (error) {
+                this.log(error);
+                ctx.ajaxErrorBody(error, '删除附件失败');
+            }
+        }
+        async saveFile(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.id) throw '缺少参数';
+                const result = await ctx.service.file.qualityFile(data.id, data.filename);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (error) {
+                this.log(error);
+                ctx.ajaxErrorBody(error, '编辑附件失败');
+            }
+        }
+
+        async rule(ctx) {
+            try {
+                if (!ctx.session.sessionProject.page_show.quality) throw '该功能已关闭';
+                const ruleGroups = await ctx.service.qualityRule.getRuleGroups(ctx.session.sessionProject.id);
+                const tenderList = await ctx.service.tender.getAllDataByCondition({ where: { project_id: ctx.session.sessionProject.id } });
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.quality.rule),
+                    ruleGroups,
+                    thirdParty: {
+                        gxby: ctx.session.sessionProject.gxby_status,
+                        dagl: ctx.session.sessionProject.dagl_status,
+                    },
+                    tenderList,
+                };
+                await this.layout('quality/rule.ejs', renderData, 'quality/rule_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '无法查看质量管理数据');
+                ctx.redirect('/quality');
+            }
+        }
+        async ruleSave(ctx) {
+            try {
+                if (!ctx.session.sessionProject.page_show.quality) throw '该功能已关闭';
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.group && !data.rule && !data.quality) throw '参数错误';
+                if (data.group) {
+                    const result = await ctx.service.qualityRule.saveGroup(data.group);
+                    ctx.body = { err: 0, msg: '', data: result };
+                } else if (data.rule) {
+                    const result = await ctx.service.qualityRule.saveRule(data.rule);
+                    ctx.body = { err: 0, msg: '', data: result };
+                } else if (data.quality) {
+                    const result = await ctx.service.quality.saveQualityManage(this.ctx.tender.id, data.quality);
+                    ctx.body = { err: 0, msg: '', data: result };
+                }
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '保存状态推送规则错误');
+            }
+        }
+
+        async _pushIncStatus(select) {
+            // todo 向其他系统推送
+            return await this.ctx.service.quality.pushIncStatus(select);
+        }
+        async _pushAllStatus(select) {
+            // todo 向其他系统推送
+            return await this.ctx.service.quality.pushAllStatus(select);
+        }
+        async pushStatus(ctx) {
+            try {
+                if (!ctx.session.sessionProject.page_show.quality) throw '该功能已关闭';
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.push_type) throw '参数错误';
+                let count;
+                switch (data.push_type) {
+                    case 'inc':
+                        count = await this._pushIncStatus(data.select);
+                        break;
+                    case 'all':
+                        count = await this._pushAllStatus();
+                        break;
+                    default: throw '参数错误';
+                }
+                ctx.body = { err: 0, msg: '', data: count };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '推送失败');
+            }
+        }
+    }
+
+    return QualityController;
+};

+ 1 - 1
app/middleware/tender_check.js

@@ -25,7 +25,7 @@ module.exports = options => {
     return function* tenderCheck(next) {
         try {
             // 读取标段数据
-            const tender = { id: parseInt(this.params.id) };
+            const tender = this.subProject ? { id: parseInt(this.params.tid) } : { id: parseInt(this.params.id) };
             if (!tender.id) {
                 throw '当前未打开标段';
             }

+ 40 - 0
app/middleware/tender_permission_check.js

@@ -0,0 +1,40 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = options => {
+    /**
+     * 标段校验 中间件
+     * 1. 读取标段数据(包括属性)
+     * 2. 检验用户是否可见标段(不校验具体权限)
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* tenderPermissionCheck(next) {
+        try {
+            // 读取标段数据
+
+            if (this.session.sessionUser.is_admin) {
+                this.permission = this.service.tenderPermission.getAdminPermission();
+            } else {
+                this.permission = yield this.service.tenderPermission.getUserPermission(this.tender.id, this.session.sessionUser.accountId);
+            }
+            yield next;
+        } catch (err) {
+            this.log(err);
+            if (this.helper.isAjax(this.request)) {
+                this.ajaxErrorBody(err, '未知错误');
+            } else {
+                this.postError(err, '未知错误');
+                this.redirect(this.request.headers.referer);
+            }
+        }
+    };
+};

+ 18 - 0
app/router.js

@@ -17,6 +17,7 @@ module.exports = app => {
     const changeApplyAuditCheck = app.middlewares.changeApplyAuditCheck();
     const changePlanAuditCheck = app.middlewares.changePlanAuditCheck();
     const uncheckTenderCheck = app.middlewares.uncheckTenderCheck();
+    const tenderPermissionCheck = app.middlewares.tenderPermissionCheck();
     // 期读取中间件
     const stageCheck = app.middlewares.stageCheck();
     const settleCheck = app.middlewares.settleCheck();
@@ -455,6 +456,23 @@ module.exports = app => {
     // app.get('/sp/:id/drawing/design', sessionAuth, subProjectCheck, 'drawingController.design');
     // app.get('/sp/:id/drawing/as-built', sessionAuth, subProjectCheck, 'drawingController.built');
 
+    // 质量管理
+    app.get('/sp/:id/quality', sessionAuth, subProjectCheck, 'qualityController.tender');
+    app.post('/sp/:id/quality/member', sessionAuth, subProjectCheck, projectManagerCheck, 'qualityController.member');
+    app.post('/sp/:id/quality/memberSave', sessionAuth, subProjectCheck, projectManagerCheck, 'qualityController.memberSave');
+    app.get('/sp/:id/quality/tender/:tid/info', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.info');
+    app.get('/sp/:id/quality/tender/:tid/flaw', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.flaw');
+    app.get('/sp/:id/quality/tender/:tid/lab', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.lab');
+    app.post('/sp/:id/quality/tender/:tid/load', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.load');
+    app.post('/sp/:id/quality/tender/:tid/save/:type', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.save');
+    app.post('/sp/:id/quality/tender/:tid/file/upload', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.uploadFile');
+    app.post('/sp/:id/quality/tender/:tid/file/del', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.delFile');
+    app.post('/sp/:id/quality/tender/:tid/file/save', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.saveFile');
+    app.post('/sp/:id/quality/tender/:tid/push', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.pushStatus');
+    app.get('/sp/:id/quality/rule', sessionAuth, subProjectCheck, projectManagerCheck, 'qualityController.rule');
+    app.post('/sp/:id/quality/rule/save', sessionAuth, subProjectCheck, projectManagerCheck, 'qualityController.ruleSave');
+    app.post('/sp/:id/quality/tender/:tid/rule/save', sessionAuth, subProjectCheck, tenderCheck, projectManagerCheck, 'qualityController.ruleSave');
+
     // ------------------------- 项目内部相关 -----------------------------
 
 

+ 1 - 1
app/service/stage_bills_final.js

@@ -119,7 +119,7 @@ module.exports = app => {
                     c.negative_qc_tp = this.ctx.helper.add(c.negative_qc_tp, cp.negative_qc_pc_tp);
                     curPc.splice(curPc.indexOf(cp), 1);
                 }
-                if (!c.unit_price) {
+                if (!c.unit_price || (!p.used && c.used)) {
                     const bills = await this.ctx.service.ledger.getDataById(c.lid);
                     c.unit_price = bills ? bills.unit_price || 0 : 0;
                 }