feat(einvoice): add electronic invoice module (T13.4)
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
package com.healthlink.his.web.einvoice.appservice;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.healthlink.his.einvoice.domain.EinvoiceHeader;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface IEinvoiceAppService {
|
||||||
|
EinvoiceHeader generate(EinvoiceHeader header);
|
||||||
|
IPage<EinvoiceHeader> page(String invoiceStatus, String patientName, Integer pageNum, Integer pageSize);
|
||||||
|
void voidInvoice(Long id, String reason);
|
||||||
|
Map<String, Object> getReconciliation(Integer pageNum, Integer pageSize);
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package com.healthlink.his.web.einvoice.appservice.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.healthlink.his.einvoice.domain.EinvoiceHeader;
|
||||||
|
import com.healthlink.his.einvoice.service.IEinvoiceHeaderService;
|
||||||
|
import com.healthlink.his.einvoice.service.IEinvoiceReconciliationService;
|
||||||
|
import com.healthlink.his.web.einvoice.appservice.IEinvoiceAppService;
|
||||||
|
import com.core.common.utils.SecurityUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class EinvoiceAppServiceImpl implements IEinvoiceAppService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IEinvoiceHeaderService headerService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IEinvoiceReconciliationService reconciliationService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EinvoiceHeader generate(EinvoiceHeader header) {
|
||||||
|
header.setInvoiceNo("EINV" + System.currentTimeMillis());
|
||||||
|
header.setInvoiceType("ELECTRONIC");
|
||||||
|
header.setInvoiceStatus("ISSUED");
|
||||||
|
header.setIssueTime(new Date());
|
||||||
|
header.setIssuerName(SecurityUtils.getUsername());
|
||||||
|
headerService.save(header);
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPage<EinvoiceHeader> page(String invoiceStatus, String patientName, Integer pageNum, Integer pageSize) {
|
||||||
|
LambdaQueryWrapper<EinvoiceHeader> w = new LambdaQueryWrapper<>();
|
||||||
|
if (StringUtils.hasText(invoiceStatus)) {
|
||||||
|
w.eq(EinvoiceHeader::getInvoiceStatus, invoiceStatus);
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(patientName)) {
|
||||||
|
w.like(EinvoiceHeader::getPatientName, patientName);
|
||||||
|
}
|
||||||
|
w.orderByDesc(EinvoiceHeader::getCreateTime);
|
||||||
|
return headerService.page(new Page<>(pageNum, pageSize), w);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void voidInvoice(Long id, String reason) {
|
||||||
|
EinvoiceHeader header = headerService.getById(id);
|
||||||
|
if (header == null) {
|
||||||
|
throw new RuntimeException("发票不存在");
|
||||||
|
}
|
||||||
|
header.setInvoiceStatus("VOID");
|
||||||
|
header.setVoidTime(new Date());
|
||||||
|
header.setVoidReason(reason);
|
||||||
|
headerService.updateById(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> getReconciliation(Integer pageNum, Integer pageSize) {
|
||||||
|
LambdaQueryWrapper<EinvoiceHeader> w = new LambdaQueryWrapper<>();
|
||||||
|
w.eq(EinvoiceHeader::getInvoiceStatus, "ISSUED");
|
||||||
|
w.orderByDesc(EinvoiceHeader::getIssueTime);
|
||||||
|
IPage<EinvoiceHeader> page = headerService.page(new Page<>(pageNum, pageSize), w);
|
||||||
|
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("records", page.getRecords());
|
||||||
|
result.put("total", page.getTotal());
|
||||||
|
|
||||||
|
LambdaQueryWrapper<EinvoiceHeader> totalW = new LambdaQueryWrapper<>();
|
||||||
|
totalW.eq(EinvoiceHeader::getInvoiceStatus, "ISSUED");
|
||||||
|
long totalCount = headerService.count(totalW);
|
||||||
|
result.put("totalCount", totalCount);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.healthlink.his.web.einvoice.controller;
|
||||||
|
|
||||||
|
import com.core.common.core.domain.AjaxResult;
|
||||||
|
import com.healthlink.his.einvoice.domain.EinvoiceHeader;
|
||||||
|
import com.healthlink.his.web.einvoice.appservice.IEinvoiceAppService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Tag(name = "电子票据管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/invoice")
|
||||||
|
public class EinvoiceController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IEinvoiceAppService einvoiceAppService;
|
||||||
|
|
||||||
|
@Operation(summary = "生成电子票据")
|
||||||
|
@PreAuthorize("@ss.hasPermi('basicmanage:invoice:edit')")
|
||||||
|
@PostMapping("/generate")
|
||||||
|
public AjaxResult generate(@RequestBody EinvoiceHeader header) {
|
||||||
|
return AjaxResult.success(einvoiceAppService.generate(header));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "电子票据分页")
|
||||||
|
@PreAuthorize("@ss.hasPermi('basicmanage:invoice:list')")
|
||||||
|
@GetMapping("/page")
|
||||||
|
public AjaxResult page(@RequestParam(required = false) String invoiceStatus,
|
||||||
|
@RequestParam(required = false) String patientName,
|
||||||
|
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||||
|
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||||
|
return AjaxResult.success(einvoiceAppService.page(invoiceStatus, patientName, pageNum, pageSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "作废电子票据")
|
||||||
|
@PreAuthorize("@ss.hasPermi('basicmanage:invoice:edit')")
|
||||||
|
@PostMapping("/void")
|
||||||
|
public AjaxResult voidInvoice(@RequestParam Long id, @RequestParam(required = false) String reason) {
|
||||||
|
einvoiceAppService.voidInvoice(id, reason);
|
||||||
|
return AjaxResult.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "票据对账")
|
||||||
|
@PreAuthorize("@ss.hasPermi('basicmanage:invoice:list')")
|
||||||
|
@GetMapping("/reconciliation")
|
||||||
|
public AjaxResult reconciliation(@RequestParam(defaultValue = "1") Integer pageNum,
|
||||||
|
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||||
|
return AjaxResult.success(einvoiceAppService.getReconciliation(pageNum, pageSize));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS invoice_header (
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
invoice_no VARCHAR(64) NOT NULL,
|
||||||
|
invoice_type VARCHAR(20) NOT NULL DEFAULT 'ELECTRONIC',
|
||||||
|
encounter_id BIGINT,
|
||||||
|
patient_id BIGINT,
|
||||||
|
patient_name VARCHAR(64),
|
||||||
|
total_amount NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||||
|
discount_amount NUMERIC(12,2) DEFAULT 0,
|
||||||
|
payable_amount NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||||
|
paid_amount NUMERIC(12,2) DEFAULT 0,
|
||||||
|
invoice_status VARCHAR(20) NOT NULL DEFAULT 'ISSUED',
|
||||||
|
issue_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
void_time TIMESTAMP,
|
||||||
|
void_reason VARCHAR(256),
|
||||||
|
issuer_id BIGINT,
|
||||||
|
issuer_name VARCHAR(64),
|
||||||
|
create_by VARCHAR(64),
|
||||||
|
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
update_by VARCHAR(64),
|
||||||
|
update_time TIMESTAMP,
|
||||||
|
tenant_id INT DEFAULT 1,
|
||||||
|
delete_flag VARCHAR(1) DEFAULT '0'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS invoice_detail (
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
header_id BIGINT NOT NULL,
|
||||||
|
item_code VARCHAR(64),
|
||||||
|
item_name VARCHAR(128) NOT NULL,
|
||||||
|
item_type VARCHAR(20),
|
||||||
|
quantity NUMERIC(10,2) DEFAULT 1,
|
||||||
|
unit_price NUMERIC(12,2) DEFAULT 0,
|
||||||
|
amount NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||||
|
create_by VARCHAR(64),
|
||||||
|
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
update_by VARCHAR(64),
|
||||||
|
update_time TIMESTAMP,
|
||||||
|
tenant_id INT DEFAULT 1,
|
||||||
|
delete_flag VARCHAR(1) DEFAULT '0'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS invoice_segment (
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
segment_name VARCHAR(128) NOT NULL,
|
||||||
|
start_no VARCHAR(64) NOT NULL,
|
||||||
|
end_no VARCHAR(64) NOT NULL,
|
||||||
|
current_no VARCHAR(64),
|
||||||
|
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||||||
|
invoice_type VARCHAR(20) DEFAULT 'ELECTRONIC',
|
||||||
|
create_by VARCHAR(64),
|
||||||
|
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
update_by VARCHAR(64),
|
||||||
|
update_time TIMESTAMP,
|
||||||
|
tenant_id INT DEFAULT 1,
|
||||||
|
delete_flag VARCHAR(1) DEFAULT '0'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS invoice_reconciliation (
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
recon_date DATE NOT NULL,
|
||||||
|
total_invoices INT DEFAULT 0,
|
||||||
|
total_amount NUMERIC(14,2) DEFAULT 0,
|
||||||
|
reconciled_count INT DEFAULT 0,
|
||||||
|
reconciled_amount NUMERIC(14,2) DEFAULT 0,
|
||||||
|
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
|
||||||
|
reconciler_id BIGINT,
|
||||||
|
reconciler_name VARCHAR(64),
|
||||||
|
reconcile_time TIMESTAMP,
|
||||||
|
create_by VARCHAR(64),
|
||||||
|
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
update_by VARCHAR(64),
|
||||||
|
update_time TIMESTAMP,
|
||||||
|
tenant_id INT DEFAULT 1,
|
||||||
|
delete_flag VARCHAR(1) DEFAULT '0'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_inv_header_no ON invoice_header(invoice_no);
|
||||||
|
CREATE INDEX idx_inv_header_status ON invoice_header(invoice_status);
|
||||||
|
CREATE INDEX idx_inv_header_patient ON invoice_header(patient_id);
|
||||||
|
CREATE INDEX idx_inv_detail_header ON invoice_detail(header_id);
|
||||||
|
CREATE INDEX idx_inv_recon_date ON invoice_reconciliation(recon_date);
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.healthlink.his.einvoice.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.core.common.core.domain.HisBaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import tools.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import tools.jackson.databind.ser.std.ToStringSerializer;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("invoice_detail")
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class EinvoiceDetail extends HisBaseEntity {
|
||||||
|
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long id;
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long headerId;
|
||||||
|
private String itemCode;
|
||||||
|
private String itemName;
|
||||||
|
private String itemType;
|
||||||
|
private BigDecimal quantity;
|
||||||
|
private BigDecimal unitPrice;
|
||||||
|
private BigDecimal amount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.healthlink.his.einvoice.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.core.common.core.domain.HisBaseEntity;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import tools.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import tools.jackson.databind.ser.std.ToStringSerializer;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("invoice_header")
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class EinvoiceHeader extends HisBaseEntity {
|
||||||
|
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long id;
|
||||||
|
private String invoiceNo;
|
||||||
|
private String invoiceType;
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long encounterId;
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long patientId;
|
||||||
|
private String patientName;
|
||||||
|
private BigDecimal totalAmount;
|
||||||
|
private BigDecimal discountAmount;
|
||||||
|
private BigDecimal payableAmount;
|
||||||
|
private BigDecimal paidAmount;
|
||||||
|
private String invoiceStatus;
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date issueTime;
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date voidTime;
|
||||||
|
private String voidReason;
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long issuerId;
|
||||||
|
private String issuerName;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.healthlink.his.einvoice.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.core.common.core.domain.HisBaseEntity;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import tools.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import tools.jackson.databind.ser.std.ToStringSerializer;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("invoice_reconciliation")
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class EinvoiceReconciliation extends HisBaseEntity {
|
||||||
|
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long id;
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
private Date reconDate;
|
||||||
|
private Integer totalInvoices;
|
||||||
|
private BigDecimal totalAmount;
|
||||||
|
private Integer reconciledCount;
|
||||||
|
private BigDecimal reconciledAmount;
|
||||||
|
private String status;
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long reconcilerId;
|
||||||
|
private String reconcilerName;
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date reconcileTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.healthlink.his.einvoice.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.core.common.core.domain.HisBaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import tools.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import tools.jackson.databind.ser.std.ToStringSerializer;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("invoice_segment")
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class EinvoiceSegment extends HisBaseEntity {
|
||||||
|
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long id;
|
||||||
|
private String segmentName;
|
||||||
|
private String startNo;
|
||||||
|
private String endNo;
|
||||||
|
private String currentNo;
|
||||||
|
private String status;
|
||||||
|
private String invoiceType;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.healthlink.his.einvoice.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.healthlink.his.einvoice.domain.EinvoiceDetail;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface EinvoiceDetailMapper extends BaseMapper<EinvoiceDetail> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.healthlink.his.einvoice.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.healthlink.his.einvoice.domain.EinvoiceHeader;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface EinvoiceHeaderMapper extends BaseMapper<EinvoiceHeader> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.healthlink.his.einvoice.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.healthlink.his.einvoice.domain.EinvoiceReconciliation;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface EinvoiceReconciliationMapper extends BaseMapper<EinvoiceReconciliation> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.healthlink.his.einvoice.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.healthlink.his.einvoice.domain.EinvoiceSegment;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface EinvoiceSegmentMapper extends BaseMapper<EinvoiceSegment> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.healthlink.his.einvoice.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.healthlink.his.einvoice.domain.EinvoiceDetail;
|
||||||
|
|
||||||
|
public interface IEinvoiceDetailService extends IService<EinvoiceDetail> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.healthlink.his.einvoice.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.healthlink.his.einvoice.domain.EinvoiceHeader;
|
||||||
|
|
||||||
|
public interface IEinvoiceHeaderService extends IService<EinvoiceHeader> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.healthlink.his.einvoice.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.healthlink.his.einvoice.domain.EinvoiceReconciliation;
|
||||||
|
|
||||||
|
public interface IEinvoiceReconciliationService extends IService<EinvoiceReconciliation> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.healthlink.his.einvoice.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.healthlink.his.einvoice.domain.EinvoiceSegment;
|
||||||
|
|
||||||
|
public interface IEinvoiceSegmentService extends IService<EinvoiceSegment> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.healthlink.his.einvoice.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.healthlink.his.einvoice.domain.EinvoiceDetail;
|
||||||
|
import com.healthlink.his.einvoice.mapper.EinvoiceDetailMapper;
|
||||||
|
import com.healthlink.his.einvoice.service.IEinvoiceDetailService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class EinvoiceDetailServiceImpl extends ServiceImpl<EinvoiceDetailMapper, EinvoiceDetail> implements IEinvoiceDetailService {
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.healthlink.his.einvoice.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.healthlink.his.einvoice.domain.EinvoiceHeader;
|
||||||
|
import com.healthlink.his.einvoice.mapper.EinvoiceHeaderMapper;
|
||||||
|
import com.healthlink.his.einvoice.service.IEinvoiceHeaderService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class EinvoiceHeaderServiceImpl extends ServiceImpl<EinvoiceHeaderMapper, EinvoiceHeader> implements IEinvoiceHeaderService {
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.healthlink.his.einvoice.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.healthlink.his.einvoice.domain.EinvoiceReconciliation;
|
||||||
|
import com.healthlink.his.einvoice.mapper.EinvoiceReconciliationMapper;
|
||||||
|
import com.healthlink.his.einvoice.service.IEinvoiceReconciliationService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class EinvoiceReconciliationServiceImpl extends ServiceImpl<EinvoiceReconciliationMapper, EinvoiceReconciliation> implements IEinvoiceReconciliationService {
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.healthlink.his.einvoice.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.healthlink.his.einvoice.domain.EinvoiceSegment;
|
||||||
|
import com.healthlink.his.einvoice.mapper.EinvoiceSegmentMapper;
|
||||||
|
import com.healthlink.his.einvoice.service.IEinvoiceSegmentService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class EinvoiceSegmentServiceImpl extends ServiceImpl<EinvoiceSegmentMapper, EinvoiceSegment> implements IEinvoiceSegmentService {
|
||||||
|
}
|
||||||
204
healthlink-his-ui/src/views/einvoice/InvoiceManagement.vue
Normal file
204
healthlink-his-ui/src/views/einvoice/InvoiceManagement.vue
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
<template>
|
||||||
|
<div style="padding: 16px">
|
||||||
|
<div style="margin-bottom: 16px; display: flex; justify-content: space-between; align-items: center">
|
||||||
|
<span style="font-size: 18px; font-weight: bold">电子票据管理</span>
|
||||||
|
<el-button type="primary" @click="handleGenerate">开具票据</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-card shadow="never" style="margin-bottom: 16px">
|
||||||
|
<div style="display: flex; gap: 8px; flex-wrap: wrap; align-items: center">
|
||||||
|
<el-input v-model="query.patientName" placeholder="患者姓名" clearable style="width: 160px" />
|
||||||
|
<el-select v-model="query.invoiceStatus" placeholder="状态" clearable style="width: 120px">
|
||||||
|
<el-option label="已开具" value="ISSUED" />
|
||||||
|
<el-option label="已作废" value="VOID" />
|
||||||
|
</el-select>
|
||||||
|
<el-button type="primary" @click="loadPage">查询</el-button>
|
||||||
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
|
<el-button @click="showReconciliation">对账</el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card shadow="never">
|
||||||
|
<el-table :data="tableData" v-loading="loading" border stripe>
|
||||||
|
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||||
|
<el-table-column prop="invoiceNo" label="票据号" width="200" />
|
||||||
|
<el-table-column prop="patientName" label="患者姓名" width="120" />
|
||||||
|
<el-table-column prop="totalAmount" label="总金额" width="120" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.totalAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="discountAmount" label="优惠金额" width="120" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.discountAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="payableAmount" label="应付金额" width="120" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.payableAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="invoiceStatus" label="状态" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag v-if="row.invoiceStatus === 'ISSUED'" type="success">已开具</el-tag>
|
||||||
|
<el-tag v-else-if="row.invoiceStatus === 'VOID'" type="danger">已作废</el-tag>
|
||||||
|
<el-tag v-else>{{ row.invoiceStatus }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="issueTime" label="开具时间" width="170" />
|
||||||
|
<el-table-column label="操作" width="100" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button v-if="row.invoiceStatus === 'ISSUED'" type="danger" size="small" @click="handleVoid(row)">作废</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="query.pageNum"
|
||||||
|
v-model:page-size="query.pageSize"
|
||||||
|
:page-sizes="[10, 20, 50]"
|
||||||
|
:total="total"
|
||||||
|
layout="total, sizes, prev, pager, next"
|
||||||
|
style="margin-top: 16px; justify-content: flex-end"
|
||||||
|
@size-change="loadPage"
|
||||||
|
@current-change="loadPage"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-dialog v-model="generateVisible" title="开具电子票据" width="500px">
|
||||||
|
<el-form :model="generateForm" label-width="90px">
|
||||||
|
<el-form-item label="就诊ID">
|
||||||
|
<el-input v-model="generateForm.encounterId" placeholder="就诊ID" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="患者ID">
|
||||||
|
<el-input v-model="generateForm.patientId" placeholder="患者ID" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="患者姓名">
|
||||||
|
<el-input v-model="generateForm.patientName" placeholder="患者姓名" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="总金额">
|
||||||
|
<el-input v-model="generateForm.totalAmount" placeholder="总金额" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="优惠金额">
|
||||||
|
<el-input v-model="generateForm.discountAmount" placeholder="优惠金额" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="应付金额">
|
||||||
|
<el-input v-model="generateForm.payableAmount" placeholder="应付金额" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="generateVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submitGenerate">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog v-model="voidVisible" title="作废票据" width="400px">
|
||||||
|
<el-form label-width="80px">
|
||||||
|
<el-form-item label="作废原因">
|
||||||
|
<el-input v-model="voidReason" type="textarea" :rows="3" placeholder="请输入作废原因" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="voidVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="confirmVoid">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog v-model="reconVisible" title="票据对账" width="700px">
|
||||||
|
<el-table :data="reconData" border stripe v-loading="reconLoading">
|
||||||
|
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||||
|
<el-table-column prop="invoiceNo" label="票据号" width="200" />
|
||||||
|
<el-table-column prop="patientName" label="患者姓名" width="120" />
|
||||||
|
<el-table-column prop="payableAmount" label="应付金额" width="120" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.payableAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="invoiceStatus" label="状态" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag v-if="row.invoiceStatus === 'ISSUED'" type="success">已开具</el-tag>
|
||||||
|
<el-tag v-else>{{ row.invoiceStatus }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="issueTime" label="开具时间" width="170" />
|
||||||
|
</el-table>
|
||||||
|
<div style="margin-top: 12px; color: #666">
|
||||||
|
共 {{ reconTotal }} 条已开具票据
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { generateInvoice, getInvoicePage, voidInvoice, getReconciliation } from './api'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const tableData = ref([])
|
||||||
|
const total = ref(0)
|
||||||
|
const query = ref({ invoiceStatus: '', patientName: '', pageNum: 1, pageSize: 10 })
|
||||||
|
|
||||||
|
const generateVisible = ref(false)
|
||||||
|
const generateForm = ref({ encounterId: '', patientId: '', patientName: '', totalAmount: 0, discountAmount: 0, payableAmount: 0 })
|
||||||
|
|
||||||
|
const voidVisible = ref(false)
|
||||||
|
const voidReason = ref('')
|
||||||
|
const voidRow = ref(null)
|
||||||
|
|
||||||
|
const reconVisible = ref(false)
|
||||||
|
const reconLoading = ref(false)
|
||||||
|
const reconData = ref([])
|
||||||
|
const reconTotal = ref(0)
|
||||||
|
|
||||||
|
function formatMoney(val) {
|
||||||
|
if (!val) return '0.00'
|
||||||
|
return Number(val).toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadPage() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getInvoicePage(query.value)
|
||||||
|
tableData.value = res.data?.records || []
|
||||||
|
total.value = res.data?.total || 0
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetQuery() {
|
||||||
|
query.value = { invoiceStatus: '', patientName: '', pageNum: 1, pageSize: 10 }
|
||||||
|
loadPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGenerate() {
|
||||||
|
generateForm.value = { encounterId: '', patientId: '', patientName: '', totalAmount: 0, discountAmount: 0, payableAmount: 0 }
|
||||||
|
generateVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitGenerate() {
|
||||||
|
await generateInvoice(generateForm.value)
|
||||||
|
ElMessage.success('开具成功')
|
||||||
|
generateVisible.value = false
|
||||||
|
loadPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleVoid(row) {
|
||||||
|
voidRow.value = row
|
||||||
|
voidReason.value = ''
|
||||||
|
voidVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmVoid() {
|
||||||
|
await voidInvoice({ id: voidRow.value.id, reason: voidReason.value })
|
||||||
|
ElMessage.success('作废成功')
|
||||||
|
voidVisible.value = false
|
||||||
|
loadPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showReconciliation() {
|
||||||
|
reconVisible.value = true
|
||||||
|
reconLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getReconciliation({ pageNum: 1, pageSize: 50 })
|
||||||
|
reconData.value = res.data?.records || []
|
||||||
|
reconTotal.value = res.data?.totalCount || 0
|
||||||
|
} finally {
|
||||||
|
reconLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => loadPage())
|
||||||
|
</script>
|
||||||
17
healthlink-his-ui/src/views/einvoice/api.js
Normal file
17
healthlink-his-ui/src/views/einvoice/api.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function generateInvoice(data) {
|
||||||
|
return request({ url: '/invoice/generate', method: 'post', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInvoicePage(params) {
|
||||||
|
return request({ url: '/invoice/page', method: 'get', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function voidInvoice(params) {
|
||||||
|
return request({ url: '/invoice/void', method: 'post', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getReconciliation(params) {
|
||||||
|
return request({ url: '/invoice/reconciliation', method: 'get', params })
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user