weapp_contract_controller.test.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. 'use strict';
  2. const assert = require('node:assert/strict');
  3. const test = require('node:test');
  4. const moment = require('moment');
  5. const makeController = require('../../../app/controller/weapp_contract_controller');
  6. test('合同目录使用 UUID 子项目 ID 查询,不转换为数字', async () => {
  7. const spid = '5472d997-c4ae-4421-8265-feb6ab0833db';
  8. const projectId = '06c6dd13-e52b-4c37-9d90-468cf32942aa';
  9. const calls = [];
  10. const ctx = {
  11. query: { spid, type: 'expenses' },
  12. session: {
  13. sessionProject: { id: projectId },
  14. sessionUser: { is_admin: true },
  15. },
  16. service: {
  17. subProject: {
  18. getDataById: async id => {
  19. calls.push([ 'subProject', id ]);
  20. return { id: spid, project_id: projectId, page_show: '{}' };
  21. },
  22. getPageShow: () => ({ openContract: true }),
  23. },
  24. subProjPermission: { adminPermission: {} },
  25. contractTree: {
  26. insertTree: async options => calls.push([ 'insertTree', options ]),
  27. getAllDataByCondition: async options => {
  28. calls.push([ 'folders', options ]);
  29. return [{ contract_id: 1, contract_pid: -1, name: '第一部分' }];
  30. },
  31. },
  32. contract: {
  33. getListByUsers: async options => {
  34. calls.push([ 'contracts', options ]);
  35. return [{ id: 'contract-row-id', contract_id: 2, contract_pid: 1, name: '测试合同' }];
  36. },
  37. },
  38. contractAtt: {
  39. getCounts: async ids => {
  40. calls.push([ 'files', ids ]);
  41. return new Map([[ 'contract-row-id', 2 ]]);
  42. },
  43. },
  44. },
  45. };
  46. const Controller = makeController({ BaseController: class {} });
  47. await Controller.prototype.catalog.call({ log: () => {} }, ctx);
  48. assert.equal(ctx.body.code, 0);
  49. assert.deepEqual(calls[0], [ 'subProject', spid ]);
  50. assert.deepEqual(calls[1], [ 'insertTree', { spid } ]);
  51. assert.deepEqual(calls[2][1].where, { spid, contract_type: 1 });
  52. assert.deepEqual(calls[4], [ 'files', [ 'contract-row-id' ] ]);
  53. assert.equal(ctx.body.data.nodes[1].fileCount, 2);
  54. });
  55. test('标段合同目录使用 tid 查询,并校验当前项目和合同成员', async () => {
  56. const spid = '5472d997-c4ae-4421-8265-feb6ab0833db';
  57. const projectId = '06c6dd13-e52b-4c37-9d90-468cf32942aa';
  58. const calls = [];
  59. const ctx = {
  60. query: { spid, tid: '4852', type: 'income' },
  61. session: {
  62. sessionProject: { id: projectId },
  63. sessionUser: { is_admin: false, accountId: 'user-id' },
  64. },
  65. service: {
  66. subProject: {
  67. getDataById: async () => ({ id: spid, project_id: projectId, page_show: '{}' }),
  68. getPageShow: () => ({ openTenderContract: true }),
  69. },
  70. subProjPermission: {
  71. getSubProjectUserPermission: async () => ({ contract_permission: [] }),
  72. },
  73. tender: {
  74. getDataById: async id => {
  75. calls.push([ 'tender', id ]);
  76. return { id, spid, project_id: projectId };
  77. },
  78. },
  79. contractAudit: {
  80. getDataByCondition: async options => {
  81. calls.push([ 'audit', options ]);
  82. return { id: 'audit-id' };
  83. },
  84. },
  85. contractTree: {
  86. insertTree: async options => calls.push([ 'insertTree', options ]),
  87. getAllDataByCondition: async options => {
  88. calls.push([ 'folders', options ]);
  89. return [];
  90. },
  91. },
  92. contract: { getListByUsers: async () => [] },
  93. contractAtt: { getCounts: async () => new Map() },
  94. },
  95. };
  96. const Controller = makeController({ BaseController: class {} });
  97. await Controller.prototype.catalog.call({ log: () => {} }, ctx);
  98. assert.equal(ctx.body.code, 0);
  99. assert.deepEqual(calls[0], [ 'tender', 4852 ]);
  100. assert.deepEqual(calls[1], [ 'audit', { tid: 4852, uid: 'user-id' } ]);
  101. assert.deepEqual(calls[2], [ 'insertTree', { tid: 4852 } ]);
  102. assert.deepEqual(calls[3][1].where, { tid: 4852, contract_type: 2 });
  103. });
  104. test('标段合同选择列表只返回当前子项目的合同标段', async () => {
  105. const spid = '5472d997-c4ae-4421-8265-feb6ab0833db';
  106. const projectId = '06c6dd13-e52b-4c37-9d90-468cf32942aa';
  107. const ctx = {
  108. query: { spid },
  109. session: {
  110. sessionProject: { id: projectId },
  111. sessionUser: { is_admin: true, accountId: 'user-id' },
  112. },
  113. service: {
  114. subProject: {
  115. getDataById: async () => ({ id: spid, project_id: projectId, page_show: '{}' }),
  116. getPageShow: () => ({ openTenderContract: true }),
  117. },
  118. projectAccount: { getDataById: async () => ({ self_category_level: '' }) },
  119. tender: {
  120. getContractList: async () => {
  121. assert.equal(ctx.subProject.id, spid);
  122. return [{ id: 4852, name: '测试标段' }];
  123. },
  124. },
  125. category: { getAllCategory: async () => [] },
  126. },
  127. };
  128. const Controller = makeController({ BaseController: class {} });
  129. await Controller.prototype.tenderList.call({ log: () => {} }, ctx);
  130. assert.equal(ctx.body.code, 0);
  131. assert.deepEqual(ctx.body.data.tenderList, [{ id: 4852, name: '测试标段' }]);
  132. });
  133. test('标段合同概况按 tid 汇总金额,并补齐近六个月趋势', async () => {
  134. const spid = '5472d997-c4ae-4421-8265-feb6ab0833db';
  135. const projectId = '06c6dd13-e52b-4c37-9d90-468cf32942aa';
  136. const scopes = [];
  137. const ctx = {
  138. query: { spid, tid: '4852' },
  139. session: {
  140. sessionProject: { id: projectId },
  141. sessionUser: { is_admin: true, accountId: 'user-id' },
  142. },
  143. helper: {
  144. add: (a, b) => Number(a) + Number(b),
  145. sub: (a, b) => Number(a) - Number(b),
  146. div: (a, b) => Number(a) / Number(b),
  147. round: (a, digits) => Number(a.toFixed(digits)),
  148. },
  149. service: {
  150. subProject: {
  151. getDataById: async () => ({ id: spid, project_id: projectId, page_show: '{}' }),
  152. getPageShow: () => ({ openTenderContract: true }),
  153. },
  154. subProjPermission: { adminPermission: {} },
  155. tender: { getDataById: async () => ({ id: 4852, spid, project_id: projectId }) },
  156. contract: {
  157. getListByUsers: async options => {
  158. scopes.push(options);
  159. return options.contract_type === 1
  160. ? [
  161. { id: 'expense-contract', total_price: 1000, yf_price: 250, sf_price: 180 },
  162. { id: 'expense-contract', total_price: 1000, yf_price: 250, sf_price: 180 },
  163. ]
  164. : [{ id: 'income-contract', total_price: 2000, yf_price: 500, sf_price: 400 }];
  165. },
  166. },
  167. contractPay: {
  168. getMonthlyYfPrice: async ids => [{
  169. month: moment().format('YYYY-MM'),
  170. amount: ids[0] === 'expense-contract' ? 100 : 200,
  171. }],
  172. },
  173. },
  174. };
  175. const Controller = makeController({ BaseController: class {} });
  176. await Controller.prototype.summary.call({ log: () => {} }, ctx);
  177. assert.equal(ctx.body.code, 0);
  178. assert.deepEqual(scopes, [
  179. { tid: 4852, contract_type: 1 },
  180. { tid: 4852, contract_type: 2 },
  181. ]);
  182. assert.deepEqual(ctx.body.data.expenses, {
  183. total: 1000, paid: 180, unpaid: 820, percent: 25,
  184. trend: [0, 0, 0, 0, 0, 100],
  185. });
  186. assert.deepEqual(ctx.body.data.income, {
  187. total: 2000, paid: 400, unpaid: 1600, percent: 25,
  188. trend: [0, 0, 0, 0, 0, 200],
  189. });
  190. assert.equal(ctx.body.data.months.length, 6);
  191. });
  192. test('项目合同概况按 spid 查询,空合同返回零值', async () => {
  193. const spid = '5472d997-c4ae-4421-8265-feb6ab0833db';
  194. const projectId = '06c6dd13-e52b-4c37-9d90-468cf32942aa';
  195. const scopes = [];
  196. const ctx = {
  197. query: { spid },
  198. session: {
  199. sessionProject: { id: projectId },
  200. sessionUser: { is_admin: true },
  201. },
  202. helper: { add: (a, b) => Number(a) + Number(b), sub: (a, b) => Number(a) - Number(b) },
  203. service: {
  204. subProject: {
  205. getDataById: async () => ({ id: spid, project_id: projectId, page_show: '{}' }),
  206. getPageShow: () => ({ openContract: true }),
  207. },
  208. subProjPermission: { adminPermission: {} },
  209. contract: {
  210. getListByUsers: async options => {
  211. scopes.push(options);
  212. return [];
  213. },
  214. },
  215. contractPay: { getMonthlyYfPrice: async () => [] },
  216. },
  217. };
  218. const Controller = makeController({ BaseController: class {} });
  219. await Controller.prototype.summary.call({ log: () => {} }, ctx);
  220. assert.equal(ctx.body.code, 0);
  221. assert.deepEqual(scopes, [
  222. { spid, contract_type: 1 },
  223. { spid, contract_type: 2 },
  224. ]);
  225. assert.deepEqual(ctx.body.data.expenses, {
  226. total: 0, paid: 0, unpaid: 0, percent: 0,
  227. trend: [0, 0, 0, 0, 0, 0],
  228. });
  229. });
  230. test('合同详情只返回当前可见合同的支付与文件', async () => {
  231. const spid = '5472d997-c4ae-4421-8265-feb6ab0833db';
  232. const projectId = '06c6dd13-e52b-4c37-9d90-468cf32942aa';
  233. const queries = [];
  234. const ctx = {
  235. query: { spid, type: 'expenses', cid: 'contract-row-id' },
  236. session: {
  237. sessionProject: { id: projectId },
  238. sessionUser: { is_admin: true },
  239. },
  240. service: {
  241. subProject: {
  242. getDataById: async () => ({ id: spid, project_id: projectId, page_show: '{}' }),
  243. getPageShow: () => ({ openContract: true }),
  244. },
  245. subProjPermission: { adminPermission: {} },
  246. contract: {
  247. getListByUsers: async () => [{
  248. id: 'contract-row-id', name: '测试合同', total_price: 1000,
  249. pay_price: 800, debit_price: 50, yf_price: 750, sf_price: 500,
  250. party_a_user: '甲方签约人', party_b_user: '乙方签约人',
  251. entity: '收款单位', settle_code: 'JS-001',
  252. }],
  253. },
  254. contractPay: {
  255. getAllDataByCondition: async options => {
  256. queries.push(options.where);
  257. return [{ id: 1, pay_type: '网上转账', sf_price: 500, pay_time: '2025-12-06' }];
  258. },
  259. },
  260. contractAtt: {
  261. getAllDataByCondition: async options => {
  262. queries.push(options.where);
  263. return [{ id: 2, filename: '合同.pdf', filesize: '1.34 MB', filepath: 'private/file.pdf' }];
  264. },
  265. },
  266. },
  267. };
  268. const Controller = makeController({ BaseController: class {} });
  269. await Controller.prototype.detail.call({ log: () => {} }, ctx);
  270. assert.equal(ctx.body.code, 0);
  271. assert.deepEqual(queries, [
  272. { spid, contract_type: 1, cid: 'contract-row-id' },
  273. { spid, contract_type: 1, cid: 'contract-row-id' },
  274. ]);
  275. assert.equal(ctx.body.data.pays[0].sf_price, 500);
  276. assert.equal(ctx.body.data.contract.pay_price, 800);
  277. assert.equal(ctx.body.data.contract.yf_price, 750);
  278. assert.equal(ctx.body.data.contract.party_a_user, '甲方签约人');
  279. assert.equal(ctx.body.data.contract.entity, '收款单位');
  280. assert.equal(ctx.body.data.contract.settle_code, 'JS-001');
  281. assert.equal(ctx.body.data.files[0].filename, '合同.pdf');
  282. assert.equal(ctx.body.data.files[0].filepath, undefined);
  283. });
  284. test('合同文件下载拒绝其他合同的文件', async () => {
  285. const spid = '5472d997-c4ae-4421-8265-feb6ab0833db';
  286. const projectId = '06c6dd13-e52b-4c37-9d90-468cf32942aa';
  287. let readOss = false;
  288. const ctx = {
  289. query: { spid, type: 'expenses', cid: 'contract-row-id' },
  290. params: { fid: '2' },
  291. session: {
  292. sessionProject: { id: projectId },
  293. sessionUser: { is_admin: true },
  294. },
  295. service: {
  296. subProject: {
  297. getDataById: async () => ({ id: spid, project_id: projectId, page_show: '{}' }),
  298. getPageShow: () => ({ openContract: true }),
  299. },
  300. subProjPermission: { adminPermission: {} },
  301. contract: {
  302. getListByUsers: async () => [{ id: 'contract-row-id', name: '测试合同' }],
  303. },
  304. contractAtt: {
  305. getDataById: async () => ({ id: 2, cid: 'another-contract', spid, contract_type: 1, filepath: 'private/file.pdf' }),
  306. },
  307. },
  308. helper: {
  309. ossFileGet: async () => {
  310. readOss = true;
  311. },
  312. },
  313. };
  314. const Controller = makeController({ BaseController: class {} });
  315. await Controller.prototype.downloadFile.call({ log: () => {} }, ctx);
  316. assert.equal(ctx.status, 404);
  317. assert.equal(ctx.body.code, -1);
  318. assert.equal(readOss, false);
  319. });
  320. test('标段合同文件通过所属标段校验后返回文件内容', async () => {
  321. const spid = '5472d997-c4ae-4421-8265-feb6ab0833db';
  322. const projectId = '06c6dd13-e52b-4c37-9d90-468cf32942aa';
  323. const headers = {};
  324. const ctx = {
  325. query: { spid, tid: '4852', type: 'income', cid: 'contract-row-id' },
  326. params: { fid: '2' },
  327. session: {
  328. sessionProject: { id: projectId },
  329. sessionUser: { is_admin: true },
  330. },
  331. service: {
  332. subProject: {
  333. getDataById: async () => ({ id: spid, project_id: projectId, page_show: '{}' }),
  334. getPageShow: () => ({ openTenderContract: true }),
  335. },
  336. subProjPermission: { adminPermission: {} },
  337. tender: { getDataById: async () => ({ id: 4852, spid, project_id: projectId }) },
  338. contract: {
  339. getListByUsers: async options => {
  340. assert.deepEqual(options, { tid: 4852, contract_type: 2 });
  341. return [{ id: 'contract-row-id' }];
  342. },
  343. },
  344. contractAtt: {
  345. getDataById: async () => ({
  346. id: 2, cid: 'contract-row-id', tid: 4852, contract_type: 2,
  347. filename: '合同.pdf', fileext: '.pdf', filepath: 'private/file.pdf',
  348. }),
  349. },
  350. },
  351. response: { set: values => Object.assign(headers, values) },
  352. helper: {
  353. ossFileGet: async path => {
  354. assert.equal(path, 'private/file.pdf');
  355. return Buffer.from('test file');
  356. },
  357. },
  358. };
  359. const Controller = makeController({ BaseController: class {} });
  360. await Controller.prototype.downloadFile.call({ log: () => {} }, ctx);
  361. assert.equal(headers['Content-Type'], 'application/pdf');
  362. assert.deepEqual(ctx.body, Buffer.from('test file'));
  363. });