'use strict'; const contractConst = require('../const/contract'); const { buildCatalog } = require('../lib/weapp_contract_catalog'); const moment = require('moment'); async function resolveContractScope(ctx) { const spid = typeof ctx.query.spid === 'string' ? ctx.query.spid.trim() : ''; const tid = ctx.query.tid === undefined ? null : Number(ctx.query.tid); if (!spid) throw '缺少项目ID'; if (tid !== null && (!Number.isSafeInteger(tid) || tid <= 0)) throw '标段ID错误'; const subProject = await ctx.service.subProject.getDataById(spid); if (!subProject || subProject.is_delete || subProject.is_folder || String(subProject.project_id) !== String(ctx.session.sessionProject.id)) { throw '项目不存在或无权查看'; } subProject.page_show = ctx.service.subProject.getPageShow(subProject.page_show); if (tid ? !subProject.page_show.openTenderContract : !subProject.page_show.openContract) { throw tid ? '该项目未开启标段合同管理' : '该项目未开启合同管理'; } const user = ctx.session.sessionUser; if (user.is_admin) { subProject.permission = ctx.service.subProjPermission.adminPermission; } else { const permission = await ctx.service.subProjPermission.getSubProjectUserPermission(spid, user.accountId); if (!permission || (!tid && ![3, 4, 5].some(value => permission.contract_permission.includes(value)))) { throw '当前账号权限不足,请联系管理员添加权限'; } subProject.permission = permission; } ctx.subProject = subProject; if (!tid) return { scope: { spid }, info: subProject, user }; const tender = await ctx.service.tender.getDataById(tid); if (!tender || String(tender.spid) !== spid || String(tender.project_id) !== String(ctx.session.sessionProject.id)) { throw '标段不存在或无权查看'; } if (!user.is_admin) { const audit = await ctx.service.contractAudit.getDataByCondition({ tid, uid: user.accountId }); if (!audit) throw '当前账号没有该标段的合同权限'; } return { scope: { tid }, info: tender, user }; } async function resolveVisibleContract(ctx) { const type = ctx.query.type; const cid = typeof ctx.query.cid === 'string' ? ctx.query.cid.trim() : ''; if (!['expenses', 'income'].includes(type) || !cid) throw '合同参数错误'; const { scope, user } = await resolveContractScope(ctx); const contractType = contractConst.type[type]; const contracts = await ctx.service.contract.getListByUsers({ ...scope, contract_type: contractType }, user); const contract = contracts.find(item => String(item.id) === cid); if (!contract) throw '合同不存在或无权查看'; return { scope, contractType, contract }; } const fileMimeTypes = { pdf: 'application/pdf', doc: 'application/msword', docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', xls: 'application/vnd.ms-excel', xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ppt: 'application/vnd.ms-powerpoint', pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', gif: 'image/gif', }; module.exports = app => { class WeappContractController extends app.BaseController { async tenderList(ctx) { try { const spid = typeof ctx.query.spid === 'string' ? ctx.query.spid.trim() : ''; if (!spid) throw '缺少项目ID'; const subProject = await ctx.service.subProject.getDataById(spid); if (!subProject || subProject.is_delete || subProject.is_folder || String(subProject.project_id) !== String(ctx.session.sessionProject.id)) { throw '项目不存在或无权查看'; } subProject.page_show = ctx.service.subProject.getPageShow(subProject.page_show); if (!subProject.page_show.openTenderContract) throw '该项目未开启标段合同管理'; const user = ctx.session.sessionUser; if (!user.is_admin) { const permission = await ctx.service.subProjPermission.getSubProjectUserPermission(spid, user.accountId); if (!permission) throw '您无权查看该项目'; subProject.permission = permission; } ctx.subProject = subProject; const accountInfo = await ctx.service.projectAccount.getDataById(user.accountId); const tenderList = await ctx.service.tender.getContractList('', null, user.is_admin ? 1 : 0); const categoryData = await ctx.service.category.getAllCategory(subProject); ctx.body = { code: 0, msg: '', data: { tenderList, categoryData, selfCategoryLevel: accountInfo ? accountInfo.self_category_level : '', }, }; } catch (error) { this.log(error); ctx.body = { code: -1, msg: error.toString(), data: null }; } } async catalog(ctx) { try { const type = ctx.query.type; if (!['expenses', 'income'].includes(type)) throw '合同目录参数错误'; const contractType = contractConst.type[type]; const { scope, info, user } = await resolveContractScope(ctx); const options = { ...scope, contract_type: contractType }; // Web 端首次打开详情时也会按项目模板初始化目录。 await ctx.service.contractTree.insertTree(scope, info); const folders = await ctx.service.contractTree.getAllDataByCondition({ where: options }); const contracts = await ctx.service.contract.getListByUsers(options, user); const contractIds = [...new Set(contracts.map(contract => contract.id))]; const fileCounts = await ctx.service.contractAtt.getCounts(contractIds); for (const contract of contracts) { contract.fileCount = fileCounts.get(String(contract.id)) || 0; } ctx.body = { code: 0, msg: '', data: { nodes: buildCatalog(folders, contracts) } }; } catch (error) { this.log(error); ctx.body = { code: -1, msg: error.toString(), data: null }; } } async summary(ctx) { try { const { scope, user } = await resolveContractScope(ctx); const now = moment().startOf('month'); const months = Array.from({ length: 6 }, (_, index) => now.clone().subtract(5 - index, 'months').format('YYYY-MM')); const start = now.clone().subtract(5, 'months').format('YYYY-MM-DD HH:mm:ss'); const end = now.clone().add(1, 'month').format('YYYY-MM-DD HH:mm:ss'); const summarize = async contractType => { const visibleContracts = await ctx.service.contract.getListByUsers({ ...scope, contract_type: contractType }, user); const contracts = [...new Map(visibleContracts.map(item => [item.id, item])).values()]; const total = contracts.reduce((sum, item) => ctx.helper.add(sum, item.total_price || 0), 0); const settled = contracts.reduce((sum, item) => ctx.helper.add(sum, item.yf_price || 0), 0); const paid = contracts.reduce((sum, item) => ctx.helper.add(sum, item.sf_price || 0), 0); const unpaid = ctx.helper.sub(total, paid); const percent = total ? ctx.helper.round(ctx.helper.div(settled, total) * 100, 2) : 0; const ids = [...new Set(contracts.map(item => item.id))]; const monthly = await ctx.service.contractPay.getMonthlyYfPrice(ids, start, end); const byMonth = new Map(monthly.map(item => [item.month, item.amount])); return { total, paid, unpaid, percent, trend: months.map(month => byMonth.get(month) || 0) }; }; const [expenses, income] = await Promise.all([ summarize(contractConst.type.expenses), summarize(contractConst.type.income), ]); ctx.body = { code: 0, msg: '', data: { months, expenses, income } }; } catch (error) { this.log(error); ctx.body = { code: -1, msg: error.toString(), data: null }; } } async detail(ctx) { try { const { scope, contractType, contract } = await resolveVisibleContract(ctx); const where = { ...scope, contract_type: contractType, cid: contract.id }; const [pays, files] = await Promise.all([ ctx.service.contractPay.getAllDataByCondition({ where, orders: [['create_time', 'desc']] }), ctx.service.contractAtt.getAllDataByCondition({ where, orders: [['upload_time', 'desc']] }), ]); ctx.body = { code: 0, msg: '', data: { contract: { id: contract.id, c_code: contract.c_code, name: contract.name, create_time: contract.create_time, total_price: contract.total_price, pay_price: contract.pay_price, debit_price: contract.debit_price, yf_price: contract.yf_price, sf_price: contract.sf_price, party_a: contract.party_a, party_a_user: contract.party_a_user, party_b: contract.party_b, party_b_user: contract.party_b_user, sign_date: contract.sign_date, address: contract.address, entity: contract.entity, bank: contract.bank, bank_account: contract.bank_account, settle_code: contract.settle_code, remark: contract.remark, }, pays: pays.map(pay => ({ id: pay.id, pay_type: pay.pay_type, pay_time: pay.pay_time, sf_price: pay.sf_price, })), files: files.map(file => ({ id: file.id, filename: file.filename, filesize: file.filesize, fileext: file.fileext, })), }, }; } catch (error) { this.log(error); ctx.body = { code: -1, msg: error.toString(), data: null }; } } async downloadFile(ctx) { try { const { scope, contractType, contract } = await resolveVisibleContract(ctx); const file = await ctx.service.contractAtt.getDataById(ctx.params.fid); if (!file || String(file.cid) !== String(contract.id) || Number(file.contract_type) !== contractType || (scope.tid && Number(file.tid) !== scope.tid) || (scope.spid && String(file.spid) !== scope.spid)) { throw '文件不存在或无权查看'; } const ext = String(file.fileext || '').replace(/^\./, '').toLowerCase(); ctx.response.set({ 'Content-Type': fileMimeTypes[ext] || 'application/octet-stream', 'Content-Disposition': `inline; filename*=UTF-8''${encodeURIComponent(file.filename || 'file')}`, }); ctx.body = await ctx.helper.ossFileGet(file.filepath); } catch (error) { this.log(error); ctx.status = 404; ctx.body = { code: -1, msg: error.toString(), data: null }; } } } return WeappContractController; };