| | |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceEntryReqVO; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceImportExcelVO; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceImportRespVO; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoicePageReqVO; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceRespVO; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceSaleOutRespVO; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceSaveReqVO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.invoice.CrmInvoiceDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; |
| | | import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; |
| | | import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; |
| | | import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; |
| | |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | |
| | | import java.io.IOException; |
| | | import java.math.BigDecimal; |
| | |
| | | return success(true); |
| | | } |
| | | |
| | | @PutMapping("/entry") |
| | | @Operation(summary = "开票申请录入发票", description = "开票申请审批通过后,录入真实发票号码、开票日期、附件,并填写收款节点") |
| | | @PreAuthorize("@ss.hasPermission('crm:invoice:update')") |
| | | public CommonResult<Boolean> entryInvoice(@Valid @RequestBody CrmInvoiceEntryReqVO entryReqVO) { |
| | | invoiceService.entryInvoice(entryReqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/sale-out-list") |
| | | @Operation(summary = "获得开票可选的销售出库单", description = "按销售订单查询 MES 已完成出库单,返回已开票金额与剩余可开票金额") |
| | | @Parameter(name = "saleOrderId", description = "销售订单编号", required = true) |
| | | @Parameter(name = "id", description = "开票单编号(编辑时传递,用于排除自身已占用金额)") |
| | | public CommonResult<List<CrmInvoiceSaleOutRespVO>> getSaleOutListForInvoice( |
| | | @RequestParam("saleOrderId") Long saleOrderId, |
| | | @RequestParam(value = "id", required = false) Long id) { |
| | | return success(invoiceService.getSaleOutListForInvoice(saleOrderId, id)); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Operation(summary = "删除开票") |
| | | @Parameter(name = "id", description = "编号", required = true) |
| | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "获得开票") |
| | | @Parameter(name = "id", description = "编号", required = true, example = "1024") |
| | | @PreAuthorize("@ss.hasPermission('crm:invoice:query')") |
| | | public CommonResult<CrmInvoiceRespVO> getInvoice(@RequestParam("id") Long id) { |
| | | CrmInvoiceDO invoice = invoiceService.getInvoice(id); |
| | | return success(buildInvoiceDetail(invoice)); |
| | |
| | | if (invoice == null) { |
| | | return null; |
| | | } |
| | | return buildInvoiceDetailList(Collections.singletonList(invoice)).get(0); |
| | | CrmInvoiceRespVO invoiceVO = buildInvoiceDetailList(Collections.singletonList(invoice)).get(0); |
| | | // 详情页补充收款节点(列表页不返回,避免 N+1 查询) |
| | | List<CrmReceivablePlanDO> plans = invoiceService.getPaymentNodeListByInvoiceId(invoice.getId()); |
| | | if (CollUtil.isNotEmpty(plans)) { |
| | | List<CrmInvoiceRespVO.PaymentNode> nodes = new ArrayList<>(plans.size()); |
| | | for (CrmReceivablePlanDO plan : plans) { |
| | | CrmInvoiceRespVO.PaymentNode node = new CrmInvoiceRespVO.PaymentNode(); |
| | | node.setId(plan.getId()); |
| | | node.setStage(plan.getStage()); |
| | | node.setAmount(plan.getPrice()); |
| | | node.setReturnTime(plan.getReturnTime()); |
| | | node.setActualTime(plan.getActualTime()); |
| | | node.setReceiptedPrice(plan.getReceiptedPrice()); |
| | | node.setStatus(plan.getStatus()); |
| | | node.setRemark(plan.getRemark()); |
| | | nodes.add(node); |
| | | } |
| | | invoiceVO.setPaymentNodes(nodes); |
| | | } |
| | | return invoiceVO; |
| | | } |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "获得开票分页") |
| | | @PreAuthorize("@ss.hasPermission('crm:invoice:query')") |
| | | public CommonResult<PageResult<CrmInvoiceRespVO>> getInvoicePage(@Valid CrmInvoicePageReqVO pageReqVO) { |
| | | PageResult<CrmInvoiceDO> pageResult = invoiceService.getInvoicePage(pageReqVO, getLoginUserId()); |
| | | return success(new PageResult<>(buildInvoiceDetailList(pageResult.getList()), pageResult.getTotal())); |
| | |
| | | // 导出 Excel |
| | | ExcelUtils.write(response, "开票.xls", "数据", CrmInvoiceRespVO.class, |
| | | buildInvoiceDetailList(list)); |
| | | } |
| | | |
| | | @GetMapping("/get-import-template") |
| | | @Operation(summary = "获得导入开票模板") |
| | | @PreAuthorize("@ss.hasPermission('crm:invoice:import')") |
| | | public void importTemplate(HttpServletResponse response) throws IOException { |
| | | List<CrmInvoiceImportExcelVO> list = Collections.singletonList( |
| | | CrmInvoiceImportExcelVO.builder() |
| | | .contractNo("HT20260101000001").invoiceType("增值税专用发票") |
| | | .invoiceNo("02564178").invoiceTitle("示例客户有限公司").price(new BigDecimal("10000.00")) |
| | | .invoiceTime("2026-01-01 10:00:00").remark("示例数据,导入前请替换") |
| | | .build()); |
| | | ExcelUtils.write(response, "开票导入模板.xls", "开票", CrmInvoiceImportExcelVO.class, list); |
| | | } |
| | | |
| | | @PostMapping("/import") |
| | | @Operation(summary = "导入开票") |
| | | @PreAuthorize("@ss.hasPermission('crm:invoice:import')") |
| | | public CommonResult<CrmInvoiceImportRespVO> importInvoiceExcel(@RequestParam("file") MultipartFile file) throws Exception { |
| | | List<CrmInvoiceImportExcelVO> list = ExcelUtils.read(file, CrmInvoiceImportExcelVO.class); |
| | | return success(invoiceService.importInvoiceList(list)); |
| | | } |
| | | |
| | | private List<CrmInvoiceRespVO> buildInvoiceDetailList(List<CrmInvoiceDO> invoiceList) { |
| | |
| | | |
| | | @GetMapping("/audit-count") |
| | | @Operation(summary = "获得待审核开票数量") |
| | | @PreAuthorize("@ss.hasPermission('crm:invoice:query')") |
| | | public CommonResult<Long> getAuditInvoiceCount() { |
| | | return success(invoiceService.getAuditInvoiceCount(getLoginUserId())); |
| | | } |
| | |
| | | @GetMapping("/simple-list") |
| | | @Operation(summary = "获得开票精简列表", description = "只包含审核通过且已上传发票附件的开票,主要用于回款创建时选择关联开票") |
| | | @Parameter(name = "contractId", description = "合同编号", required = true) |
| | | @PreAuthorize("@ss.hasPermission('crm:invoice:query')") |
| | | public CommonResult<List<CrmInvoiceRespVO>> getInvoiceSimpleList(@RequestParam("contractId") Long contractId) { |
| | | List<CrmInvoiceDO> invoices = invoiceService.getInvoiceListByContractIdAndStatus(contractId, |
| | | List.of(CrmAuditStatusEnum.APPROVE.getStatus())); |
| | | if (CollUtil.isEmpty(invoices)) { |
| | | return success(Collections.emptyList()); |
| | | } |
| | | // 过滤:已上传发票附件 |
| | | invoices.removeIf(invoice -> CollUtil.isEmpty(storageAttachmentApi.listAttachments( |
| | | // 过滤:必须是已录入发票(entryStatus=10);历史数据无录入状态时,以是否存在发票附件兜底 |
| | | invoices.removeIf(invoice -> !Integer.valueOf(10).equals(invoice.getEntryStatus()) |
| | | && CollUtil.isEmpty(storageAttachmentApi.listAttachments( |
| | | StorageRecordTypeEnum.CRM_INVOICE.getType(), invoice.getId()))); |
| | | if (CollUtil.isEmpty(invoices)) { |
| | | return success(Collections.emptyList()); |