2 Commits

Author SHA1 Message Date
038213a26c Merge remote-tracking branch 'origin/develop' into develop 2026-02-24 17:30:36 +08:00
ff41aa9c04 feat(dict): 新增字典注解删除标记字段支持并修复库存计算空指针异常
- 在Dict注解中新增deleteFlag字段用于指定删除标记字段名
- 修改DictAspect切面逻辑支持删除标记字段的过滤查询
- 更新ProductDetailAppMapper.xml中的关联查询条件排序
- 修复ProductDetailAppServiceImpl中partPercent为空时的空指针异常
- 为ReceiptPageDto中的字典字段添加删除标记过滤配置
- 新增药物统计管理门户页面提供各类统计报表入口
2026-02-24 17:30:23 +08:00
6 changed files with 306 additions and 105 deletions

View File

@@ -173,9 +173,10 @@ public class ProductDetailAppServiceImpl extends ServiceImpl<InventoryItemMapper
productDetailPageDto.setInventoryStatusEnum_enumText( productDetailPageDto.setInventoryStatusEnum_enumText(
EnumUtils.getInfoByValue(PublicationStatus.class, productDetailPageDto.getInventoryStatusEnum())); EnumUtils.getInfoByValue(PublicationStatus.class, productDetailPageDto.getInventoryStatusEnum()));
// 计算包装单位数量 // 计算包装单位数量 - 防止partPercent为null导致的空指针异常
BigDecimal[] results BigDecimal partPercent = productDetailPageDto.getPartPercent();
= productDetailPageDto.getQuantity().divideAndRemainder(productDetailPageDto.getPartPercent()); if (partPercent != null && partPercent.compareTo(BigDecimal.ZERO) != 0) {
BigDecimal[] results = productDetailPageDto.getQuantity().divideAndRemainder(partPercent);
// 整数 // 整数
productDetailPageDto.setNumber(results[0]); productDetailPageDto.setNumber(results[0]);
// 余数 // 余数
@@ -186,7 +187,7 @@ public class ProductDetailAppServiceImpl extends ServiceImpl<InventoryItemMapper
productDetailPageDto productDetailPageDto
.setTotalPurchasePrice(productDetailPageDto.getPurchasePrice().multiply(results[0]) .setTotalPurchasePrice(productDetailPageDto.getPurchasePrice().multiply(results[0])
.add(productDetailPageDto.getPurchasePrice() .add(productDetailPageDto.getPurchasePrice()
.divide(productDetailPageDto.getPartPercent(), 6, RoundingMode.HALF_UP) .divide(partPercent, 6, RoundingMode.HALF_UP)
.multiply(results[1]))); .multiply(results[1])));
} }
@@ -194,9 +195,26 @@ public class ProductDetailAppServiceImpl extends ServiceImpl<InventoryItemMapper
if (productDetailPageDto.getSalePrice() != null) { if (productDetailPageDto.getSalePrice() != null) {
productDetailPageDto.setTotalSalePrice(productDetailPageDto.getSalePrice().multiply(results[0]) productDetailPageDto.setTotalSalePrice(productDetailPageDto.getSalePrice().multiply(results[0])
.add(productDetailPageDto.getSalePrice() .add(productDetailPageDto.getSalePrice()
.divide(productDetailPageDto.getPartPercent(), 6, RoundingMode.HALF_UP) .divide(partPercent, 6, RoundingMode.HALF_UP)
.multiply(results[1]))); .multiply(results[1])));
} }
} else {
// 如果partPercent为空或为0则直接设置数量避免计算
productDetailPageDto.setNumber(productDetailPageDto.getQuantity());
productDetailPageDto.setRemainder(BigDecimal.ZERO);
// 计算采购总额
if (productDetailPageDto.getPurchasePrice() != null) {
productDetailPageDto.setTotalPurchasePrice(
productDetailPageDto.getPurchasePrice().multiply(productDetailPageDto.getQuantity()));
}
// 计算售价总额
if (productDetailPageDto.getSalePrice() != null) {
productDetailPageDto.setTotalSalePrice(
productDetailPageDto.getSalePrice().multiply(productDetailPageDto.getQuantity()));
}
}
} }
} }
return R.ok(productDetailsPage); return R.ok(productDetailsPage);
@@ -552,9 +570,10 @@ public class ProductDetailAppServiceImpl extends ServiceImpl<InventoryItemMapper
productDetailPageDto.setInventoryStatusEnum_enumText( productDetailPageDto.setInventoryStatusEnum_enumText(
EnumUtils.getInfoByValue(PublicationStatus.class, productDetailPageDto.getInventoryStatusEnum())); EnumUtils.getInfoByValue(PublicationStatus.class, productDetailPageDto.getInventoryStatusEnum()));
// 计算包装单位数量 // 计算包装单位数量 - 防止partPercent为null导致的空指针异常
BigDecimal[] results BigDecimal partPercent = productDetailPageDto.getPartPercent();
= productDetailPageDto.getQuantity().divideAndRemainder(productDetailPageDto.getPartPercent()); if (partPercent != null && partPercent.compareTo(BigDecimal.ZERO) != 0) {
BigDecimal[] results = productDetailPageDto.getQuantity().divideAndRemainder(partPercent);
// 整数 // 整数
productDetailPageDto.setNumber(results[0]); productDetailPageDto.setNumber(results[0]);
// 余数 // 余数
@@ -565,7 +584,7 @@ public class ProductDetailAppServiceImpl extends ServiceImpl<InventoryItemMapper
productDetailPageDto productDetailPageDto
.setTotalPurchasePrice(productDetailPageDto.getPurchasePrice().multiply(results[0]) .setTotalPurchasePrice(productDetailPageDto.getPurchasePrice().multiply(results[0])
.add(productDetailPageDto.getPurchasePrice() .add(productDetailPageDto.getPurchasePrice()
.divide(productDetailPageDto.getPartPercent(), 6, RoundingMode.HALF_UP) .divide(partPercent, 6, RoundingMode.HALF_UP)
.multiply(results[1]))); .multiply(results[1])));
} }
@@ -573,9 +592,26 @@ public class ProductDetailAppServiceImpl extends ServiceImpl<InventoryItemMapper
if (productDetailPageDto.getSalePrice() != null) { if (productDetailPageDto.getSalePrice() != null) {
productDetailPageDto.setTotalSalePrice(productDetailPageDto.getSalePrice().multiply(results[0]) productDetailPageDto.setTotalSalePrice(productDetailPageDto.getSalePrice().multiply(results[0])
.add(productDetailPageDto.getSalePrice() .add(productDetailPageDto.getSalePrice()
.divide(productDetailPageDto.getPartPercent(), 6, RoundingMode.HALF_UP) .divide(partPercent, 6, RoundingMode.HALF_UP)
.multiply(results[1]))); .multiply(results[1])));
} }
} else {
// 如果partPercent为空或为0则直接设置数量避免计算
productDetailPageDto.setNumber(productDetailPageDto.getQuantity());
productDetailPageDto.setRemainder(BigDecimal.ZERO);
// 计算采购总额
if (productDetailPageDto.getPurchasePrice() != null) {
productDetailPageDto.setTotalPurchasePrice(
productDetailPageDto.getPurchasePrice().multiply(productDetailPageDto.getQuantity()));
}
// 计算售价总额
if (productDetailPageDto.getSalePrice() != null) {
productDetailPageDto.setTotalSalePrice(
productDetailPageDto.getSalePrice().multiply(productDetailPageDto.getQuantity()));
}
}
} }
} }
return R.ok(productDetailList); return R.ok(productDetailList);

