فهرست منبع

1. 导入、更新其他标段工程量清单时,根据清单编号重排顺序
2. 中间计量,附件相关

MaiXinRong 3 سال پیش
والد
کامیت
9f64449d9c

+ 96 - 0
app/controller/stage_controller.js

@@ -325,6 +325,7 @@ module.exports = app => {
                             break;
                             break;
                         case 'detail':
                         case 'detail':
                             responseData.data.detailData = await this._getStageDetailData(ctx);
                             responseData.data.detailData = await this._getStageDetailData(ctx);
+                            responseData.data.detailAtt = await this.ctx.service.stageDetailAtt.getStageData(ctx.stage.id, ctx.stage.im_type);
                             break;
                             break;
                         case 'change':
                         case 'change':
                             responseData.data.changeData = await this._getStageChangeData(ctx);
                             responseData.data.changeData = await this._getStageChangeData(ctx);
@@ -739,6 +740,101 @@ module.exports = app => {
             }
             }
         }
         }
 
 
+        async uploadImFile(ctx) {
+            let stream;
+            try {
+                const parts = ctx.multipart({ autoFields: true });
+                let index = 0;
+                const create_time = Date.parse(new Date()) / 1000;
+                stream = await parts();
+                const uploadFiles = [];
+                while (stream !== undefined) {
+                    if (!stream.filename) throw '未发现上传文件!';
+
+                    const fileInfo = path.parse(stream.filename);
+                    const dirName = path.join('app', 'public', 'upload', this.ctx.tender.id.toString(), 'im');
+                    const fileName = `${ctx.session.sessionUser.accountId}_${create_time}_${index}${fileInfo.ext}`;
+
+                    // 保存文件
+                    await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, dirName, fileName));
+                    await sendToWormhole(stream);
+
+                    // 插入到stage_pay对应的附件列表中
+                    uploadFiles.push({
+                        file_id: this.ctx.app.uuid.v4(),
+                        filename: fileInfo.name,
+                        fileext: fileInfo.ext,
+                        filesize: Array.isArray(parts.field.size) ? parts.field.size[index] : parts.field.size,
+                        filepath: path.join(dirName, fileName),
+                        uid: ctx.session.sessionUser.accountId,
+                        in_time: moment(create_time * 1000).format('YYYY-MM-DD'),
+                        renew: ctx.stage.status === auditConst.status.checked,
+                    });
+                    ++index;
+                    if (Array.isArray(parts.field.size) && index < parts.field.size.length) {
+                        stream = await parts();
+                    } else {
+                        stream = undefined;
+                    }
+                }
+                const baseInfo = JSON.parse(parts.field.base);
+                const result = await ctx.service.stageDetailAtt.addFiles(baseInfo, uploadFiles);
+                ctx.body = { err: 0, mgs: '', data: result };
+            } catch (err) {
+                console.log(err);
+                // 失败需要消耗掉stream 以防卡死
+                if (stream) await sendToWormhole(stream);
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '上传附件失败');
+            }
+        }
+
+        async deleteImFile(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.uuid || !data.file_id) throw '数据错误';
+
+                const result = await ctx.service.stageDetailAtt.delFiles(data.uuid, data.file_id);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '删除附件失败');
+            }
+        }
+
+        async downloadImFile(ctx) {
+            try {
+                const uuid = ctx.query.im_id;
+                const file_id = ctx.query.file_id;
+                if (!uuid || !file_id) throw '数据错误';
+
+                const fileInfo = await ctx.service.stageDetailAtt.getFiles(uuid, file_id);
+                if (!fileInfo) throw '文件不存在';
+
+                const fileName = path.join(this.app.baseDir, fileInfo.filepath);
+                // 解决中文无法下载问题
+                const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
+                let disposition = '';
+                if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
+                    disposition = 'attachment; filename=' + encodeURIComponent(fileInfo.filename + fileInfo.fileext);
+                } else if (userAgent.indexOf('firefox') >= 0) {
+                    disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(fileInfo.filename + fileInfo.fileext) + '"';
+                } else {
+                    /* safari等其他非主流浏览器只能自求多福了 */
+                    disposition = 'attachment; filename=' + new Buffer(fileInfo.filename + fileInfo.fileext).toString('binary');
+                }
+                ctx.response.set({
+                    'Content-Type': 'application/octet-stream',
+                    'Content-Disposition': disposition,
+                    'Content-Length': fileInfo.filesize,
+                });
+                ctx.body = await fs.createReadStream(fileName);
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '下载文件失败');
+            }
+        }
+
         async _updateStageCache(ctx, payCalculator) {
         async _updateStageCache(ctx, payCalculator) {
             await ctx.service.stage.update({
             await ctx.service.stage.update({
                 check_calc: false,
                 check_calc: false,

+ 28 - 2
app/lib/sum_load.js

@@ -134,6 +134,21 @@ class loadGclBaseTree {
     gather(source, parent) {}
     gather(source, parent) {}
     getUpdateData() {}
     getUpdateData() {}
 
 
+    resortChildrenByCode(node) {
+        const helper = this.ctx.helper;
+        if (!node.children || node.children.length === 0) return;
+        node.children.sort((x, y) => {
+            return helper.compareCode(x.b_code, y.b_code);
+        });
+        for (const [i, c] of node.children.entries()) {
+            this.resortChildrenByCode(c);
+            c.order = i + 1;
+        }
+    }
+    resortByCode() {
+        this.resortChildrenByCode(this.parent);
+    }
+
     recursiveCalculate(dealBills, node) {
     recursiveCalculate(dealBills, node) {
         if (node.children && node.children.length > 0) {
         if (node.children && node.children.length > 0) {
             for (const child of node.children) {
             for (const child of node.children) {
@@ -258,13 +273,22 @@ class updateReviseGclTree extends loadGclBaseTree {
                         b_code: bn.b_code, name: bn.name, unit: bn.unit,
                         b_code: bn.b_code, name: bn.name, unit: bn.unit,
                         deal_qty: bn.deal_qty, sgfh_qty: bn.sgfh_qty, sjcl_qty: bn.sjcl_qty, qtcl_qty: bn.qtcl_qty, qty: bn.quantity, type: 'less',
                         deal_qty: bn.deal_qty, sgfh_qty: bn.sgfh_qty, sjcl_qty: bn.sjcl_qty, qtcl_qty: bn.qtcl_qty, qty: bn.quantity, type: 'less',
                     });
                     });
+                    if (bn.order !== bn.org_order) {
+                        result.update.push({
+                            id: bn.id, ledger_id: bn.ledger_id, order: bn.order,
+                        });
+                    }
                 } else if (bn.sjcl_qty !== bn.org_sjcl_qty || bn.qtcl_qty !== bn.org_qtcl_qty || bn.sgfh_qty !== bn.org_sgfh_qty) {
                 } else if (bn.sjcl_qty !== bn.org_sjcl_qty || bn.qtcl_qty !== bn.org_qtcl_qty || bn.sgfh_qty !== bn.org_sgfh_qty) {
                     result.update.push({
                     result.update.push({
-                        id: bn.id, ledger_id: bn.ledger_id, unit_price: bn.unit_price || 0,
+                        id: bn.id, ledger_id: bn.ledger_id, unit_price: bn.unit_price || 0, order: bn.order,
                         deal_qty: bn.deal_qty, deal_tp: bn.deal_tp || 0,
                         deal_qty: bn.deal_qty, deal_tp: bn.deal_tp || 0,
                         sgfh_qty: bn.sgfh_qty, sjcl_qty: bn.sjcl_qty, qtcl_qty: bn.qtcl_qty, quantity: bn.quantity,
                         sgfh_qty: bn.sgfh_qty, sjcl_qty: bn.sjcl_qty, qtcl_qty: bn.qtcl_qty, quantity: bn.quantity,
                         sgfh_tp: bn.sgfh_tp || 0, sjcl_tp: bn.sjcl_tp || 0, qtcl_tp: bn.qtcl_tp || 0, total_price: bn.total_price || 0,
                         sgfh_tp: bn.sgfh_tp || 0, sjcl_tp: bn.sjcl_tp || 0, qtcl_tp: bn.qtcl_tp || 0, total_price: bn.total_price || 0,
-                    })
+                    });
+                } else if (bn.order != bn.org_order) {
+                    result.update.push({
+                        id: bn.id, ledger_id: bn.ledger_id, order: bn.order,
+                    });
                 }
                 }
             }
             }
         }
         }
@@ -411,6 +435,7 @@ class sumLoad {
                 this.recusiveLoadGatherGcl(top, null);
                 this.recusiveLoadGatherGcl(top, null);
             }
             }
         }
         }
+        this.loadTree.resortByCode();
         const dealBills = await this.ctx.service.dealBills.getAllDataByCondition({ where: { tender_id: this.ctx.tender.id } });
         const dealBills = await this.ctx.service.dealBills.getAllDataByCondition({ where: { tender_id: this.ctx.tender.id } });
         this.loadTree.calculateAll(dealBills);
         this.loadTree.calculateAll(dealBills);
         return this.loadTree;
         return this.loadTree;
@@ -441,6 +466,7 @@ class sumLoad {
                 this.recusiveLoadGatherGcl(top, null);
                 this.recusiveLoadGatherGcl(top, null);
             }
             }
         }
         }
