cmvr-wms/sql/wms_project_management_20260922.sql

363 lines
22 KiB
MySQL
Raw Normal View History

-- 项目经费、库存来源批次、跨项目使用及 AI 指令审计。
-- 用于从当前 WMS 结构升级。所有增量字段和索引均先检查再创建,允许在中断后重新执行。
-- 执行前必须显式确认 database() 是目标库,并完成数据库备份。
create table if not exists project_info (
id bigint(20) not null auto_increment comment '主键',
project_code varchar(64) not null comment '项目编码',
project_name varchar(200) not null comment '项目名称',
manager_id bigint(20) default null comment '项目负责人用户ID',
dept_id bigint(20) default null comment '归属部门ID',
start_date date default null comment '开始日期',
end_date date default null comment '结束日期',
project_status tinyint(1) not null default 1 comment '状态:0筹备 1进行中 2已完成 3已归档',
currency varchar(8) not null default 'CNY' comment '币种',
remark varchar(500) default null comment '备注',
create_by varchar(64) default null comment '创建人',
create_time datetime default null comment '创建时间',
update_by varchar(64) default null comment '更新人',
update_time datetime default null comment '更新时间',
primary key (id),
unique key uk_project_info_code (project_code),
key idx_project_info_name (project_name),
key idx_project_info_manager (manager_id),
key idx_project_info_status (project_status)
) engine=innodb default charset=utf8mb4 comment='项目主表';
create table if not exists project_member (
id bigint(20) not null auto_increment comment '主键',
project_id bigint(20) not null comment '项目ID',
user_id bigint(20) not null comment '用户ID',
member_role varchar(32) not null default 'MEMBER' comment '成员角色',
create_by varchar(64) default null comment '创建人',
create_time datetime default null comment '创建时间',
primary key (id),
unique key uk_project_member (project_id, user_id),
key idx_project_member_user (user_id)
) engine=innodb default charset=utf8mb4 comment='项目成员表';
create table if not exists expense_category (
id bigint(20) not null auto_increment comment '主键',
category_code varchar(64) not null comment '经费科目编码',
category_name varchar(100) not null comment '经费科目名称',
sort_order int not null default 0 comment '显示顺序',
status tinyint(1) not null default 0 comment '状态:0正常 1停用',
remark varchar(500) default null comment '备注',
create_by varchar(64) default null comment '创建人',
create_time datetime default null comment '创建时间',
update_by varchar(64) default null comment '更新人',
update_time datetime default null comment '更新时间',
primary key (id),
unique key uk_expense_category_code (category_code),
unique key uk_expense_category_name (category_name)
) engine=innodb default charset=utf8mb4 comment='项目经费科目';
create table if not exists project_budget (
id bigint(20) not null auto_increment comment '主键',
project_id bigint(20) not null comment '项目ID',
expense_category_id bigint(20) not null comment '经费科目ID',
budget_amount decimal(18,2) not null default 0.00 comment '预算金额',
committed_amount decimal(18,2) not null default 0.00 comment '已占用金额',
spent_amount decimal(18,2) not null default 0.00 comment '已支出金额',
alert_percent decimal(5,2) not null default 80.00 comment '预警百分比',
version int not null default 0 comment '乐观锁版本',
remark varchar(500) default null comment '备注',
create_by varchar(64) default null comment '创建人',
create_time datetime default null comment '创建时间',
update_by varchar(64) default null comment '更新人',
update_time datetime default null comment '更新时间',
primary key (id),
unique key uk_project_budget (project_id, expense_category_id),
key idx_project_budget_category (expense_category_id)
) engine=innodb default charset=utf8mb4 comment='项目分科目预算';
create table if not exists project_fund_ledger (
id bigint(20) not null auto_increment comment '主键',
ledger_no varchar(64) not null comment '流水号',
project_id bigint(20) not null comment '项目ID',
expense_category_id bigint(20) not null comment '经费科目ID',
biz_type varchar(32) not null comment '业务类型',
biz_id bigint(20) default null comment '业务主键',
biz_no varchar(64) default null comment '业务单号',
budget_delta decimal(18,2) not null default 0.00 comment '预算变化额',
committed_delta decimal(18,2) not null default 0.00 comment '占用变化额',
spent_delta decimal(18,2) not null default 0.00 comment '支出变化额',
balance_budget decimal(18,2) not null default 0.00 comment '变更后预算额',
balance_committed decimal(18,2) not null default 0.00 comment '变更后占用额',
balance_spent decimal(18,2) not null default 0.00 comment '变更后支出额',
idempotency_key varchar(128) default null comment '幂等键',
operator varchar(64) default null comment '操作人',
remark varchar(500) default null comment '说明',
create_time datetime not null default current_timestamp comment '创建时间',
primary key (id),
unique key uk_project_fund_ledger_no (ledger_no),
unique key uk_project_fund_idempotency (idempotency_key),
key idx_project_fund_project_category (project_id, expense_category_id, create_time),
key idx_project_fund_biz (biz_type, biz_id)
) engine=innodb default charset=utf8mb4 comment='项目经费不可变流水';
create table if not exists stock_lot (
id bigint(20) not null auto_increment comment '主键',
lot_no varchar(64) not null comment '库存批次号',
area_id bigint(20) not null comment '库区ID',
product_id bigint(20) not null comment '产品ID',
source_project_id bigint(20) not null comment '采购或来源项目ID',
expense_category_id bigint(20) default null comment '来源经费科目ID',
purchase_request_id bigint(20) default null comment '采购申请ID',
arrival_id bigint(20) default null comment '到货ID',
in_record_id bigint(20) default null comment '入库记录ID',
received_quantity decimal(16,3) not null comment '入库数量',
available_quantity decimal(16,3) not null comment '可用数量',
unit_cost decimal(18,2) not null default 0.00 comment '单位成本',
lot_status tinyint(1) not null default 0 comment '状态:0可用 1用尽 2冻结',
remark varchar(500) default null comment '备注',
create_by varchar(64) default null comment '创建人',
create_time datetime default null comment '创建时间',
update_by varchar(64) default null comment '更新人',
update_time datetime default null comment '更新时间',
primary key (id),
unique key uk_stock_lot_no (lot_no),
unique key uk_stock_lot_in_record (in_record_id),
key idx_stock_lot_available (area_id, product_id, lot_status, create_time),
key idx_stock_lot_source_project (source_project_id),
key idx_stock_lot_purchase (purchase_request_id)
) engine=innodb default charset=utf8mb4 comment='库存来源批次';
create table if not exists out_record_lot (
id bigint(20) not null auto_increment comment '主键',
out_record_id bigint(20) not null comment '出库记录ID',
stock_lot_id bigint(20) not null comment '库存批次ID',
source_project_id bigint(20) not null comment '来源项目ID',
usage_project_id bigint(20) not null comment '使用项目ID',
quantity decimal(16,3) not null comment '出库数量',
unit_cost decimal(18,2) not null default 0.00 comment '单位成本',
amount decimal(18,2) not null default 0.00 comment '使用成本',
create_time datetime not null default current_timestamp comment '创建时间',
primary key (id),
key idx_out_record_lot_out (out_record_id),
key idx_out_record_lot_stock (stock_lot_id),
key idx_out_record_lot_project_flow (source_project_id, usage_project_id)
) engine=innodb default charset=utf8mb4 comment='出库库存批次分摊';
create table if not exists ai_command (
id bigint(20) not null auto_increment comment '主键',
user_id bigint(20) not null comment '发起用户ID',
username varchar(64) not null comment '发起账号',
action varchar(64) not null comment '动作',
required_permission varchar(128) default null comment '所需权限',
request_text varchar(1000) default null comment '原始文本',
plan_json json not null comment '服务器确认的执行计划',
result_json json default null comment '执行结果',
command_status varchar(20) not null comment 'WAIT_CONFIRM/EXECUTING/SUCCEEDED/REJECTED/EXPIRED/FAILED',
expires_at datetime not null comment '过期时间',
confirmed_at datetime default null comment '人工确认时间',
executed_at datetime default null comment '执行完成时间',
error_message varchar(1000) default null comment '失败原因',
create_time datetime not null default current_timestamp comment '创建时间',
update_time datetime default null comment '更新时间',
primary key (id),
key idx_ai_command_user_status (user_id, command_status, create_time),
key idx_ai_command_expire (expires_at)
) engine=innodb default charset=utf8mb4 comment='AI 待确认业务指令';
set @column_exists = (select count(*) from information_schema.columns
where table_schema = database() and table_name = 'purchase_request' and column_name = 'project_id');
set @ddl = if(@column_exists = 0,
'alter table purchase_request add column project_id bigint(20) default null comment ''采购项目ID'' after purchase_batch_id',
'select 1');
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
set @column_exists = (select count(*) from information_schema.columns
where table_schema = database() and table_name = 'purchase_request' and column_name = 'expense_category_id');
set @ddl = if(@column_exists = 0,
'alter table purchase_request add column expense_category_id bigint(20) default null comment ''经费科目ID'' after project_id',
'select 1');
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
set @column_exists = (select count(*) from information_schema.columns
where table_schema = database() and table_name = 'purchase_request' and column_name = 'estimated_unit_price');
set @ddl = if(@column_exists = 0,
'alter table purchase_request add column estimated_unit_price decimal(18,2) default null comment ''预计含税单价'' after quantity',
'select 1');
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
set @column_exists = (select count(*) from information_schema.columns
where table_schema = database() and table_name = 'purchase_request' and column_name = 'estimated_amount');
set @ddl = if(@column_exists = 0,
'alter table purchase_request add column estimated_amount decimal(18,2) default null comment ''预计总金额'' after estimated_unit_price',
'select 1');
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
set @idx_exists = (select count(*) from information_schema.statistics
where table_schema = database() and table_name = 'purchase_request'
and index_name = 'idx_purchase_request_project');
set @ddl = if(@idx_exists = 0,
'alter table purchase_request add key idx_purchase_request_project (project_id, expense_category_id)',
'select 1');
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
set @column_exists = (select count(*) from information_schema.columns
where table_schema = database() and table_name = 'purchase_arrival' and column_name = 'actual_unit_price');
set @ddl = if(@column_exists = 0,
'alter table purchase_arrival add column actual_unit_price decimal(18,2) default null comment ''本批实际含税单价'' after arrival_quantity',
'select 1');
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
set @column_exists = (select count(*) from information_schema.columns
where table_schema = database() and table_name = 'purchase_arrival' and column_name = 'actual_amount');
set @ddl = if(@column_exists = 0,
'alter table purchase_arrival add column actual_amount decimal(18,2) default null comment ''本批实际金额'' after actual_unit_price',
'select 1');
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
set @column_exists = (select count(*) from information_schema.columns
where table_schema = database() and table_name = 'in_record' and column_name = 'source_project_id');
set @ddl = if(@column_exists = 0,
'alter table in_record add column source_project_id bigint(20) default null comment ''来源项目ID'' after arrival_id',
'select 1');
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
set @column_exists = (select count(*) from information_schema.columns
where table_schema = database() and table_name = 'in_record' and column_name = 'expense_category_id');
set @ddl = if(@column_exists = 0,
'alter table in_record add column expense_category_id bigint(20) default null comment ''来源经费科目ID'' after source_project_id',
'select 1');
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
set @idx_exists = (select count(*) from information_schema.statistics
where table_schema = database() and table_name = 'in_record'
and index_name = 'idx_in_record_source_project');
set @ddl = if(@idx_exists = 0,
'alter table in_record add key idx_in_record_source_project (source_project_id)',
'select 1');
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
set @column_exists = (select count(*) from information_schema.columns
where table_schema = database() and table_name = 'out_record' and column_name = 'usage_project_id');
set @ddl = if(@column_exists = 0,
'alter table out_record add column usage_project_id bigint(20) default null comment ''使用项目ID'' after product_id',
'select 1');
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
set @column_exists = (select count(*) from information_schema.columns
where table_schema = database() and table_name = 'out_record' and column_name = 'cross_project_flag');
set @ddl = if(@column_exists = 0,
'alter table out_record add column cross_project_flag tinyint(1) not null default 0 comment ''是否跨项目使用'' after source_no',
'select 1');
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
set @column_exists = (select count(*) from information_schema.columns
where table_schema = database() and table_name = 'out_record' and column_name = 'cross_project_reason');
set @ddl = if(@column_exists = 0,
'alter table out_record add column cross_project_reason varchar(500) default null comment ''跨项目使用原因'' after cross_project_flag',
'select 1');
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
set @idx_exists = (select count(*) from information_schema.statistics
where table_schema = database() and table_name = 'out_record'
and index_name = 'idx_out_record_usage_project');
set @ddl = if(@idx_exists = 0,
'alter table out_record add key idx_out_record_usage_project (usage_project_id)',
'select 1');
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
insert into project_info(project_code, project_name, manager_id, project_status, currency, remark, create_by, create_time)
select 'LEGACY', '历史待归属项目', 1, 3, 'CNY', '升级前数据统一归入该项目,后续不得虚构原始归属', 'migration', sysdate()
where not exists (select 1 from project_info where project_code = 'LEGACY');
insert into expense_category(category_code, category_name, sort_order, status, remark, create_by, create_time)
select 'MATERIAL', '材料费', 10, 0, '材料及耗材', 'migration', sysdate()
where not exists (select 1 from expense_category where category_code = 'MATERIAL');
insert into expense_category(category_code, category_name, sort_order, status, remark, create_by, create_time)
select 'EQUIPMENT', '设备费', 20, 0, '仪器设备购置', 'migration', sysdate()
where not exists (select 1 from expense_category where category_code = 'EQUIPMENT');
insert into expense_category(category_code, category_name, sort_order, status, remark, create_by, create_time)
select 'TRIAL', '试制费', 30, 0, '试制及加工费用', 'migration', sysdate()
where not exists (select 1 from expense_category where category_code = 'TRIAL');
insert into expense_category(category_code, category_name, sort_order, status, remark, create_by, create_time)
select 'OTHER', '其他费用', 90, 0, '其他项目费用', 'migration', sysdate()
where not exists (select 1 from expense_category where category_code = 'OTHER');
insert into expense_category(category_code, category_name, sort_order, status, remark, create_by, create_time)
select 'LEGACY', '历史待归属', 99, 0, '升级前无法确认的经费科目', 'migration', sysdate()
where not exists (select 1 from expense_category where category_code = 'LEGACY');
set @legacy_project_id = (select id from project_info where project_code = 'LEGACY' limit 1);
set @legacy_category_id = (select id from expense_category where category_code = 'LEGACY' limit 1);
insert ignore into project_member(project_id, user_id, member_role, create_by, create_time)
values(@legacy_project_id, 1, 'MANAGER', 'migration', sysdate());
update purchase_request
set project_id = @legacy_project_id,
expense_category_id = @legacy_category_id,
estimated_unit_price = coalesce(estimated_unit_price, 0),
estimated_amount = coalesce(estimated_amount, 0)
where project_id is null;
update in_record
set source_project_id = @legacy_project_id,
expense_category_id = @legacy_category_id
where source_project_id is null;
update out_record
set usage_project_id = @legacy_project_id
where usage_project_id is null;
insert ignore into stock_lot(
lot_no, area_id, product_id, source_project_id, expense_category_id,
received_quantity, available_quantity, unit_cost, lot_status, remark, create_by, create_time
)
select concat('LEGACY-', si.id), si.area_id, si.product_id, @legacy_project_id, @legacy_category_id,
si.stock_num, si.stock_num, 0,
case when si.stock_num > 0 then 0 else 1 end,
'由升级时库存汇总生成,原采购来源未知', 'migration', coalesce(si.create_time, sysdate())
from stock_info si
where not exists (select 1 from stock_lot sl where sl.lot_no = concat('LEGACY-', si.id));
-- 项目管理菜单及权限。固定使用 2100-2113,当前项目菜单最大编号为 2042。
insert into sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, route_name,
is_frame, is_cache, menu_type, visible, status, perms, icon,
create_by, create_time, update_by, update_time, remark)
select 2100, '项目管理', 0, 7, 'project', null, '', '', 1, 0, 'M', '0', '0', '', 'money',
'admin', sysdate(), '', null, '项目、经费和跨项目使用管理'
where not exists (select 1 from sys_menu where menu_id = 2100);
insert into sys_menu values
(2101, '项目与经费', 2100, 1, 'info', 'project/info/index', '', '', 1, 0, 'C', '0', '0', 'project:project:list', 'tree', 'admin', sysdate(), '', null, '项目及经费预算管理'),
(2102, '项目查询', 2101, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:project:query', '#', 'admin', sysdate(), '', null, ''),
(2103, '项目新增', 2101, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:project:add', '#', 'admin', sysdate(), '', null, ''),
(2104, '项目修改', 2101, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:project:edit', '#', 'admin', sysdate(), '', null, ''),
(2105, '项目删除', 2101, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:project:remove', '#', 'admin', sysdate(), '', null, ''),
(2106, '项目成员维护', 2101, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:project:member', '#', 'admin', sysdate(), '', null, ''),
(2107, '项目经费查询', 2101, 6, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:budget:list', '#', 'admin', sysdate(), '', null, ''),
(2108, '项目经费维护', 2101, 7, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:budget:edit', '#', 'admin', sysdate(), '', null, ''),
(2109, '经费科目维护', 2101, 8, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:category:edit', '#', 'admin', sysdate(), '', null, ''),
(2110, '项目总览权限', 2101, 9, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:project:manage', '#', 'admin', sysdate(), '', null, ''),
(2111, '项目物资使用', 2101, 10, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:inventory:use', '#', 'admin', sysdate(), '', null, ''),
(2112, '跨项目使用', 2101, 11, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:inventory:cross-use', '#', 'admin', sysdate(), '', null, ''),
(2113, 'AI助手使用', 2101, 12, '#', '', '', '', 1, 0, 'F', '1', '0', 'ai:assistant:use', '#', 'admin', sysdate(), '', null, '所有正常角色默认拥有,仅控制入口,具体动作仍校验业务权限')
on duplicate key update menu_id = values(menu_id);
-- 所有现有正常角色均可进入 AI 助手;业务查询和写入仍由原仓库权限控制。
insert ignore into sys_role_menu(role_id, menu_id)
select role_id, 2113 from sys_role where status = '0';
-- 延续现有业务角色能力:采购和库存角色可查看自己参与的项目及经费;原有出库角色获得项目物资使用权限。
insert ignore into sys_role_menu(role_id, menu_id)
select distinct role_id, target_menu_id
from (
select rm.role_id, targets.target_menu_id
from sys_role_menu rm
join sys_menu source_menu on source_menu.menu_id = rm.menu_id
join (
select 2100 target_menu_id union all select 2101 union all select 2102 union all select 2107
) targets
where source_menu.perms in ('warehouse:purchase:add', 'warehouse:purchase:manage', 'warehouse:stock:list')
) role_project_menu;
insert ignore into sys_role_menu(role_id, menu_id)
select distinct rm.role_id, 2111
from sys_role_menu rm
join sys_menu m on m.menu_id = rm.menu_id
where m.perms in ('warehouse:stock:out', 'warehouse:out:add');