Jelajahi Sumber

Merge branch 'dev' of http://192.168.1.41:3000/maixinrong/Calculation into dev

Tony Kang 1 hari lalu
induk
melakukan
cbb5e8d431
45 mengubah file dengan 2006 tambahan dan 926 penghapusan
  1. 4 0
      app/const/contract.js
  2. 31 1
      app/controller/contract_controller.js
  3. 82 45
      app/controller/ledger_controller.js
  4. 48 0
      app/controller/stage_controller.js
  5. 1 1
      app/middleware/revise_check.js
  6. 3 3
      app/public/js/change_information.js
  7. 21 3
      app/public/js/change_revise.js
  8. 1024 348
      app/public/js/contract_detail.js
  9. 1 0
      app/public/js/ledger.js
  10. 1 1
      app/public/js/pos_calc_tmpl.js
  11. 15 1
      app/public/js/setting.js
  12. 154 23
      app/public/js/shares/ai_trans.js
  13. 1 0
      app/public/js/shares/cs_tools.js
  14. 38 356
      app/public/js/stage.js
  15. 5 1
      app/router.js
  16. 18 3
      app/service/contract.js
  17. 184 0
      app/service/contract_pay.js
  18. 15 0
      app/service/contract_tree.js
  19. 1 0
      app/service/cost_stage_book.js
  20. 4 3
      app/service/pay.js
  21. 21 2
      app/service/phase_pay.js
  22. 3 3
      app/service/shenpi_audit.js
  23. 6 1
      app/service/stage.js
  24. 19 20
      app/service/stage_att.js
  25. 31 0
      app/service/stage_pay.js
  26. 1 1
      app/service/std_gcl_list.js
  27. 1 1
      app/service/sub_proj_permission.js
  28. 1 1
      app/service/temp_file.js
  29. 1 0
      app/service/tender.js
  30. 3 3
      app/view/change/modal.ejs
  31. 6 1
      app/view/change/revise.ejs
  32. 7 5
      app/view/contract/col_set.ejs
  33. 7 1
      app/view/contract/detail.ejs
  34. 94 2
      app/view/contract/detail_modal.ejs
  35. 5 4
      app/view/setting/user_modal.ejs
  36. 85 10
      app/view/shares/ai_trans.ejs
  37. 26 0
      app/view/shares/upload_att.ejs
  38. 1 1
      app/view/stage/index.ejs
  39. 2 21
      app/view/stage/modal.ejs
  40. 0 26
      app/view/sub_proj/progress_modal.ejs
  41. 1 27
      app/view/sub_proj/push_modal.ejs
  42. 17 2
      app/view/tender/detail.ejs
  43. 4 3
      app/view/tender/detail_modal.ejs
  44. 2 1
      config/web.js
  45. 11 1
      sql/update.sql

+ 4 - 0
app/const/contract.js

@@ -28,6 +28,7 @@ const colSet = {
         { name: '合同类型', field: 'type', fixed: ['alias'], gd: true },
         { name: '甲方', field: 'party_a', fixed: ['alias'], gd: true },
         { name: '乙方', field: 'party_b', fixed: ['alias'], gd: true },
+        { name: '结算金额', field: 'settle_price', fixed: ['alias'], gd: true },
         { name: '累计应付', field: 'yf_price', fixed: ['alias'] },
         { name: '应付进度', field: 'stackedBar', fixed: ['alias'] },
         { name: '累计实付', field: 'sf_price', fixed: ['alias'] },
@@ -41,6 +42,7 @@ const colSet = {
         { name: '合同类型', field: 'type', fixed: ['alias'], gd: true },
         { name: '甲方', field: 'party_a', fixed: ['alias'], gd: true },
         { name: '乙方', field: 'party_b', fixed: ['alias'], gd: true },
+        { name: '结算金额', field: 'settle_price', fixed: ['alias'], gd: true },
         { name: '累计应回', field: 'yf_price', fixed: ['alias'] },
         { name: '应回进度', field: 'stackedBar', fixed: ['alias'] },
         { name: '累计实回', field: 'sf_price', fixed: ['alias'] },
@@ -57,6 +59,7 @@ const defaultColSet = {
         { field: 'type', show: 1, gd: true },
         { field: 'party_a', show: 0, gd: true },
         { field: 'party_b', show: 0, gd: true },
+        { field: 'settle_price', show: 0, gd: true },
         { field: 'yf_price', show: 1 },
         { field: 'stackedBar', show: 1 },
         { field: 'sf_price', show: 0 },
@@ -70,6 +73,7 @@ const defaultColSet = {
         { field: 'type', show: 1, gd: true },
         { field: 'party_a', show: 0, gd: true },
         { field: 'party_b', show: 0, gd: true },
+        { field: 'settle_price', show: 0, gd: true },
         { field: 'yf_price', show: 1 },
         { field: 'stackedBar', show: 1 },
         { field: 'sf_price', show: 0 },

+ 31 - 1
app/controller/contract_controller.js

@@ -282,7 +282,7 @@ module.exports = app => {
                 const contractOptions = ctx.helper._.cloneDeep(ctx.contractOptions);
                 contractOptions.contract_type = ctx.contract_type;
                 const contractTreeAudits = await ctx.service.contractTreeAudit.getAllDataByCondition({ where: contractOptions });
-                const [stdBills, stdChapters] = ctx.contract.tender ? await this.ctx.service.valuation.getValuationStdList(
+                const [stdBills, stdChapters] = ctx.contract_tender ? await this.ctx.service.valuation.getValuationStdList(
                     ctx.contract.valuation, ctx.contract.measure_type) : await ctx.service.budgetStd.getStdList(ctx.subProject.std_id, 'yu');
                 const commonJson = ctx.subProject.common_json ? JSON.parse(ctx.subProject.common_json) : null;
                 const types = commonJson && commonJson[(ctx.contract_tender ? 'tender_' : '') + 'contract_type'] ? commonJson[(ctx.contract_tender ? 'tender_' : '') + 'contract_type'] : [];
@@ -634,6 +634,36 @@ module.exports = app => {
                     case 'del-contract-supplement':
                         responseData.data = await ctx.service.contractSupplement.del(options, data.postData.select, data.postData.supplement);
                         break;
+                    case 'get-tenders':
+                        // 获取用户新建标段权利
+                        const accountInfo = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                        const userPermission = accountInfo !== undefined && accountInfo.permission !== ''
+                            ? JSON.parse(accountInfo.permission) : null;
+                        const tenderList = await ctx.service.tender.getBuildList('', userPermission, ctx.session.sessionUser.is_admin);
+                        responseData.data.categoryData = await this.ctx.service.category.getAllCategory(ctx.subProject);
+                        responseData.data.tenders = tenderList;
+                        break;
+                    case 'get-tender-pays':
+                        responseData.data.tender = await ctx.service.tender.getDataById(data.postData.tid);
+                        if (!responseData.data.tender) {
+                            throw '关联标段不存在';
+                        }
+                        // 判断项目开没开启独立支付合同
+                        if (ctx.subProject.page_show.phasePay) {
+                            // 直接读phasePay表数据并展示
+                            responseData.data.stages = await ctx.service.phasePay.getDatasForContract(data.postData.tid);
+                        } else {
+                            // 读取期支付数据
+                            responseData.data.stages = await ctx.service.stagePay.getDatasForContract(data.postData.tid);
+                        }
+                        break;
+                    case 'add-tender-pays':
+                        responseData.data = await ctx.service.contractPay.addTenderPays(
+                            options,
+                            data.postData.select,
+                            data.postData.pays
+                        );
+                        break;
                     default:
                         throw '未知操作';
                 }

+ 82 - 45
app/controller/ledger_controller.js

@@ -1186,6 +1186,77 @@ module.exports = app => {
             }
         }
 
+        async aiRelaFile(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.rela_id || !data.rela_type) throw '参数错误';
+
+                const files = await ctx.service.tempFile.getFiles({ where: { tid: ctx.tender.id, rela_id: data.rela_id, rela_type: data.rela_type} });
+                ctx.body = { err: 0, msg: '', data: files }
+            } catch(err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '获取历史文件失败');
+            }
+        }
+        async aiUploadFile(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 user = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                const rela_id = parts.field.rela_id;
+                const rela_type = parts.field.rela_type;
+
+                const uploadfiles = [];
+                while (stream !== undefined) {
+                    if (!stream.filename) throw '未发现文件!';
+
+                    const fileInfo = path.parse(stream.filename);
+                    const filepath = `app/public/upload/${ctx.tender.id}/temp/${ctx.moment().format('YYYYMMDD')}/${create_time + '_' + index + fileInfo.ext}`;
+
+                    // 保存文件
+                    await ctx.app.fujianOss.put(ctx.app.config.fujianOssFolder + filepath, stream);
+                    await sendToWormhole(stream);
+
+                    // 插入到stage_pay对应的附件列表中
+                    uploadfiles.push({
+                        rela_id, rela_type,
+                        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.tempFile.addFiles(ctx.tender.id, uploadfiles, user);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                ctx.log(err);
+                if (stream) await sendToWormhole(stream);
+                ctx.ajaxErrorBody(err, '上传文件失败');
+            }
+        }
+        async aiDeleteFile(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.fid) throw '参数错误';
+
+                await ctx.service.tempFile.delFiles(data.fid);
+                ctx.body = {err: 0, msg: '', data: data.fid};
+            } catch(err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '删除文件错误');
+            }
+        }
+
         async _aiCompliePosCalcDetail(ctx, data) {
             const pos = await this.ctx.service.pos.getDataById(data.pos);
             if (!pos) throw '选择的计量单元不存在,请刷新页面后再试';
@@ -1285,63 +1356,29 @@ module.exports = app => {
             }
         }
         async aiComplie(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 user = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
-                const rela_id = parts.field.rela_id;
-                const rela_type = parts.field.rela_type;
-                const billsName = parts.field.rela_bills_name;
-                const rela_template = parts.field.rela_template;
-                const template = await ctx.service.calcTmpl.getTemplate(rela_template);
-                const keyword = parts.field.rela_keyword;
-                const billsType = parts.field.rela_billsType;
-
-                const uploadfiles = [];
-                while (stream !== undefined) {
-                    if (!stream.filename) throw '未发现上传文件!';
-
-                    const fileInfo = path.parse(stream.filename);
-                    const filepath = `app/public/upload/${ctx.tender.id}/temp/${ctx.moment().format('YYYYMMDD')}/${create_time + '_' + index + fileInfo.ext}`;
-
-                    // 保存文件
-                    await ctx.app.fujianOss.put(ctx.app.config.fujianOssFolder + filepath, stream);
-                    await sendToWormhole(stream);
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.rela_id || !data.rela_type || !data.template || !data.fid) throw '参数错误';
 
-                    // 插入到stage_pay对应的附件列表中
-                    uploadfiles.push({
-                        rela_id, rela_type,
-                        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.tempFile.addFiles(ctx.tender.id, uploadfiles, user);
+                const billsName = data.bills_name;
+                const template = await ctx.service.calcTmpl.getTemplate(data.template);
+                const keyword = data.keyword || '';
+                const billsType = data.billsType;
+                const files = await ctx.service.tempFile.getFiles({ where: { id: data.fid }});
+                if (!files || files.length === 0) throw '文件已被删除,请刷新页面后重试';
 
-                switch(rela_type) {
+                switch(data.rela_type) {
                     case 'pos':
-                        await this._aiCompliePosCalcDetail(ctx, { billsName, template, pos: rela_id, files: result, keyword });
+                        await this._aiCompliePosCalcDetail(ctx, { billsName, template, pos: data.rela_id, files, keyword });
                         break;
                     case 'bills':
-                        await this._aiCompliePosAndDetail(ctx, { billsName, template, bills: rela_id, files: result, keyword, billsType });
+                        await this._aiCompliePosAndDetail(ctx, { billsName, template, bills: data.rela_id, files, keyword, billsType });
                         break;
                     default:
                         throw '暂不提供该ai识别内容';
                 }
             } catch (err) {
                 ctx.log(err);
-                if (stream) await sendToWormhole(stream);
                 ctx.ajaxErrorBody(err, 'ai识别填写失败');
             }
         }

+ 48 - 0
app/controller/stage_controller.js

@@ -562,6 +562,12 @@ module.exports = app => {
                 if (ctx.stage.status === auditConst.status.uncheck || ctx.stage.status === auditConst.status.checkNo) {
                     await this._innerCheckChangeBillsOver(ctx, checkData);
                 }
+                if (!ctx.stage.update_expr_calc_time || (ctx.stage.update_calc_time && ctx.stage.update_expr_calc_time < ctx.stage.update_calc_time)) {
+                    const exprCount = await ctx.service.expr.count({ tid: ctx.stage.tid, calc_module: 'stage' });
+                    if (exprCount > 0) {
+                        checkData.checkResult.error.push({ errorType: 'expr_no_calc', name: '修改数据后,未计算本期合同计量' })
+                    }
+                }
                 ctx.body = { err: 0, msg: '', data: checkData.checkResult };
             } catch (err) {
                 this.log(err);
@@ -724,6 +730,7 @@ module.exports = app => {
                 }
                 await ctx.service.stage.updateCheckCalcFlag(ctx.stage, true);
                 await ctx.service.stage.updateCacheTime(ctx.stage.id);
+                if (data.isExprCalc) await ctx.service.stage.updateExprCalcTime(ctx.stage);
                 ctx.body = responseData;
             } catch (err) {
                 this.log(err);
@@ -2529,6 +2536,47 @@ module.exports = app => {
             }
         }
 
+
+        async uploadBigFile(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.type || !data.lid || !data.fileInfo) throw '缺少参数';
+
+                let result;
+                const fileInfo = path.parse(data.fileInfo.filename);
+                switch(data.type) {
+                    case 'begin':
+                        const create_time = Date.parse(new Date()) / 1000;
+                        result = {
+                            filename: `app/public/upload/${this.ctx.tender.id}/stage/fujian_${create_time + fileInfo.ext}`,
+                        };
+                        result.filepath = ctx.app.config.fujianOssFolder + result.filename;
+                        result.oss = await ctx.helper.getOssToken(ctx.app.fujianOss);
+                        break;
+                    case 'end':
+                        const user = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                        const extra_upload = ctx.stage.status === auditConst.status.checked;
+                        const fileData = {
+                            tid: ctx.params.id, sid: ctx.params.order,
+                            in_time: Date.parse(new Date()) / 1000,
+                            filepath: data.filepath,
+                            filename: fileInfo.name, fileext: fileInfo.ext, filesize: data.fileInfo.filesize,
+                            extra_upload,
+                        };
+                        result = await ctx.service.stageAtt.save({ lid: data.lid}, fileData, ctx.session.sessionUser.accountId);
+                        if (!result) {
+                            throw '导入数据库保存失败';
+                        }
+                        result = await ctx.service.stageAtt.getDataByFid(result.insertId);
+                        result = [result];
+                        break;
+                }
+                ctx.body = {err: 0, msg: '', data: result };
+            } catch (error) {
+                ctx.log(error);
+                ctx.body = this.ajaxErrorBody(error, '上传附件失败,请重试');
+            }
+        }
     }
 
     return StageController;