+        this.loadTree.resortByCode();
         const dealBills = await this.ctx.service.dealBills.getAllDataByCondition({ where: { tender_id: this.ctx.tender.id } });
         const dealBills = await this.ctx.service.dealBills.getAllDataByCondition({ where: { tender_id: this.ctx.tender.id } });
         this.loadTree.calculateAll(dealBills);
         this.loadTree.calculateAll(dealBills);
         return this.loadTree;
         return this.loadTree;

+ 94 - 1
app/public/js/stage.js

@@ -2052,7 +2052,7 @@ $(document).ready(() => {
         SpreadJsObj.resetTopAndSelect(spSpread.getActiveSheet());
         SpreadJsObj.resetTopAndSelect(spSpread.getActiveSheet());
         // 加载中间计量
         // 加载中间计量
         stageIm.init(stage, imType, tenderInfo.decimal);
         stageIm.init(stage, imType, tenderInfo.decimal);
-        stageIm.loadData(result.ledgerData, result.posData, result.detailData, result.changeData);
+        stageIm.loadData(result.ledgerData, result.posData, result.detailData, result.changeData, result.detailAtt);
 
 
         errorList.loadHisErrorData();
         errorList.loadHisErrorData();
         checkList.loadHisCheckData();
         checkList.loadHisCheckData();
@@ -2469,6 +2469,7 @@ $(document).ready(() => {
     class Detail {
     class Detail {
         constructor (obj) {
         constructor (obj) {
             const self = this;
             const self = this;
+
             this.spreadSetting = {
             this.spreadSetting = {
                 cols: [
                 cols: [
                     {title: '编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
                     {title: '编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
@@ -2478,6 +2479,12 @@ $(document).ready(() => {
                         title: stage.im_type === imType.tz.value ? '本期计量金额' : '本期计量数量',
                         title: stage.im_type === imType.tz.value ? '本期计量金额' : '本期计量数量',
                         colSpan: '1', rowSpan: '1', field: 'jl', hAlign: 2, width: 85, formatter: '@', readOnly: true
                         colSpan: '1', rowSpan: '1', field: 'jl', hAlign: 2, width: 85, formatter: '@', readOnly: true
                     },
                     },
+                    {
+                        title: '附件', colSpan: '1', rowSpan: '1', field: 'attachment', hAlign: 0, width: 60, readOnly: true, cellType: 'imageBtn',
+                        normalImg: '#rela-file-icon', hoverImg: '#rela-file-hover', getValue: function (data) {
+                            return data.attachment ? data.attachment.length : 0;
+                        }
+                    },
                 ],
                 ],
                 headRows: 1,
                 headRows: 1,
                 emptyRows: 0,
                 emptyRows: 0,
@@ -2488,6 +2495,10 @@ $(document).ready(() => {
                 font: '12px 微软雅黑',
                 font: '12px 微软雅黑',
                 readOnly: readOnly,
                 readOnly: readOnly,
                 selectedBackColor: '#fffacd',
                 selectedBackColor: '#fffacd',
+                imageClick: function (data) {
+                    self.makeAttTable(data);
+                    $('#im-file').modal('show');
+                }
             };
             };
             this.spread = SpreadJsObj.createNewSpread(obj[0]);
             this.spread = SpreadJsObj.createNewSpread(obj[0]);
             this.sheet = this.spread.getActiveSheet();
             this.sheet = this.spread.getActiveSheet();
@@ -2678,10 +2689,92 @@ $(document).ready(() => {
             this._initImTypeSetRela();
             this._initImTypeSetRela();
             this._initModifyDetail();
             this._initModifyDetail();
             this._initLocateRela();
             this._initLocateRela();
+            this._initAttRela();
             // 草图相关
             // 草图相关
             this._initImageRela();
             this._initImageRela();
             this.reBuildImData();
             this.reBuildImData();
         }
         }
+        makeAttTable (data) {
+            let html = [];
+            if (data.attachment) {
+                for (const att of data.attachment) {
+                    const delHtml = (parseInt(att.uid) === userID || data.uid === userID || (data.uid === -1 && userID === stage.user_id))
+                        ? '<a class="delete-att text-danger" href="javascript:void(0);" data-imid="'+ data.att_uuid +'" data-attid="'+ att.file_id +'" title="删除"><i class="fa fa-remove "></i></a>'
+                        : '';
+                    html.push('<tr><td style="width: 200px">' + att.filename + att.fileext + '</td><td>' + att.username + '</td><td>' + att.in_time + '</td>',
+                        '<td><a href="'+ window.location.pathname + '/im-file/download?im_id='+ data.att_uuid +'&file_id='+ att.file_id +'" title="下载"><i class="fa fa-download "></i></a> ',
+                        delHtml, '</td></tr>');
+                }
+            }
+            $('#im-attList').html(html.join(''));
+        };
+        _initAttRela() {
+            // 上传附件
+            const self = this;
+            $('#upload-im-file').change(function () {
+                const files = this.files;
+                const sels = self.sheet.getSelections();
+                const select = SpreadJsObj.getSelectObject(self.sheet);
+                if (!select) return;
+
+                const formData = new FormData();
+                const baseInfo = {};
+                if (select.att_uuid) {
+                    baseInfo.uuid = select.att_uuid;
+                } else {
+                    baseInfo.im_type = select.im_type;
+                    baseInfo.lid = select.lid;
+                    baseInfo.pid = select.pid;
+                    baseInfo.code = select.code;
+                    baseInfo.name = select.name;
+                    baseInfo.unit = select.unit;
+                    baseInfo.unit_price = select.unit_price;
+                    baseInfo.pos_name = select.pos_name;
+                }
+                formData.append('base', JSON.stringify(baseInfo));
+
+                for (const file of files) {
+                    if (file === undefined) {
+                        toast('未选择上传文件!', 'error');
+                        return false;
+                    }
+                    const filesize = file.size;
+                    if (filesize > 30 * 1024 * 1024) {
+                        toast('存在上传文件大小过大!', 'error');
+                        return false;
+                    }
+                    const fileext = '.' + file.name.toLowerCase().split('.').splice(-1)[0];
+                    if (whiteList.indexOf(fileext) === -1) {
+                        toast('只能上传指定格式的附件!', 'error');
+                        return false;
+                    }
+                    formData.append('size', filesize);
+                    formData.append('file[]', file);
+                }
+                postDataWithFile(window.location.pathname + '/im-file/upload', formData, function (data) {
+                    stageIm.loadUpdateDetailAtt(data);
+                    SpreadJsObj.reLoadRowData(self.sheet, sels[0].row);
+                    self.makeAttTable(select);
+                    $('#upload-im-file').val('');
+                });
+            });
+
+            // 删除附件
+            $('body').on('click', '.delete-att' ,function () {
+                const sels = self.sheet.getSelections();
+                const select = SpreadJsObj.getSelectObject(self.sheet);
+                if (!select) return;
+
+                const uuid = $(this).attr('data-imid');
+                const file_id = $(this).attr('data-attid');
+
+                postData(window.location.pathname + '/im-file/del', { uuid, file_id }, function (result) {
+                    stageIm.loadUpdateDetailAtt(result);
+                    SpreadJsObj.reLoadRowData(self.sheet, sels[0]);
+                    self.makeAttTable(select);
+                });
+            });
+        }
         _initImTypeSetRela() {
         _initImTypeSetRela() {
             const self = this;
             const self = this;
             const gatherConfirmPopover = {
             const gatherConfirmPopover = {

+ 100 - 36
app/public/js/stage_im.js

@@ -13,7 +13,7 @@ const stageIm = (function () {
     const resetFields = ['peg', 'bw', 'xm', 'drawing_code', 'calc_memo', 'position', 'jldy'];
     const resetFields = ['peg', 'bw', 'xm', 'drawing_code', 'calc_memo', 'position', 'jldy'];
     const splitChar = '-';
     const splitChar = '-';
     const mergeChar = ';';
     const mergeChar = ';';
-    let stage, imType, decimal, details, changes, ImData, pre, orgImData;
+    let stage, imType, decimal, details, changes, detailsAtt, ImData, pre, orgImData;
     const gsTreeSetting = {
     const gsTreeSetting = {
         id: 'ledger_id',
         id: 'ledger_id',
         pid: 'ledger_pid',
         pid: 'ledger_pid',
@@ -66,7 +66,7 @@ const stageIm = (function () {
         }
         }
     }
     }
 
 
-    function loadData (ledger, pos, stageDetail, stageChange) {
+    function loadData (ledger, pos, stageDetail, stageChange, stageDetailAtt) {
         gsTree.loadDatas(ledger);
         gsTree.loadDatas(ledger);
         treeCalc.calculateAll(gsTree);
         treeCalc.calculateAll(gsTree);
 
 
@@ -77,9 +77,11 @@ const stageIm = (function () {
         details = stageDetail;
         details = stageDetail;
 
 
         changes = stageChange;
         changes = stageChange;
+
+        detailsAtt = stageDetailAtt;
     }
     }
 
 
-    function loadData4Rela(ledger, pos, stageDetail, stageChange) {
+    function loadData4Rela(ledger, pos, stageDetail, stageChange, stageDetailAtt) {
         gsTree.loadDatas(ledger);
         gsTree.loadDatas(ledger);
         treeCalc.calculateAll(gsTree);
         treeCalc.calculateAll(gsTree);
 
 
@@ -89,6 +91,8 @@ const stageIm = (function () {
         details = stageDetail;
         details = stageDetail;
 
 
         changes = stageChange;
         changes = stageChange;
+
+        detailsAtt = stageDetailAtt;
     }
     }
 
 
     // function loadData (ledger, curStage, pos, curPosStage, stageDetail) {
     // function loadData (ledger, curStage, pos, curPosStage, stageDetail) {
@@ -286,54 +290,81 @@ const stageIm = (function () {
         im.calc_img_org = detail.calc_img_org;
         im.calc_img_org = detail.calc_img_org;
         im.calc_img_remark = detail.calc_img_remark;
         im.calc_img_remark = detail.calc_img_remark;
     }
     }
+    function loadAtt(im, att) {
+        im.att_uuid = att.uuid;
+        im.attachment = att.attachment;
+    }
 
 
-    function checkTzCustomDetail(im) {
-        const cd = _.find(details, function (d) {
-            return im.lid === d.lid &&
-                (!im.code || im.code === d.code) &&
-                (!im.name || im.name === d.name) &&
-                (!im.unit || im.unit === d.unit) &&
-                (!im.pid || im.pid === d.pid) &&
-                (!im.pos_name || im.pos_name === d.pos_name);
+    function findTzRela(rela, data) {
+        return _.find(rela, function (d) {
+            return data.lid === d.lid &&
+                (!data.code || data.code === d.code) &&
+                (!data.name || data.name === d.name) &&
+                (!data.unit || data.unit === d.unit) &&
+                (!data.pid || data.pid === d.pid) &&
+                (!data.pos_name || data.pos_name === d.pos_name);
+        });
+    }
+    function findZlRela(rela, data) {
+        return _.find(rela, function (d) {
+            return data.lid === d.lid &&
+                (!data.code || data.code === d.code) &&
+                (!data.name || data.name === d.name) &&
+                (!data.unit || data.unit === d.unit) &&
+                checkZero(ZhCalc.sub(data.unit_price, d.unit_price)) &&
+                (!data.pid || data.pid === d.pid) &&
+                (!data.pos_name || data.pos_name === d.pos_name);
+        });
+    }
+
+    function findBwRela(rela, data) {
+        return _.find(rela, function (d) {
+            return data.lid === d.lid &&
+                (!data.code || data.code === d.code) &&
+                (!data.name || data.name === d.name) &&
+                (!data.unit || data.unit === d.unit) &&
+                checkZero(ZhCalc.sub(data.unit_price, d.unit_price)) &&
+                (!data.pid || data.pid === d.pid) &&
+                (!data.pos_name || data.pos_name === d.pos_name);
         });
         });
+    }
+
+    function findBbRela(rela, data) {
+        return _.find(rela, function (d) {
+            return data.lid === d.lid &&
+                (!data.name || data.name === d.name) &&
+                (!data.unit || data.unit === d.unit) &&
+                (!data.pid || data.pid === d.pid) &&
+                (!data.pos_name || data.pos_name === d.pos_name);
+        });
+    }
+
+    function checkTzCustomDetail(im, details) {
+        const cd = findTzRela(details, im);
         if (cd) loadCustomDetail(im, cd);
         if (cd) loadCustomDetail(im, cd);
+        const att = findTzRela(detailsAtt, im);
+        if (att) loadAtt(im, att);
     }
     }
 
 
     function checkZlCustomDetail(im) {
     function checkZlCustomDetail(im) {
-        const cd = _.find(details, function (d) {
-            return im.lid === d.lid &&
-                (!im.code || im.code === d.code) &&
-                (!im.name || im.name === d.name) &&
-                (!im.unit || im.unit === d.unit) &&
-                checkZero(ZhCalc.sub(im.unit_price, d.unit_price)) &&
-                (!im.pid || im.pid === d.pid) &&
-                (!im.pos_name || im.pos_name === d.pos_name);
-        });
+        const cd = findZlRela(details, im);
         if (cd) loadCustomDetail(im, cd);
         if (cd) loadCustomDetail(im, cd);
+        const att = findZlRela(detailsAtt, im);
+        if (att) loadAtt(im, att);
     }
     }
 
 
     function checkBwCustomDetail(im) {
     function checkBwCustomDetail(im) {
-        const cd = _.find(details, function (d) {
-            return im.lid === d.lid &&
-                (!im.code || im.code === d.code) &&
-                (!im.name || im.name === d.name) &&
-                (!im.unit || im.unit === d.unit) &&
-                checkZero(ZhCalc.sub(im.unit_price, d.unit_price)) &&
-                (!im.pid || im.pid === d.pid) &&
-                (!im.pos_name || im.pos_name === d.pos_name);
-        });
+        const cd = findBwRela(details, im);
         if (cd) loadCustomDetail(im, cd);
         if (cd) loadCustomDetail(im, cd);
+        const att = findBwRela(detailsAtt, im);
+        if (att) loadAtt(im, att);
     }
     }
 
 
     function checkBbCustomDetail(im) {
     function checkBbCustomDetail(im) {
-        const cd = _.find(details, function (d) {
-            return im.lid === d.lid &&
-                (!im.name || im.name === d.name) &&
-                (!im.unit || im.unit === d.unit) &&
-                (!im.pid || im.pid === d.pid) &&
-                (!im.pos_name || im.pos_name === d.pos_name);
-        });
+        const cd = findBbRela(details, im);
         if (cd) loadCustomDetail(im, cd);
         if (cd) loadCustomDetail(im, cd);
+        const att = findBbRela(detailsAtt, im);
+        if (att) loadAtt(im, att);
     }
     }
 
 
     function checkCustomDetail(im) {
     function checkCustomDetail(im) {
@@ -1027,6 +1058,38 @@ const stageIm = (function () {
         }
         }
     }
     }
 
 
+    function loadUpdateDetailAtt (data) {
+        const datas = data instanceof Array ? data : [data];
+        for (const d of datas) {
+            const detail = _.find(detailsAtt, {uuid: d.uuid});
+            if (detail) {
+                _.assignInWith(detail, d, function (oV, sV) {
+                    return !_.isUndefined(sV) ? sV : oV;
+                });
+            } else {
+                details.push(d);
+            }
+            let imData = _.find(ImData, {lid: d.lid, att_uuid: d.uuid});
+            if (!imData) {
+                switch (stage.im_type) {
+                    case imType.tz.value:
+                        imData = findTzRela(ImData, d);
+                        break;
+                    case imType.zl.value:
+                        imData = findZlRela(ImData, d);
+                        break;
+                    case imType.bw.value:
+                        imData = findBwRela(ImData, d);
+                        break;
+                    case imType.bb.value:
+                        imData = findBbRela(ImData, d);
+                        break;
+                }
+            }
+            if (imData) loadAtt(imData, d);
+        }
+    }
+
     function loadUpdateLedgerData(data, refreshNodes) {
     function loadUpdateLedgerData(data, refreshNodes) {
         gsTree.loadPostStageData(data);
         gsTree.loadPostStageData(data);
         if (data.change) {
         if (data.change) {
@@ -1115,6 +1178,7 @@ const stageIm = (function () {
         loadData4Rela,
         loadData4Rela,
         buildImData,
         buildImData,
         loadUpdateDetailData,
         loadUpdateDetailData,
+        loadUpdateDetailAtt,
         loadUpdateLedgerData,
         loadUpdateLedgerData,
         loadUpdatePosData,
         loadUpdatePosData,
         loadUpdateChangeData,
         loadUpdateChangeData,

+ 3 - 0
app/router.js

@@ -260,6 +260,9 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/use-change', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.useChange');
     app.post('/tender/:id/measure/stage/:order/use-change', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.useChange');
     app.post('/tender/:id/measure/stage/:order/check', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.check');
     app.post('/tender/:id/measure/stage/:order/check', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.check');
     app.post('/tender/:id/measure/stage/:order/save/cooperation', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.saveCooperationData');
     app.post('/tender/:id/measure/stage/:order/save/cooperation', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.saveCooperationData');
+    app.post('/tender/:id/measure/stage/:order/im-file/upload', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.uploadImFile');
+    app.post('/tender/:id/measure/stage/:order/im-file/del', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.deleteImFile');
+    app.get('/tender/:id/measure/stage/:order/im-file/download', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.downloadImFile');
 
 
     // 计量附件
     // 计量附件
     app.post('/tender/:id/measure/stage/:order/upload/file', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.uploadFile');
     app.post('/tender/:id/measure/stage/:order/upload/file', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.uploadFile');

+ 0 - 2
app/service/stage_bonus.js

@@ -107,8 +107,6 @@ module.exports = app => {
             const datas = data instanceof Array ? data : [data];
             const datas = data instanceof Array ? data : [data];
             const orgDatas = await this.getAllDataByCondition({where: {sid: this.ctx.stage.id, id: this.ctx.helper._.map(datas, 'id')}});
             const orgDatas = await this.getAllDataByCondition({where: {sid: this.ctx.stage.id, id: this.ctx.helper._.map(datas, 'id')}});
             for (const od of orgDatas) {
             for (const od of orgDatas) {
-                console.log(od);
-                console.log(this.ctx.stage.id);
                 if (od.sid !== this.ctx.stage.id) throw '非本期新增数据,不可删除';
                 if (od.sid !== this.ctx.stage.id) throw '非本期新增数据,不可删除';
             }
             }
             await this.db.delete(this.tableName, {id: datas});
             await this.db.delete(this.tableName, {id: datas});

+ 2 - 1
app/service/stage_change.js

@@ -409,7 +409,8 @@ module.exports = app => {
             const sql = 'SELECT sc.*, c.quality FROM ' + this.tableName + ' sc' +
             const sql = 'SELECT sc.*, c.quality FROM ' + this.tableName + ' sc' +
                 '  LEFT JOIN ' + this.ctx.service.change.tableName + ' c ON sc.cid = c.cid' +
                 '  LEFT JOIN ' + this.ctx.service.change.tableName + ' c ON sc.cid = c.cid' +
                 '  WHERE sid = ?';
                 '  WHERE sid = ?';
-            const data = await this.db.query(sql, [stage.id]);
+            let data = await this.db.query(sql, [stage.id]);
+            data = helper.filterLastestData(data, ['lid', 'pid', 'cbid'], 'stimes', 'sorder');
             const bqData = [];
             const bqData = [];
             for (const d of data) {
             for (const d of data) {
                 if (!d.qty) continue;
                 if (!d.qty) continue;

+ 140 - 0
app/service/stage_detail_att.js

@@ -0,0 +1,140 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2021/10/25
+ * @version
+ */
+
+const ImTypeConst = require('../const/tender').imType;
+
+module.exports = app => {
+    class StageDetailAtt extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'stage_detail_attachment';
+        }
+
+        async getDetailAtt(field) {
+            if (!field.uuid && field.im_type === undefined) throw '数据错误';
+
+            if (field.uuid) {
+                return this.getDataByCondition({ uuid: field.uuid });
+            } else {
+                switch (field.im_type) {
+                    case ImTypeConst.zl.value:
+                    case ImTypeConst.bw.value:
+                        return this.getDataByCondition({
+                            im_type: field.im_type,
+                            lid: field.lid, code: field.code, name: field.name, unit: field.unit, unit_price: field.unit_price,
+                            pid: field.pid, pos_name: field.pos_name
+                        });
+                    case ImTypeConst.tz.value:
+                        return this.getDataByCondition({
+                            im_type: field.im_type,
+                            lid: field.lid, code: field.code, name: field.name, unit: field.unit,
+                            pid: field.pid, pos_name: field.pos_name
+                        });
+                    case ImTypeConst.bb.value:
+                        return this.getDataByCondition({
+                            im_type: field.im_type,
+                            lid: field.lid, name: field.name, unit: field.unit,
+                            pid: field.pid, pos_name: field.pos_name
+                        });
+                }
+            }
+        }
+
+        _analysisDetailAtt(data) {
+            if (data instanceof Array) {
+                for (const a of data) {
+                    a.attachment = a.attachment ? JSON.parse(a.attachment) : [];
+                }
+            } else {
+                data.attachment = data.attachment ? JSON.parse(data.attachment) : [];
+            }
+        }
+
+        async getUserTemp(uid) {
+            if (!this.userTemp[uid])
+            this.userTemp[uid] = await this.ctx.service.projectAccount.getAccountInfoById(uid);
+            return this.userTemp[uid];
+        }
+
+        async _complete4Output(data) {
+            this.userTemp = {};
+            const datas = data instanceof Array ? data : [data];
+            for (const d of datas) {
+                for (const a of d.attachment) {
+                    delete a.filepath;
+                    a.username = await this.getUserTemp.name;
+                }
+            }
+        }
+
+        async getStageData(sid, im_type) {
+            const result = await this.getAllDataByCondition({ where: { sid: sid, im_type: im_type } });
+            this._analysisDetailAtt(result);
+            await this._complete4Output(result);
+            return result;
+        }
+
+        async addFiles(field, files) {
+            const detailAtt = await this.getDetailAtt(field);
+            if (detailAtt) {
+                detailAtt && this._analysisDetailAtt(detailAtt);
+                for (const f of files) {
+                    detailAtt.attachment.push(f);
+                }
+                await this.db.update(this.tableName, { id: detailAtt.id, attachment: JSON.stringify(detailAtt.attachment) });
+                await this._complete4Output(detailAtt);
+                return detailAtt;
+            } else {
+                const insertData = {
+                    tid: this.ctx.tender.id, sid: this.ctx.stage.id, sorder: this.ctx.stage.order,
+                    uuid: this.uuid.v4(), im_type: field.im_type,
+                    lid: field.lid, code: field.code, name: field.name, unit: field.unit, unit_price: field.unit_price,
+                    pid: field.pid, pos_name: field.pos_name,
+                    attachment: JSON.stringify(files),
+                };
+                await this.db.insert(this.tableName, insertData);
+                this._analysisDetailAtt(insertData);
+                await this._complete4Output(insertData);
+                return insertData;
+            }
+        }
+
+        async delFiles(uuid, fileId) {
+            const detailAtt = await this.getDetailAtt({ uuid });
+            if (!detailAtt) throw '数据错误';
+            await this._analysisDetailAtt(detailAtt);
+
+            const index = detailAtt.attachment.findIndex(x => { return x.file_id === fileId });
+            if (index < 0) throw '不存在改文件';
+            if (detailAtt.attachment[index].uid !== this.ctx.session.sessionUser.accountId) throw '您无权删除该文件';
+
+            detailAtt.attachment.splice(index, 1);
+            await this.db.update(this.tableName, { id: detailAtt.id, attachment: JSON.stringify(detailAtt.attachment) });
+            await this._complete4Output(detailAtt);
+            return detailAtt;
+        }
+
+        async getFiles(uuid, fileId) {
+            const detailAtt = await this.getDetailAtt({ uuid });
+            if (!detailAtt) throw '数据错误';
+            await this._analysisDetailAtt(detailAtt);
+
+            return detailAtt.attachment.find(x => { return x.file_id === fileId });
+        }
+    }
+
+    return StageDetailAtt;
+};

+ 1 - 1
config/config.default.js

@@ -147,7 +147,7 @@ module.exports = appInfo => {
     config.gzip = {
     config.gzip = {
         threshold: 2048,
         threshold: 2048,
         // 下载的url要用正则忽略
         // 下载的url要用正则忽略
-        ignore: /(\w*)(\/download\/file)|(\/profile\/qrCode)|(\/download\/compresse-file)|(\/compresse\/file)(\w*)/ig,
+        ignore: /(\w*)(\/download\/file)|(\/profile\/qrCode)|(\/download\/compresse-file)|(\/compresse\/file)|(\/im-file\/download)(\w*)/ig,
     };
     };
 
 
     config.customLogger = {
     config.customLogger = {

+ 2 - 1
sql/update.sql

@@ -46,4 +46,5 @@ CREATE TABLE `zh_stage_temp_land` (
   `pre_used` tinyint(4) DEFAULT '0' COMMENT '往期是否使用',
   `pre_used` tinyint(4) DEFAULT '0' COMMENT '往期是否使用',
   `memo` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '备注',
   `memo` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '备注',
   PRIMARY KEY (`id`)
   PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+