完成了 BOM 管理和生产管理,完成部分发料单、采购计划和调拨单。

This commit is contained in:
c
2026-02-28 18:14:57 +08:00
parent 6d609f8156
commit bdea5287cb
88 changed files with 1693 additions and 518 deletions

View File

@@ -12,6 +12,12 @@ springdoc:
api-docs:
path: /v3/api-docs
group-configs:
- group: 'default'
- group: '权限控制'
paths-to-match: '/**'
packages-to-scan: com.niuan.erp.module.sys.controller
packages-to-scan: com.niuan.erp.module.auth.controller
- group: '系统设置'
paths-to-match: '/**'
packages-to-scan: com.niuan.erp.module.sys.controller
- group: '生产管理'
paths-to-match: '/**'
packages-to-scan: com.niuan.erp.module.production.controller

View File

@@ -13,7 +13,7 @@ spring:
# mybatis 设置
mybatis-plus:
type-aliases-package: com.niuan.erp.module.*.entity
type-aliases-package: com.niuan.erp.module.*.entity,com.niuan.erp.module.*.controller.dto,com.niuan.erp.common.base
configuration:
map-underscore-to-camel-case: false
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

View File

@@ -12,8 +12,68 @@ validation.common.pageParams.pageSize.notNull=页数不能为空
validation.common.pageParams.page.min=页数不能小于0
validation.common.pageParams.pageSize.min=页数不能小于0
warehouse.warehouse_item.part_number.not_null=编号不能为空
# ==========>> 仓库管理
warehouse.stock_transfer_order.validate.form_code.not_null=调拨单编号不能为空
warehouse.stock_transfer_order.validate.form_name.not_null=调拨单名称不能为空
warehouse.stock_transfer_order.validate.store_no.not_null=入库仓库 ID 不能为空
warehouse.stock_transfer_order.validate.store_name.not_null=入库仓库名称不能为空
warehouse.stock_transfer_order.validate.out_store_no.not_null=出库仓库 ID 不能为空
warehouse.stock_transfer_order.validate.out_store_name.not_null=出库仓库名称不能为空
warehouse.stock_transfer_order.validate.transfer_order_items.not_null=调拨单明细不能为空
warehouse.stock_transfer_order.validate.part_number.not_null=物料编号不能为空
warehouse.stock_transfer_order.validate.product_spec.not_null=物料型号不能为空
warehouse.stock_transfer_order.validate.product_count.not_null=调拨数量不能为空
warehouse.stock_transfer_order.validate.product_count.min=调拨数量不能小于 1
warehouse.stock_transfer_order.validate.part_id.not_null=物料 ID 不能为空
# ==========>> 生产管理
production.bom.validate.bom_id.not_null=BOM Id 不能为空
production.bom.validate.bom_no.not_null=BOM 编号不能为空
production.bom.validate.bom_name.not_null=BOM 名称不能为空
production.bom.validate.manufacturer.not_null=厂家 / 型号不能为空
production.bom.validate.spec.not_null=封装规格不能为空
production.bom.validate.brand_name.not_null=品牌不能为空
production.bom.validate.part_number.not_null=物料编号不能为空
production.bom.validate.manufacture_count.not_null=用量不能为空
production.bom.validate.manufacture_count.min=用量最小为 1
production.bom.validate.item_position.not_null=位号不能为空
production.bom.exception.duplicate_bom_name=BOM 名字重复
production.bom.exception.duplicate_bom_item=BOM 明细重复
production.bom.exception.unpair_position_count=BOM 明细中存在位点和数量不一致
production.bom.exception.unexists_bom_item=BOM 物料有不存在的物料
production.production_plan.validate.ids.not_null=生产计划 ID 不能为空
production.production_plan.validate.issue.not_null=发料单数据不能为空
production.production_plan.validate.form_code.not_null=发料单编号不能为空
production.production_plan.validate.store_no.not_null=发料仓库 ID 不能为空
production.production_plan.validate.store_name.not_null=发料仓库名称不能为空
production.production_plan.validate.items.not_null=发料单明细不能为空
production.production_plan.validate.part_number.not_null=物料编号不能为空
production.production_plan.validate.product_count.not_null=实发数量不能为空
production.production_plan.validate.product_count.min=实发数量不能小于 1
production.production_plan.validate.demand_count.not_null=需求数量不能为空
production.production_plan.validate.demand_count.min=需求数量不能小于 1
production.production_plan.exception.must_no_complete=选中的行其中有不是未完成的状态
production.production_plan.exception.more_than_one_warehouse=选中的行不能有多个仓库
sys.operationType.codeNotExists=编号不存在
# ==========>> 采购管理
purchase.purchase_plan.validate.plan_no.not_null=采购计划编号不能为空
purchase.purchase_plan.validate.store_no.not_null=收货仓库 ID 不能为空
purchase.purchase_plan.validate.store_name.not_null=收货仓库名称不能为空
purchase.purchase_plan.validate.plan_name.not_null=采购计划名称不能为空
purchase.purchase_plan.validate.part_number.not_null=物料编号不能为空
purchase.purchase_plan.validate.part_id.not_null=物料 ID 不能为空
purchase.purchase_plan.validate.purchase_count.not_null=购买数量不能为空
purchase.purchase_plan.validate.purchase_count.min=购买数量不能小于 0
# ==========> 类型
loginName.notNull=登录名称不能为空