View File

@@ -53,7 +53,7 @@ public class ReceiptPageDto {
/** /**
* 供应商 * 供应商
*/ */
@Dict(dictCode = "id", dictText = "name", dictTable = "adm_supplier") @Dict(dictCode = "id", dictText = "name", dictTable = "adm_supplier", deleteFlag = "delete_flag")
@JsonSerialize(using = ToStringSerializer.class) @JsonSerialize(using = ToStringSerializer.class)
private Long supplierId; private Long supplierId;
private String supplierId_dictText; private String supplierId_dictText;
@@ -63,7 +63,7 @@ public class ReceiptPageDto {
/** /**
* 目的仓库 * 目的仓库
*/ */
@Dict(dictCode = "id", dictText = "name", dictTable = "adm_location") @Dict(dictCode = "id", dictText = "name", dictTable = "adm_location", deleteFlag = "delete_flag")
@JsonSerialize(using = ToStringSerializer.class) @JsonSerialize(using = ToStringSerializer.class)
private Long purposeLocationId; private Long purposeLocationId;
private String purposeLocationId_dictText; private String purposeLocationId_dictText;
@@ -73,7 +73,7 @@ public class ReceiptPageDto {
/** /**
* 目的仓位 * 目的仓位
*/ */
@Dict(dictCode = "id", dictText = "name", dictTable = "adm_location") @Dict(dictCode = "id", dictText = "name", dictTable = "adm_location", deleteFlag = "delete_flag")
@JsonSerialize(using = ToStringSerializer.class) @JsonSerialize(using = ToStringSerializer.class)
private Long purposeLocationStoreId; private Long purposeLocationStoreId;
private String purposeLocationStoreId_dictText; private String purposeLocationStoreId_dictText;
@@ -86,7 +86,7 @@ public class ReceiptPageDto {
/** /**
* 经手人 * 经手人
*/ */
@Dict(dictCode = "id", dictText = "name", dictTable = "adm_practitioner") @Dict(dictCode = "id", dictText = "name", dictTable = "adm_practitioner", deleteFlag = "delete_flag")
@JsonSerialize(using = ToStringSerializer.class) @JsonSerialize(using = ToStringSerializer.class)
private Long practitionerId; private Long practitionerId;
private String practitionerId_dictText; private String practitionerId_dictText;
@@ -96,7 +96,7 @@ public class ReceiptPageDto {
/** /**
* 审批人 * 审批人
*/ */
@Dict(dictCode = "id", dictText = "name", dictTable = "adm_practitioner") @Dict(dictCode = "id", dictText = "name", dictTable = "adm_practitioner", deleteFlag = "delete_flag")
private Long approverId; private Long approverId;
private String approverId_dictText; private String approverId_dictText;
@Excel(name = "审批人", sort = 10) @Excel(name = "审批人", sort = 10)
@@ -111,7 +111,7 @@ public class ReceiptPageDto {
/** /**
* 申请人 * 申请人
*/ */
@Dict(dictCode = "id", dictText = "name", dictTable = "adm_practitioner") @Dict(dictCode = "id", dictText = "name", dictTable = "adm_practitioner", deleteFlag = "delete_flag")
@JsonSerialize(using = ToStringSerializer.class) @JsonSerialize(using = ToStringSerializer.class)
private Long applicantId; private Long applicantId;
private String applicantId_dictText; private String applicantId_dictText;

View File

@@ -85,6 +85,7 @@
LEFT JOIN adm_charge_item_def_detail acidd LEFT JOIN adm_charge_item_def_detail acidd
ON acid.id = acidd.definition_id ON acid.id = acidd.definition_id
AND acidd.condition_value = wii.lot_number AND acidd.condition_value = wii.lot_number
AND acidd.condition_code = #{lotNumberCost}
AND acidd.delete_flag = '0' AND acidd.delete_flag = '0'
LEFT JOIN adm_location al LEFT JOIN adm_location al
ON wii.location_id = al.id ON wii.location_id = al.id
@@ -96,7 +97,6 @@
ON wii.supplier_id = as2.id ON wii.supplier_id = as2.id
AND as2.delete_flag = '0' AND as2.delete_flag = '0'
WHERE wii.item_table = #{medMedicationDefinition} WHERE wii.item_table = #{medMedicationDefinition}
AND acidd.condition_code = #{lotNumberCost}
AND wii.delete_flag = '0' AND wii.delete_flag = '0'
ORDER BY wii.py_str) ORDER BY wii.py_str)
UNION UNION
@@ -156,7 +156,6 @@
ON wii.supplier_id = as2.id ON wii.supplier_id = as2.id
AND as2.delete_flag = '0' AND as2.delete_flag = '0'
WHERE wii.item_table = #{admDeviceDefinition} WHERE wii.item_table = #{admDeviceDefinition}
AND acidd.condition_code = #{lotNumberCost}
AND wii.delete_flag = '0' AND wii.delete_flag = '0'
ORDER BY wii.py_str) ORDER BY wii.py_str)
) AS ii ) AS ii
@@ -249,6 +248,7 @@
AND acid.delete_flag = '0' AND acid.delete_flag = '0'
LEFT JOIN adm_charge_item_def_detail acidd LEFT JOIN adm_charge_item_def_detail acidd
ON acid.id = acidd.definition_id ON acid.id = acidd.definition_id
AND acidd.condition_code = #{lotNumberCost}
AND acidd.condition_value = wii.lot_number AND acidd.condition_value = wii.lot_number
AND acidd.delete_flag = '0' AND acidd.delete_flag = '0'
LEFT JOIN adm_location al LEFT JOIN adm_location al
@@ -261,7 +261,6 @@
ON wii.supplier_id = as2.id ON wii.supplier_id = as2.id
AND as2.delete_flag = '0' AND as2.delete_flag = '0'
WHERE wii.item_table = #{medMedicationDefinition} WHERE wii.item_table = #{medMedicationDefinition}
AND acidd.condition_code = #{lotNumberCost}
AND wii.delete_flag = '0' AND wii.delete_flag = '0'
UNION UNION
SELECT wii.id AS inventory_id, SELECT wii.id AS inventory_id,
@@ -319,7 +318,6 @@
ON wii.supplier_id = as2.id ON wii.supplier_id = as2.id
AND as2.delete_flag = '0' AND as2.delete_flag = '0'
WHERE wii.item_table = #{admDeviceDefinition} WHERE wii.item_table = #{admDeviceDefinition}
AND acidd.condition_code = #{lotNumberCost}
AND wii.delete_flag = '0') AS ii AND wii.delete_flag = '0') AS ii
LEFT JOIN sys_dict_data sdd ON ii.unit_code = sdd.dict_value AND sdd.dict_type = 'unit_code' LEFT JOIN sys_dict_data sdd ON ii.unit_code = sdd.dict_value AND sdd.dict_type = 'unit_code'
LEFT JOIN sys_dict_data sdd2 LEFT JOIN sys_dict_data sdd2

