'use strict'; const assert = require('node:assert/strict'); const test = require('node:test'); const moment = require('moment'); const makeController = require('../../../app/controller/weapp_contract_controller'); test('合同目录使用 UUID 子项目 ID 查询,不转换为数字', async () => { const spid = '5472d997-c4ae-4421-8265-feb6ab0833db'; const projectId = '06c6dd13-e52b-4c37-9d90-468cf32942aa'; const calls = []; const ctx = { query: { spid, type: 'expenses' }, session: { sessionProject: { id: projectId }, sessionUser: { is_admin: true }, }, service: { subProject: { getDataById: async id => { calls.push([ 'subProject', id ]); return { id: spid, project_id: projectId, page_show: '{}' }; }, getPageShow: () => ({ openContract: true }), }, subProjPermission: { adminPermission: {} }, contractTree: { insertTree: async options => calls.push([ 'insertTree', options ]), getAllDataByCondition: async options => { calls.push([ 'folders', options ]); return [{ contract_id: 1, contract_pid: -1, name: '第一部分' }]; }, }, contract: { getListByUsers: async options => { calls.push([ 'contracts', options ]); return [{ id: 'contract-row-id', contract_id: 2, contract_pid: 1, name: '测试合同' }]; }, }, contractAtt: { getCounts: async ids => { calls.push([ 'files', ids ]); return new Map([[ 'contract-row-id', 2 ]]); }, }, }, }; const Controller = makeController({ BaseController: class {} }); await Controller.prototype.catalog.call({ log: () => {} }, ctx); assert.equal(ctx.body.code, 0); assert.deepEqual(calls[0], [ 'subProject', spid ]); assert.deepEqual(calls[1], [ 'insertTree', { spid } ]); assert.deepEqual(calls[2][1].where, { spid, contract_type: 1 }); assert.deepEqual(calls[4], [ 'files', [ 'contract-row-id' ] ]); assert.equal(ctx.body.data.nodes[1].fileCount, 2); }); test('标段合同目录使用 tid 查询,并校验当前项目和合同成员', async () => { const spid = '5472d997-c4ae-4421-8265-feb6ab0833db'; const projectId = '06c6dd13-e52b-4c37-9d90-468cf32942aa'; const calls = []; const ctx = { query: { spid, tid: '4852', type: 'income' }, session: { sessionProject: { id: projectId }, sessionUser: { is_admin: false, accountId: 'user-id' }, }, service: { subProject: { getDataById: async () => ({ id: spid, project_id: projectId, page_show: '{}' }), getPageShow: () => ({ openTenderContract: true }), }, subProjPermission: { getSubProjectUserPermission: async () => ({ contract_permission: [] }), }, tender: { getDataById: async id => { calls.push([ 'tender', id ]); return { id, spid, project_id: projectId }; }, }, contractAudit: { getDataByCondition: async options => { calls.push([ 'audit', options ]); return { id: 'audit-id' }; }, }, contractTree: { insertTree: async options => calls.push([ 'insertTree', options ]), getAllDataByCondition: async options => { calls.push([ 'folders', options ]); return []; }, }, contract: { getListByUsers: async () => [] }, contractAtt: { getCounts: async () => new Map() }, }, }; const Controller = makeController({ BaseController: class {} }); await Controller.prototype.catalog.call({ log: () => {} }, ctx); assert.equal(ctx.body.code, 0); assert.deepEqual(calls[0], [ 'tender', 4852 ]); assert.deepEqual(calls[1], [ 'audit', { tid: 4852, uid: 'user-id' } ]); assert.deepEqual(calls[2], [ 'insertTree', { tid: 4852 } ]); assert.deepEqual(calls[3][1].where, { tid: 4852, contract_type: 2 }); }); test('标段合同选择列表只返回当前子项目的合同标段', async () => { const spid = '5472d997-c4ae-4421-8265-feb6ab0833db'; const projectId = '06c6dd13-e52b-4c37-9d90-468cf32942aa'; const ctx = { query: { spid }, session: { sessionProject: { id: projectId }, sessionUser: { is_admin: true, accountId: 'user-id' }, }, service: { subProject: { getDataById: async () => ({ id: spid, project_id: projectId, page_show: '{}' }), getPageShow: () => ({ openTenderContract: true }), }, projectAccount: { getDataById: async () => ({ self_category_level: '' }) }, tender: { getContractList: async () => { assert.equal(ctx.subProject.id, spid); return [{ id: 4852, name: '测试标段' }]; }, }, category: { getAllCategory: async () => [] }, }, }; const Controller = makeController({ BaseController: class {} }); await Controller.prototype.tenderList.call({ log: () => {} }, ctx); assert.equal(ctx.body.code, 0); assert.deepEqual(ctx.body.data.tenderList, [{ id: 4852, name: '测试标段' }]); }); test('标段合同概况按 tid 汇总金额,并补齐近六个月趋势', async () => { const spid = '5472d997-c4ae-4421-8265-feb6ab0833db'; const projectId = '06c6dd13-e52b-4c37-9d90-468cf32942aa'; const scopes = []; const ctx = { query: { spid, tid: '4852' }, session: { sessionProject: { id: projectId }, sessionUser: { is_admin: true, accountId: 'user-id' }, }, helper: { add: (a, b) => Number(a) + Number(b), sub: (a, b) => Number(a) - Number(b), div: (a, b) => Number(a) / Number(b), round: (a, digits) => Number(a.toFixed(digits)), }, service: { subProject: { getDataById: async () => ({ id: spid, project_id: projectId, page_show: '{}' }), getPageShow: () => ({ openTenderContract: true }), }, subProjPermission: { adminPermission: {} }, tender: { getDataById: async () => ({ id: 4852, spid, project_id: projectId }) }, contract: { getListByUsers: async options => { scopes.push(options); return options.contract_type === 1 ? [ { id: 'expense-contract', total_price: 1000, yf_price: 250, sf_price: 180 }, { id: 'expense-contract', total_price: 1000, yf_price: 250, sf_price: 180 }, ] : [{ id: 'income-contract', total_price: 2000, yf_price: 500, sf_price: 400 }]; }, }, contractPay: { getMonthlyYfPrice: async ids => [{ month: moment().format('YYYY-MM'), amount: ids[0] === 'expense-contract' ? 100 : 200, }], }, }, }; const Controller = makeController({ BaseController: class {} }); await Controller.prototype.summary.call({ log: () => {} }, ctx); assert.equal(ctx.body.code, 0); assert.deepEqual(scopes, [ { tid: 4852, contract_type: 1 }, { tid: 4852, contract_type: 2 }, ]); assert.deepEqual(ctx.body.data.expenses, { total: 1000, paid: 180, unpaid: 820, percent: 25, trend: [0, 0, 0, 0, 0, 100], }); assert.deepEqual(ctx.body.data.income, { total: 2000, paid: 400, unpaid: 1600, percent: 25, trend: [0, 0, 0, 0, 0, 200], }); assert.equal(ctx.body.data.months.length, 6); }); test('项目合同概况按 spid 查询,空合同返回零值', async () => { const spid = '5472d997-c4ae-4421-8265-feb6ab0833db'; const projectId = '06c6dd13-e52b-4c37-9d90-468cf32942aa'; const scopes = []; const ctx = { query: { spid }, session: { sessionProject: { id: projectId }, sessionUser: { is_admin: true }, }, helper: { add: (a, b) => Number(a) + Number(b), sub: (a, b) => Number(a) - Number(b) }, service: { subProject: { getDataById: async () => ({ id: spid, project_id: projectId, page_show: '{}' }), getPageShow: () => ({ openContract: true }), }, subProjPermission: { adminPermission: {} }, contract: { getListByUsers: async options => { scopes.push(options); return []; }, }, contractPay: { getMonthlyYfPrice: async () => [] }, }, }; const Controller = makeController({ BaseController: class {} }); await Controller.prototype.summary.call({ log: () => {} }, ctx); assert.equal(ctx.body.code, 0); assert.deepEqual(scopes, [ { spid, contract_type: 1 }, { spid, contract_type: 2 }, ]); assert.deepEqual(ctx.body.data.expenses, { total: 0, paid: 0, unpaid: 0, percent: 0, trend: [0, 0, 0, 0, 0, 0], }); }); test('合同详情只返回当前可见合同的支付与文件', async () => { const spid = '5472d997-c4ae-4421-8265-feb6ab0833db'; const projectId = '06c6dd13-e52b-4c37-9d90-468cf32942aa'; const queries = []; const ctx = { query: { spid, type: 'expenses', cid: 'contract-row-id' }, session: { sessionProject: { id: projectId }, sessionUser: { is_admin: true }, }, service: { subProject: { getDataById: async () => ({ id: spid, project_id: projectId, page_show: '{}' }), getPageShow: () => ({ openContract: true }), }, subProjPermission: { adminPermission: {} }, contract: { getListByUsers: async () => [{ id: 'contract-row-id', name: '测试合同', total_price: 1000, pay_price: 800, debit_price: 50, yf_price: 750, sf_price: 500, party_a_user: '甲方签约人', party_b_user: '乙方签约人', entity: '收款单位', settle_code: 'JS-001', }], }, contractPay: { getAllDataByCondition: async options => { queries.push(options.where); return [{ id: 1, pay_type: '网上转账', sf_price: 500, pay_time: '2025-12-06' }]; }, }, contractAtt: { getAllDataByCondition: async options => { queries.push(options.where); return [{ id: 2, filename: '合同.pdf', filesize: '1.34 MB', filepath: 'private/file.pdf' }]; }, }, }, }; const Controller = makeController({ BaseController: class {} }); await Controller.prototype.detail.call({ log: () => {} }, ctx); assert.equal(ctx.body.code, 0); assert.deepEqual(queries, [ { spid, contract_type: 1, cid: 'contract-row-id' }, { spid, contract_type: 1, cid: 'contract-row-id' }, ]); assert.equal(ctx.body.data.pays[0].sf_price, 500); assert.equal(ctx.body.data.contract.pay_price, 800); assert.equal(ctx.body.data.contract.yf_price, 750); assert.equal(ctx.body.data.contract.party_a_user, '甲方签约人'); assert.equal(ctx.body.data.contract.entity, '收款单位'); assert.equal(ctx.body.data.contract.settle_code, 'JS-001'); assert.equal(ctx.body.data.files[0].filename, '合同.pdf'); assert.equal(ctx.body.data.files[0].filepath, undefined); }); test('合同文件下载拒绝其他合同的文件', async () => { const spid = '5472d997-c4ae-4421-8265-feb6ab0833db'; const projectId = '06c6dd13-e52b-4c37-9d90-468cf32942aa'; let readOss = false; const ctx = { query: { spid, type: 'expenses', cid: 'contract-row-id' }, params: { fid: '2' }, session: { sessionProject: { id: projectId }, sessionUser: { is_admin: true }, }, service: { subProject: { getDataById: async () => ({ id: spid, project_id: projectId, page_show: '{}' }), getPageShow: () => ({ openContract: true }), }, subProjPermission: { adminPermission: {} }, contract: { getListByUsers: async () => [{ id: 'contract-row-id', name: '测试合同' }], }, contractAtt: { getDataById: async () => ({ id: 2, cid: 'another-contract', spid, contract_type: 1, filepath: 'private/file.pdf' }), }, }, helper: { ossFileGet: async () => { readOss = true; }, }, }; const Controller = makeController({ BaseController: class {} }); await Controller.prototype.downloadFile.call({ log: () => {} }, ctx); assert.equal(ctx.status, 404); assert.equal(ctx.body.code, -1); assert.equal(readOss, false); }); test('标段合同文件通过所属标段校验后返回文件内容', async () => { const spid = '5472d997-c4ae-4421-8265-feb6ab0833db'; const projectId = '06c6dd13-e52b-4c37-9d90-468cf32942aa'; const headers = {}; const ctx = { query: { spid, tid: '4852', type: 'income', cid: 'contract-row-id' }, params: { fid: '2' }, session: { sessionProject: { id: projectId }, sessionUser: { is_admin: true }, }, service: { subProject: { getDataById: async () => ({ id: spid, project_id: projectId, page_show: '{}' }), getPageShow: () => ({ openTenderContract: true }), }, subProjPermission: { adminPermission: {} }, tender: { getDataById: async () => ({ id: 4852, spid, project_id: projectId }) }, contract: { getListByUsers: async options => { assert.deepEqual(options, { tid: 4852, contract_type: 2 }); return [{ id: 'contract-row-id' }]; }, }, contractAtt: { getDataById: async () => ({ id: 2, cid: 'contract-row-id', tid: 4852, contract_type: 2, filename: '合同.pdf', fileext: '.pdf', filepath: 'private/file.pdf', }), }, }, response: { set: values => Object.assign(headers, values) }, helper: { ossFileGet: async path => { assert.equal(path, 'private/file.pdf'); return Buffer.from('test file'); }, }, }; const Controller = makeController({ BaseController: class {} }); await Controller.prototype.downloadFile.call({ log: () => {} }, ctx); assert.equal(headers['Content-Type'], 'application/pdf'); assert.deepEqual(ctx.body, Buffer.from('test file')); });