View File

@@ -21,4 +21,22 @@
<result column="reserve2" property="reserve2" />
</resultMap>
<select id="getBomItemListByBomId" parameterType="Long" resultType="com.niuan.erp.module.production.controller.dto.BomItemDto">
SELECT b.PartNumber, p.ProductType, p.ProductSpecs, p.ProductPacksize, p.ProductBrand,
b.ManufactureCount, b.ItemPosition, b.SameUseCount, b.ProductMark
FROM bom b
INNER JOIN product p ON b.PartNumber = p.PartNumber
WHERE b.BomId = ${bomId}
</select>
<select id="getBomItemListByBomIdAndPartNumber" parameterType="Long" resultType="com.niuan.erp.module.production.controller.dto.BomItemDto">
SELECT b.PartNumber, p.ProductType, p.ProductSpecs, p.ProductPacksize, p.ProductBrand,
b.ManufactureCount, b.ItemPosition, b.SameUseCount, b.ProductMark
FROM bom b
INNER JOIN product p ON b.PartNumber = p.PartNumber
<where>
b.BomId = #{bomId} AND b.PartNumber Like CONCAT('%', #{partNumber}, '%')
</where>
</select>
</mapper>

View File

@@ -2,25 +2,25 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.niuan.erp.module.production.mapper.BomMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.niuan.erp.module.production.entity.Bom">
<id column="Id" property="id" />
<result column="Status" property="status" />
<result column="CreateDate" property="createDate" />
<result column="CreateUserId" property="createUserId" />
<result column="CreateUserName" property="createUserName" />
<result column="UpdateDate" property="updateDate" />
<result column="UpdateUserId" property="updateUserId" />
<result column="UpdateUserName" property="updateUserName" />
<result column="ParentId" property="parentId" />
<result column="BomNo" property="bomNo" />
<result column="Manufacturer" property="manufacturer" />
<result column="BomName" property="bomName" />
<result column="Spec" property="spec" />
<result column="BrandName" property="brandName" />
<result column="FormMark" property="formMark" />
<result column="customerName" property="customerName" />
<result column="CustomerId" property="customerId" />
</resultMap>
<select id="selectPageByPartNumber" resultType="com.niuan.erp.module.production.entity.Bom">
SELECT DISTINCT b.*
FROM bom_list b
INNER JOIN bom i ON i.BomId = b.Id
INNER JOIN product p ON i.PartNumber = p.PartNumber
<where>
<if test="searchCode != null and searchCode != ''">
AND (b.BomNo LIKE CONCAT('%', #{searchCode}, '%')
OR b.BomName LIKE CONCAT('%', #{searchCode}, '%'))
</if>
<if test="partNumber != null and searchCode != ''">
AND i.PartNumber LIKE CONCAT('%', #{partNumber}, '%')
</if>
</where>
ORDER BY b.CreateDate DESC
</select>
<select id="getBomSelectList" resultType="BaseSelectDto">
SELECT Id, BomName FROM bom_list
</select>
</mapper>

View File

@@ -29,4 +29,83 @@
<result column="CustomerId" property="customerId" />
</resultMap>
<select id="getProductionPlanRequiredQtyList" resultType="ProductionPlanShortageDto">
SELECT b.Id AS id, b.PartNumber AS partNumber, MAX(pro.ProductSpecs) AS productSpecs,
SUM(p.ProductionCount * b.ManufactureCount) AS requiredQty, COALESCE(MAX(s.ProductCount), 0) AS stockQty,
NULL AS diffQty
FROM produceorder p
INNER JOIN bom b ON p.ProjectId = b.BomId
INNER JOIN product pro ON b.PartNumber = pro.PartNumber
LEFT JOIN storagecount s ON s.PartNumber = pro.PartNumber
<if test="warehouseId != null">
AND s.StoreNo = #{warehouseId}
</if>
<where>
p.Id IN
<foreach item="id" collection="ids" open="(" separator="," close=")">
#{id}
</foreach>
</where>
GROUP BY b.Id, b.PartNumber, pro.ProductSpecs
</select>
<select id="selectProduceOrderData" resultType="ProduceOrderList">
SELECT
d.Id,
d.PartNumber AS Partnumber,
d.ProductSpecs AS Productspecs,
a.StoreNo,
b.SameUseCount,
b.SameUseNum1 AS SameNum1,
b.SameUseNum2 AS SameNum2,
b.SameUseNum3 AS SameNum3,
b.ManufactureCount AS BomTotal,
a.ProductionCount AS OrderTotal,
<!-- 1. 主料库存 -->
COALESCE(f_main.ProductCount, 0) AS MainStock,
<!-- 2. 替换料1库存 (根据 SameNum1 关联) -->
COALESCE(f_sub1.ProductCount, 0) AS Sub1Stock,
<!-- 3. 替换料2库存 (根据 SameNum2 关联) -->
COALESCE(f_sub2.ProductCount, 0) AS Sub2Stock,
<!-- 4. 替换料3库存 (根据 SameNum3 关联) -->
COALESCE(f_sub3.ProductCount, 0) AS Sub3Stock,
<!-- 占用数量 (示例设为0实际请根据表结构补充) -->
0 AS OccupyTotal
FROM produceorder a
INNER JOIN bom b ON a.ProjectId = b.BomId
INNER JOIN product d ON b.PartNumber = d.PartNumber AND d.CustomerId = a.CustomerId
<!-- 关联主料库存 -->
LEFT JOIN storagecount f_main
ON a.StoreNo = f_main.StoreNo AND b.PartNumber = f_main.PartNumber
<!-- 关联替换料1库存 -->
LEFT JOIN storagecount f_sub1
ON a.StoreNo = f_sub1.StoreNo AND b.SameUseNum1 = f_sub1.PartNumber
<!-- 关联替换料2库存 -->
LEFT JOIN storagecount f_sub2
ON a.StoreNo = f_sub2.StoreNo AND b.SameUseNum2 = f_sub2.PartNumber
<!-- 关联替换料3库存 -->
LEFT JOIN storagecount f_sub3
ON a.StoreNo = f_sub3.StoreNo AND b.SameUseNum3 = f_sub3.PartNumber
<where>
a.Id IN
<foreach item="itemId" collection="ids" open="(" separator="," close=")">
#{itemId}
</foreach>
</where>
</select>
</mapper>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.niuan.erp.module.sys.mapper.RolePermissionMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.niuan.erp.module.sys.entity.RolePermission">
<id column="role_id" property="roleId" />
<id column="permission_id" property="permissionId" />
</resultMap>
</mapper>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.niuan.erp.module.sys.mapper.SysPermissionMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.niuan.erp.module.sys.entity.SysPermission">
<id column="id" property="id" />
<result column="parent_id" property="parentId" />
<result column="status" property="status" />
<result column="create_date" property="createDate" />
<result column="create_user_id" property="createUserId" />
<result column="create_user_name" property="createUserName" />
<result column="update_date" property="updateDate" />
<result column="update_user_id" property="updateUserId" />
<result column="update_user_name" property="updateUserName" />
<result column="permission_name" property="permissionName" />
<result column="permission_i18n" property="permissionI18n" />
<result column="permission_type" property="permissionType" />
<result column="page_link" property="pageLink" />
<result column="view_link" property="viewLink" />
<result column="permission_code" property="permissionCode" />
<result column="event_name" property="eventName" />
<result column="class_name" property="className" />
<result column="icon_name" property="iconName" />
<result column="sort" property="sort" />
<result column="hidden" property="hidden" />
</resultMap>
<select id="selectByUserId" parameterType="Long" resultMap="BaseResultMap">
SELECT p.* FROM sys_permission p
LEFT JOIN role_permission rp ON rp.permission_id = p.id
LEFT JOIN yy_sysrole r ON r.Id = rp.role_id
LEFT JOIN yy_usersrolemapping ur ON ur.RoleId = r.Id
WHERE ur.UserId = #{userId} AND r.status = 1 AND p.status = 0
</select>
</mapper>

View File

@@ -22,4 +22,30 @@
<result column="ProductOccupyTotal" property="productOccupyTotal" />
</resultMap>
<select id="selectPageByParams" resultType="com.niuan.erp.module.warehouse.controller.dto.StockDto">
SELECT s.StoreName as storeName, p.ProductType as productType, p.PartNumber as partNumber,
p.ProductSpecs as productSpecs, p.ProductPacking as productPacking, p.ProductBrand as productBrand,
p.ProductPackSize as productPackSize, c.ProductCount as productCount, null as storeId, null as searchCode
FROM product p
INNER JOIN storagecount c ON p.PartNumber = c.PartNumber
INNER JOIN storage_list s ON s.Id = c.StoreNo
<where>
<if test="searchParams != null">
<if test="searchParams.productType != null and searchParams.productType != ''">
AND p.ProductType = #{searchParams.productType}
</if>
<if test="searchParams.productBrand != null and searchParams.productBrand != ''">
AND p.ProductBrand = #{searchParams.productBrand}
</if>
<if test="searchParams.storeId != null">
AND s.Id = #{searchParams.storeId}
</if>
<if test="searchParams.searchCode != null and searchParams.searchCode != ''">
AND (p.PartNumber LIKE CONCAT("%", #{searchParams.searchCode}, "%")
OR p.ProductSpecs LIKE CONCAT("%", #{searchParams.searchCode}, "%"))
</if>
</if>
</where>
</select>
</mapper>

View File

@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.niuan.erp.module.warehouse.mapper.StockTransferOrderMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.niuan.erp.module.warehouse.entity.StockTransferOrder">
<id column="Id" property="id" />
<result column="Status" property="status" />
<result column="CreateDate" property="createDate" />
<result column="CreateUserId" property="createUserId" />
<result column="CreateUserName" property="createUserName" />
<result column="UpdateDate" property="updateDate" />
<result column="UpdateUserId" property="updateUserId" />
<result column="UpdateUserName" property="updateUserName" />
<result column="StoreNo" property="storeNo" />
<result column="StoreName" property="storeName" />
<result column="FormType" property="formType" />
<result column="FormCode" property="formCode" />
<result column="FormName" property="formName" />
<result column="FormStatus" property="formStatus" />
<result column="FormMark" property="formMark" />
<result column="reserve1" property="reserve1" />
<result column="reserve2" property="reserve2" />
<result column="VendorNo" property="vendorNo" />
<result column="VendorName" property="vendorName" />
<result column="TotalValue" property="totalValue" />
<result column="OutStoreNo" property="outStoreNo" />
<result column="OutStoreName" property="outStoreName" />
<result column="GroupId" property="groupId" />
<result column="CustomerId" property="customerId" />
</resultMap>
</mapper>

View File

@@ -32,5 +32,25 @@
<result column="ProductOccupyTotal" property="productOccupyTotal" />
<result column="CustomerId" property="customerId" />
</resultMap>
<select id="getProductTypeSelectList" resultType="java.lang.String">
SELECT DISTINCT ProductType FROM product
</select>
<select id="getProductBrandSelectList" resultType="java.lang.String">
SELECT DISTINCT ProductBrand FROM product
</select>
<select id="getWarehouseItemStockListByPartNumbers" resultType="java.util.Map">
SELECT p.PartNumber AS partNumber, COALESCE(s.ProductCount, 0) AS productCount
FROM product p
LEFT JOIN storagecount s ON p.PartNumber = s.PartNumber
<where>
p.PartNumber IN
<foreach collection="partNumbers" item="partNumber" open="(" separator=", " close=")">
#{partNumber}
</foreach>
</where>
</select>
</mapper>

View File

@@ -21,4 +21,8 @@
<result column="CustomerId" property="customerId" />
</resultMap>
<select id="getWarehouseSelectList" resultType="com.niuan.erp.common.base.BaseSelectDto">
SELECT Id, StoreName FROM storage_list
</select>
</mapper>

View File

@@ -0,0 +1,15 @@
INSERT INTO sys_permission (
id, parent_id, permission_name, permission_i18n, permission_type, page_link,
view_link, permission_code, event_name, class_name, icon_name, sort
) SELECT
c.Id, c.ParentId, c.ChannelName, c.EventName,
CASE
WHEN c.IsMenuShow = 1 THEN 0
WHEN c.IsMenuShow = 0 and (c.EventName = 'add' or c.EventName = 'import') THEN 1
WHEN c.IsMenuShow = 0 and (c.EventName = 'enable' or c.EventName = 'disable') THEN 3
ELSE 2
END,
c.EventName, c.ChannelLink, c.EventName, c.EventName,
NULL, NULL, c.Sort
FROM yy_syschannel c;

View File

@@ -0,0 +1,37 @@
CREATE TABLE IF NOT EXISTS sys_permission (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
parent_id BIGINT NOT NULL DEFAULT 0 COMMENT '父权限ID0表示根节点',
status INT NOT NULL DEFAULT 0 COMMENT '状态0-禁用1-启用',
hidden BOOL NOT NULL DEFAULT FALSE COMMENT '是否隐藏',
create_date DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
create_user_id BIGINT NULL COMMENT '创建人ID',
create_user_name VARCHAR(50) NULL COMMENT '创建人姓名',
update_date DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
update_user_id BIGINT NULL COMMENT '更新人ID',
update_user_name VARCHAR(50) NULL COMMENT '更新人姓名',
permission_name VARCHAR(100) NOT NULL COMMENT '权限名字,没有 i18n 时,作为显示名称',
permission_i18n VARCHAR(100) DEFAULT NULL COMMENT '权限 i18n 键,用于前端多语言取值',
permission_type TINYINT NOT NULL COMMENT '权限类型关系到权限的位置0-菜单1-Table 上方按钮2-Table 操作栏按钮3-状态栏按钮',
page_link VARCHAR(100) DEFAULT NULL COMMENT '前端页面路由地址',
view_link VARCHAR(100) DEFAULT NULL COMMENT '前端 Vue 组件路径',
permission_code VARCHAR(50) DEFAULT NULL COMMENT '后端鉴权用的权限编码',
event_name VARCHAR(50) DEFAULT NULL COMMENT '前端按钮绑定的方法名',
class_name VARCHAR(30) DEFAULT NULL COMMENT '前端样式类名',
icon_name VARCHAR(30) DEFAULT NULL COMMENT '前端图标名称',
sort INT NOT NULL DEFAULT 0 COMMENT '排序值,越小越靠前'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统权限表';
CREATE TABLE IF NOT EXISTS role_permission (
role_id BIGINT NOT NULL COMMENT '角色ID',
permission_id BIGINT NOT NULL COMMENT '权限ID',
-- 联合主键:确保 (role_id, permission_id) 唯一,避免重复授权
PRIMARY KEY (role_id, permission_id),
-- 外键约束(可选但推荐):确保引用的角色和权限真实存在
CONSTRAINT fk_role_permission_role
FOREIGN KEY (role_id) REFERENCES yy_sysrole(Id) ON DELETE CASCADE,
CONSTRAINT fk_role_permission_permission
FOREIGN KEY (permission_id) REFERENCES sys_permission(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色-权限关联表,用的是原有框架的 yy_sysrole';