View File

@@ -11,4 +11,5 @@ public @interface Dict {
String dictCode(); // 字典类型字段 String dictCode(); // 字典类型字段
String dictText() default ""; // 回显字段,默认为空 String dictText() default ""; // 回显字段,默认为空
String dictTable() default ""; // 表名,默认为空 String dictTable() default ""; // 表名,默认为空
String deleteFlag() default ""; // 删除标记字段名,默认为空(不过滤)
} }

View File

@@ -95,8 +95,9 @@ public class DictAspect {
String dictCode = dictAnnotation.dictCode(); String dictCode = dictAnnotation.dictCode();
String dictText = dictAnnotation.dictText(); String dictText = dictAnnotation.dictText();
String dictTable = dictAnnotation.dictTable(); String dictTable = dictAnnotation.dictTable();
String deleteFlag = dictAnnotation.deleteFlag();
// 查询字典值 // 查询字典值
String dictLabel = queryDictLabel(dictTable, dictCode, dictText, fieldValue.toString()); String dictLabel = queryDictLabel(dictTable, dictCode, dictText, deleteFlag, fieldValue.toString());
if (dictLabel != null) { if (dictLabel != null) {
try { try {
// 动态生成 _dictText 字段名 // 动态生成 _dictText 字段名
@@ -115,7 +116,7 @@ public class DictAspect {
} }
} }
private String queryDictLabel(String dictTable, String dictCode, String dictText, String dictValue) { private String queryDictLabel(String dictTable, String dictCode, String dictText, String deleteFlag, String dictValue) {
if (!StringUtils.hasText(dictTable)) { if (!StringUtils.hasText(dictTable)) {
// 场景 1默认字典走DictUtils缓存dictTable 为空时) // 场景 1默认字典走DictUtils缓存dictTable 为空时)
return DictUtils.getDictLabel(dictCode, dictValue); return DictUtils.getDictLabel(dictCode, dictValue);
@@ -126,7 +127,15 @@ public class DictAspect {
// 如果 dictText 为空,回退到字典缓存查询 // 如果 dictText 为空,回退到字典缓存查询
return DictUtils.getDictLabel(dictCode, dictValue); return DictUtils.getDictLabel(dictCode, dictValue);
} }
String sql = String.format("SELECT %s FROM %s WHERE %s::varchar = ? LIMIT 1", dictText, dictTable, dictCode); // 构建SQL支持 delete_flag 过滤
StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.append(String.format("SELECT %s FROM %s WHERE %s::varchar = ?", dictText, dictTable, dictCode));
// 如果指定了 deleteFlag 字段名,添加过滤条件
if (StringUtils.hasText(deleteFlag)) {
sqlBuilder.append(String.format(" AND %s = '0'", deleteFlag));
}
sqlBuilder.append(" LIMIT 1");
String sql = sqlBuilder.toString();
try { try {
return jdbcTemplate.queryForObject(sql, String.class, dictValue); return jdbcTemplate.queryForObject(sql, String.class, dictValue);
} catch (DataAccessException e) { } catch (DataAccessException e) {

View File

@@ -0,0 +1,157 @@
<template>
<div class="app-container">
<div class="statistics-portal">
<h2 class="portal-title">药物统计管理门户</h2>
<p class="portal-description">选择您想要查看的统计报告类型</p>
<div class="statistics-grid">
<!-- 库存统计 -->
<el-card class="statistic-card" @click="goToPage('/medicationmanagement/statisticalManagement')">
<div class="card-content">
<div class="card-icon">
<i class="el-icon-data-analysis" style="font-size: 36px; color: #409EFF;"></i>
</div>
<div class="card-text">
<h3>库存统计</h3>
<p>查看药品库存情况</p>
</div>
</div>
</el-card>
<!-- 效期预警 -->
<el-card class="statistic-card" @click="goToPage('/medicationmanagement/statisticalManagement/earlyWarning')">
<div class="card-content">
<div class="card-icon">
<i class="el-icon-warning-outline" style="font-size: 36px; color: #E6A23C;"></i>
</div>
<div class="card-text">
<h3>效期预警</h3>
<p>查看即将过期的药品</p>
</div>
</div>
</el-card>
<!-- 药品使用情况 -->
<el-card class="statistic-card" @click="goToPage('/medicationmanagement/statisticalManagement/medicationUsageDetails')">
<div class="card-content">
<div class="card-icon">
<i class="el-icon-trend-charts" style="font-size: 36px; color: #67C23A;"></i>
</div>
<div class="card-text">
<h3>药品使用情况</h3>
<p>查看药品使用统计</p>
</div>
</div>
</el-card>
<!-- 药品销售情况 -->
<el-card class="statistic-card" @click="goToPage('/medicationmanagement/statisticalManagement/medicationSaleDetails')">
<div class="card-content">
<div class="card-icon">
<i class="el-icon-sell" style="font-size: 36px; color: #F56C6C;"></i>
</div>
<div class="card-text">
<h3>药品销售情况</h3>
<p>查看药品销售统计</p>
</div>
</div>
</el-card>
<!-- 采购入库明细 -->
<el-card class="statistic-card" @click="goToPage('/medicationmanagement/statisticalManagement/medicationInboundDetails')">
<div class="card-content">
<div class="card-icon">
<i class="el-icon-shopping-cart-full" style="font-size: 36px; color: #909399;"></i>
</div>
<div class="card-text">
<h3>采购入库明细</h3>
<p>查看采购入库统计</p>
</div>
</div>
</el-card>
<!-- 出库明细 -->
<el-card class="statistic-card" @click="goToPage('/medicationmanagement/statisticalManagement/stockOutDetail')">
<div class="card-content">
<div class="card-icon">
<i class="el-icon-shopping-cart-1" style="font-size: 36px; color: #409EFF;"></i>
</div>
<div class="card-text">
<h3>出库明细</h3>
<p>查看药品出库统计</p>
</div>
</div>
</el-card>
</div>
</div>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router';
const router = useRouter();
const goToPage = (path) => {
router.push(path);
};
</script>
<style lang="scss" scoped>
.statistics-portal {
padding: 20px;
.portal-title {
text-align: center;
margin-bottom: 10px;
color: #303133;
}
.portal-description {
text-align: center;
color: #909399;
margin-bottom: 30px;
}
.statistics-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
}
.statistic-card {
cursor: pointer;
transition: all 0.3s ease;
&:hover {
transform: translateY(-5px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
}
.card-content {
display: flex;
align-items: center;
.card-icon {
margin-right: 15px;
}
.card-text {
flex: 1;
h3 {
margin: 0 0 5px 0;
font-size: 16px;
color: #303133;
}
p {
margin: 0;
font-size: 14px;
color: #909399;
}
}
}
}
}
</style>