+ 1 - 1
app/middleware/revise_check.js

@@ -35,7 +35,7 @@ module.exports = options => {
                 const auditorsId = this.helper._.map(auditors, 'audit_id');
                 revise.reviseUsers.push(...auditorsId);
             }
-            if (revise.reviseUsers.indexOf(this.session.sessionUser.accountId) < 0 && this.session.sessionUser.loginType !== 4) throw '您无权查看该数据';
+            if (revise.reviseUsers.indexOf(this.session.sessionUser.accountId) < 0 && !this.tender.isTourist && !this.session.sessionUser.is_admin && this.session.sessionUser.loginType !== 4) throw '您无权查看该数据';
             revise.readOnly = revise.uid !== this.session.sessionUser.accountId ||
                 revise.status === auditConst.status.checking || revise.status === auditConst.status.checked;
             revise.priceCount = yield this.service.revisePrice.count({ rid: revise.id });

+ 3 - 3
app/public/js/change_information.js

@@ -5837,7 +5837,7 @@ $(document).ready(() => {
     if (cca !== null && cca !== undefined) {
         $('#customCheck1').prop('checked', cca !== 'false');
     }
-    changeSpreadSheet.setColumnVisible(6,$('#customCheck1').is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
+    changeSpreadSheet.setColumnVisible(7,$('#customCheck1').is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
     xmjSheet.setColumnVisible(7,$('#customCheck1').is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
     ledgerSheet.setColumnVisible(6,$('#customCheck1').is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
     posSheet.setColumnVisible(2,$('#customCheck1').is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
@@ -5848,7 +5848,7 @@ $(document).ready(() => {
         }
         // // 设置用户项目本地记录展示和隐藏情况
         setLocalCache('change-checkbox-account-'+ accountId, $(this).is(':checked'));
-        changeSpreadSheet.setColumnVisible(6,$(this).is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
+        changeSpreadSheet.setColumnVisible(7,$(this).is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
         xmjSheet.setColumnVisible(7,$(this).is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
         ledgerSheet.setColumnVisible(6,$(this).is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
         posSheet.setColumnVisible(2,$(this).is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
@@ -6628,7 +6628,7 @@ function makePushBwmx(clinfo, listinfo, removeList, updateList, showTips = false
                         oneUpdate[key] = leafInfo[key];
                         clinfo[key] = leafInfo[key];
                         needUpdate = true;
-                    } else if (leafInfo[key] === undefined && leafInfo.jldy !== (clinfo[key] || null)) {
+                    } else if (leafInfo[key] === undefined && leafInfo.jldy !== (clinfo[key] || clinfo.xmj_jldy || null)) {
                         oneUpdate[key] = leafInfo.jldy;
                         clinfo[key] = leafInfo.jldy;
                         needUpdate = true;

+ 21 - 3
app/public/js/change_revise.js

@@ -53,7 +53,7 @@ const checkOption = {
 };
 
 $(document).ready(() => {
-    let stdXmj, stdGcl, searchLedger;
+    let stdXmj, stdGcl, searchLedger, posDisplay;
     let changeGclData = false;
     autoFlashHeight();
     billsSpreadSetting.cols.unshift({ title: '变更清单', colSpan: '1', rowSpan: '2', field: 'is_change', hAlign: 1, width: 60, cellType: 'checkbox', readOnly: 'readOnly.isChangeList', });
@@ -3754,7 +3754,7 @@ $(document).ready(() => {
         makeGclGatherData();
         console.log(billsTree);
     }, null);
-    $.divResizer({
+    const mainSpr = $.divResizer({
         select: '#revise-resize',
         callback: function () {
             billsSpread.refresh();
@@ -3763,6 +3763,25 @@ $(document).ready(() => {
             posSpread.refresh();
         }
     });
+    const changePosDisplay = function (show, init = false) {
+        $('#pos-show').html(show ? '<i class="fa fa-eye-slash"></i> 隐藏计量单元' : '<i class="fa fa-eye"></i> 显示计量单元');
+        if (!init) {
+            posDisplay = show;
+            setLocalCache(posShowCache, show ? 1 : 0);
+        }
+        mainSpr.showSprPart(show);
+        billsSpread.refresh();
+        posSpread.refresh();
+    };
+    const reloadPosDisplayCache = function () {
+        posDisplay = getLocalCache(posShowCache);
+        posDisplay = posDisplay ? parseInt(posDisplay) : 1;
+        changePosDisplay(posDisplay, true);
+    };
+    reloadPosDisplayCache();
+    $('#pos-show').click(function() {
+        changePosDisplay(!posDisplay);
+    });
 
     function makeGclGatherData() {
         if (changeGclData || !gclGatherData) {
@@ -5938,7 +5957,6 @@ $(document).ready(() => {
             },
         };
 
-
         $('#batch-pos').on('show.bs.modal', function (e) {
             makeBatchPosSjsHtml();
         });

File diff ditekan karena terlalu besar
+ 1024 - 348
app/public/js/contract_detail.js


+ 1 - 0
app/public/js/ledger.js

@@ -4223,6 +4223,7 @@ $(document).ready(function() {
         billsTag.loadDatas(data.tags);
         SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
         SpreadJsObj.loadTopAndSelect(ledgerSpread.getActiveSheet(), ckBillsSpread);
+        aiTrans.setRelaInfo('bills', SpreadJsObj.getSelectObject(ledgerSheet), SpreadJsObj.getSelectObject(ledgerSheet));
 
         posOperationObj.loadCurPosData();
         SpreadJsObj.resetTopAndSelect(posSpread.getActiveSheet());

+ 1 - 1
app/public/js/pos_calc_tmpl.js

@@ -547,7 +547,7 @@ $(document).ready(() => {
                     { title: '类型', colSpan: '1', rowSpan: '1', field: 'type', hAlign: 1, width: 80, formatter: '@', readOnly: true, getValue: getTypeValue },
                     { title: '列名', colSpan: '1', rowSpan: '1', field: 'title', hAlign: 0, width: 130, formatter: '@' },
                     { title: '列宽', colSpan: '1', rowSpan: '1', field: 'width', hAlign: 1, width: 70, type: 'Number' },
-                    { title: '单位', colSpan: '1', rowSpan: '1', field: 'unit', hAlign: 1, width: 60, cellType: 'unit' },
+                    { title: '单位', colSpan: '1', rowSpan: '1', field: 'unit', hAlign: 1, width: 60, cellType: 'unit', comboEdit: true },
                     { title: '计算代号', colSpan: '1', rowSpan: '1', field: 'calc_code', hAlign: 1, width: 80, cellType: 'customizeCombo', comboItems: validCalcCode, },
                     { title: '小数位数', colSpan: '1', rowSpan: '1', field: 'decimal', hAlign: 1, width: 60, type: 'Number' },
                     { title: '规格类型', colSpan: '1', rowSpan: '1', field: 'spec_set', hAlign: 1, width: 80, cellType: 'customizeCombo', cellTypeKey: 'spec', comboItems: specList, },

+ 15 - 1
app/public/js/setting.js

@@ -16,6 +16,13 @@ $(document).ready(() => {
         width: '100%'
     });
 
+    // 初始化编辑用户单位名称下拉框为可搜索
+    $('#edit_user_company').select2({
+        placeholder: '请选择',
+        allowClear: true,
+        width: '100%'
+    });
+
     // 启用和停用账号
     $('.account-switch-btn').on('click', function () {
         const data = {
@@ -32,13 +39,19 @@ $(document).ready(() => {
         const account = $(this).data('account');
         $('#edit-user input[name="account"]').val(account.account);
         $('#edit-user input[name="name"]').val(account.name);
+        // 先设置下拉框的值
         $('#edit-user select[name="company"]').val(account.company);
+        // 触发 Select2 更新显示
+        $('#edit_user_company').trigger('change.select2');
         $('#edit-user input[name="role"]').val(account.role);
         $('#edit-user input[name="mobile"]').val(account.mobile);
         $('#edit-user input[name="telephone"]').val(account.telephone);
         $('#edit-user input[name="id"]').val(account.id);
         $('#edit-user input[name="account_group"]').val(account.account_group);
-        $('#edit-user input[class="account-check"]').val(account.account);
+        // 根据单位名称查找对应的单位ID
+        const oneUnit = _.find(unitList, { name: account.company });
+        $('#edit-user input[name="company_id"]').val(oneUnit ? oneUnit.id : '');
+        $('#edit-user input[class="account-check"').val(account.account);
         $('#edit-user input[data-mobile="auth-mobile"]').val(account.auth_mobile);
         $('#edit-user input[name="mobile"]').attr('readOnly', account.bind === 1);
         if (account.bind === 1) {
@@ -57,6 +70,7 @@ $(document).ready(() => {
         const company = $(this).val();
         const oneUnit = _.find(unitList, { name: company });
         $(this).siblings('input[name="account_group"]').val(oneUnit ? oneUnit.type : 7);
+        $(this).siblings('input[name="company_id"]').val(oneUnit ? oneUnit.id : '');
     });
 
     // 分配随机密码

+ 154 - 23
app/public/js/shares/ai_trans.js

@@ -7,6 +7,90 @@ const AITrans = function() {
                 this.spread = SpreadJsObj.createNewSpread($('#ai-trans-grid-spread')[0]);
                 this.spread.options.tabStripVisible = true;
             }
+        },
+        getFileMiniHtml: function(file) {
+            const html = [];
+            html.push(`<div class="card mb-2 p-1 thumbnail-container" fid="${file.id}" name="ai-his-file" title="${file.filename+file.fileext}">`);
+            html.push('<div class="ai-del-btn text-danger"><span aria-hidden="true">&times;</span></div>');
+            html.push(`<img src="${file.viewpath}">`);
+            html.push('</div>');
+            return html.join('');
+        },
+        selectFile: function(fid) {
+            $('[name=ai-his-file]').removeClass('thumbnail-active');
+            $(`[fid="${fid}"]`).addClass('thumbnail-active');
+            transResultObj.showFile(fid);
+        },
+        afterUploadFile: function(file) {
+            if (!this.rela_node.aiRelaFile) this.rela_node.aiRelaFile = [];
+            this.rela_node.aiRelaFile.push(file);
+            $('#ai-trans-file-add').parent().append(this.getFileMiniHtml(file));
+            this.selectFile(file.id);
+        },
+        uploadFile: function(files) {
+            const formData = new FormData();
+            formData.append('rela_type', transResultObj.rela_type);
+            formData.append('rela_id', transResultObj.rela_id);
+            for (const file of files) {
+                const filesize = file.size;
+                if (filesize > 50 * 1024 * 1024) {
+                    toastr.error('图片大小过大!');
+                    return false;
+                }
+                formData.append('size', filesize);
+                formData.append('file[]', file);
+            }
+            postDataWithFile(window.location.pathname + '/ai/uploadFile', formData, function (data) {
+                transResultObj.afterUploadFile(data[0]);
+            });
+        },
+        deleteFile: function(fid) {
+            const fileIndex = transResultObj.rela_node.aiRelaFile.findIndex(x => { return x.id === fid; });
+            const file = transResultObj.rela_node.aiRelaFile[fileIndex];
+            if (!file) return;
+            const fileObj = $(`[fid="${fid}"]`);
+            if (fileObj.hasClass('thumbnail-active')) {
+                $('#ai-trans-show-file').attr('src', "");
+            }
+
+            postData(window.location.pathname + '/ai/deleteFile', { fid }, function(){
+                transResultObj.rela_node.aiRelaFile.splice(fileIndex, 1);
+                fileObj.remove();
+            });
+        },
+        reloadAiRelaFile: function() {
+            $('#ai-trans-file-add').nextAll().remove();
+            const html = [];
+            for (const file of this.rela_node.aiRelaFile) {
+                html.push(this.getFileMiniHtml(file));
+            }
+            $('#ai-trans-file-add').after(html.join(''));
+        },
+        loadAiRelaFile: async function() {
+            if (this.rela_node.aiRelaFile) return;
+            this.rela_node.aiRelaFile = await postDataAsync(window.location.pathname + '/ai/file', { rela_id: this.rela_node.id, rela_type: this.rela_type });
+            this.reloadAiRelaFile();
+            $('#ai-trans-show-file').attr('src', '');
+        },
+        showFile: function(fid) {
+            const file = this.rela_node.aiRelaFile.find(x => { return x.id === fid; });
+            $('#ai-trans-show-file').attr('src', file.viewpath);
+        },
+        pasteImage: function(e) {
+            if (transResultObj.rela_node.aiRelaFile && transResultObj.rela_node.aiRelaFile.length >= 3) {
+                toastr.warning('保存的图片超过限制,请先删除旧图片后再操作');
+                return;
+            }
+
+            let file = null;
+            const data = (event.clipboardData || window.clipboardData).items;
+            for (let i = 0; i < data.length; i += 1) {
+                if ((data[i].kind == 'file') && (data[i].type.match('^image/'))) {
+                    file = data[i].getAsFile();
+                    break;
+                }
+            }
+            transResultObj.uploadFile([file]);
         }
     };
     $('#ai-trans-grid').bind('shown.bs.modal', function() {
@@ -57,46 +141,90 @@ const AITrans = function() {
         $('#ai-trans-grid').modal('show');
     };
 
-    $('#ai-trans-ok').click(function() {
-        const files = $('#ai-trans-file')[0].files;
-        if (!files || files.length === 0) return;
+    document.getElementById('ai-trans-file-add').addEventListener('dragenter', function(e) {
+        e.preventDefault();
+        this.style.backgroundColor = '#f0f0f0';
+    });
+    document.getElementById('ai-trans-file-add').addEventListener('dragleave', function(e) {
+        if (e.relatedTarget !== this) {
+            this.style.backgroundColor = '';
+        }
+    });
+    document.getElementById('ai-trans-file-add').addEventListener('dragover', function(e) {
+        e.preventDefault();
+        e.dataTransfer.dropEffect = 'copy';
+    });
+    document.getElementById('ai-trans-file-add').addEventListener('drop', function(e) {
+        e.preventDefault();
 
-        const formData = new FormData();
-        formData.append('rela_type', transResultObj.rela_type);
-        formData.append('rela_id', transResultObj.rela_id);
-        if (transResultObj.rela_type === 'ancGcl') {
-            formData.append('rela_template', transResultObj.rela_node.calc_template);
-        } else {
-            formData.append('rela_template', transResultObj.rela_bills.calc_template);
+        if (transResultObj.rela_node.aiRelaFile && transResultObj.rela_node.aiRelaFile.length >= 3) {
+            toastr.warning('保存的图片超过限制,请先删除旧图片后再操作');
+            return;
         }
-        formData.append('rela_bills_name', transResultObj.rela_bills.name);
-        formData.append('rela_keyword', $('#ai-trans-keyword').val());
-        formData.append('rela_billsType', transResultObj.billsType);
+        const files = e.dataTransfer.files;
         for (const file of files) {
             if (file === undefined) {
                 toastr.error('未选择图片!');
                 return false;
             }
-            const filesize = file.size;
-            if (filesize > 50 * 1024 * 1024) {
-                toastr.error('选择图片大小过大!');
-                return false;
-            }
             const fileext = '.' + file.name.toLowerCase().split('.').splice(-1)[0];
             if (whiteList.indexOf(fileext) === -1) {
                 toastr.error('只能上传指定格式的图片!');
                 return false;
             }
-            formData.append('size', filesize);
-            formData.append('file[]', file);
         }
-        postDataWithFile(window.location.pathname + '/aiComplie', formData, function (data) {
+
+        transResultObj.uploadFile(files);
+    });
+    $('body').on('click', '[name=ai-his-file]', function() {
+        transResultObj.selectFile(this.getAttribute('fid'));
+    });
+    $('#ai-trans').on('paste', transResultObj.pasteImage);
+    $('body').on('click', '.ai-del-btn', function() {
+        const fid = $(this).parent().attr('fid');
+        transResultObj.deleteFile(fid);
+        e.stopPropagation();
+    });
+    $('#ai-trans-ok').click(function() {
+        const files = $('.thumbnail-container');
+        if (files.length === 0) {
+            toastr.warning('请先上传/拖动/粘贴图片');
+            return;
+        }
+        const select = $('.thumbnail-active')[0];
+        if (!select) {
+            toastr.warning('请选择历史图片,或者重新上传/拖动/粘贴图片');
+            return;
+        }
+        const complieData = {
+            rela_id: transResultObj.rela_node.id,
+            rela_type: transResultObj.rela_type,
+            fid: select.getAttribute('fid'),
+            billsType: transResultObj.billsType,
+            keyword: $('#ai-trans-keyword').val(),
+            bills_name: transResultObj.rela_bills.name,
+        };
+        if (transResultObj.rela_type === 'ancGcl') {
+            complieData.template = transResultObj.rela_node.calc_template;
+        } else {
+            complieData.template = transResultObj.rela_bills.calc_template;
+        }
+
+        postData(window.location.pathname + '/ai/complie', complieData, function (data) {
             if (transResultObj.callback) transResultObj.callback(data);
             $('#ai-trans').modal('hide');
         });
     });
-    const transDetail = function(callback) {
+    const transDetail = async function(callback) {
+        if (transResultObj.rela_type === 'bills') {
+            $('#ai-trans-title').html(`AI识别部位(${transResultObj.rela_node.b_code} ${transResultObj.rela_node.name})`);
+        } else if (transResultObj.rela_type === 'pos') {
+            $('#ai-trans-title').html(`AI识别设计量细目(${transResultObj.rela_bills.b_code} ${transResultObj.rela_bills.name} / ${transResultObj.rela_node.name})`);
+        } else if (transResultObj.rela_type === 'ancGcl') {
+            $('#ai-trans-title').html(`AI识别附属工程量明细(${transResultObj.rela_bills.b_code} ${transResultObj.rela_bills.name} / ${transResultObj.rela_node.name})`);
+        }
         transResultObj.callback = callback;
+        transResultObj.loadAiRelaFile();
         $('#ai-trans').modal('show');
     };
     const billsIdentify = function (callback) {
@@ -113,5 +241,8 @@ const AITrans = function() {
         transResultObj.rela_id = rela_node ? rela_node.id : undefined;
         transResultObj.rela_bills = rela_bills;
     };
-    return { show, setRelaInfo, billsIdentify, transDetail, rela_type: function() {return transResultObj.rela_type} }
+    return {
+        show,
+        setRelaInfo, billsIdentify, transDetail, rela_type: function() {return transResultObj.rela_type}
+    }
 };

+ 1 - 0
app/public/js/shares/cs_tools.js

@@ -87,6 +87,7 @@ const showSelectTab = function(select, spread, afterShow) {
                                 case 'minus_cb': return '负变更清单漏计';
                                 case 'change_over': return '变更令超计';
                                 case 'settle': return '结算清单';
+                                case 'expr_no_calc': return '公式计算';
                                 default: return '';
                             }
                         }

+ 38 - 356
app/public/js/stage.js

@@ -107,66 +107,6 @@ function initTreeColSettingEvents(setting) {
     }
 }
 
-// 生成所有附件列表
-function getAllList(currPageNum = 1) {
-    // 每页最多几个附件
-    const pageCount = 20;
-    // 附件总数
-    const total = attData.length;
-    // 总页数
-    const pageNum = Math.ceil(total/pageCount);
-    $('#totalPage').text(pageNum);
-    $('#currentPage').text(total === 0 ? 0 : currPageNum);
-    // 当前页附件内容
-    const currPageAttData = attData.slice((currPageNum-1)*pageCount, currPageNum*pageCount);
-    currPageFileData = currPageAttData;
-    let html = '';
-    // '/tender/' + tender.id + '/measure/stage/' + stage.order + '/download/file/' + att.id
-    for(const att of currPageAttData) {
-        const delHtml = deleteFilePermission && parseInt(cur_uid) === att.uid
-            ? `<a class="ml-1 text-danger" href="javascript:void(0)" name="att-delete" file-id="${att.id}"><i class="fa fa-close" title="删除"></i></a>`
-            : '';
-        html += `<tr>
-        <td width="25"><input type="checkbox" class="check-file" file-id=${att.id}></td>
-        <td>
-        <div class="d-flex">
-            <a href="javascript:void(0)" class="pl-0 col-10 att-file-name" file-id=${att.id}>${att.filename}${att.fileext}</a>
-            <div class="att-file-btn col-2"><a href="/tender/${tender.id}/measure/stage/${stage.order}/download/file/${att.id}" class="pl-0"><i class="fa fa-download"></i></a>${delHtml}</div>
-        </div>
-        </td><td>${att.username}</td></tr>`
-    }
-    $('#alllist-table').html(html);
-    $('#alllist-table').on('click', 'tr', function() {
-        $('#alllist-table tr').removeClass('bg-light')
-        $(this).addClass('bg-light')
-    });
-}
-// 生成当前节点列表
-function getNodeList(node) {
-    let html = '';
-    for(const att of attData) {
-        if (node === att.lid) {
-            const delHtml = deleteFilePermission && parseInt(cur_uid) === att.uid
-                ? `<a class="ml-1 text-danger" href="javascript:void(0)" name="att-delete" file-id="${att.id}"><i class="fa fa-close" title="删除"></i></a>`
-                : '';
-            // html += '<tr><td><a href="javascript:void(0)" file-id="'+ att.id +'">'+ att.filename + att.fileext +'</a></td><td>'+ att.username +'</td></tr>';
-            html += `<tr>
-            <td width="25"><input type="checkbox" class="check-file" file-id=${att.id}></td>
-            <td>
-            <div class="d-flex">
-                <a href="javascript:void(0)" class="pl-0 col-10 att-file-name" file-id=${att.id}>${att.filename}${att.fileext}</a>
-                <div class="att-file-btn col-2"><a href="/tender/${tender.id}/measure/stage/${stage.order}/download/file/${att.id}" class="pl-0"><i class="fa fa-download"></i></a>${delHtml}</div>
-            </div>
-            </td><td>${att.username}</td></tr>`
-        }
-    }
-    $('#nodelist-table').html(html);
-    $('#nodelist-table').on('click', 'tr', function() {
-        $('#nodelist-table tr').removeClass('bg-light');
-        $(this).addClass('bg-light');
-    })
-}
-
 function getGxbyText(data) {
     const def = thirdParty.gxby.find(function (x) {
         return x.value === data.gxby_status;
@@ -857,9 +797,6 @@ $(document).ready(() => {
     };
     SpreadJsObj.initSheet(slSpread.getActiveSheet(), ledgerSpreadSetting);
 
-    //初始化所有附件列表
-    getAllList();
-
     // 初始化 计量单元 Spread
     const spSpread = SpreadJsObj.createNewSpread($('#stage-pos')[0]);
     const spSheet = spSpread.getActiveSheet();
@@ -1122,6 +1059,7 @@ $(document).ready(() => {
             SpreadJsObj.resetTopAndSelect(posSheet);
             stagePosSpreadObj.loadCurPosData();
             if (posSearch) posSearch.search();
+            stageFile.getCurAttHtml(SpreadJsObj.getSelectObject(billsSheet));
         },
         selectionChanged: function (e, info) {
             if (!info.oldSelections || !info.oldSelections[0] || info.newSelections[0].row !== info.oldSelections[0].row) {
@@ -1946,7 +1884,7 @@ $(document).ready(() => {
                         return data;
                     });
 
-                    postData(window.location.pathname + '/update', {bills: { stage: updateData }}, function (result) {
+                    postData(window.location.pathname + '/update', {bills: { stage: updateData }, isExprCalc: true }, function (result) {
                         const nodes = stageTree.loadPostStageData(result);
                         stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
                         if (detail) {
@@ -2251,7 +2189,6 @@ $(document).ready(() => {
                     }
                 });
                 SpreadJsObj.loadSheetData(spSpread.getActiveSheet(), 'data', posData, posReadOnly);
-                getNodeList(node.id);
             } else {
                 spSpread.getActiveSheet().zh_setting.readOnly = true;
                 SpreadJsObj.loadSheetData(spSpread.getActiveSheet(), 'data', [], true);
@@ -2783,6 +2720,12 @@ $(document).ready(() => {
         ledgerData = result.ledgerData;
         posData = result.posData;
         makeShouFang();
+
+        for (const r of attData) {
+            r.node = stageTree.datas.find(x => {return x.id === r.lid});
+        }
+        stageFile.loadDatas(attData);
+        stageFile.getCurAttHtml(SpreadJsObj.getSelectObject(slSheet));
     }, null, true);
     spSpread.bind(spreadNS.Events.EditEnded, stagePosSpreadObj.editEnded);
     spSpread.bind(spreadNS.Events.ClipboardPasting, stagePosSpreadObj.clipboardPasting);
@@ -3971,7 +3914,7 @@ $(document).ready(() => {
                 const select = SpreadJsObj.getSelectObject(self.spread.getActiveSheet());
                 if (select && select.source) {
                     SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), select.source[curIndex-1].id, true);
-                    stagePosSpreadObj.loadCurPosData();
+                    stageTreeSpreadObj.loadRelaData();
                     if (select.source[curIndex-1].pos_id) {
                         SpreadJsObj.locateDataBy(spSpread.getActiveSheet(), function(data){ return data.id === select.source[curIndex-1].pos_id});
                     }
@@ -3983,7 +3926,7 @@ $(document).ready(() => {
                 if (select && select.source) {
                     const targetIndex = Math.max(curIndex-1, 1);
                     SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), select.source[targetIndex-1].id, true);
-                    stagePosSpreadObj.loadCurPosData();
+                    stageTreeSpreadObj.loadRelaData();
                     $('#im-locate-info')[0].innerText = targetIndex + '/' + select.source.length;
                 }
             });
@@ -3993,7 +3936,7 @@ $(document).ready(() => {
                 if (select && select.source) {
                     const targetIndex = Math.min(curIndex+1, select.source.length);
                     SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), select.source[targetIndex-1].id, true);
-                    stagePosSpreadObj.loadCurPosData();
+                    stageTreeSpreadObj.loadRelaData();
                     $('#im-locate-info')[0].innerText = targetIndex + '/' + select.source.length;
                 }
 
@@ -4581,7 +4524,7 @@ $(document).ready(() => {
 
                                     SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), changeBills.pos[0].ledger_id, true);
                                     SpreadJsObj.resetTopAndSelect(spSpread.getActiveSheet());
-                                    stagePosSpreadObj.loadCurPosData();
+                                    stageTreeSpreadObj.loadRelaData();
                                 }
                             } else if (changeBills.gcl_id) {
                                 const node = stageTree.nodes.find(x => {return x.id === changeBills.gcl_id});
@@ -4589,7 +4532,7 @@ $(document).ready(() => {
 
                                 SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), node.ledger_id, true);
                                 SpreadJsObj.resetTopAndSelect(spSpread.getActiveSheet());
-                                stagePosSpreadObj.loadCurPosData();
+                                stageTreeSpreadObj.loadRelaData();
                                 const posData = spSpread.getActiveSheet().zh_data;
                                 if (!posData || posData.length === 0) return;
 
@@ -4615,7 +4558,7 @@ $(document).ready(() => {
                                     if (_.isMatch(cb, b)) {
                                         SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), node.ledger_id);
                                         SpreadJsObj.resetTopAndSelect(spSpread.getActiveSheet());
-                                        stagePosSpreadObj.loadCurPosData();
+                                        stageTreeSpreadObj.loadRelaData();
                                         return;
                                     }
                                 }
@@ -4877,6 +4820,29 @@ $(document).ready(() => {
         }
     }
     // 展开收起附件
+    const stageFile = $.ledger_att({
+        selector: '#fujian',
+        key: 'id',
+        masterKey: 'lid',
+        uploadUrl: window.location.pathname + '/upload/file',
+        deleteUrl: window.location.pathname + '/delete/file',
+        uploadBigUrl: window.location.pathname + '/file/upload/big',
+        checked: stageComplete,
+        zipName: `${tender.name}-附件.zip`,
+        readOnly: false,
+        bigValid: true,
+        // fileInfo: {
+        //     user_name: 'user_name',
+        //     user_id: 'uid',
+        //     create_time: 'in_time',
+        // },
+        getCurHint: function(node) { return ''; },
+        locate: function (att) {
+            if (!att) return;
+            SpreadJsObj.locateDataBy(slSheet, function(x) { return x.id === att.node.id; });
+            stageFile.getCurAttHtml(att.node);
+        }
+    });
     $('a', '.right-nav').bind('click', function () {
         //const main = $('#main-view'), tool = $('#tools-view');
         const tab = $(this), tabPanel = $(tab.attr('content'));
@@ -4965,10 +4931,6 @@ $(document).ready(() => {
                 });
                 searchLedger.spread.refresh();
             }
-            if (tab.attr('content') === '#fujian') {
-                const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
-                node && getNodeList(node.id);
-            }
             if (tab.attr('content') === '#zhongjian') {
                 if (!detail) {
                     detail = new Detail($('#detail-spread'));
@@ -5021,43 +4983,6 @@ $(document).ready(() => {
         }
         $('#showAttachment').hide();
     });
-    // 上传附件
-    $('#upload-file-btn').click(function () {
-        const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
-        if (!node) return
-        const files = $('#upload-file')[0].files;
-        const formData = new FormData();
-        formData.append('lid', node.id);
-        for (const file of files) {
-            if (file === undefined) {
-                toastr.error('未选择上传文件!');
-                return false;
-            }
-            const filesize = file.size;
-            if (filesize > 50 * 1024 * 1024) {
-                toastr.error('存在上传文件大小过大!');
-                return false;
-            }
-            const fileext = '.' + file.name.toLowerCase().split('.').splice(-1)[0];
-            if (whiteList.indexOf(fileext) === -1) {
-                toastr.error('只能上传指定格式的附件!');
-                return false;
-            }
-            formData.append('size', filesize);
-            formData.append('file[]', file);
-        }
-        postDataWithFile('/tender/' + tender.id + '/measure/stage/' + stage.order + '/upload/file', formData, function (data) {
-            $('#upload').modal('hide');
-            // 插入到attData中
-            attData = data.concat(attData);
-            // 重新生成List
-            getAllList();
-            getNodeList(node.id);
-        }, function () {
-            toastr.error('附件上传失败');
-        });
-        $('#upload-file').val('');
-    });
 
     // 获取附件信息
     $('.list-table').on('click', '.att-file-name', function () {
@@ -5105,221 +5030,6 @@ $(document).ready(() => {
             toastr.error('附件信息获取失败');
         }
     });
-    // $('body').on('click', '.alllist-table a', handleFileList);
-    $('body').on('click', '#btn-att a', function () {
-        const content = $(this).attr('content');
-        const fid = $('#showAttachment').attr('file-id');
-        const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
-        if (content === 'edit') {
-            $('#btn-att a').eq(3).hide();
-            $('#btn-att a').eq(2).show();
-            $('#btn-att a').eq(4).show();
-            $('#btn-att a').eq(5).show();
-            $('#show-att').hide();
-            $('#edit-att').show();
-            const att = attData.find(function (item) {
-                return item.id === parseInt(fid);
-            });
-            $('#edit-att .form-group').eq(0).find('input').val(att.filename);
-            $('#edit-att .form-group').eq(0).find('span').eq(1).text(att.fileext);
-            const name = att.code !== null && att.code !== '' ? att.code : (att.b_code !== null ? att.b_code : '');
-            $('#edit-att .form-group').eq(1).find('input').val($.trim(name + ' ' + att.lname));
-            $('#edit-att .form-group').eq(2).find('input').val(att.in_time);
-            $('#edit-att .form-group').eq(3).find('input').val(att.remark);
-        } else if (content === 'cancel') {
-            $('#show-att').show();
-            $('#edit-att').hide();
-            $('#btn-att a').eq(3).show();
-            $('#btn-att a').eq(2).hide();
-            $('#btn-att a').eq(4).hide();
-            $('#btn-att a').eq(5).hide();
-        } else if (content === 'save') {
-            const formData = new FormData();
-            formData.append('id', fid);
-            formData.append('filename', $('#edit-att .form-group').eq(0).find('input').val());
-            formData.append('fileext', $('#edit-att .form-group').eq(0).find('span').eq(1).text());
-            formData.append('remark', $('#edit-att .form-group').eq(3).find('input').val());
-            const file = $('#change-att-btn')[0];
-            if (file.files[0] !== undefined) {
-                const filesize = file.files[0].size;
-                formData.append('size', filesize);
-                formData.append('file', file.files[0]);
-            }
-            postDataWithFile('/tender/' + tender.id + '/measure/stage/' + stage.order + '/save/file', formData, function (data) {
-                // 替换到attData中
-                const att_index = attData.findIndex(function (item) {
-                    return item.id === parseInt(fid);
-                });
-                attData.splice(att_index, 1, data);
-                // 重新生成List
-                getAllList(parseInt($('#currentPage').text()));
-                getNodeList(node.id);
-                $('#show-att').show();
-                $('#edit-att').hide();
-                $('#show-att tr').eq(0).children('td').text(data.filename + data.fileext);
-                const name = data.code !== null && data.code !== '' ? data.code : (data.b_code !== null ? data.b_code : '');
-                $('#show-att tr').eq(1).children('td').text($.trim(name + ' ' + data.lname));
-                // $('#show-att tr').eq(2).find('a').attr('href', '/tender/' + tender.id + '/measure/stage/' + stage.order + '/download/file/' + data.id);
-                $('#show-att tr').eq(3).children('td').eq(0).text(data.username);
-                $('#show-att tr').eq(3).children('td').eq(1).text(data.in_time);
-                $('#show-att tr').eq(4).children('td').text(data.remark);
-                $('#btn-att a').eq(3).show();
-                $('#btn-att a').eq(2).hide();
-                $('#btn-att a').eq(4).hide();
-                $('#btn-att a').eq(5).hide();
-            }, function () {
-                toastr.error('附件上传失败');
-            });
-            $('#change-att-btn').val('');
-        } else if (content === 'del') {
-            const att = attData.find(function (item) { return item.id === parseInt(fid); });
-            deleteAfterHint(function () {
-                const data = {id: fid};
-                postData('/tender/' + tender.id + '/measure/stage/' + stage.order + '/delete/file', data, function (result) {
-                    // 删除到attData中
-                    const att_index = attData.findIndex(function (item) {
-                        return item.id === parseInt(fid);
-                    });
-                    attData.splice(att_index, 1);
-                    // 重新生成List
-
-                    if ($('#alllist-table tr').length === 1) {
-                        getAllList(parseInt($('#currentPage').text()) - 1);
-
-                    } else {
-                        getAllList(parseInt($('#currentPage').text()));
-                    }
-                    getNodeList(node.id);
-                    $('#showAttachment').hide();
-                    $('#showAttachment').attr('file-id', '');
-                });
-            }, `确认删除附件「${att.filename}」?`);
-
-        } else if (content === 'view') {
-            const att = attData.find(item => item.id === parseInt(fid));
-            window.open(att.viewpath || att.orginpath);
-        } else if (content === 'location') {
-            const att = attData.find(item => item.id === parseInt(fid));
-            if (Object.keys(att).length) {
-                SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), att.ledger_id, true);
-                stagePosSpreadObj.loadCurPosData();
-            }
-        }
-    });
-
-    // 替换附件
-    $('#change-att-btn').on('change', function () {
-        const file = $('#change-att-btn')[0].files[0];
-        const name = file.name;
-        const filename = name.substring(0, name.lastIndexOf("."));
-        const fileext = name.substr(name.indexOf("."));
-        const filesize = file.size;
-        if (filesize > 50 * 1024 * 1024) {
-            toastr.error('文件大小过大!');
-            $('#change-att-btn').val('');
-            return false;
-        }
-        if (whiteList.indexOf(fileext) === -1) {
-            toastr.error('只能上传指定格式的附件!');
-            $('#change-att-btn').val('');
-            return false;
-        }
-        $('#edit-att .form-group').eq(0).find('input').val(filename);
-        $('#edit-att .form-group').eq(0).find('span').eq(1).text(fileext);
-    });
-
-    // 切换页数
-    $('.page-select').on('click', function () {
-        const totalPageNum = parseInt($('#totalPage').text());
-        const lastPageNum = parseInt($('#currentPage').text());
-        const status = $(this).attr('content');
-        if (status === 'pre' && lastPageNum > 1) {
-            getAllList(lastPageNum-1);
-            $('#showAttachment').hide();
-            $('#syfujian .check-all-file').prop('checked', false)
-        } else if (status === 'next' && lastPageNum < totalPageNum) {
-            getAllList(lastPageNum+1);
-            $('#showAttachment').hide();
-            $('#syfujian .check-all-file').prop('checked', false)
-        }
-    });
-
-    // 批量下载
-    $('#bach-download').click(function() {
-        const fileIds = [];
-        const type = $(this).attr('type');
-        let node = ''
-        if (type === 'curr') {
-            node = '#nodelist-table .check-file:checked'
-        } else {
-            node = '#alllist-table .check-file:checked'
-        }
-        $(node).each(function() {
-            const fileId = $(this).attr('file-id');
-            fileId && fileIds.push(fileId);
-        });
-
-        if (fileIds.length) {
-            if (fileIds.length > 20) {
-              return toastr.warning(`最大允许20个文件(当前${fileIds.length}个)`)
-            }
-            toastr.success('正在进行压缩文件...', '', { timeOut: 0, extendedTimeOut: 0})
-            $(this).attr('disabled', "true")
-            const btn = $(this);
-
-            const fileArr = [];
-            for (const id of fileIds) {
-                const fileInfo = _.find(currPageFileData, { id: parseInt(id) });
-                fileArr.push({
-                    url: fileInfo.orginpath, //文件的oss存储路径 (必填)
-                    name: fileInfo.filename, // 文件名 (可选, 不需要填扩展名)
-                    foldPath: '' // (可选, 文件在压缩包中的存储路径)
-                });
-            }
-            const packageName = `${tender.name}-计量台账-第${stage.order}期-附件.zip`;
-            try {
-                zipOss.downloadFromAliOss(fileArr, packageName, btn);
-            } catch (e) {
-                btn.removeAttr('disabled');
-                toastr.clear();
-                toastr.error('批量下载失败');
-            }
-            // postCompressFile(`/tender/${tender.id}/measure/stage/${stage.order}/download/compresse-file`, {fileIds}, function(result) {
-            //   toastr.clear()
-            //   toastr.success('压缩文件成功')
-            //   btn.removeAttr('disabled')
-            //   const href = window.URL.createObjectURL(result)
-            //   $('#zipDown').attr('href', href);
-            //   $('#zipDown').attr('download', `${tender.name}-计量台账-第${stage.order}期-附件.zip`);
-            //   $("#zipDown")[0].click();
-            // }, () => {
-            //   btn.removeAttr('disabled')
-            //   toastr.clear()
-            //   toastr.error('批量下载失败')
-            // });
-            // const url = `/tender/${tender.id}/measure/stage/${stage.order}/download/compresse-file?fileIds=${JSON.stringify(fileIds)}`;
-            // $('#zipDown').attr('href', url);
-            // $("#zipDown")[0].click();
-        }
-    });
-
-    // 监听附件check是否选中
-    $('.list-table').on('click', '.check-file', function() {
-        const checkedList = $(this).parents('.list-table').children().find('input:checked');
-        const childs = $(this).parents('.list-table').children().length;
-        const checkBox = $(this).parents('.list-table').parent().find('.check-all-file');
-        if (checkedList.length === childs) {
-            checkBox.prop("checked", true);
-        } else {
-            checkBox.prop("checked", false);
-        }
-    });
-    $('.check-all-file').click(function() {
-        const isCheck = $(this).is(':checked');
-        $(this).parents('table').find('.list-table').each(function() {
-            $(this).find('input:checkbox').prop("checked", isCheck);
-        })
-    });
 
     // 显示层次
     (function (select, sheet) {
@@ -5665,7 +5375,7 @@ $(document).ready(() => {
         if (sfInfo) {
             const lData = _.find(ledgerData, { id: sfInfo.lid });
             SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), lData.ledger_id, true);
-            stagePosSpreadObj.loadCurPosData();
+            stageTreeSpreadObj.loadRelaData();
             if (sfInfo.pid) {
                 const posData = _.find(spSpread.getActiveSheet().zh_data, { id: sfInfo.pid });
                 SpreadJsObj.locateData(spSpread.getActiveSheet(), posData);
@@ -5763,34 +5473,6 @@ $(document).ready(() => {
             stageAssistRela.refreshAssistTable();
         });
     });
-    $('body').on('click', 'a[name=att-delete]', function() {
-        const fid = this.getAttribute('file-id');
-        if (!fid) return;
-
-        const att = attData.find(function (item) { return item.id === parseInt(fid); });
-        deleteAfterHint(function() {
-            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
-            const data = {id: fid};
-            postData('/tender/' + tender.id + '/measure/stage/' + stage.order + '/delete/file', data, function (result) {
-                // 删除到attData中
-                const att_index = attData.findIndex(function (item) {
-                    return item.id === parseInt(fid);
-                });
-                attData.splice(att_index, 1);
-                // 重新生成List
-
-                if ($('#alllist-table tr').length === 1) {
-                    getAllList(parseInt($('#currentPage').text()) - 1);
-
-                } else {
-                    getAllList(parseInt($('#currentPage').text()));
-                }
-                getNodeList(node.id);
-                $('#showAttachment').hide();
-                $('#showAttachment').attr('file-id', '');
-            });
-        }, `确认删除附件「${att.filename}」?`);
-    });
 });
 function makeOneShouFang(sf) {
     const lData = _.find(ledgerData, { id: sf.lid });

+ 5 - 1
app/router.js

@@ -712,7 +712,10 @@ module.exports = app => {
     app.post('/tender/:id/ledger/deal2sgfh', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.deal2sgfh');
     app.post('/tender/:id/ledger/sgfh2deal', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.sgfh2deal');
     app.post('/tender/:id/ledger/check', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.check');
-    app.post('/tender/:id/ledger/aiComplie', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.aiComplie');
+    app.post('/tender/:id/ledger/ai/file', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.aiRelaFile');
+    app.post('/tender/:id/ledger/ai/uploadFile', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.aiUploadFile');
+    app.post('/tender/:id/ledger/ai/deleteFile', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.aiDeleteFile');
+    app.post('/tender/:id/ledger/ai/complie', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.aiComplie');
     app.post('/tender/:id/measure/ledger/auditors', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.ledgerAuditors');
     app.get('/tender/:id/ledger/cpd', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.exportLedgerData');
     app.post('/tender/:id/ledger/cpd/load', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.importLedgerData');
@@ -820,6 +823,7 @@ module.exports = app => {
     app.get('/tender/:id/measure/stage/:order/im-file/download', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, stageCheck, 'stageController.downloadImFile');
     app.get('/tender/:id/measure/stage/:order/cpd', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, stageCheck, 'stageController.exportStageData');
     app.post('/tender/:id/measure/stage/:order/cpd/load', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, stageCheck, 'stageController.importStageData');
+    app.post('/tender/:id/measure/stage/:order/file/upload/big', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, stageCheck, 'stageController.uploadBigFile');
     app.post('/tender/:id/measure/stage/:order/audit/spgroup', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, stageCheck, 'stageController.changeSpGroup');
 
     // 暂存计量

+ 18 - 3
app/service/contract.js

@@ -42,7 +42,7 @@ module.exports = app => {
             const insertId = data.cid || this.uuid.v4();
             const transaction = await this.db.beginTransaction();
             try {
-                const add_key = ['c_code', 'name', 'tax', 'price', 'party_a', 'party_a_user', 'party_b', 'party_b_user', 'sign_date', 'address', 'entity', 'bank', 'bank_account'];
+                const add_key = ['c_code', 'name', 'tax', 'price', 'party_a', 'party_a_user', 'party_b', 'party_b_user', 'sign_date', 'address', 'entity', 'bank', 'bank_account', 'type', 'rela_tender', 'remark'];
                 const maxId = await this.ctx.service.contractTree._getMaxLid(options);
                 const insertData = {
                     id: insertId,
@@ -62,7 +62,7 @@ module.exports = app => {
                     // party_b: data.party_b,
                     // sign_date: data.sign_date,
                     // remark: data.remark,
-                    type: data.type,
+                    // type: data.type,
                     create_time: new Date(),
                     need_shenpi: options.spid ? this.ctx.subProject.page_show.openContractSubProjectShenpi : this.ctx.subProject.page_show.openContractTenderShenpi,
                 };
@@ -71,7 +71,7 @@ module.exports = app => {
                         insertData[key] = data[key] ? parseFloat(data[key]) : 0;
                         insertData.total_price = data[key] ? parseFloat(data[key]) : 0;
                         continue;
-                    } else if (key === 'tax') {
+                    } else if (key === 'tax' || key === 'rela_tender') {
                         insertData[key] = data[key] ? parseFloat(data[key]) : null;
                         continue;
                     } else if (key === 'sign_date') {
@@ -141,6 +141,21 @@ module.exports = app => {
                     if (!updateNode) {
                         throw '提交数据错误';
                     }
+                    if (Object.prototype.hasOwnProperty.call(row, 'settle_price')) {
+                        const settlePrice = Number(row.settle_price || 0);
+                        if (!Number.isFinite(settlePrice) || settlePrice < 0) {
+                            throw '结算金额只能输入大于等于0的数字';
+                        }
+                        const actionName = options.contract_type === 1 ? '付' : '回';
+                        const yfPrice = Number(updateNode.yf_price || 0);
+                        if (settlePrice === 0 && Number(updateNode.total_price || 0) < yfPrice) {
+                            throw '合同金额不能小于累计应' + actionName + '金额';
+                        }
+                        if (settlePrice !== 0 && settlePrice < yfPrice) {
+                            throw '结算金额不能小于累计应' + actionName + '金额';
+                        }
+                        row.settle_price = settlePrice;
+                    }
                     updateDatas.push(row);
                 }
                 if (updateDatas.length > 0) await transaction.updateRows(this.tableName, updateDatas);

+ 184 - 0
app/service/contract_pay.js

@@ -25,6 +25,29 @@ module.exports = app => {
             this.dataId = 'id';
         }
 
+        async _isSettlePriceEnabled(options, node) {
+            const colSet = await this.ctx.service.contractColSet.getContractColSet(
+                options.spid || null,
+                options.tid || null,
+                node.contract_type
+            );
+            const settlePriceCol = colSet && colSet.info
+                ? colSet.info.find(col => col.field === 'settle_price')
+                : null;
+            return !!(settlePriceCol && Number(settlePriceCol.show) === 1);
+        }
+
+        async _checkPayLimit(options, node, yfPrice) {
+            const useSettlePrice = await this._isSettlePriceEnabled(options, node)
+                && Number(node.settle_price || 0) !== 0;
+            const limit = useSettlePrice ? Number(node.settle_price) : Number(node.total_price || 0);
+            if (Number(yfPrice) > limit) {
+                const limitName = useSettlePrice ? '结算金额' : '合同金额';
+                const actionName = node.contract_type === contractConst.type.expenses ? '付' : '回';
+                throw '累计应' + actionName + '金额不能大于' + limitName;
+            }
+        }
+
         async getPays(options, cid) {
             const sql = 'SELECT * FROM ?? WHERE ' + this.ctx.helper._getOptionsSql(options) + ' AND `cid` = ? ORDER BY `create_time` DESC';
             const sqlParams = [this.tableName, cid];
@@ -62,6 +85,10 @@ module.exports = app => {
             if (!node) {
                 throw '合同不存在';
             }
+            await this._checkPayLimit(options, node, this.ctx.helper.add(
+                Number(node.yf_price || 0),
+                Number(data.yf_price || 0)
+            ));
             const transaction = await this.db.beginTransaction();
             try {
                 const insertData = {
@@ -93,6 +120,155 @@ module.exports = app => {
             return { pays: await this.getPays(options, cid), node: { update: node } };
         }
 
+        async addTenderPays(options, cid, stageIds) {
+            const node = await this.ctx.service.contract.getDataById(cid);
+            if (!node) {
+                throw '合同不存在';
+            }
+            if (!node.rela_tender) {
+                throw '该合同没有关联标段';
+            }
+            if (!Array.isArray(stageIds) || stageIds.length === 0) {
+                throw '请选择至少一个付款阶段';
+            }
+
+            const ids = this.ctx.helper._.uniq(stageIds.map(id => String(id).trim()).filter(id => id));
+            if (ids.length !== stageIds.length) {
+                throw '付款阶段参数有误';
+            }
+
+            const stages = this.ctx.subProject.page_show.phasePay
+                ? await this.ctx.service.phasePay.getDatasForContract(node.rela_tender)
+                : await this.ctx.service.stagePay.getDatasForContract(node.rela_tender);
+            const selectedStages = ids.map(id => stages.find(stage => stage.id + '' === id));
+            if (selectedStages.some(stage => !stage)) {
+                throw '付款阶段不存在或未通过审批';
+            }
+            const addYfPrice = selectedStages.reduce((total, stage) => this.ctx.helper.add(total, Number(stage.yf_price || 0)), 0);
+            await this._checkPayLimit(options, node, this.ctx.helper.add(Number(node.yf_price || 0), addYfPrice));
+
+            const existedPays = await this.getAllDataByCondition({ where: { cid, rela_stage: ids } });
+            if (existedPays.length > 0) {
+                throw '选择的付款阶段已关联该合同';
+            }
+
+            const isPhasePay = !!this.ctx.subProject.page_show.phasePay;
+            const sourcePays = isPhasePay
+                ? await this.ctx.service.phasePay.getAllDataByCondition({ where: { id: ids } })
+                : await this.ctx.service.stage.getAllDataByCondition({ where: { id: ids } });
+            const transaction = await this.db.beginTransaction();
+            try {
+                for (const stage of selectedStages) {
+                    const sourcePay = sourcePays.find(item => item.id + '' === stage.id + '');
+                    if (!sourcePay) {
+                        throw '付款阶段源数据不存在';
+                    }
+                    const insertData = {
+                        spid: options.spid || null,
+                        tid: options.tid || null,
+                        contract_type: options.contract_type,
+                        cid,
+                        uid: isPhasePay ? sourcePay.create_user_id : sourcePay.user_id,
+                        pay_time: stage.pay_time,
+                        used: stage.used || '合同',
+                        pay_price: stage.pay_price,
+                        debit_price: stage.debit_price,
+                        yf_price: stage.yf_price,
+                        sf_price: stage.sf_price,
+                        pay_type: stage.pay_type,
+                        remark: '',
+                        rela_stage: stage.id,
+                        create_time: new Date(),
+                        status: auditConst.status.checked,
+                        times: 1,
+                        final_auditor_str: sourcePay.final_auditor_str || '',
+                        need_shenpi: options.spid
+                            ? this.ctx.subProject.page_show.openContractPaySubProjectShenpi
+                            : this.ctx.subProject.page_show.openContractPayTenderShenpi,
+                    };
+                    const result = await transaction.insert(this.tableName, insertData);
+                    insertData.id = result.insertId;
+                    await this.copyTenderPayAudits(transaction, insertData, sourcePay, isPhasePay);
+                }
+                await this.calcContract(transaction, node);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return { pays: await this.getPays(options, cid), node: { update: node } };
+        }
+
+        async copyTenderPayAudits(transaction, contractPay, sourcePay, isPhasePay) {
+            const sourceAudits = isPhasePay
+                ? await this.ctx.service.phasePayAudit.getAuditors(sourcePay.id, sourcePay.audit_times)
+                : await this.ctx.service.stageAudit.getAuditors(sourcePay.id, sourcePay.times);
+            const audits = [];
+            if (isPhasePay) {
+                let preAuditTime = sourcePay.create_time;
+                for (const audit of sourceAudits) {
+                    audits.push({
+                        spid: contractPay.spid,
+                        tid: contractPay.tid,
+                        cid: contractPay.cid,
+                        cpid: contractPay.id,
+                        csid: null,
+                        aid: audit.audit_id,
+                        order: audit.active_order,
+                        times: 1,
+                        status: auditConst.status.checked,
+                        begin_time: preAuditTime,
+                        end_time: audit.audit_time,
+                        opinion: audit.opinion || '',
+                        audit_type: audit.audit_type,
+                        audit_order: audit.audit_order,
+                    });
+                    if (audit.audit_time) {
+                        preAuditTime = audit.audit_time;
+                    }
+                }
+            } else {
+                const firstAudit = sourceAudits.length > 0 ? sourceAudits[0] : null;
+                audits.push({
+                    spid: contractPay.spid,
+                    tid: contractPay.tid,
+                    cid: contractPay.cid,
+                    cpid: contractPay.id,
+                    csid: null,
+                    aid: sourcePay.user_id,
+                    order: 0,
+                    times: 1,
+                    status: auditConst.status.checked,
+                    begin_time: sourcePay.create_time,
+                    end_time: firstAudit && firstAudit.begin_time ? firstAudit.begin_time : sourcePay.create_time,
+                    opinion: '',
+                    audit_type: auditType.key.common,
+                    audit_order: 0,
+                });
+                for (const audit of sourceAudits) {
+                    audits.push({
+                        spid: contractPay.spid,
+                        tid: contractPay.tid,
+                        cid: contractPay.cid,
+                        cpid: contractPay.id,
+                        csid: null,
+                        aid: audit.aid,
+                        order: audit.order,
+                        times: 1,
+                        status: auditConst.status.checked,
+                        begin_time: audit.begin_time,
+                        end_time: audit.end_time,
+                        opinion: audit.opinion || '',
+                        audit_type: audit.audit_type,
+                        audit_order: audit.audit_order,
+                    });
+                }
+            }
+            if (audits.length > 0) {
+                await transaction.insert(this.ctx.service.contractSpAudit.tableName, audits);
+            }
+        }
+
         async save(options, cid, data) {
             if (!data.id) {
                 throw '参数有误';
@@ -105,6 +281,14 @@ module.exports = app => {
             if (!cpInfo) {
                 throw '合同' + contractConst.typeName[cpInfo.contract_type] + '不存在';
             }
+            if (cpInfo.rela_stage) {
+                throw '关联标段期的合同支付不能编辑';
+            }
+            const newYfPrice = this.ctx.helper.add(
+                this.ctx.helper.sub(Number(node.yf_price || 0), Number(cpInfo.yf_price || 0)),
+                Number(data.yf_price || 0)
+            );
+            await this._checkPayLimit(options, node, newYfPrice);
             const transaction = await this.db.beginTransaction();
             try {
                 await transaction.update(this.tableName, data);

+ 15 - 0
app/service/contract_tree.js

@@ -190,6 +190,21 @@ module.exports = app => {
                     if (!updateNode) {
                         const contractNode = await this.ctx.service.contract.getDataById(row.id);
                         if (contractNode) {
+                          if (Object.prototype.hasOwnProperty.call(row, 'settle_price')) {
+                              const settlePrice = Number(row.settle_price || 0);
+                              if (!Number.isFinite(settlePrice) || settlePrice < 0) {
+                                  throw '结算金额只能输入大于等于0的数字';
+                              }
+                              const actionName = options.contract_type === contractConst.type.expenses ? '付' : '回';
+                              const yfPrice = Number(contractNode.yf_price || 0);
+                              if (settlePrice === 0 && Number(contractNode.total_price || 0) < yfPrice) {
+                                  throw '合同金额不能小于累计应' + actionName + '金额';
+                              }
+                              if (settlePrice !== 0 && settlePrice < yfPrice) {
+                                  throw '结算金额不能小于累计应' + actionName + '金额';
+                              }
+                              row.settle_price = settlePrice;
+                          }
                           const updateContractData = !updateAll ? this._filterUpdateInvalidField(contractNode.id, row) : row;
                           updateContractDatas.push(updateContractData);
                           continue;

+ 1 - 0
app/service/cost_stage_book.js

@@ -250,6 +250,7 @@ module.exports = app => {
                 if (detailUpdateData.length > 0) await conn.updateRows(this.ctx.service.costStageBookDetail.tableName, detailUpdateData);
                 await conn.commit();
             } catch(err) {
+                this.ctx.log(err);
                 await conn.rollback();
                 return { calc: false, bills: [], detail: [] };
             }

+ 4 - 3
app/service/pay.js

@@ -8,6 +8,7 @@
  * @version
  */
 const payConst = require('../const/deal_pay.js');
+const auditConst = require('../const/audit.js').stage;
 const writableFields = ['name', 'minus', 'sprice', 'sexpr', 'rprice', 'rexpr', 'is_yf', 'dl_type', 'dl_count', 'dl_tp_type', 'dl_tp'];
 
 module.exports = app => {
@@ -34,10 +35,10 @@ module.exports = app => {
 
             try {
                 const projectData = await this.ctx.service.project.getDataById(this.ctx.session.sessionProject.id);
-                const pays = projectData.dealpay_json 
+                const pays = projectData.dealpay_json
                     ? JSON.parse(projectData.dealpay_json)
                     : JSON.parse(JSON.stringify(payConst.payTemplate));
-    
+
                 for (const p of pays) {
                     p.tid = tid;
                     p.csid = 0;
@@ -212,4 +213,4 @@ module.exports = app => {
     }
 
     return Pay;
-};
+};

+ 21 - 2
app/service/phase_pay.js

@@ -553,7 +553,26 @@ module.exports = app => {
             const fp = this.ctx.helper._.orderBy(allPhase.filter(x => { return x.maxRelaStageOrder > 0; }), ['maxRelaStageOrder'], ['desc']);
             this.ctx.stageRelaPhasePay = fp.length > 0 ? fp[0] : null;
         }
-    }
 
+        async getDatasForContract(tid) {
+            const pays = await this.getAllDataByCondition({ where: { tid, audit_status: audit.status.checked }, orders: [['phase_order', 'asc']] });
+            const stages = [];
+            for (const p of pays) {
+                const oneStage = {
+                    id: p.id,
+                    pay_time: p.phase_date ? p.phase_date + '-01' : '',
+                    used: '合同',
+                    order: p.phase_order,
+                    pay_price: p.pay_tp,
+                    debit_price: p.cut_tp,
+                    yf_price: p.yf_tp,
+                    sf_price: p.sf_tp,
+                    pay_type: '网上转账',
+                };
+                stages.push(oneStage);
+            }
+            return stages;
+        }
+    }
     return PhasePay;
-};
+};

+ 3 - 3
app/service/shenpi_audit.js

@@ -273,7 +273,7 @@ module.exports = app => {
                                         audit_group: s.audit_group || '',
                                         audit_group_order: s.audit_group_order || 0,
                                         audit_group_limit: s.audit_group_limit || 0,
-                                        audit_checkno_valid: s.audit_checkno_valid,
+                                        audit_checkno_valid: s.audit_checkno_valid || 1,
                                         audit_group_need: s.audit_group_need || 1,
                                         sp_group: result.insertId,
                                     });
@@ -298,7 +298,7 @@ module.exports = app => {
                                     audit_group: s.audit_group || '',
                                     audit_group_order: s.audit_group_order || 0,
                                     audit_group_limit: s.audit_group_limit || 0,
-                                    audit_checkno_valid: s.audit_checkno_valid,
+                                    audit_checkno_valid: s.audit_checkno_valid || 1,
                                     audit_group_need: s.audit_group_need || 1,
                                     sp_group: 0,
                                 });
@@ -512,7 +512,7 @@ module.exports = app => {
                     audit_group: a.audit_group || '',
                     audit_group_order: a.audit_group_order || 0,
                     audit_group_limit: a.audit_group_limit || 0,
-                    audit_checkno_valid: a.audit_checkno_valid,
+                    audit_checkno_valid: a.audit_checkno_valid || 1,
                     audit_group_need: a.audit_group_need || 1,
                     audit_ledger_id: a.audit_ledger_id || '',
                     sp_group,

+ 6 - 1
app/service/stage.js

@@ -756,7 +756,12 @@ module.exports = app => {
         }
 
         async updateCheckCalcFlag(stage, check) {
-            const result = await this.db.update(this.tableName, { id: stage.id, check_calc: check });
+            const result = await this.db.update(this.tableName, { id: stage.id, check_calc: check, update_calc_time: new Date() });
+            return result.affectedRows === 1;
+        }
+
+        async updateExprCalcTime(stage) {
+            const result = await this.db.update(this.tableName, { id: stage.id, update_expr_calc_time: new Date() });
             return result.affectedRows === 1;
         }
 

+ 19 - 20
app/service/stage_att.js

@@ -58,6 +58,22 @@ module.exports = app => {
             return result.affectedRows === 1;
         }
 
+        analysisAttData(data) {
+            const datas = data instanceof Array ? data : [data];
+            datas.forEach(item => {
+                item.orginpath = this.ctx.app.config.fujianOssPath + item.filepath;
+                item.filepath = this.ctx.app.config.fujianOssPath + item.filepath;
+                item.viewpath = this.ctx.helper.getPreviewPath(item.fileext, item.filepath);
+                delete item.filepath;
+                // if (!this.ctx.helper.canPreview(item.fileext)) {
+                //     item.filepath = `/tender/${this.ctx.tender.id}/measure/stage/${this.ctx.params.order}/download/file/${item.id}`;
+                // } else {
+                //     item.filepath = this.ctx.app.config.fujianOssPath + item.filepath;
+                // }
+                item.in_time = this.ctx.moment(item.in_time * 1000).format('YYYY-MM-DD');
+            });
+        }
+
         /**
          * 获取所有附件
          * @param {int} tid - 标段id
@@ -72,18 +88,8 @@ module.exports = app => {
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.ctx.service.ledger.departTableName(tid), tid, sid];
             // return await this.db.query(sql, sqlParam);
             const result = await this.db.query(sql, sqlParam);
-            return result.map(item => {
-                item.orginpath = this.ctx.app.config.fujianOssPath + item.filepath;
-                item.filepath = this.ctx.app.config.fujianOssPath + item.filepath;
-                item.viewpath = this.ctx.helper.getPreviewPath(item.fileext, item.filepath);
-                delete item.filepath;
-                // if (!this.ctx.helper.canPreview(item.fileext)) {
-                //     item.filepath = `/tender/${this.ctx.tender.id}/measure/stage/${this.ctx.params.order}/download/file/${item.id}`;
-                // } else {
-                //     item.filepath = this.ctx.app.config.fujianOssPath + item.filepath;
-                // }
-                return item;
-            });
+            this.analysisAttData(result);
+            return result;
         }
 
         /**
@@ -93,20 +99,13 @@ module.exports = app => {
          * @return {void}
          */
         async getDataByFid(id) {
-            const { ctx } = this;
             const sql = 'SELECT att.id, att.lid, att.uid, att.filepath, att.filename, att.extra_upload, att.fileext, att.filesize, att.remark, att.in_time,' +
                 ' pa.name as `username`, leg.name as `lname`, leg.code as `code`, leg.ledger_id as `ledger_id`,leg.b_code as `b_code`' +
                 ' FROM ?? AS att,?? AS pa,?? AS leg' +
                 ' WHERE leg.id = att.lid AND pa.id = att.uid AND att.id = ? ORDER BY att.in_time DESC';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.ctx.service.ledger.tableName, id];
             const result = await this.db.queryOne(sql, sqlParam);
-            result.orginpath = this.ctx.app.config.fujianOssPath + result.filepath;
-            if (!ctx.helper.canPreview(result.fileext)) {
-                result.filepath = `/tender/${ctx.tender.id}/measure/stage/${ctx.params.order}/download/file/${result.id}`;
-            } else {
-                // result.filepath = result.filepath.replace(/^app|\/app/, '');
-                result.filepath = this.ctx.app.config.fujianOssPath + result.filepath;
-            }
+            this.analysisAttData(result);
             return result;
         }
 

+ 31 - 0
app/service/stage_pay.js

@@ -559,6 +559,37 @@ module.exports = app => {
             }
             if (updateData.length > 0) await transaction.updateRows(this.tableName, updateData);
         }
+
+        async getDatasForContract(tid) {
+            const stages = await this.ctx.service.stage.getAllDataByCondition({ where: { tid, status: auditConst.stage.status.checked }, orders: [['order', 'asc']] });
+            const stagePays = [];
+            for (const s of stages) {
+                const pay = await this.getStageLastestPays(s);
+                const pay_price_pays = this._.filter(pay, function(p) {
+                    return p.ptype === 4 || (p.ptype === 1 && p.minus === 0);
+                });
+                const pay_price = pay_price_pays.length > 0 ? this.ctx.helper.sum(this._.map(pay_price_pays, 'tp')) : 0;
+                const debit_price_pays = this._.filter(pay, { ptype: 1, minus: 1 });
+                const debit_price = debit_price_pays.length > 0 ? this.ctx.helper.sum(this._.map(debit_price_pays, 'tp')) : 0;
+                const yf_price_pays = this._.filter(pay, { ptype: 2 });
+                const yf_price = yf_price_pays.length > 0 ? this.ctx.helper.sum(this._.map(yf_price_pays, 'tp')) : 0;
+                const sf_price_pays = this._.filter(pay, { ptype: 3 });
+                const sf_price = sf_price_pays.length > 0 ? this.ctx.helper.sum(this._.map(sf_price_pays, 'tp')) : 0;
+                const oneStage = {
+                    id: s.id,
+                    pay_time: s.period ? (s.period.split('~')[1] ? s.period.split('~')[1] : s.period) : '',
+                    used: '合同',
+                    order: s.order,
+                    pay_price,
+                    debit_price,
+                    yf_price,
+                    sf_price,
+                    pay_type: '网上转账',
+                };
+                stagePays.push(oneStage);
+            }
+            return stagePays;
+        }
     }
 
     return StagePay;

+ 1 - 1
app/service/std_gcl_list.js

@@ -22,7 +22,7 @@ module.exports = app => {
         }
 
         async getSafeGcl () {
-            return await this.getAllDataByCondition({ where: { sub_type: 1 } });
+            return await this.getAllDataByCondition({ where: { sub_type: 1 }, orders: [['remark', 'asc']] });
         }
     }
 

+ 1 - 1
app/service/sub_proj_permission.js

@@ -145,7 +145,7 @@ module.exports = app => {
             return {
                 budget_permission: [this.PermissionConst.budget.view.value],
                 file_permission: [this.PermissionConst.file.view.value],
-                manage_permission: [this.PermissionConst.manage.view.value],
+                manage_permission: [],
                 filing_type: 'all',
                 info_permission: [this.PermissionConst.info.view.value],
                 datacollect_permission: [this.PermissionConst.datacollect.view.value],

+ 1 - 1
app/service/temp_file.js

@@ -35,7 +35,7 @@ module.exports = app => {
         }
 
         async getFiles(condition) {
-            condition.orders = [['create_time', 'desc']];
+            condition.orders = [['create_time', 'asc']];
             const result = await this.getAllDataByCondition(condition);
             this._analysisData(result);
             return result;

+ 1 - 0
app/service/tender.js

@@ -19,6 +19,7 @@ const commonQueryColumns = [
     's2b_gxby_check', 's2b_gxby_limit', 's2b_dagl_check', 's2b_dagl_limit', 's2b_multi_check', 's2b_multi_limit',
     'has_rela', 'his_id', 'rpt_show_level',
     'build_status', 'settle_order', 'spid',
+    'share_token', 'share_expiration',
 ];
 
 module.exports = app => {

+ 3 - 3
app/view/change/modal.ejs

@@ -921,12 +921,12 @@
                 for (const [index,clinfo] of cInfo.changeList.entries()) {
                     if (clinfo.lid != 0) {
                         let listinfo = changeListData.find(function (item) {
-                            return (item.id !== undefined && item.id == clinfo.lid) || (item.id === undefined && item.leafXmjs !== undefined && item.leafXmjs.length !== 0 && item.leafXmjs[0].gcl_id == clinfo.lid);
+                            return (item.leafXmjs && item.leafXmjs.length > 0 && _.find(item.leafXmjs, {gcl_id: clinfo.lid })) || (item.id && item.id == clinfo.lid);
                         });
                         if (listinfo === undefined || (clinfo.lid && clinfo.gcl_id && clinfo.lid !== clinfo.gcl_id)) {
                             // 有可能这部分台账发生变化,此时要更新清单lid信息,防止数据丢失
                             const newlistinfo = changeListData.find(function (item) {
-                                return (item.id !== undefined && item.id == clinfo.gcl_id) || (item.id === undefined && item.leafXmjs !== undefined && item.leafXmjs.length !== 0 && _.find(item.leafXmjs, {gcl_id: clinfo.gcl_id }));
+                                return (item.leafXmjs && item.leafXmjs.length > 0 && _.find(item.leafXmjs, {gcl_id: clinfo.gcl_id })) || (item.id && item.id == clinfo.gcl_id);
                             });
                             if ((listinfo === undefined && newlistinfo) || (listinfo && newlistinfo && !isObjEqual(listinfo, newlistinfo))) {
                                 listinfo = newlistinfo;
@@ -1154,7 +1154,7 @@
                                 oneUpdate[key] = leafInfo[key];
                                 // clinfo[key] = leafInfo[key];
                                 needUpdate = true;
-                            } else if (leafInfo[key] === undefined && leafInfo.jldy !== clinfo[key]) {
+                            } else if (leafInfo[key] === undefined && leafInfo.jldy !== (clinfo[key] || clinfo.xmj_jldy || null)) {
                                 oneUpdate[key] = leafInfo.jldy;
                                 // clinfo[key] = leafInfo.jldy;
                                 needUpdate = true;

+ 6 - 1
app/view/change/revise.ejs

@@ -38,6 +38,11 @@
                     <a href="javascript: void(0);" name="cpc" type="paste" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="粘贴"><i class="fa fa-clipboard" aria-hidden="true"></i></a>
                 </div>
                 <div class="d-inline-block">
+                    <div class="btn-group">
+                        <button href="javascript: void(0);" class="btn btn-sm btn-light text-primary" id="pos-show"><i class="fa fa-eye-slash"></i> 隐藏计量单元</button>
+                    </div>
+                </div>
+                <div class="d-inline-block">
                     <div class="input-group input-group-sm ml-2">
                         <div class="input-group-prepend">
                             <span class="input-group-text" id="basic-addon1">表达式</span>
@@ -62,7 +67,7 @@
                 <div class="sjs-height-1" style="overflow: hidden" id="bills-spread">
                 </div>
                 <div class="bcontent-wrap">
-                    <div id="revise-resize" class="resize-y" id="top-spr" r-Type="height" div1=".sjs-height-1" div2=".bcontent-wrap" title="调整大小"><!--调整上下高度条--></div>
+                    <div id="revise-resize" class="resize-y" r-Type="height" div1=".sjs-height-1" div2=".bcontent-wrap" title="调整大小"><!--调整上下高度条--></div>
                     <div class="bc-bar mb-1">
                         <ul class="nav nav-tabs">
                             <li class="nav-item">

+ 7 - 5
app/view/contract/col_set.ejs

@@ -68,10 +68,12 @@ const _initialColSetTbodyHtml = $tbody.html();
 
 // 更新上下移链接显示状态
 function updateMoveButtons() {
-    const $currentRows = $tbody.find('tr');
-    const totalRows = $currentRows.length;
+    const $movableRows = $tbody.find('tr').filter(function() {
+        return $(this).find('.move-up, .move-down').length > 0;
+    });
+    const totalRows = $movableRows.length;
 
-    $currentRows.each(function(index) {
+    $movableRows.each(function(index) {
         const $row = $(this);
         const $upLink = $row.find('.move-up');
         const $downLink = $row.find('.move-down');
@@ -114,7 +116,7 @@ $tbody.on('click', '.move-up', function(e) {
     const $currentRow = $(this).closest('tr');
     const $prevRow = $currentRow.prev('tr');
 
-    if ($prevRow.length) {
+    if ($prevRow.find('.move-up, .move-down').length) {
         $currentRow.insertBefore($prevRow);
         updateMoveButtons();
     }
@@ -125,7 +127,7 @@ $tbody.on('click', '.move-down', function(e) {
     const $currentRow = $(this).closest('tr');
     const $nextRow = $currentRow.next('tr');
 
-    if ($nextRow.length) {
+    if ($nextRow.find('.move-up, .move-down').length) {
         $currentRow.insertAfter($nextRow);
         updateMoveButtons();
     }

+ 7 - 1
app/view/contract/detail.ejs

@@ -34,6 +34,7 @@
                 </div>
             <% } %>
             <div class="ml-auto">
+                <a href="javascript:void(0);" id="contract-total-btn" class="btn btn-sm btn-primary mr-2">合计</a>
                 <% if (ctx.session.sessionUser.is_admin) { %>
                     <a href="#col-set" data-toggle="modal" data-target="#col-set" class="btn btn-sm btn-primary mr-2">列设置</a>
                     <a href="javascript:void(0);" data-stid="<%- ctx.contract_tender ? ctx.contractOptions.tid : '' %>" class="btn btn-sm btn-primary get-audits mr-2">成员管理</a>
@@ -60,6 +61,8 @@
                     <div class="c-body" id="right-view" style="display: none; width: 30%">
                         <div class="resize-x" id="right-spr" r-Type="width" div1="#left-view" div2="#right-view" title="调整大小" a-type="percent"><!--调整左右高度条--></div>
                         <div class="tab-content">
+                            <div id="contract-search" class="tab-pane tab-select-show">
+                            </div>
                             <div id="std-xmj" class="tab-pane">
                             </div>
                         </div>
@@ -91,6 +94,7 @@
                                 <!-- 合同支付的按钮 -->
                                 <!--                                <a href="#cons-addpay2" data-toggle="modal" data-target="#cons-addpay2" class="btn btn-primary btn-sm pull-right mr-2">添加回款2</a>-->
                                 <a href="javascript:void(0);" id="add_contract_pay_btn" style="display: none;" class="btn btn-primary btn-sm pull-right mr-2">添加<% if (ctx.contract_type === contractConst.type.expenses) { %>支付<% } else if (ctx.contract_type === contractConst.type.income) { %>回款<% } %></a>
+                                <% if (ctx.contract_type === contractConst.type.expenses) { %><a href="javascript:void(0);" id="add_tender_contract_pay_btn" style="display: none;" class="btn btn-primary btn-sm pull-right mr-2">添加标段支付记录</a><% } %>
                                 <!-- 合同详情的按钮,点击编辑出现确定和取消按钮 -->
                                 <a href="javascript:void(0);" id="cancel_contract_btn" style="display: none" class="btn btn-secondary btn-sm pull-right mr-2">取消</a>
                                 <a href="javascript:void(0);" id="save_contract_btn" style="display: none" class="btn btn-primary btn-sm pull-right mr-2">确定</a>
@@ -296,6 +300,9 @@
                 <!--右侧菜单-->
                 <ul class="nav flex-column right-nav">
                     <li class="nav-item">
+                        <a class="nav-link" content="#contract-search" href="javascript: void(0);">查找定位</a>
+                    </li>
+                    <li class="nav-item">
                         <a class="nav-link" content="#std-xmj" href="javascript: void(0);">项目节</a>
                     </li>
                 </ul>
@@ -333,5 +340,4 @@
     types.push('');
     let contractPays = [];
     let contractSupplements = [];
-</script>)
 </script>

+ 94 - 2
app/view/contract/detail_modal.ejs

@@ -1,5 +1,30 @@
 <% include ../shares/delete_hint_modal.ejs %>
 <% include ./col_set.ejs %>
+<!--合计弹窗-->
+<div class="modal fade" id="cons-total" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">合计</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="modal-height-500" style="overflow:auto;">
+                    <table class="table table-sm table-bordered text-center" id="contract-total-table" style="word-break:break-all; table-layout: fixed">
+                        <thead id="contract-total-thead"></thead>
+                        <tbody id="contract-total-tbody"></tbody>
+                        <tfoot id="contract-total-tfoot"></tfoot>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
 <% if (ctx.session.sessionUser.is_admin || audit_permission.permission_add) { %>
 <!--新增合同-->
 <div class="modal fade" id="cons-add" data-backdrop="static">
@@ -53,10 +78,18 @@
                         </div>
                     </div>
                     <div class="col-md-6">
+                        <% if (ctx.contract_type === contractConst.type.expenses) { %>
+                        <div class="form-group">
+                            <label>关联标段</label>
+                            <select class="form-control form-control-sm" name="rela_tender">
+                            </select>
+                        </div>
+                        <% } else { %>
                         <div class="form-group">
                             <label>签订日期</label>
                             <input class="datepicker-here form-control form-control-sm" name="sign_date" placeholder="点击选择时间" data-date-format="yyyy-MM-dd" data-language="zh" type="text" autocomplete="off">
                         </div>
+                        <% } %>
                     </div>
                 </div>
 
@@ -97,6 +130,14 @@
                             <input class="form-control form-control-sm" placeholder="" type="text" name="address">
                         </div>
                     </div>
+                    <% if (ctx.contract_type === contractConst.type.expenses) { %>
+                    <div class="col-md-6">
+                        <div class="form-group">
+                            <label>签订日期</label>
+                            <input class="datepicker-here form-control form-control-sm" name="sign_date" placeholder="点击选择时间" data-date-format="yyyy-MM-dd" data-language="zh" type="text" autocomplete="off">
+                        </div>
+                    </div>
+                    <% } %>
                 </div>
 
                 <div class="payee-fields-group">
@@ -488,6 +529,41 @@
         </div>
     </div>
 </div>
+<% if (ctx.contract_type === contractConst.type.expenses) { %>
+<!--添加支付,非关联合同显示-->
+<div class="modal fade" id="cons-tender-addpay" data-backdrop="static">
+    <div class="modal-dialog modal-xl" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">添加标段<% if (ctx.contract_type === contractConst.type.expenses) { %>支付<% } else if (ctx.contract_type === contractConst.type.income) { %>回款<% } %>记录</h5>
+            </div>
+            <div class="modal-body">
+                <div class="mb-2 text-left">
+                    <span class="text-muted">关联标段名称:</span>
+                    <span id="tender-contract-pay-name"></span>
+                </div>
+                <div class="modal-height-500" style="overflow:auto;">
+                    <table class="table table-sm table-bordered text-center" style="word-break:break-all; table-layout: fixed">
+                        <thead>
+                        <% if (ctx.contract_type === contractConst.type.income) { %>
+                            <th width="3%"></th><th>期数</th><th>回款日期</th><th>回款金额</th><th>扣款金额</th><th>应回金额</th><th>实回金额</th><th>回款方式</th>
+                        <% } else if (ctx.contract_type === contractConst.type.expenses) { %>
+                            <th width="3%"></th><th>期数</th><th>支付日期</th><th>付款金额</th><th>扣款金额</th><th>应付金额</th><th>实付金额</th><th>支付方式</th>
+                        <% } %>
+                        </thead>
+                        <tbody id="stages-pay">
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-primary" id="add-tender-contract-pay">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% } %>
 <!--附件-->
 <div class="modal fade" id="cons-pay-file" data-backdrop="static" style="z-index: 1049">
     <input type="hidden" name="cpid">
@@ -640,7 +716,6 @@
         </div>
     </div>
 </div>
-<% if (ctx.session.sessionUser.is_admin || audit_permission.permission_edit) { %>
 <div class="modal fade" id="cons-move" data-backdrop="static">
     <div class="modal-dialog" role="document">
         <div class="modal-content">
@@ -698,7 +773,24 @@
         </div>
     </div>
 </div>
-<% } %>
+<div class="modal fade" id="rela-tender" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">关联标段</h5>
+            </div>
+            <div class="modal-body">
+                <div class="mb-2">合同编号: <b class="mr-3" id="rela-contract"></b> 原关联标段: <b class="mr-3 old-rela-tender"></b> 新关联标段: <b class="new-rela-tender"></b></div>
+                <div class="modal-height-500 scroll-y" id="tender-list">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-primary" id="rela-tender-ok">确认</button>
+            </div>
+        </div>
+    </div>
+</div>
 <% if (shenpi_status || pay_shenpi_status) { %>
 <!--上报审批-->
 <div class="modal fade" id="sub-sp" data-backdrop="static">

+ 5 - 4
app/view/setting/user_modal.ejs

@@ -47,11 +47,12 @@
                 <div class="form-group">
                     <label>单位名称<b class="text-danger">*</b></label>
                     <!--<input class="form-control form-control-sm" value="" name="company" type="text">-->
-                    <input value="" name="account_group" type="hidden">
-                    <select class="form-control form-control-sm" name="company">
-                        <option>请选择</option>
+                    <input value="" name="account_group" type="hidden" id="edit_user_account_group">
+                    <input value="" name="company_id" type="hidden" id="edit_user_company_id">
+                    <select class="form-control form-control-sm" name="company" id="edit_user_company">
+                        <option value="">请选择</option>
                         <% for (const u of unitList) { %>
-                            <option <% if (company && company === u.name) { %>selected<% } %>><%- u.name %></option>
+                            <option value="<%- u.name %>" data-id="<%- u.id %>" data-type="<%- u.type %>" <% if (company && company === u.name) { %>selected<% } %>><%- u.name %></option>
                         <% } %>
                     </select>
                 </div>

+ 85 - 10
app/view/shares/ai_trans.ejs

@@ -19,31 +19,106 @@
         </div>
     </div>
 </div>
+<!--<div class="modal fade" id="ai-trans" data-backdrop="static">-->
+    <!--<div class="modal-dialog modal-lg" role="document">-->
+        <!--<div class="modal-content">-->
+            <!--<div class="modal-header">-->
+                <!--<h5 class="modal-title">AI识别</h5>-->
+                <!--<button type="button" class="close" data-dismiss="modal" aria-label="Close">-->
+                    <!--<span aria-hidden="true">&times;</span>-->
+                <!--</button>-->
+            <!--</div>-->
+            <!--<div class="modal-body">-->
+                <!--<div class="form-group">-->
+                    <!--<label for="ai-trans-file">选择文件</label><i class="fa fa-spinner fa-pulse fa-lg fa-fw text-primary" id="select-excel-loading" style="display: none;"></i>-->
+                    <!--<input type="file" class="form-control-file" id="ai-trans-file" accept="*.json" name="file">-->
+                <!--</div>-->
+                <!--<div class="form-group">-->
+                    <!--<label for="ai-trans-file">高级设置</label>-->
+                    <!--<div class="input-group input-group-sm ml-2 mt-1">-->
+                        <!--<div class="input-group-prepend">-->
+                            <!--<span class="input-group-text">仅识别:</span>-->
+                        <!--</div>-->
+                        <!--<input type="text" class="form-control mr-3" style="width: 240px" value="" id="ai-trans-keyword">-->
+                    <!--</div>-->
+                <!--</div>-->
+            <!--</div>-->
+            <!--<div class="modal-footer">-->
+                <!--<button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>-->
+                <!--<a href="javascript: void(0);" class="btn btn-sm btn-primary" id="ai-trans-ok">确定</a>-->
+            <!--</div>-->
+        <!--</div>-->
+    <!--</div>-->
+<!--</div>-->
 <div class="modal fade" id="ai-trans" data-backdrop="static">
+    <style>
+        .thumbnail-active {
+            border: 1px solid dodgerblue; !important;
+        }
+        .thumbnail-container img {
+            width: 100%;
+            height: 90px;
+            border: 1px solid #ddd;
+            border-radius: 4px;
+            transition: transform 0.2s;
+        }
+        .ai-del-btn {
+            position: absolute;
+            top: 0;
+            right: 0;
+            background: none;
+            border: none;
+            cursor: pointer;
+            font-size: 20px;
+            font-weight: bolder;
+            line-height: normal;
+            margin-top: -0.5em;
+        }
+        img[src=""],img:not([src]){
+            opacity:0;
+        }
+    </style>
     <div class="modal-dialog modal-lg" role="document">
         <div class="modal-content">
             <div class="modal-header">
-                <h5 class="modal-title">AI识别</h5>
+                <h5 class="modal-title" id="ai-trans-title">AI识别</h5>
                 <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                     <span aria-hidden="true">&times;</span>
                 </button>
             </div>
             <div class="modal-body">
                 <div class="form-group">
-                    <label for="ai-trans-file">选择文件</label><i class="fa fa-spinner fa-pulse fa-lg fa-fw text-primary" id="select-excel-loading" style="display: none;"></i>
-                    <input type="file" class="form-control-file" id="ai-trans-file" accept="*.json" name="file">
-                </div>
-                <div class="form-group">
-                    <label for="ai-trans-file">高级设置</label>
-                    <div class="input-group input-group-sm ml-2 mt-1">
-                        <div class="input-group-prepend">
-                            <span class="input-group-text">仅识别:</span>
+                    <div class="row">
+                        <div class="col-2">
+                            <div class="text-black-50 mb-2">可Ctrl+V粘贴图片</div>
+                            <div class="card mb-2" id="ai-trans-file-add">
+                                <div class="card-body">
+                                    <div class="d-flex p-1" >
+                                        <!--<i class="fa fa-plus fa-3x text-black-50"></i>-->
+                                        <span class="text-black-50 p-1 d-flex" style="font-size: 60px; width: 50px; height: 50px; line-height: 30px">+</span>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="col-10">
+                            <div class="image-view border" style="height: 500px">
+                                <img class="w-100 h-100" id="ai-trans-show-file" style="object-fit: contain">
+                            </div>
                         </div>
-                        <input type="text" class="form-control mr-3" style="width: 240px" value="" id="ai-trans-keyword">
                     </div>
                 </div>
+                <!--<div class="form-group">-->
+                    <!--<label for="ai-trans-file">高级设置</label>-->
+                    <!--<div class="input-group input-group-sm ml-2 mt-1">-->
+                        <!--<div class="input-group-prepend">-->
+                            <!--<span class="input-group-text">仅识别:</span>-->
+                        <!--</div>-->
+                        <!--<input type="text" class="form-control mr-3" style="width: 240px" value="" id="ai-trans-keyword">-->
+                    <!--</div>-->
+                <!--</div>-->
             </div>
             <div class="modal-footer">
+                <input type="hidden" class="form-control-file" id="ai-trans-file" name="file">
                 <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
                 <a href="javascript: void(0);" class="btn btn-sm btn-primary" id="ai-trans-ok">确定</a>
             </div>

+ 26 - 0
app/view/shares/upload_att.ejs

@@ -18,6 +18,32 @@
         </div>
     </div>
 </div>
+<div class="modal fade" id="add-big-file" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">上传附件</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group text-danger" style="display: none" id="upload-big-file-hint">
+                </div>
+                <div class="form-group">
+                    <label for="formGroupExampleInput">文件大小限制:500MB,支持<span data-toggle="tooltip" data-placement="bottom" title="" data-original-title="doc,docx,xls,xlsx,ppt,pptx,pdf">office等文档格式</span>、<span data-toggle="tooltip" data-placement="bottom" title="" data-original-title="jpg,png,bmp">图片格式</span>、<span data-toggle="tooltip" data-placement="bottom" title="" data-original-title="rar,zip">压缩包格式</span></label>
+                    <input type="file" class="" id="upload-big-file">
+                </div>
+                <div class="form-group progress">
+                    <div id="upload-big-file-progress" class="progress-bar" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100"></div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-primary btn-sm" id="add-big-file-stop" style="display: none">暂停</button>
+                <button type="button" class="btn btn-primary btn-sm" id="add-big-file-resume" style="display: none">重新上传</button>
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary btn-sm" id="add-big-file-ok">确认</button>
+            </div>
+        </div>
+    </div>
+</div>
 <script>
     const whiteList = JSON.parse('<%- JSON.stringify(ctx.app.config.multipart.whitelist) %>');
 </script>

+ 1 - 1
app/view/stage/index.ejs

@@ -571,7 +571,6 @@
     const measureType = JSON.parse('<%- JSON.stringify(measureType) %>');
     const stage = JSON.parse(unescape('<%- escape(JSON.stringify(ctx.stage)) %>'));
     const imType = JSON.parse('<%- JSON.stringify(imType) %>');
-    const whiteList = JSON.parse('<%- JSON.stringify(whiteList) %>');
     let attData = JSON.parse(unescape('<%- escape(JSON.stringify(attData)) %>'));
     const ckColSetting = 'stage-col-visible-1.0.3-<%- tender.id %>';
     const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');
@@ -586,6 +585,7 @@
     const deleteFilePermission = <%- deleteFilePermission %>;
     const limits = JSON.parse('<%- JSON.stringify(limits) %>');
     const correctCalcContractTp = <%- !!ctx.subProject.page_show.correctCalcContractTp %>;
+    const stageComplete = <%- ctx.stage.status === auditConst.status.checked ? 1 : 0 %>;
 </script>
 <style>
 

+ 2 - 21
app/view/stage/modal.ejs

@@ -197,26 +197,6 @@
         </div>
     </div>
 </div>
-<!--上传附件-->
-<div class="modal fade" id="upload" data-backdrop="static">
-    <div class="modal-dialog" role="document">
-        <div class="modal-content">
-            <div class="modal-header">
-                <h5 class="modal-title">上传附件</h5>
-            </div>
-            <div class="modal-body">
-                <div class="form-group">
-                    <label for="formGroupExampleInput">单个文件大小限制:50MB,支持<span data-toggle="tooltip" data-placement="bottom" title="" data-original-title="doc,docx,xls,xlsx,ppt,pptx,pdf">office等文档格式</span>、<span data-toggle="tooltip" data-placement="bottom" title="" data-original-title="jpg,png,bmp">图片格式</span>、<span data-toggle="tooltip" data-placement="bottom" title="" data-original-title="rar,zip">压缩包格式</span></label>
-                    <input type="file" class="" id="upload-file" multiple>
-                </div>
-            </div>
-            <div class="modal-footer">
-                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
-                <button type="button" class="btn btn-primary btn-sm" id="upload-file-btn">确认</button>
-            </div>
-        </div>
-    </div>
-</div>
 
 <% if (stage.revising) { %>
 <!--正在修订提示-->
@@ -738,4 +718,5 @@
 <% include ../shares/batch_import_modal.ejs%>
 <% include ../shares/import_file_modal.ejs%>
 <% include ../spss/spss_select_modal.ejs %>
-<% include ../shares/check_modal.ejs %>
+<% include ../shares/check_modal.ejs %>
+<% include ../shares/upload_att.ejs %>

+ 0 - 26
app/view/sub_proj/progress_modal.ejs

@@ -1,31 +1,5 @@
 <% include ../shares/delete_hint_modal.ejs %>
 <% include ../shares/upload_att.ejs %>
-<div class="modal fade" id="add-big-file" data-backdrop="static">
-    <div class="modal-dialog" role="document">
-        <div class="modal-content">
-            <div class="modal-header">
-                <h5 class="modal-title">上传附件</h5>
-            </div>
-            <div class="modal-body">
-                <div class="form-group text-danger" style="display: none" id="upload-big-file-hint">
-                </div>
-                <div class="form-group">
-                    <label for="formGroupExampleInput">文件大小限制:500MB,支持<span data-toggle="tooltip" data-placement="bottom" title="" data-original-title="doc,docx,xls,xlsx,ppt,pptx,pdf">office等文档格式</span>、<span data-toggle="tooltip" data-placement="bottom" title="" data-original-title="jpg,png,bmp">图片格式</span>、<span data-toggle="tooltip" data-placement="bottom" title="" data-original-title="rar,zip">压缩包格式</span></label>
-                    <input type="file" class="" id="upload-big-file">
-                </div>
-                <div class="form-group progress">
-                    <div id="upload-big-file-progress" class="progress-bar" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100"></div>
-                </div>
-            </div>
-            <div class="modal-footer">
-                <button type="button" class="btn btn-primary btn-sm" id="add-big-file-stop" style="display: none">暂停</button>
-                <button type="button" class="btn btn-primary btn-sm" id="add-big-file-resume" style="display: none">重新上传</button>
-                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
-                <button type="button" class="btn btn-primary btn-sm" id="add-big-file-ok">确认</button>
-            </div>
-        </div>
-    </div>
-</div>
 <div class="modal fade" id="spp-col-set" data-backdrop="static">
     <div class="modal-dialog" role="document">
         <div class="modal-content">

+ 1 - 27
app/view/sub_proj/push_modal.ejs

@@ -1,27 +1 @@
-<% include ../shares/upload_att.ejs %>
-<div class="modal fade" id="add-big-file" data-backdrop="static">
-    <div class="modal-dialog" role="document">
-        <div class="modal-content">
-            <div class="modal-header">
-                <h5 class="modal-title">上传附件</h5>
-            </div>
-            <div class="modal-body">
-                <div class="form-group text-danger" style="display: none" id="upload-big-file-hint">
-                </div>
-                <div class="form-group">
-                    <label for="formGroupExampleInput">文件大小限制:500MB,支持<span data-toggle="tooltip" data-placement="bottom" title="" data-original-title="doc,docx,xls,xlsx,ppt,pptx,pdf">office等文档格式</span>、<span data-toggle="tooltip" data-placement="bottom" title="" data-original-title="jpg,png,bmp">图片格式</span>、<span data-toggle="tooltip" data-placement="bottom" title="" data-original-title="rar,zip">压缩包格式</span></label>
-                    <input type="file" class="" id="upload-big-file">
-                </div>
-                <div class="form-group progress">
-                    <div id="upload-big-file-progress" class="progress-bar" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100"></div>
-                </div>
-            </div>
-            <div class="modal-footer">
-                <button type="button" class="btn btn-primary btn-sm" id="add-big-file-stop" style="display: none">暂停</button>
-                <button type="button" class="btn btn-primary btn-sm" id="add-big-file-resume" style="display: none">重新上传</button>
-                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
-                <button type="button" class="btn btn-primary btn-sm" id="add-big-file-ok">确认</button>
-            </div>
-        </div>
-    </div>
-</div>
+<% include ../shares/upload_att.ejs %>

+ 17 - 2
app/view/tender/detail.ejs

@@ -59,7 +59,7 @@
                     </div>
                 </div>
                 <% if (ctx.session.sessionUser.is_admin) { %>
-                    <div class="dropdown d-flex mt-1">
+                    <div class="dropdown float-left d-flex mt-1">
                         <a class="btn btn-sm btn-outline-dark dropdown-toggle dropdown-wd" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                             管理员
                         </a>
@@ -75,6 +75,9 @@
                         </div>
                     </div>
                 <% } %>
+                <% if (ctx.tender.data.share_token) { %>
+                <a href="javascript:void(0)" title="分享" id="share-tender" style="font-size: 16px" share-url="<%- (ctx.tender.data.share_token ? ctx.request.href + '?share=' + ctx.tender.data.share_token : '' ) %>" class="float-left ml-3"><i class="fa fa-share"></i></a>
+                <% } %>
             </div>
         </div>
     </div>
@@ -1500,6 +1503,18 @@
             }
             return `${year}<span class="${dayMode ? 'text-light' : ''}">${mon}-${day}</span><span class="${dayMode ? 'text-light' : ''}">${hour}:${minute}:${scond}</span>`;
         };
-    })
+    });
+
+    $('#share-tender').click(function() {
+        var textarea = document.createElement('textarea');
+        textarea.style.position = 'fixed';
+        textarea.style.opacity = 0;
+        textarea.value = this.getAttribute('share-url');
+        document.body.appendChild(textarea);
+        textarea.select();
+        document.execCommand('copy');
+        document.body.removeChild(textarea);
+        toastr.success('已复制标段分享,可粘贴分享给他人');
+    });
 </script>
 <script src="/public/js/moment/moment.min.js"></script>

+ 4 - 3
app/view/tender/detail_modal.ejs

@@ -1992,7 +1992,8 @@
                             <div class="input-group-prepend">
                                 <span class="input-group-text">分享码</span>
                             </div>
-                            <input class="form-control nospin" value="" id="share-token" readonly>
+                            <% console.log(ctx.tender.data) %>
+                            <input class="form-control nospin" id="share-token" value="<%- ctx.tender.data.share_token %>" readonly>
                             <button class="ml-1 btn btn-sm btn-primary" id="reset-share-token"  data-toggle="tooltip" data-placement="top" data-original-title="重置"><i class="fa fa-repeat"></i></button>
                         </div>
                     </div>
@@ -2001,7 +2002,7 @@
                             <div class="input-group-prepend">
                                 <span class="input-group-text">到期时间</span>
                             </div>
-                            <input class="form-control nospin" value="" id="share-expiration" readonly>
+                            <input class="form-control nospin" id="share-expiration" value="<%- (ctx.tender.data.share_expiration ? ctx.moment(ctx.tender.data.share_expiration).format('YYYY-MM-DD') : '') %>" readonly>
                             <button class="ml-1 btn btn-sm btn-primary" id="refresh-share-expiration" data-toggle="tooltip" data-placement="top" data-original-title="刷新"><i class="fa fa-refresh"></i></button>
                         </div>
                     </div>
@@ -2010,7 +2011,7 @@
                             <div class="input-group-prepend">
                                 <span class="input-group-text">分享链接</span>
                             </div>
-                            <input class="form-control nospin" value="" id="share-url" readonly>
+                            <input class="form-control nospin" value="<%- (ctx.tender.data.share_token ? ctx.request.href + '?share=' + ctx.tender.data.share_token : '' ) %>" id="share-url" readonly>
                             <button class="ml-1 btn btn-sm btn-primary" id="copy-share-url" data-toggle="tooltip" data-placement="bottom" data-original-title="分享"><i class="fa fa-share"></i></button>
                         </div>
                     </div>

+ 2 - 1
config/web.js

@@ -396,9 +396,9 @@ const JsFiles = {
                     '/public/js/spreadjs/sheets/v11/interop/gc.spread.excelio.11.2.2.min.js',
                     '/public/js/decimal.min.js',
                     '/public/js/math.min.js',
-                    // '/public/js/file-saver/FileSaver.js',
                     '/public/js/axios/axios.min.js', '/public/js/js-xlsx/jszip.min.js', '/public/js/file-saver/FileSaver.min.js',
                     '/public/js/datepicker/datepicker.min.js', '/public/js/datepicker/datepicker.zh.js',
+                    '/public/js/shares/aliyun-oss-sdk.min.js',
                 ],
                 mergeFiles: [
                     '/public/js/sub_menu.js',
@@ -421,6 +421,7 @@ const JsFiles = {
                     '/public/js/shares/batch_import.js',
                     '/public/js/shares/tender_select_multi.js',
                     // '/public/js/shares/stage_excel.js',
+                    '/public/js/shares/tools_att.js',
                     '/public/js/ledger_check.js',
                     '/public/js/stage.js',
                     '/public/js/stage_audit.js',

+ 11 - 1
sql/update.sql

@@ -15,7 +15,17 @@ ALTER TABLE `zh_tender`
 ADD COLUMN `share_token` varchar(20) NOT NULL DEFAULT '' COMMENT '标段分享token' AFTER `filter_fund`,
 ADD COLUMN `share_expiration` datetime NULL COMMENT '分享token到期时间' AFTER `share_token`;
 
+ALTER TABLE `zh_stage`
+ADD COLUMN `update_calc_time` timestamp NULL COMMENT '最后修改计算数据时间' AFTER `cache_time_l`,
+ADD COLUMN `update_expr_calc_time` timestamp NULL COMMENT '最后使用公式计算时间' AFTER `update_calc_time`;
+
+ALTER TABLE `zh_contract`
+ADD COLUMN `rela_tender` int NULL DEFAULT NULL COMMENT '关联标段id' AFTER `exist_supplement`,
+ADD COLUMN `settle_price` decimal(30, 8) NOT NULL DEFAULT 0 COMMENT '结算金额(独立字段,非合同结算功能)' AFTER `total_price`;
+
+ALTER TABLE `zh_contract_pay`
+ADD COLUMN `rela_stage` varchar(255) NULL DEFAULT NULL COMMENT '关联的标段期id或者独立支付期id' AFTER `remark`;
 
 ------------------------------------
 -- 表数据
-------------------------------------
+------------------------------------