先来一道开胃菜。这是典型的Javaer对于类似问题的代码写法。
public class XXService {
...
private Boolean isTax(PurchaseBillItem purchaseBillItem) {
if (purchaseBillItem.getIsTax() == null) {
throw new BizException("是否是税标志isTax不能为空");
}
if (purchaseBillItem.getIsTax() == 0) {// 不是税
return false;
}
if (purchaseBillItem.getIsTax() == 1) {//是税
return true;
}
return true;
}
...
}
//调用 isTax
if (!isTax(purchaseBillItem)) {
...
}
尝尝这么写香不香。(将is_tax从int改为tinyint(1))
|
|
V
public class PurchaseBillItem {
...
public boolean isTax() {
return isTax;
}
...
}
再来看一个。
/**
* 判断进项税科目
*
* @param vo
* @param adCreateFeeRequestVO
* @return 0:进项税科目22210113 1:进项税科目22210114 -1:无
*/
private String purchaseUpdateInputTax(AdCreateRequestVO vo, AdCreateFeeRequestVO adCreateFeeRequestVO) {
if ("0".equals(vo.getCompanyArea()) //境内
&& "1".equals(vo.getCompanyTaxPayerNature())) { //一般纳税人
if ("1".equals(vo.getSupplierArea())) { //供应商为境外
return "1";
} else { //供应商为境内
if ("1".equals(vo.getSupplierTaxPayerNature())) { //供应商为一般纳税人
return "0";
} else if ("2".equals(vo.getSupplierTaxPayerNature())) { //小规模
//需判断发票类型
if ("1".equals(adCreateFeeRequestVO.getInvoiceType())) { //增值税专用发票
return "0";
}
}
}
}
return "-1";
}
首先是这个方法名,purchase这个动词说明是跟业务有关系,而且还是一个很大的业务逻辑,而如果真看代码则根本不是那么回事,其实加个get前缀才符合这个方法的气质。其次,宁可用一堆魔数加注释都舍不得定义几个常量。当然,更合适的方式是将这些equals判断放入vo中,然后给方法起一个符合业务的名字,比如 getSubjectOfInputTax()。
接下来换个口味。
/**
* 生成分录明细信息列表
*
* @param subjectCode 科目编码
* @param dcFlag 借贷方向
* @param adCreateRequestVO 凭证请求对象
* @param amount 金额
* @param entrySeq 分录行号
* @param accountDocumentInfo 凭证信息
* @param abstrctRule 摘要规则
* @return
*/
private List<AdAccountDocumentDetailPO> populateDocumentDetails(String subjectCode, Integer dcFlag, AdCreateRequestVO adCreateRequestVO, BigDecimal amount, int entrySeq, AdAccountDocumentInfoPO accountDocumentInfo, DataDicPO abstrctRule) {
List<AdAccountDocumentDetailPO> accountDocumentDetails = new ArrayList<AdAccountDocumentDetailPO>();
AdAccountDocumentDetailPO accountDocumentDetail;
String remark = "";
if (AdConstants.SPECIAL_ACC_SUBJECT_CODE_FEES.equals(subjectCode)) { //特殊科目:0
List<AdCreateFeeRequestVO> fees = this.getFeesForVoucherType(adCreateRequestVO, accountDocumentInfo.getVoucherType());
if (CollectionUtils.isEmpty(fees)) {
log.info("凭证生成时,会计科目配置为0,但缺少明细行数据");
throw new BizException("凭证生成时,缺少明细行数据");
}
Map<Long, AdAccountDocumentDetailPO> feeMap = new HashMap<Long, AdAccountDocumentDetailPO>();
//费用行对应的分录
for (AdCreateFeeRequestVO fee : fees) {
BigDecimal feeAmount = fee.getAmount();
log.info("费用行数据详情:{}", JSON.toJSONString(fee));
String actualSubjectCode = "";
if (BillTypeKindConstants.BILL_TYPE_KIND_PURCHASE.equals(adCreateRequestVO.getBillTypeKind())) {
Map<String, Object> temp = purchaseHandler(adCreateRequestVO, accountDocumentInfo, fee, dcFlag);
feeAmount = (BigDecimal) temp.get("feeAmount");
actualSubjectCode = temp.get("actualSubjectCode").toString();
} else {
actualSubjectCode = adAccountRuleService.getSubjectCodeByFee(fee.getFeeItemsCode(), fee.getDeptType(), fee.getAssetFlag());
if (StringUtils.isEmpty(actualSubjectCode)) {
log.error("特殊科目0处理时,按费用行生成分录时没有找到对应的会计科目,feeItemCode={}, deptType={}, assetFlag={}", fee.getFeeItemsCode(), fee.getDeptType(), fee.getAssetFlag());
throw new BizException(MessageFormat.format("费用行[{0}]部门类型[{1}-{2}]未找到对应会计科目"
, fee.getFeeItemsCode(), fee.getDeptType(), "10".equals(fee.getDeptType()) ? "管理" : "销售"));
}
}
//特殊处理参数 借款核销单行上部门取一级部门名称
if (!StringUtils.isEmpty(fee.getDeptNameLong())) {
this.filterRequestParams(accountDocumentInfo.getVoucherType(), adCreateRequestVO, fee, adCreateRequestVO.getDepts());
}
//构造摘要信息
log.info("本次生成摘要===1");
remark = this.buildAbstract(adCreateRequestVO, fee, abstrctRule);
accountDocumentDetail = this.adAccountDocumentDetailService.populateDocumentDetail(actualSubjectCode, dcFlag, adCreateRequestVO, feeAmount, entrySeq++, accountDocumentInfo, remark);
//按费用行生成分录时辅助核算项有可能与行有关
accountDocumentDetail.setFee(fee);
accountDocumentDetail.setFeeJson(JSON.toJSONString(fee));
accountDocumentDetails.add(accountDocumentDetail);
//应付,id不为空时,记录凭证详情,供后续使用
if (AmountTypeEnum.BILL_AMOUNT.getVoucherType().equals(accountDocumentInfo.getVoucherType()) && fee.getId() != null) {
feeMap.put(fee.getId(), accountDocumentDetail);
}
}
adCreateRequestVO.setFeeMap(feeMap);
} else if (AdConstants.SPECIAL_ACC_SUBJECT_CODE_BANK.equals(subjectCode)) { //特殊科目:1
String companyCode = adCreateRequestVO.getCompanyCode(); //公司
String accNo = adCreateRequestVO.getPayerAccNo(); //账号
//为关联方交易
if ("0".equals(adCreateRequestVO.getRelatedParty())) {
//收款凭证
if (AmountTypeEnum.COLLECT_AMOUNT.getVoucherType().equals(accountDocumentInfo.getVoucherType())) {
log.info("特殊科目为1且为收款凭证,公司编码取供应商对应公司编码,账号取收款账号...");
companyCode = adCreateRequestVO.getSupplierCompanyCode();
accNo = adCreateRequestVO.getPayeeAccNo();
}
}
//关联方交易付款凭证和之前流程一样
log.info("获取公司对应的银行科目 cf_ad_company_acc_subject.SUBJECT_CODE");
String actualSubjectCode = adAccountRuleService.getSubjectCodeByCompanyCode(companyCode, accNo);
if (StringUtils.isEmpty(actualSubjectCode)) {
log.error("特殊科目1处理时,按公司没有找到对应的银行会计科目,companyCode={}, 支付账号={}", companyCode, accNo);
throw new BizException(MessageFormat.format("公司[{0}]没找到对应的银行会计科目", companyCode));
}
log.info("本次生成摘要===2");
remark = this.buildAbstract(adCreateRequestVO, null, abstrctRule);
accountDocumentDetail = this.adAccountDocumentDetailService.populateDocumentDetail(actualSubjectCode, dcFlag, adCreateRequestVO, amount, entrySeq++, accountDocumentInfo, remark);
accountDocumentDetails.add(accountDocumentDetail);
} else if (AdConstants.SPECIAL_ACC_SUBJECT_CODE_TEMP.equals(subjectCode)) { //特殊科目:2
//为验收单生成凭证使用
String actualSubjectCode;
List<AdCreateDetailAmountRequestVO> detailAmountRequestVOS = adCreateRequestVO.getDetailAmounts();
if (CollectionUtils.isEmpty(detailAmountRequestVOS)) {
log.info("凭证生成时,会计科目配置为2,但缺少金额明细数据");
throw new BizException("凭证生成时,缺少金额明细数据");
}
for (AdCreateDetailAmountRequestVO amountRequestVO : detailAmountRequestVOS) {
actualSubjectCode = adAccountRuleService.getSubjectCodeByFee(amountRequestVO.getFeeItemsCode(), null, amountRequestVO.getAssetFlag());
if (StringUtils.isEmpty(actualSubjectCode)) {
log.error("特殊科目2处理时,按费用行生成分录时没有找到对应的会计科目,feeItemCode={},assetFlag={}", amountRequestVO.getFeeItemsCode(), amountRequestVO.getAssetFlag());
throw new BizException(MessageFormat.format("金额明细行[{0}]没找到对应的会计科目", amountRequestVO.getFeeItemsName()));
}
//构造摘要信息
log.info("本次生成摘要===4");
AdCreateFeeRequestVO tempFee = new AdCreateFeeRequestVO();
BeanUtils.copyProperties(amountRequestVO, tempFee);
remark = this.buildAbstract(adCreateRequestVO, tempFee, abstrctRule);
accountDocumentDetail = this.adAccountDocumentDetailService.populateDocumentDetail(actualSubjectCode, dcFlag, adCreateRequestVO, amountRequestVO.getAmount(), entrySeq++, accountDocumentInfo, remark);
accountDocumentDetail.setFee(tempFee);
accountDocumentDetail.setFeeJson(JSON.toJSONString(amountRequestVO));
accountDocumentDetails.add(accountDocumentDetail);
}
} else {
AdCreateFeeRequestVO fee = null;
if (AmountTypeEnum.DEPOSIT_LOAN_AMOUNT.getVoucherType().equals(accountDocumentInfo.getVoucherType())) {
//借款核销单的押金类型 摘要特殊处理,取任一押金行的供应商,其他字段不使用
List<AdCreateFeeRequestVO> fees = this.getFeesForVoucherType(adCreateRequestVO, accountDocumentInfo.getVoucherType());
fee = new AdCreateFeeRequestVO();
fee.setSupplierIdDetail(fees.get(0).getSupplierIdDetail());
fee.setSupplierNameDetail(fees.get(0).getSupplierNameDetail());
}
//如果是关联交易,任意取一条
if (adCreateRequestVO.isRelatedParty()) {
List<AdCreateFeeRequestVO> fees = this.getFeesForVoucherType(adCreateRequestVO, accountDocumentInfo.getVoucherType());
if (!CollectionUtils.isEmpty(fees)) {
fee = fees.get(0);
}
}
log.info("本次生成摘要===3");
remark = this.buildAbstract(adCreateRequestVO, fee, abstrctRule);
accountDocumentDetail = this.adAccountDocumentDetailService.populateDocumentDetail(subjectCode, dcFlag, adCreateRequestVO, amount, entrySeq++, accountDocumentInfo, remark);
//借款核销单的供应商取行上第一条
if (!Objects.isNull(fee)) {
accountDocumentDetail.setFee(fee);
accountDocumentDetail.setFeeJson(JSON.toJSONString(fee));
}
accountDocumentDetails.add(accountDocumentDetail);
}
return accountDocumentDetails;
}
上面这个方法给人的第一感觉就是代码较多。首先,方法的主体框架是由多个 if else 对于subjectCode进行判断构成,显然用状态模式来重构这个方法是再合适不过了。其次,当一个方法的参数多于3个时最好用一个参数对象来封装这些入参。除上述两个主要的优化点外,如果想做的更好话,还是有不少地方可以优化,比如其中调用的一个方法——getSubjectCodeByFee。
actualSubjectCode = adAccountRuleService.getSubjectCodeByFee(fee.getFeeItemsCode(), fee.getDeptType(), fee.getAssetFlag());
因为这个方法的入参全部来自于fee对象,所以完全可以将 getSubjectCodeByFee() 方法放入到fee类中,像下面这样,从而让贫血的Fee变成一个充血的Fee。
public class Fee {
private String feeItemsCode;
private String deptType;
private Boolean isAsset;
... 其他属性省略
public String getSubjectCode() {
Map<String, Object> param = new HashMap<>(2);
param.put("feeItemCode", feeItemCode);
if (isAsset()) {
param.put("isAsset", isAsset);
} else {
param.put("deptType", deptType);
}
SpringUtil.getBean(XXDao.class).getSubjectCodeByFee(param);
}
}
当然也可以这样。
public String getSubjectCode(XXDao dao) {
Map<String, Object> param = new HashMap<>(2);
param.put("feeItemCode", feeItemCode);
if (isAsset()) {
param.put("isAsset", isAsset);
} else {
param.put("deptType", deptType);
}
dao.getSubjectCodeByFee(param);
}
return Result还是 throw Exception,这是个问题
public class BillCommonLogic {
...
public Result checkContractAmtOccupy(Long billId) {
Bill bill = billService.getOneById(billId);
if (bill == null) {
return Result.FAILURE.setMessage(ErrorMsgConstants.OBJECT_NOT_EXIST);
}
if (BillConstants.CONTRACT_AMT_OCCUPY_FAILED.equals(bill.getContractAmtOccupySuccess())) {
return Result.FAILURE.setMessage(ErrorMsgConstants.NOT_ALLOW);
}
return Result.ok();
}
public Bill checkBillStatus(Long billId, String taskId) {
Bill bill = billService.getOneById(billId);
if (bill == null) {
throw new BizException("单据不存在!");
}
if (bill.getBillStatus().equals(BillStateConstants.NOT_SUBMIT)) {
logger.error(Constants.TASK_IS_REVOCATION);
throw new BizException(Constants.TASK_IS_REVOCATION);
}
return bill;
}
...
}
重构之后
public class Bill {
...
private String billStatus;
private String contractAmtOccupySuccess;
...
public void checkOccupationOfContractAmount() {
BizAssert.isTrue(!BillConstants.CONTRACT_AMT_OCCUPY_FAILED.equals(contractAmtOccupySuccess)
, "合同金额占用失败");
}
public void checkStatusForProcess() {
BizAssert.isTrue(!billStatus.equals(BillStatusConstants.NOT_SUBMIT), "单据已被撤回");
}
...
}
即使对BillCommonLogic中这两个方法进行了重构,但这两个方法在很多位置都有调用(差不多有一百多处),在一定程度上来说这也是一种代码重复。
如何简化这些调用,也就是最好只写一次Bill.checkStatusForProcess()便可替代的所有调用。是的,AOP是改动最小的方案。
先这样吧,等看到不爽的再来吐。