HospProfitAndLossServiceImpl.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. package com.imed.costaccount.service.impl;
  2. import cn.hutool.core.collection.CollUtil;
  3. import cn.hutool.core.date.DateTime;
  4. import cn.hutool.core.date.DateUtil;
  5. import cn.hutool.core.io.IoUtil;
  6. import cn.hutool.core.util.RandomUtil;
  7. import cn.hutool.core.util.ReUtil;
  8. import cn.hutool.core.util.StrUtil;
  9. import cn.hutool.poi.excel.ExcelUtil;
  10. import cn.hutool.poi.excel.ExcelWriter;
  11. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  12. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  13. import com.imed.costaccount.common.enums.CalcTypeEnum;
  14. import com.imed.costaccount.common.enums.ReportTypeEnum;
  15. import com.imed.costaccount.common.exception.CostException;
  16. import com.imed.costaccount.common.util.BeanUtil;
  17. import com.imed.costaccount.common.util.PageUtils;
  18. import com.imed.costaccount.model.*;
  19. import com.imed.costaccount.model.vo.HospProfitVO;
  20. import com.imed.costaccount.model.vo.RelationVO;
  21. import com.imed.costaccount.model.vo.ReportFormVO;
  22. import com.imed.costaccount.service.*;
  23. import org.springframework.stereotype.Service;
  24. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  25. import com.imed.costaccount.mapper.HospProfitAndLossMapper;
  26. import org.springframework.transaction.annotation.Propagation;
  27. import org.springframework.transaction.annotation.Transactional;
  28. import javax.servlet.ServletOutputStream;
  29. import javax.servlet.http.HttpServletResponse;
  30. import java.io.IOException;
  31. import java.math.BigDecimal;
  32. import java.util.*;
  33. import java.util.concurrent.ConcurrentHashMap;
  34. import java.util.concurrent.atomic.AtomicReference;
  35. import java.util.stream.Collectors;
  36. @Service("hospProfitAndLossService")
  37. public class HospProfitAndLossServiceImpl extends ServiceImpl<HospProfitAndLossMapper, HospProfitAndLoss> implements HospProfitAndLossService {
  38. private final ReportFormService reportFormService;
  39. private final IncomeCollectionService collectionService;
  40. private final AllocationQueryService allocationQueryService;
  41. private final AllocationService allocationService;
  42. private final ReportRelationService reportRelationService;
  43. private final CostShareLevelService shareLevelService;
  44. private final CostOtherPaymentsDataService otherPaymentsDataService;
  45. private final ResponsibilityService responsibilityService;
  46. public HospProfitAndLossServiceImpl(ReportFormService reportFormService,
  47. IncomeCollectionService collectionService,
  48. AllocationQueryService allocationQueryService,
  49. AllocationService allocationService,
  50. ReportRelationService reportRelationService,
  51. CostShareLevelService shareLevelService,
  52. CostOtherPaymentsDataService otherPaymentsDataService, ResponsibilityService responsibilityService) {
  53. this.reportFormService = reportFormService;
  54. this.collectionService = collectionService;
  55. this.allocationQueryService = allocationQueryService;
  56. this.allocationService = allocationService;
  57. this.reportRelationService = reportRelationService;
  58. this.shareLevelService = shareLevelService;
  59. this.otherPaymentsDataService = otherPaymentsDataService;
  60. this.responsibilityService = responsibilityService;
  61. }
  62. /**
  63. * 计算全院损益
  64. *
  65. * @param date yyyy-MM-dd 时间
  66. * @param hospId 医院id
  67. */
  68. @Override
  69. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
  70. public void calc(String date, Long hospId) {
  71. DateTime parse = DateUtil.parse(date);
  72. int year = DateUtil.year(parse);
  73. int month = DateUtil.month(parse) + 1;
  74. this.remove(new LambdaQueryWrapper<HospProfitAndLoss>().eq(HospProfitAndLoss::getDateYear, year).eq(HospProfitAndLoss::getDateMonth, month).eq(HospProfitAndLoss::getHospId, hospId));
  75. // 得到全院损益计算报表
  76. List<ReportForm> reportForms = reportFormService.getListByReportType(hospId, ReportTypeEnum.HOSP_PROFIT_LOSS.getType());
  77. if (CollUtil.isEmpty(reportForms)) {
  78. throw new CostException("医院未设置全院损益计算报表");
  79. }
  80. // 得到这个月所有收入数据
  81. List<IncomeCollection> incomes = collectionService.getCollectionsByDate(year, month, hospId);
  82. if (incomes.isEmpty()) {
  83. throw new CostException("医院未归集本月收入数据");
  84. }
  85. // 得到这个月的所有成本数据
  86. List<AllocationQuery> allocationQueries = allocationQueryService.getAllByDate(hospId, year, month);
  87. if (allocationQueries.isEmpty()) {
  88. throw new CostException("医院未分摊本月数据");
  89. }
  90. List<HospProfitAndLoss> list = new ArrayList<>();
  91. reportForms.forEach(i -> {
  92. Integer calcType = i.getCalcType();
  93. if (calcType == CalcTypeEnum.BY_ACCOUNT.getType()) {
  94. // 按会计科目计算
  95. HospProfitAndLoss loss = calcByAccount(hospId, i, incomes, allocationQueries);
  96. if (Objects.isNull(loss)) {
  97. return;
  98. }
  99. loss.setDateMonth(month);
  100. loss.setDateYear(year);
  101. list.add(loss);
  102. return;
  103. } else if (calcType == CalcTypeEnum.BY_SHARE_LEVEL.getType()) {
  104. // 分摊层级计算
  105. HospProfitAndLoss loss = calcByShareLevel(hospId, i, year, month);
  106. if (Objects.isNull(loss)) {
  107. return;
  108. }
  109. loss.setDateMonth(month);
  110. loss.setDateYear(year);
  111. list.add(loss);
  112. return;
  113. } else if (calcType == CalcTypeEnum.LITTER_COUNT.getType()) {
  114. return;
  115. } else if (calcType == CalcTypeEnum.CALC_FORMULA.getType()) {
  116. // todo 留在最后加减
  117. return;
  118. } else if (calcType == CalcTypeEnum.BY_RESPONSIBILITY.getType()) {
  119. // 责任中心
  120. HospProfitAndLoss loss = calcByResponsibility(hospId, i, incomes, allocationQueries);
  121. if (loss == null) {
  122. return;
  123. }
  124. loss.setDateMonth(month);
  125. loss.setDateYear(year);
  126. list.add(loss);
  127. } else {
  128. // 不设置不计算
  129. return;
  130. }
  131. });
  132. // 先处理按公式
  133. calcByFormula(year, month, reportForms, list);
  134. // 处理小计
  135. calcByLitterCount(year, month, reportForms, list, hospId);
  136. // 处理医院其他收支
  137. List<CostOtherPaymentsData> otherPaymentsDatas = otherPaymentsDataService.getByMonth(year, month, hospId);
  138. if (!otherPaymentsDatas.isEmpty()) {
  139. otherPaymentsDatas.forEach(ele -> {
  140. HospProfitAndLoss loss = new HospProfitAndLoss();
  141. loss.setDateYear(year).setDateMonth(month).setReportName(ele.getPaymentsName()).setReportNum(ele.getId().intValue())
  142. .setCreateTime(System.currentTimeMillis()).setAmount(ele.getTotalAmount()).setHospId(hospId);
  143. // if (ele.getPaymentsType() == 2) {
  144. // loss.setAmount(BigDecimal.ZERO.subtract(ele.getTotalAmount()));
  145. // }
  146. list.add(loss);
  147. });
  148. }
  149. this.saveBatch(list);
  150. }
  151. // 计算公式中钱
  152. private BigDecimal calcAmount(List<HospProfitAndLoss> list, String calcFormula) {
  153. String replace = calcFormula.replace("[", "").replace("]", "").replace("-", ",").replace("+", ",");
  154. ArrayList<String> numList = CollUtil.newArrayList(replace.split(","));
  155. // 得到数字
  156. Map<Integer, Object> numMap = new ConcurrentHashMap<>();
  157. for (int j = 0; j < numList.size(); j++) {
  158. numMap.put(j, numList.get(j));
  159. }
  160. List<String> expressions = ReUtil.findAll("[^0-9]", "+" + calcFormula.replace("[", "").replace("]", "").trim(), 0)
  161. .stream().filter(StrUtil::isNotBlank).collect(Collectors.toList());
  162. // 得到预算表达式
  163. Map<Integer, String> expressionMap = new ConcurrentHashMap<>();
  164. for (int k = 0; k < expressions.size(); k++) {
  165. expressionMap.put(k, expressions.get(k));
  166. }
  167. Set<Integer> numSet = numMap.keySet();
  168. List<Integer> nums = new ArrayList<>(numSet);
  169. Map<Integer, BigDecimal> map = new HashMap<>();
  170. for (int l = 0; l < nums.size(); l++) {
  171. for (HospProfitAndLoss z : list) {
  172. if (z.getReportNum().equals(nums.get(l))) {
  173. map.put(l, z.getAmount());
  174. }
  175. }
  176. }
  177. Set<Integer> integers = map.keySet();
  178. List<Integer> mapList = new ArrayList<>(integers);
  179. AtomicReference<BigDecimal> total = new AtomicReference<>();
  180. total.set(BigDecimal.ZERO);
  181. for (int x = 0; x < mapList.size(); x++) {
  182. BigDecimal bigDecimal = map.get(x);
  183. if (Objects.isNull(bigDecimal)) {
  184. bigDecimal = BigDecimal.ZERO;
  185. }
  186. if ("+".equals(expressionMap.get(x))) {
  187. total.set(total.get().add(bigDecimal));
  188. } else {
  189. total.set(total.get().subtract(bigDecimal));
  190. }
  191. }
  192. return total.get();
  193. }
  194. /**
  195. * 按责任中心计算
  196. *
  197. * @param hospId 医院id
  198. * @param reportForm 报表
  199. * @param incomes 归集收入数据
  200. * @param allocationQueries 分摊成本数据
  201. */
  202. private HospProfitAndLoss calcByResponsibility(Long hospId, ReportForm reportForm, List<IncomeCollection> incomes, List<AllocationQuery> allocationQueries) {
  203. List<RelationVO> responsibilities = reportRelationService.getAccountRelation(reportForm.getId(), hospId);
  204. if (responsibilities.isEmpty()) {
  205. return null;
  206. }
  207. List<String> accountCodes = responsibilities.stream().map(RelationVO::getCode).collect(Collectors.toList());
  208. AtomicReference<BigDecimal> calcTotal = new AtomicReference<>();
  209. calcTotal.set(BigDecimal.ZERO);
  210. accountCodes.forEach(i -> {
  211. BigDecimal costAmount = allocationQueries.stream().filter(j -> i.equals(j.getResponsibilityCode())).map(AllocationQuery::getAmount)
  212. .reduce(BigDecimal.ZERO, BigDecimal::add);
  213. calcTotal.set(calcTotal.get().add(costAmount));
  214. });
  215. HospProfitAndLoss loss = new HospProfitAndLoss();
  216. return loss.setReportName(reportForm.getReportName()).setReportNum(reportForm.getNum())
  217. .setAmount(calcTotal.get()).setCreateTime(System.currentTimeMillis()).setHospId(hospId);
  218. }
  219. /**
  220. * 按计算公式计算
  221. */
  222. private void calcByFormula(Integer year, Integer month, List<ReportForm> reportForms, List<HospProfitAndLoss> list) {
  223. List<ReportForm> calcFormulas = reportForms.stream().filter(i -> i.getCalcType() == CalcTypeEnum.CALC_FORMULA.getType()).collect(Collectors.toList());
  224. calcFormulas.forEach(i -> {
  225. String calcFormula = i.getCalcFormula();
  226. // TODO: 2021/8/27 校验公式合法性
  227. if (StrUtil.isBlank(calcFormula)) {
  228. throw new CostException("reportForm名称为" + i.getReportName() + "计算公式不正确");
  229. }
  230. BigDecimal bigDecimal = calcAmount(list, calcFormula);
  231. HospProfitAndLoss loss = new HospProfitAndLoss();
  232. loss.setDateYear(year).setDateMonth(month).setReportNum(i.getNum()).setReportName(i.getReportName())
  233. .setAmount(bigDecimal).setCreateTime(System.currentTimeMillis()).setHospId(i.getHospId());
  234. list.add(loss);
  235. });
  236. }
  237. /**
  238. * 按小计计算
  239. */
  240. private void calcByLitterCount(Integer year, Integer month, List<ReportForm> reportForms, List<HospProfitAndLoss> list, Long hospId) {
  241. List<ReportForm> litterCounts = reportForms.stream().filter(i -> i.getCalcType() == CalcTypeEnum.LITTER_COUNT.getType()).collect(Collectors.toList());
  242. litterCounts.forEach(reportForm -> {
  243. Long parentId = reportForm.getParentId();
  244. List<ReportForm> reportFormByParents = reportFormService.getByParentId(hospId, parentId);
  245. List<Integer> reportNums = new ArrayList<>();
  246. reportFormByParents.forEach(form -> {
  247. // 去掉自己
  248. if (form.getId().equals(reportForm.getId())) {
  249. return;
  250. }
  251. reportNums.add(form.getNum());
  252. });
  253. AtomicReference<BigDecimal> total = new AtomicReference<>();
  254. total.set(BigDecimal.ZERO);
  255. reportNums.forEach(num -> {
  256. list.forEach(item -> {
  257. if (num.equals(item.getReportNum())) {
  258. total.set(total.get().add(item.getAmount()));
  259. }
  260. });
  261. });
  262. HospProfitAndLoss loss = new HospProfitAndLoss();
  263. loss.setDateYear(year).setDateMonth(month).setReportNum(reportForm.getNum())
  264. .setReportName(reportForm.getReportName()).setAmount(total.get()).setCreateTime(System.currentTimeMillis()).setHospId(hospId);
  265. list.add(loss);
  266. });
  267. }
  268. /**
  269. * 按分摊层级计算、
  270. *
  271. * @param hospId 医院id
  272. * @param reportForm 报表
  273. * @param year
  274. * @param month
  275. */
  276. private HospProfitAndLoss calcByShareLevel(Long hospId, ReportForm reportForm, int year, int month) {
  277. List<RelationVO> shareLevels = reportRelationService.getAccountRelation(reportForm.getId(), hospId);
  278. List<Long> shareLevelId = shareLevels.stream().map(RelationVO::getCode).map(Long::valueOf).collect(Collectors.toList());
  279. if (CollUtil.isEmpty(shareLevelId)) {
  280. return null;
  281. }
  282. // 分摊层级计算金额
  283. // TODO: 2021/8/31
  284. List<CostShareLevel> costShareLevels = shareLevelService.listByIds(shareLevelId);
  285. if (costShareLevels.isEmpty()) {
  286. throw new CostException("医院分摊层级设置错误," + reportForm.getReportName());
  287. }
  288. List<Integer> levelSorts = costShareLevels.stream().map(CostShareLevel::getLeverSort).collect(Collectors.toList());
  289. List<AllocationQuery> allocations = allocationQueryService.getByDate(year, month, hospId, levelSorts);
  290. if (allocations.isEmpty()) {
  291. throw new CostException("医院未分摊本月数据");
  292. }
  293. BigDecimal reduce = allocations.stream().map(AllocationQuery::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
  294. HospProfitAndLoss loss = new HospProfitAndLoss();
  295. return loss.setReportName(reportForm.getReportName()).setReportNum(reportForm.getNum())
  296. .setAmount(reduce).setCreateTime(System.currentTimeMillis()).setHospId(hospId);
  297. }
  298. /**
  299. * 计算按会计科目下的科目名称
  300. *
  301. * @param hospId 医院id
  302. * @param reportForm 报表
  303. * @param incomes 归集收入数据
  304. * @param allocationQueries 分摊成本数据
  305. */
  306. private HospProfitAndLoss calcByAccount(Long hospId, ReportForm reportForm, List<IncomeCollection> incomes, List<AllocationQuery> allocationQueries) {
  307. List<RelationVO> accountRelations = reportRelationService.getAccountRelation(reportForm.getId(), hospId);
  308. if (accountRelations.isEmpty()) {
  309. return null;
  310. }
  311. List<String> accountCodes = accountRelations.stream().map(RelationVO::getCode).collect(Collectors.toList());
  312. AtomicReference<BigDecimal> calcTotal = new AtomicReference<>();
  313. calcTotal.set(BigDecimal.ZERO);
  314. accountCodes.forEach(i -> {
  315. BigDecimal incomeAmount = incomes.stream().filter(j -> i.equals(j.getAccountingCode())).map(IncomeCollection::getAmount)
  316. .reduce(BigDecimal.ZERO, BigDecimal::add);
  317. BigDecimal costAmount = allocationQueries.stream().filter(j -> i.equals(j.getAccountingCode())).map(AllocationQuery::getAmount)
  318. .reduce(BigDecimal.ZERO, BigDecimal::add);
  319. BigDecimal total = incomeAmount.add(costAmount);
  320. calcTotal.set(calcTotal.get().add(total));
  321. });
  322. HospProfitAndLoss loss = new HospProfitAndLoss();
  323. return loss.setReportName(reportForm.getReportName()).setReportNum(reportForm.getNum())
  324. .setAmount(calcTotal.get()).setCreateTime(System.currentTimeMillis()).setHospId(hospId);
  325. }
  326. /**
  327. * 全院损益列表
  328. *
  329. * @param current 当前页
  330. * @param pageSize 每页展示数据大小
  331. * @param date 日期
  332. * @param hospId 医院id
  333. * @return PageUtils
  334. */
  335. @Override
  336. public PageUtils getHospProfits(Integer current, Integer pageSize, String date, Long hospId) {
  337. DateTime parse = DateUtil.parse(date);
  338. int year = DateUtil.year(parse);
  339. int month = DateUtil.month(parse) + 1;
  340. Page<HospProfitAndLoss> page = new Page<>(current, pageSize);
  341. Page<HospProfitAndLoss> pages = this.page(
  342. page,
  343. new LambdaQueryWrapper<HospProfitAndLoss>()
  344. .eq(HospProfitAndLoss::getHospId, hospId)
  345. .eq(HospProfitAndLoss::getDateMonth, month)
  346. .eq(HospProfitAndLoss::getDateYear, year)
  347. );
  348. List<HospProfitAndLoss> records = pages.getRecords();
  349. List<HospProfitVO> hospProfitVOS = BeanUtil.convertList(records, HospProfitVO.class);
  350. PageUtils pageUtils = new PageUtils(pages);
  351. pageUtils.setList(hospProfitVOS);
  352. return pageUtils;
  353. }
  354. /**
  355. * 导出全院损益计算
  356. *
  357. * @param date yyyy-MM-dd
  358. * @param hospId
  359. * @param response
  360. */
  361. @Override
  362. public void hospProfitReport(String date, Long hospId, HttpServletResponse response) {
  363. DateTime parse = DateUtil.parse(date);
  364. int year = DateUtil.year(parse);
  365. int month = DateUtil.month(parse) + 1;
  366. // 得到所有责任中心的子节点
  367. List<Responsibility> leafResp = responsibilityService.getLeafResp(hospId);
  368. ExcelWriter writer = ExcelUtil.getWriter();
  369. List<String> secondTitleList = leafResp.stream().map(Responsibility::getResponsibilityName).collect(Collectors.toList());
  370. writer.merge(leafResp.size() + 1, "全院损益计算导出 \n" + "制表时间:" + DateUtil.now());
  371. writer.setColumnWidth(-1, 20);
  372. writer.passCurrentRow();
  373. writer.merge(2, 2, 0, 1, "项目", false);
  374. for (int i = 0; i < secondTitleList.size(); i++) {
  375. String str = secondTitleList.get(i);
  376. writer.writeCellValue(i + 2, 2, str);
  377. }
  378. // 得到全院损益报表处理 树状结构(only 2层)
  379. List<ReportFormVO> allHospList = reportFormService.getAllHospList(hospId);
  380. if (allHospList.isEmpty()) {
  381. return;
  382. }
  383. int lastRow = 3;
  384. for (int i = 0; i < allHospList.size(); i++) {
  385. ReportFormVO parentFormVO = allHospList.get(i);
  386. List<ReportFormVO> children = parentFormVO.getChildren();
  387. if (CollUtil.isEmpty(children)) {
  388. continue;
  389. }
  390. int size = children.size();
  391. writer.merge(lastRow, lastRow + size - 1, 0, 0, parentFormVO.getReportName(), true);
  392. // 具体的报表项目
  393. for (int j = 0; j < size; j++) {
  394. // todo 可以抽取出单独方法
  395. ReportFormVO childFormVO = children.get(j);
  396. writer.writeCellValue(1, lastRow + j, childFormVO.getReportName());
  397. // 单独计每个数据的责任中心对应的金额
  398. }
  399. lastRow = lastRow + size;
  400. }
  401. response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
  402. response.setHeader("Content-Disposition", "attachment;filename=" + year + month + ".xls");
  403. ServletOutputStream out = null;
  404. try {
  405. out = response.getOutputStream();
  406. } catch (IOException e) {
  407. e.printStackTrace();
  408. }
  409. writer.flush(out, true);
  410. writer.close();
  411. IoUtil.close(out);
  412. }
  413. }