Compare commits

...

15 Commits

@ -0,0 +1,77 @@
-- BIT 样品仓菜单补充 v2
-- 说明:
-- 1. 2026-06-22-bit-sample-wms.sql 已作为种子执行后,不再修改原文件。
-- 2. 本文件仅补充 BIT 样品仓复用的产品物料分类入口,方便 BIT 仓库管理员维护产品/物料/备件分类。
-- 3. 贴码上架只在 PDA 端操作Web 不新增贴码上架页面。
SET @menu_seed := GREATEST((SELECT IFNULL(MAX(id), 0) FROM system_menu), 900000);
SET @erp_menu_id := (
SELECT id FROM system_menu
WHERE path = '/erp' AND parent_id = 0 AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @bit_wms_id := (
SELECT id FROM system_menu
WHERE parent_id = @erp_menu_id AND path = 'bitwms' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @bit_product_category_menu_id := (
SELECT id FROM system_menu
WHERE parent_id = @bit_wms_id AND path = 'product-category' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '产品物料分类', 'erp:product-category:query', 2, 25, @bit_wms_id, 'product-category', 'fa:certificate', 'erp/product/category/index', 'ErpBitWmsProductCategory', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_wms_id IS NOT NULL AND @bit_product_category_menu_id IS NULL;
SET @bit_product_category_menu_id := (
SELECT id FROM system_menu
WHERE parent_id = @bit_wms_id AND path = 'product-category' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '分类查询', 'erp:product-category:query', 3, 10, @bit_product_category_menu_id, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_product_category_menu_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM system_menu
WHERE parent_id = @bit_product_category_menu_id AND permission = 'erp:product-category:query' AND deleted = b'0'
);
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '分类创建', 'erp:product-category:create', 3, 20, @bit_product_category_menu_id, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_product_category_menu_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM system_menu
WHERE parent_id = @bit_product_category_menu_id AND permission = 'erp:product-category:create' AND deleted = b'0'
);
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '分类更新', 'erp:product-category:update', 3, 30, @bit_product_category_menu_id, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_product_category_menu_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM system_menu
WHERE parent_id = @bit_product_category_menu_id AND permission = 'erp:product-category:update' AND deleted = b'0'
);
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '分类删除', 'erp:product-category:delete', 3, 40, @bit_product_category_menu_id, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_product_category_menu_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM system_menu
WHERE parent_id = @bit_product_category_menu_id AND permission = 'erp:product-category:delete' AND deleted = b'0'
);

@ -0,0 +1,436 @@
-- BIT 样品仓储 v1.0
-- Run against besure_bit_dev after review.
-- 样品基础字段
SET @schema_name := DATABASE();
SET @column_exists := (
SELECT COUNT(1)
FROM information_schema.columns
WHERE table_schema = @schema_name AND table_name = 'erp_product' AND column_name = 'is_sample'
);
SET @sql := IF(@column_exists = 0,
'ALTER TABLE erp_product ADD COLUMN is_sample bit(1) NOT NULL DEFAULT b''0'' COMMENT ''是否样品'' AFTER bar_code',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @column_exists := (
SELECT COUNT(1)
FROM information_schema.columns
WHERE table_schema = @schema_name AND table_name = 'erp_product' AND column_name = 'sample_category'
);
SET @sql := IF(@column_exists = 0,
'ALTER TABLE erp_product ADD COLUMN sample_category varchar(64) NULL COMMENT ''样品分类'' AFTER is_sample',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @column_exists := (
SELECT COUNT(1)
FROM information_schema.columns
WHERE table_schema = @schema_name AND table_name = 'erp_product' AND column_name = 'biz_unit'
);
SET @sql := IF(@column_exists = 0,
'ALTER TABLE erp_product ADD COLUMN biz_unit varchar(64) NULL COMMENT ''所属事业部'' AFTER sample_category',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @column_exists := (
SELECT COUNT(1)
FROM information_schema.columns
WHERE table_schema = @schema_name AND table_name = 'erp_product' AND column_name = 'customer_id'
);
SET @sql := IF(@column_exists = 0,
'ALTER TABLE erp_product ADD COLUMN customer_id bigint NULL COMMENT ''客户ID'' AFTER biz_unit',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @column_exists := (
SELECT COUNT(1)
FROM information_schema.columns
WHERE table_schema = @schema_name AND table_name = 'erp_product' AND column_name = 'customer_name'
);
SET @sql := IF(@column_exists = 0,
'ALTER TABLE erp_product ADD COLUMN customer_name varchar(128) NULL COMMENT ''客户名称快照'' AFTER customer_id',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @index_exists := (
SELECT COUNT(1)
FROM information_schema.statistics
WHERE table_schema = @schema_name AND table_name = 'erp_product' AND index_name = 'idx_erp_product_sample'
);
SET @sql := IF(@index_exists = 0,
'CREATE INDEX idx_erp_product_sample ON erp_product (is_sample, biz_unit, category_id, sub_category_id)',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @index_exists := (
SELECT COUNT(1)
FROM information_schema.statistics
WHERE table_schema = @schema_name AND table_name = 'erp_product' AND index_name = 'idx_erp_product_customer'
);
SET @sql := IF(@index_exists = 0,
'CREATE INDEX idx_erp_product_customer ON erp_product (customer_id)',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 字典:样品分类、出库原因
INSERT INTO system_dict_type
(name, type, status, remark, creator, create_time, updater, update_time, deleted)
SELECT 'BIT样品分类', 'bit_sample_category', 0, 'BIT样品仓样品分类', '1', NOW(), '1', NOW(), b'0'
WHERE NOT EXISTS (SELECT 1 FROM system_dict_type WHERE type = 'bit_sample_category');
INSERT INTO system_dict_type
(name, type, status, remark, creator, create_time, updater, update_time, deleted)
SELECT 'BIT样品出库原因', 'bit_sample_out_reason', 0, 'BIT样品仓出库原因', '1', NOW(), '1', NOW(), b'0'
WHERE NOT EXISTS (SELECT 1 FROM system_dict_type WHERE type = 'bit_sample_out_reason');
INSERT INTO system_dict_data
(sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
SELECT 10, '客户样品', 'CUSTOMER_SAMPLE', 'bit_sample_category', 0, 'primary', '', '', '1', NOW(), '1', NOW(), b'0'
WHERE NOT EXISTS (SELECT 1 FROM system_dict_data WHERE dict_type = 'bit_sample_category' AND value = 'CUSTOMER_SAMPLE');
INSERT INTO system_dict_data
(sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
SELECT 20, '内部样品', 'INTERNAL_SAMPLE', 'bit_sample_category', 0, 'success', '', '', '1', NOW(), '1', NOW(), b'0'
WHERE NOT EXISTS (SELECT 1 FROM system_dict_data WHERE dict_type = 'bit_sample_category' AND value = 'INTERNAL_SAMPLE');
INSERT INTO system_dict_data
(sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
SELECT 30, '测试样品', 'TEST_SAMPLE', 'bit_sample_category', 0, 'warning', '', '', '1', NOW(), '1', NOW(), b'0'
WHERE NOT EXISTS (SELECT 1 FROM system_dict_data WHERE dict_type = 'bit_sample_category' AND value = 'TEST_SAMPLE');
INSERT INTO system_dict_data
(sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
SELECT 10, '测试', 'TEST', 'bit_sample_out_reason', 0, 'warning', '', '', '1', NOW(), '1', NOW(), b'0'
WHERE NOT EXISTS (SELECT 1 FROM system_dict_data WHERE dict_type = 'bit_sample_out_reason' AND value = 'TEST');
INSERT INTO system_dict_data
(sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
SELECT 20, '寄样', 'SEND_SAMPLE', 'bit_sample_out_reason', 0, 'primary', '', '', '1', NOW(), '1', NOW(), b'0'
WHERE NOT EXISTS (SELECT 1 FROM system_dict_data WHERE dict_type = 'bit_sample_out_reason' AND value = 'SEND_SAMPLE');
INSERT INTO system_dict_data
(sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
SELECT 30, '领用', 'USE', 'bit_sample_out_reason', 0, 'success', '', '', '1', NOW(), '1', NOW(), b'0'
WHERE NOT EXISTS (SELECT 1 FROM system_dict_data WHERE dict_type = 'bit_sample_out_reason' AND value = 'USE');
INSERT INTO system_dict_data
(sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
SELECT 40, '报废', 'SCRAP', 'bit_sample_out_reason', 0, 'danger', '', '', '1', NOW(), '1', NOW(), b'0'
WHERE NOT EXISTS (SELECT 1 FROM system_dict_data WHERE dict_type = 'bit_sample_out_reason' AND value = 'SCRAP');
INSERT INTO system_dict_data
(sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
SELECT 50, '其他', 'OTHER', 'bit_sample_out_reason', 0, 'info', '', '', '1', NOW(), '1', NOW(), b'0'
WHERE NOT EXISTS (SELECT 1 FROM system_dict_data WHERE dict_type = 'bit_sample_out_reason' AND value = 'OTHER');
-- 编码规则:样品码、库位码,默认自动生成且后续可在系统中调整
INSERT INTO erp_autocode_rule
(rule_code, rule_name, rule_desc, max_length, is_padded, padded_char, padded_method, remark, is_enable, creator, create_time, updater, update_time, deleted)
SELECT 'BIT_SAMPLE_CODE', 'BIT样品码', 'BIT样品仓样品码默认规则', 18, 'N', '0', 'L', '', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE NOT EXISTS (SELECT 1 FROM erp_autocode_rule WHERE rule_code = 'BIT_SAMPLE_CODE');
SET @bit_sample_rule_id := (SELECT id FROM erp_autocode_rule WHERE rule_code = 'BIT_SAMPLE_CODE' ORDER BY id LIMIT 1);
INSERT INTO erp_autocode_part
(rule_id, part_index, part_type, part_code, part_name, part_length, datetime_format, input_character, fix_character, seria_start_no, seria_step, seria_now_no, cycle_flag, cycle_method, remark, is_enable, creator, create_time, updater, update_time, deleted)
SELECT @bit_sample_rule_id, 1, 'FIXCHAR', 'BIT_SAMPLE_PREFIX', '固定前缀', 2, '', '', 'SP', NULL, NULL, NULL, 'N', 'OTHER', '', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_sample_rule_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM erp_autocode_part WHERE rule_id = @bit_sample_rule_id AND part_code = 'BIT_SAMPLE_PREFIX');
INSERT INTO erp_autocode_part
(rule_id, part_index, part_type, part_code, part_name, part_length, datetime_format, input_character, fix_character, seria_start_no, seria_step, seria_now_no, cycle_flag, cycle_method, remark, is_enable, creator, create_time, updater, update_time, deleted)
SELECT @bit_sample_rule_id, 2, 'NOWDATE', 'BIT_SAMPLE_DATE', '年月日', 8, 'yyyyMMdd', '', '', NULL, NULL, NULL, 'N', 'OTHER', '', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_sample_rule_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM erp_autocode_part WHERE rule_id = @bit_sample_rule_id AND part_code = 'BIT_SAMPLE_DATE');
INSERT INTO erp_autocode_part
(rule_id, part_index, part_type, part_code, part_name, part_length, datetime_format, input_character, fix_character, seria_start_no, seria_step, seria_now_no, cycle_flag, cycle_method, remark, is_enable, creator, create_time, updater, update_time, deleted)
SELECT @bit_sample_rule_id, 3, 'SERIALNO', 'BIT_SAMPLE_SERIAL', '流水号', 4, '', '', '', 1, 1, 0, 'Y', 'DAY', '', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_sample_rule_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM erp_autocode_part WHERE rule_id = @bit_sample_rule_id AND part_code = 'BIT_SAMPLE_SERIAL');
INSERT INTO erp_autocode_record
(rule_id, gen_date, gen_index, last_result, last_serial_no, last_input_char, remark, is_enable, creator, create_time, updater, update_time, deleted)
SELECT @bit_sample_rule_id, DATE_FORMAT(NOW(), '%Y%m%d'), 0, '', 0, '', '', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_sample_rule_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM erp_autocode_record WHERE rule_id = @bit_sample_rule_id);
INSERT INTO erp_autocode_rule
(rule_code, rule_name, rule_desc, max_length, is_padded, padded_char, padded_method, remark, is_enable, creator, create_time, updater, update_time, deleted)
SELECT 'BIT_WAREHOUSE_LOCATION_CODE', 'BIT库位码', 'BIT样品仓库位码默认规则', 18, 'N', '0', 'L', '', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE NOT EXISTS (SELECT 1 FROM erp_autocode_rule WHERE rule_code = 'BIT_WAREHOUSE_LOCATION_CODE');
SET @bit_location_rule_id := (SELECT id FROM erp_autocode_rule WHERE rule_code = 'BIT_WAREHOUSE_LOCATION_CODE' ORDER BY id LIMIT 1);
INSERT INTO erp_autocode_part
(rule_id, part_index, part_type, part_code, part_name, part_length, datetime_format, input_character, fix_character, seria_start_no, seria_step, seria_now_no, cycle_flag, cycle_method, remark, is_enable, creator, create_time, updater, update_time, deleted)
SELECT @bit_location_rule_id, 1, 'FIXCHAR', 'BIT_LOCATION_PREFIX', '固定前缀', 3, '', '', 'LOC', NULL, NULL, NULL, 'N', 'OTHER', '', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_location_rule_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM erp_autocode_part WHERE rule_id = @bit_location_rule_id AND part_code = 'BIT_LOCATION_PREFIX');
INSERT INTO erp_autocode_part
(rule_id, part_index, part_type, part_code, part_name, part_length, datetime_format, input_character, fix_character, seria_start_no, seria_step, seria_now_no, cycle_flag, cycle_method, remark, is_enable, creator, create_time, updater, update_time, deleted)
SELECT @bit_location_rule_id, 2, 'SERIALNO', 'BIT_LOCATION_SERIAL', '流水号', 5, '', '', '', 1, 1, 0, 'N', 'OTHER', '', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_location_rule_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM erp_autocode_part WHERE rule_id = @bit_location_rule_id AND part_code = 'BIT_LOCATION_SERIAL');
INSERT INTO erp_autocode_record
(rule_id, gen_date, gen_index, last_result, last_serial_no, last_input_char, remark, is_enable, creator, create_time, updater, update_time, deleted)
SELECT @bit_location_rule_id, '', 0, '', 0, '', '', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_location_rule_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM erp_autocode_record WHERE rule_id = @bit_location_rule_id);
-- 菜单和按钮权限:后续 Web 页面与后端接口按这些权限标识实现
SET @menu_seed := GREATEST((SELECT IFNULL(MAX(id), 0) FROM system_menu), 900000);
SET @erp_menu_id := (SELECT id FROM system_menu WHERE path = '/erp' AND parent_id = 0 AND deleted = b'0' ORDER BY id LIMIT 1);
SET @bit_wms_id := (
SELECT id FROM system_menu
WHERE parent_id = @erp_menu_id AND path = 'bitwms' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, 'BIT样品仓', '', 1, 90, @erp_menu_id, 'bitwms', 'ep:box', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @erp_menu_id IS NOT NULL AND @bit_wms_id IS NULL;
SET @bit_wms_id := (
SELECT id FROM system_menu
WHERE parent_id = @erp_menu_id AND path = 'bitwms' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @bit_sample_menu_id := (
SELECT id FROM system_menu
WHERE parent_id = @bit_wms_id AND path = 'sample' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '样品信息', 'erp:bit-wms-sample:query', 2, 10, @bit_wms_id, 'sample', 'ep:goods', 'erp/bitwms/sample/index', 'ErpBitWmsSample', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_wms_id IS NOT NULL AND @bit_sample_menu_id IS NULL;
SET @bit_sample_menu_id := (
SELECT id FROM system_menu
WHERE parent_id = @bit_wms_id AND path = 'sample' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @bit_location_menu_id := (
SELECT id FROM system_menu
WHERE parent_id = @bit_wms_id AND path = 'location' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '库位信息', 'erp:bit-wms-location:query', 2, 20, @bit_wms_id, 'location', 'ep:coordinate', 'erp/bitwms/location/index', 'ErpBitWmsLocation', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_wms_id IS NOT NULL AND @bit_location_menu_id IS NULL;
SET @bit_location_menu_id := (
SELECT id FROM system_menu
WHERE parent_id = @bit_wms_id AND path = 'location' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @bit_in_menu_id := (
SELECT id FROM system_menu
WHERE parent_id = @bit_wms_id AND path = 'in' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '样品入库', 'erp:bit-wms-in:query', 2, 30, @bit_wms_id, 'in', 'ep:download', 'erp/bitwms/in/index', 'ErpBitWmsIn', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_wms_id IS NOT NULL AND @bit_in_menu_id IS NULL;
SET @bit_in_menu_id := (
SELECT id FROM system_menu
WHERE parent_id = @bit_wms_id AND path = 'in' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @bit_out_menu_id := (
SELECT id FROM system_menu
WHERE parent_id = @bit_wms_id AND path = 'out' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '样品出库', 'erp:bit-wms-out:query', 2, 40, @bit_wms_id, 'out', 'ep:upload', 'erp/bitwms/out/index', 'ErpBitWmsOut', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_wms_id IS NOT NULL AND @bit_out_menu_id IS NULL;
SET @bit_out_menu_id := (
SELECT id FROM system_menu
WHERE parent_id = @bit_wms_id AND path = 'out' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @bit_move_menu_id := (
SELECT id FROM system_menu
WHERE parent_id = @bit_wms_id AND path = 'move' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '样品移库', 'erp:bit-wms-move:query', 2, 50, @bit_wms_id, 'move', 'ep:sort', 'erp/bitwms/move/index', 'ErpBitWmsMove', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_wms_id IS NOT NULL AND @bit_move_menu_id IS NULL;
SET @bit_move_menu_id := (
SELECT id FROM system_menu
WHERE parent_id = @bit_wms_id AND path = 'move' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @bit_stock_menu_id := (
SELECT id FROM system_menu
WHERE parent_id = @bit_wms_id AND path = 'stock' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '样品库存', 'erp:bit-wms-stock:query', 2, 60, @bit_wms_id, 'stock', 'ep:grid', 'erp/bitwms/stock/index', 'ErpBitWmsStock', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_wms_id IS NOT NULL AND @bit_stock_menu_id IS NULL;
SET @bit_stock_menu_id := (
SELECT id FROM system_menu
WHERE parent_id = @bit_wms_id AND path = 'stock' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @bit_record_menu_id := (
SELECT id FROM system_menu
WHERE parent_id = @bit_wms_id AND path = 'record' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '出入库明细', 'erp:bit-wms-record:query', 2, 70, @bit_wms_id, 'record', 'ep:document', 'erp/bitwms/record/index', 'ErpBitWmsRecord', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_wms_id IS NOT NULL AND @bit_record_menu_id IS NULL;
SET @bit_record_menu_id := (
SELECT id FROM system_menu
WHERE parent_id = @bit_wms_id AND path = 'record' AND deleted = b'0'
ORDER BY id LIMIT 1
);
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '样品创建', 'erp:bit-wms-sample:create', 3, 10, @bit_sample_menu_id, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_sample_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM system_menu WHERE permission = 'erp:bit-wms-sample:create' AND deleted = b'0');
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '样品更新', 'erp:bit-wms-sample:update', 3, 20, @bit_sample_menu_id, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_sample_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM system_menu WHERE permission = 'erp:bit-wms-sample:update' AND deleted = b'0');
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '样品打印', 'erp:bit-wms-sample:print', 3, 30, @bit_sample_menu_id, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_sample_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM system_menu WHERE permission = 'erp:bit-wms-sample:print' AND deleted = b'0');
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '库位创建', 'erp:bit-wms-location:create', 3, 10, @bit_location_menu_id, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_location_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM system_menu WHERE permission = 'erp:bit-wms-location:create' AND deleted = b'0');
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '库位更新', 'erp:bit-wms-location:update', 3, 20, @bit_location_menu_id, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_location_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM system_menu WHERE permission = 'erp:bit-wms-location:update' AND deleted = b'0');
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '库位打印', 'erp:bit-wms-location:print', 3, 30, @bit_location_menu_id, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_location_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM system_menu WHERE permission = 'erp:bit-wms-location:print' AND deleted = b'0');
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '执行入库', 'erp:bit-wms-in:create', 3, 10, @bit_in_menu_id, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_in_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM system_menu WHERE permission = 'erp:bit-wms-in:create' AND deleted = b'0');
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '执行出库', 'erp:bit-wms-out:create', 3, 10, @bit_out_menu_id, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_out_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM system_menu WHERE permission = 'erp:bit-wms-out:create' AND deleted = b'0');
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '执行移库', 'erp:bit-wms-move:create', 3, 10, @bit_move_menu_id, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_move_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM system_menu WHERE permission = 'erp:bit-wms-move:create' AND deleted = b'0');
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '库存导出', 'erp:bit-wms-stock:export', 3, 20, @bit_stock_menu_id, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_stock_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM system_menu WHERE permission = 'erp:bit-wms-stock:export' AND deleted = b'0');
SET @menu_seed := @menu_seed + 1;
INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '明细导出', 'erp:bit-wms-record:export', 3, 20, @bit_record_menu_id, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_record_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM system_menu WHERE permission = 'erp:bit-wms-record:export' AND deleted = b'0');
-- 5. 调拨项库区字段:支持 BIT 样品仓同仓不同库区移库
SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'erp_stock_move_item' AND column_name = 'from_area_id') = 0,
'ALTER TABLE erp_stock_move_item ADD COLUMN from_area_id bigint NULL COMMENT ''调出库区编号'' AFTER from_warehouse_id',
'SELECT 1');
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'erp_stock_move_item' AND column_name = 'from_area_name') = 0,
'ALTER TABLE erp_stock_move_item ADD COLUMN from_area_name varchar(64) NULL COMMENT ''调出库区名称'' AFTER from_area_id',
'SELECT 1');
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'erp_stock_move_item' AND column_name = 'to_area_id') = 0,
'ALTER TABLE erp_stock_move_item ADD COLUMN to_area_id bigint NULL COMMENT ''调入库区编号'' AFTER to_warehouse_id',
'SELECT 1');
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'erp_stock_move_item' AND column_name = 'to_area_name') = 0,
'ALTER TABLE erp_stock_move_item ADD COLUMN to_area_name varchar(64) NULL COMMENT ''调入库区名称'' AFTER to_area_id',
'SELECT 1');
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

@ -0,0 +1,29 @@
-- BIT 样品仓打印模板类型补充 v3
-- 说明:
-- 1. 2026-06-22-bit-sample-wms.sql 已作为种子执行后,不再修改原文件。
-- 2. 保留现有 print_template_type: 6 = 产线。
-- 3. 本文件仅补充 BIT 样品标签、BIT 库位标签两个打印模板类型。
INSERT INTO system_dict_data
(sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
SELECT 7, 'BIT样品标签', '7', 'print_template_type', 0, 'primary', '', 'qrcodeUrl,name,code,sampleCategory,customerName', '1', NOW(), '1', NOW(), b'0'
WHERE NOT EXISTS (
SELECT 1 FROM system_dict_data
WHERE dict_type = 'print_template_type' AND value = '7'
);
UPDATE system_dict_data
SET remark = 'qrcodeUrl,name,code,sampleCategory,customerName',
updater = '1',
update_time = NOW()
WHERE dict_type = 'print_template_type'
AND value = '7'
AND deleted = b'0';
INSERT INTO system_dict_data
(sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
SELECT 8, 'BIT库位标签', '8', 'print_template_type', 0, 'success', '', 'qrcodeUrl,name,code', '1', NOW(), '1', NOW(), b'0'
WHERE NOT EXISTS (
SELECT 1 FROM system_dict_data
WHERE dict_type = 'print_template_type' AND value = '8'
);

@ -0,0 +1,21 @@
-- BIT 样品仓静默打印业务场景补充 v4
-- 说明:
-- 1. 2026-06-22-bit-sample-wms.sql 已作为种子执行后,不再修改原文件。
-- 2. 本文件补充打印机配置管理使用的 mes_business_scenario 字典数据。
-- 3. print_template_type 仍由 v3 文件维护7 = BIT样品标签8 = BIT库位标签。
INSERT INTO system_dict_data
(sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
SELECT 90, 'BIT样品标签打印', 'BIT_SAMPLE_LABEL_PRINT', 'mes_business_scenario', 0, 'primary', '', 'BIT样品仓样品标签静默打印', '1', NOW(), '1', NOW(), b'0'
WHERE NOT EXISTS (
SELECT 1 FROM system_dict_data
WHERE dict_type = 'mes_business_scenario' AND value = 'BIT_SAMPLE_LABEL_PRINT'
);
INSERT INTO system_dict_data
(sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
SELECT 91, 'BIT库位标签打印', 'BIT_LOCATION_LABEL_PRINT', 'mes_business_scenario', 0, 'success', '', 'BIT样品仓库位标签静默打印', '1', NOW(), '1', NOW(), b'0'
WHERE NOT EXISTS (
SELECT 1 FROM system_dict_data
WHERE dict_type = 'mes_business_scenario' AND value = 'BIT_LOCATION_LABEL_PRINT'
);

@ -0,0 +1,48 @@
-- BIT 样品仓库位库存台账
CREATE TABLE IF NOT EXISTS `erp_bit_wms_location_stock` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`product_id` bigint NOT NULL COMMENT '样品产品ID',
`warehouse_id` bigint NOT NULL COMMENT '仓库ID',
`area_id` bigint NULL COMMENT '库区ID',
`location_id` bigint NOT NULL COMMENT '库位ID',
`count` decimal(24, 6) NOT NULL DEFAULT 0.000000 COMMENT '当前库存数量',
`unit_id` bigint NULL COMMENT '单位ID',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_product_location` (`product_id`, `location_id`, `deleted`, `tenant_id`) USING BTREE,
KEY `idx_location` (`location_id`) USING BTREE,
KEY `idx_warehouse_area` (`warehouse_id`, `area_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='BIT 样品库位库存';
CREATE TABLE IF NOT EXISTS `erp_bit_wms_location_stock_record` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`product_id` bigint NOT NULL COMMENT '样品产品ID',
`warehouse_id` bigint NOT NULL COMMENT '仓库ID',
`area_id` bigint NULL COMMENT '库区ID',
`location_id` bigint NOT NULL COMMENT '库位ID',
`count` decimal(24, 6) NOT NULL COMMENT '变动数量',
`total_count` decimal(24, 6) NOT NULL COMMENT '变动后库位结存',
`unit_id` bigint NULL COMMENT '单位ID',
`biz_direction` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务方向',
`biz_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务类型',
`biz_id` bigint NULL COMMENT '业务单ID',
`biz_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '业务单号',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '备注',
`record_time` datetime NOT NULL COMMENT '操作时间',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_product_location` (`product_id`, `location_id`) USING BTREE,
KEY `idx_biz_no` (`biz_no`) USING BTREE,
KEY `idx_record_time` (`record_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='BIT 样品库位库存流水';

@ -70,6 +70,11 @@
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
</project>

@ -13,6 +13,7 @@ public enum QrcodeBizTypeEnum {
KEY_PART("KEY_PART", "关键件"),
MOLD("MOLD", "模具"),
PALLET("PALLET", "托盘"),
WAREHOUSE_LOCATION("WAREHOUSE_LOCATION", "库位"),
SPARE("SPARE", "备件");
private final String code;

@ -67,6 +67,8 @@ public interface QrcodeRecordService {
String selectQrcodeUrlByIdAndCode(String code, Long id, String code1);
String selectQrcodeUrlByIdAndCodeAndType(String bizType, Long bizId, String bizCode, CodeTypeEnum codeType);
Map<Long, String> selectQrcodeUrlMapByBizTypeAndIds(String bizType, Collection<Long> bizIds);
Map<String, Object> resolveScanBizId(String type, Long id, String code);

@ -177,6 +177,7 @@ public class QrcodeRecordServiceImpl implements QrcodeRecordService {
record.setBizType(bizType.getCode());
record.setBizId(bizId);
record.setBizCode(bizCode);
record.setCodeType(CodeTypeEnum.QR.getCode());
record.setQrScene(qrScene);
record.setQrContent(qrContent);
record.setFileName(fileName);
@ -336,6 +337,26 @@ public class QrcodeRecordServiceImpl implements QrcodeRecordService {
}
@Override
public String selectQrcodeUrlByIdAndCodeAndType(String bizType, Long bizId, String bizCode, CodeTypeEnum codeType) {
if (StrUtil.isBlank(bizType) || bizId == null || StrUtil.isBlank(bizCode) || codeType == null) {
return null;
}
QrcodeRecordDO record = qrcodeRecordMapper.selectOne(Wrappers.<QrcodeRecordDO>lambdaQuery()
.eq(QrcodeRecordDO::getBizType, bizType)
.eq(QrcodeRecordDO::getBizId, bizId)
.eq(QrcodeRecordDO::getBizCode, bizCode)
.eq(QrcodeRecordDO::getCodeType, codeType.getCode())
.eq(QrcodeRecordDO::getStatus, 1)
.last("limit 1"));
if (record == null || StrUtil.isBlank(record.getQrcodeFileUrl())) {
return null;
}
return record.getQrcodeFileUrl();
}
@Override
public Map<Long, String> selectQrcodeUrlMapByBizTypeAndIds(String bizType, Collection<Long> bizIds) {
if (StrUtil.isBlank(bizType) || bizIds == null || bizIds.isEmpty()) {
@ -529,7 +550,7 @@ public class QrcodeRecordServiceImpl implements QrcodeRecordService {
.eq(QrcodeRecordDO::getBizType, bizType.getCode())
.eq(QrcodeRecordDO::getBizId, bizId)
.eq(QrcodeRecordDO::getBizCode, bizCode)
// .eq(QrcodeRecordDO::getCodeType, normalizedCodeType)
.eq(QrcodeRecordDO::getCodeType, normalizedCodeType)
.eq(QrcodeRecordDO::getQrScene, qrScene)
.last("limit 1")
);

@ -0,0 +1,85 @@
package cn.iocoder.yudao.module.common.service.qrcordrecord;
import cn.iocoder.yudao.module.common.dal.dataobject.qrcoderecord.QrcodeRecordDO;
import cn.iocoder.yudao.module.common.dal.mysql.qrcoderecord.QrcodeRecordMapper;
import cn.iocoder.yudao.module.common.enums.CodeTypeEnum;
import cn.iocoder.yudao.module.common.enums.QrcodeBizTypeEnum;
import cn.iocoder.yudao.module.infra.api.file.FileApi;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class QrcodeRecordServiceImplTest {
private QrcodeRecordServiceImpl qrcodeRecordService;
@Mock
private FileApi fileApi;
@Mock
private QrcodeRecordMapper qrcodeRecordMapper;
@BeforeEach
void setUp() {
qrcodeRecordService = new QrcodeRecordServiceImpl(Collections.emptyList());
ReflectionTestUtils.setField(qrcodeRecordService, "fileApi", fileApi);
ReflectionTestUtils.setField(qrcodeRecordService, "qrcodeRecordMapper", qrcodeRecordMapper);
ReflectionTestUtils.setField(qrcodeRecordService, "width", 120);
ReflectionTestUtils.setField(qrcodeRecordService, "height", 120);
ReflectionTestUtils.setField(qrcodeRecordService, "defaultBucket", "common-qrcode");
}
@Test
void generateOrRefresh_whenQr_storesQrCodeType() throws Exception {
Map<String, String> file = new HashMap<>();
file.put("fileUrl", "https://file/qr.png");
when(fileApi.createFile(any(), any(), any())).thenReturn(file);
qrcodeRecordService.generateOrRefresh(QrcodeBizTypeEnum.PRODUCT, 10L, "SP001", "DETAIL");
ArgumentCaptor<QrcodeRecordDO> recordCaptor = ArgumentCaptor.forClass(QrcodeRecordDO.class);
verify(qrcodeRecordMapper).insert(recordCaptor.capture());
assertEquals("QR", recordCaptor.getValue().getCodeType());
}
@Test
void selectQrcodeUrlByIdAndCodeAndType_filtersByCodeType() {
QrcodeRecordDO qrRecord = new QrcodeRecordDO();
qrRecord.setQrcodeFileUrl("https://file/qr.png");
when(qrcodeRecordMapper.selectOne(any())).thenReturn(qrRecord);
String url = qrcodeRecordService.selectQrcodeUrlByIdAndCodeAndType(
QrcodeBizTypeEnum.PRODUCT.getCode(), 10L, "SP001", CodeTypeEnum.QR);
assertEquals("https://file/qr.png", url);
verify(qrcodeRecordMapper).selectOne(any());
}
@Test
void regenerateByCodeType_filtersExistingRecordByRequestedCodeType() throws Exception {
Map<String, String> file = new HashMap<>();
file.put("fileUrl", "https://file/qr.png");
when(fileApi.createFile(any(), any(), any())).thenReturn(file);
qrcodeRecordService.regenerateByCodeType(QrcodeBizTypeEnum.PRODUCT, 10L, "SP001", "DETAIL", "QR");
verify(qrcodeRecordMapper).selectOne(any());
ArgumentCaptor<QrcodeRecordDO> recordCaptor = ArgumentCaptor.forClass(QrcodeRecordDO.class);
verify(qrcodeRecordMapper).insert(recordCaptor.capture());
assertEquals("QR", recordCaptor.getValue().getCodeType());
}
}

@ -0,0 +1,106 @@
package cn.iocoder.yudao.module.erp.controller.admin.bitwms;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsInboundReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLabelRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRecordPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRecordRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsMoveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsOutboundReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsScanRespVO;
import cn.iocoder.yudao.module.erp.service.bitwms.BitWmsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.math.BigDecimal;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - BIT 样品仓")
@RestController
@RequestMapping("/erp/bit-wms")
@Validated
public class BitWmsController {
@Resource
private BitWmsService bitWmsService;
@PostMapping("/inbound")
@Operation(summary = "BIT 样品入库")
@PreAuthorize("@ss.hasPermission('erp:bit-wms-in:create')")
public CommonResult<Long> inbound(@Valid @RequestBody BitWmsInboundReqVO reqVO) {
return success(bitWmsService.inbound(reqVO));
}
@PostMapping("/outbound")
@Operation(summary = "BIT 样品出库")
@PreAuthorize("@ss.hasPermission('erp:bit-wms-out:create')")
public CommonResult<Long> outbound(@Valid @RequestBody BitWmsOutboundReqVO reqVO) {
return success(bitWmsService.outbound(reqVO));
}
@PostMapping("/move")
@Operation(summary = "BIT 样品移库")
@PreAuthorize("@ss.hasPermission('erp:bit-wms-move:create')")
public CommonResult<Long> move(@Valid @RequestBody BitWmsMoveReqVO reqVO) {
return success(bitWmsService.move(reqVO));
}
@GetMapping("/scan")
@Operation(summary = "BIT 样品仓扫码")
@PreAuthorize("@ss.hasPermission('erp:bit-wms-stock:query')")
public CommonResult<BitWmsScanRespVO> scan(@RequestParam("code") String code) {
return success(bitWmsService.scan(code));
}
@GetMapping("/location-stock/page")
@Operation(summary = "获得 BIT 样品库位库存分页")
@PreAuthorize("@ss.hasPermission('erp:bit-wms-stock:query')")
public CommonResult<PageResult<BitWmsLocationStockRespVO>> getLocationStockPage(
@Valid BitWmsLocationStockPageReqVO pageReqVO) {
return success(bitWmsService.getLocationStockPage(pageReqVO));
}
@GetMapping("/location-stock/count")
@Operation(summary = "获得 BIT 样品当前库位库存")
@PreAuthorize("@ss.hasPermission('erp:bit-wms-stock:query')")
public CommonResult<BigDecimal> getLocationStockCount(@RequestParam("productId") Long productId,
@RequestParam("locationId") Long locationId) {
return success(bitWmsService.getLocationStockCount(productId, locationId));
}
@GetMapping("/location-stock-record/page")
@Operation(summary = "获得 BIT 样品库位流水分页")
@PreAuthorize("@ss.hasPermission('erp:bit-wms-record:query')")
public CommonResult<PageResult<BitWmsLocationStockRecordRespVO>> getLocationStockRecordPage(
@Valid BitWmsLocationStockRecordPageReqVO pageReqVO) {
return success(bitWmsService.getLocationStockRecordPage(pageReqVO));
}
@GetMapping("/sample-label")
@Operation(summary = "获得样品标签打印数据")
@PreAuthorize("@ss.hasPermission('erp:bit-wms-sample:print')")
public CommonResult<BitWmsLabelRespVO> getSampleLabel(@RequestParam("productId") Long productId) {
return success(bitWmsService.getSampleLabel(productId));
}
@GetMapping("/location-label")
@Operation(summary = "获得库位标签打印数据")
@PreAuthorize("@ss.hasPermission('erp:bit-wms-location:print')")
public CommonResult<BitWmsLabelRespVO> getLocationLabel(@RequestParam("locationId") Long locationId) {
return success(bitWmsService.getLocationLabel(locationId));
}
}

@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Schema(description = "管理后台 - BIT 样品仓入库 Request VO")
@Data
public class BitWmsInboundReqVO {
@Schema(description = "样品产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "样品产品ID不能为空")
private Long productId;
@Schema(description = "仓库ID", example = "20")
private Long warehouseId;
@Schema(description = "库区ID", example = "30")
private Long areaId;
@Schema(description = "库位ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "40")
@NotNull(message = "库位不能为空")
private Long locationId;
@Schema(description = "入库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@NotNull(message = "入库数量不能为空")
@DecimalMin(value = "0.000001", message = "入库数量必须大于 0")
private BigDecimal count;
@Schema(description = "单位ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
@NotNull(message = "单位不能为空")
private Long unitId;
@Schema(description = "供应商ID", example = "1001")
private Long supplierId;
@Schema(description = "备注", example = "first shelf")
private String remark;
}

@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Map;
@Schema(description = "管理后台 - BIT 样品仓标签打印 Response VO")
@Data
public class BitWmsLabelRespVO {
@Schema(description = "标签对象类型SAMPLE 样品LOCATION 库位", example = "SAMPLE")
private String type;
@Schema(description = "业务ID", example = "10")
private Long id;
@Schema(description = "编码", example = "SP202606220001")
private String code;
@Schema(description = "显示名称", example = "样品 A")
private String name;
@Schema(description = "二维码地址")
private String qrcodeUrl;
@Schema(description = "打印模板 JSON")
private String templateJson;
@Schema(description = "打印数据")
private Map<String, Object> printData;
}

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - BIT 样品库位库存分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class BitWmsLocationStockPageReqVO extends PageParam {
@Schema(description = "样品产品ID", example = "10")
private Long productId;
@Schema(description = "仓库ID", example = "20")
private Long warehouseId;
@Schema(description = "库区ID", example = "30")
private Long areaId;
@Schema(description = "库位ID", example = "40")
private Long locationId;
}

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - BIT 样品库位流水分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class BitWmsLocationStockRecordPageReqVO extends PageParam {
@Schema(description = "样品产品ID", example = "10")
private Long productId;
@Schema(description = "仓库ID", example = "20")
private Long warehouseId;
@Schema(description = "库区ID", example = "30")
private Long areaId;
@Schema(description = "库位ID", example = "40")
private Long locationId;
@Schema(description = "业务单号", example = "QTRK20260623001")
private String bizNo;
}

@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - BIT 样品库位流水 Response VO")
@Data
public class BitWmsLocationStockRecordRespVO {
private Long id;
private Long productId;
private String productName;
private String barCode;
private Long warehouseId;
private String warehouseName;
private Long areaId;
private String areaName;
private Long locationId;
private String locationCode;
private String locationName;
private BigDecimal count;
private BigDecimal totalCount;
private Long unitId;
private String unitName;
private String bizDirection;
private String bizType;
private Long bizId;
private String bizNo;
private String remark;
private String creator;
private String creatorName;
private LocalDateTime recordTime;
private LocalDateTime createTime;
}

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - BIT 样品库位库存 Response VO")
@Data
public class BitWmsLocationStockRespVO {
private Long id;
private Long productId;
private String productName;
private String barCode;
private String sampleCategory;
private String customerName;
private Long warehouseId;
private String warehouseName;
private Long areaId;
private String areaName;
private Long locationId;
private String locationCode;
private String locationName;
private BigDecimal count;
private Long unitId;
private String unitName;
}

@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Schema(description = "管理后台 - BIT 样品仓移库 Request VO")
@Data
public class BitWmsMoveReqVO {
@Schema(description = "样品产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "样品产品ID不能为空")
private Long productId;
@Schema(description = "调出库位ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "40")
@NotNull(message = "调出库位不能为空")
private Long fromLocationId;
@Schema(description = "调入库位ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "41")
@NotNull(message = "调入库位不能为空")
private Long toLocationId;
@Schema(description = "调出仓库ID", example = "20")
private Long fromWarehouseId;
@Schema(description = "调出库区ID", example = "30")
private Long fromAreaId;
@Schema(description = "调入仓库ID", example = "20")
private Long toWarehouseId;
@Schema(description = "调入库区ID", example = "31")
private Long toAreaId;
@Schema(description = "移库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "移库数量不能为空")
@DecimalMin(value = "0.000001", message = "移库数量必须大于 0")
private BigDecimal count;
@Schema(description = "备注", example = "上架调整")
private String remark;
}

@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Schema(description = "管理后台 - BIT 样品仓出库 Request VO")
@Data
public class BitWmsOutboundReqVO {
@Schema(description = "样品产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "样品产品ID不能为空")
private Long productId;
@Schema(description = "仓库ID", example = "20")
private Long warehouseId;
@Schema(description = "库区ID", example = "30")
private Long areaId;
@Schema(description = "库位ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "40")
@NotNull(message = "库位不能为空")
private Long locationId;
@Schema(description = "出库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@NotNull(message = "出库数量不能为空")
@DecimalMin(value = "0.000001", message = "出库数量必须大于 0")
private BigDecimal count;
@Schema(description = "出库原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "TEST")
@NotBlank(message = "出库原因不能为空")
private String outReason;
@Schema(description = "备注", example = "测试出库")
private String remark;
}

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - BIT 样品仓扫码 Response VO")
@Data
public class BitWmsScanRespVO {
@Schema(description = "扫码对象类型SAMPLE 样品LOCATION 库位", example = "SAMPLE")
private String type;
@Schema(description = "业务ID", example = "10")
private Long id;
@Schema(description = "编码", example = "SP202606220001")
private String code;
@Schema(description = "名称", example = "样品 A")
private String name;
@Schema(description = "单位ID仅样品返回", example = "5")
private Long unitId;
@Schema(description = "单位名称,仅样品返回", example = "个")
private String unitName;
}

@ -116,6 +116,28 @@ public class ErpProductController {
public CommonResult<PageResult<ErpProductRespVO>> getProductPage(@Valid ErpProductPageReqVO pageReqVO) {
return success(productService.getProductVOPage(pageReqVO));
}
@GetMapping("/sample-page")
@Operation(summary = "获得 BIT 样品分页")
@PreAuthorize("@ss.hasPermission('erp:bit-wms-sample:query')")
public CommonResult<PageResult<ErpProductRespVO>> getSamplePage(@Valid ErpProductPageReqVO pageReqVO) {
pageReqVO.setIsSample(true);
pageReqVO.setBizUnit("BIT");
return success(productService.getProductVOPage(pageReqVO));
}
@GetMapping("/sample-simple-list")
@Operation(summary = "获得 BIT 样品精简列表")
@PreAuthorize("@ss.hasPermission('erp:bit-wms-sample:query')")
public CommonResult<List<ErpProductRespVO>> getSampleSimpleList(
@RequestParam(name = "categoryType", required = false) Integer categoryType) {
ErpProductListReqVO reqVO = new ErpProductListReqVO();
reqVO.setIsSample(true);
reqVO.setBizUnit("BIT");
reqVO.setCategoryType(categoryType);
return success(productService.getProductVOList(reqVO));
}
@GetMapping("/simple-list-all")
@Operation(summary = "获得所有精简列表", description = "只包含被开启的产品,主要用于前端的下拉选项")
public CommonResult<List<ErpProductRespVO>> getAllSimpleList(@RequestParam(name = "categoryId", required = false) Integer categoryId) {

@ -24,6 +24,21 @@ public class ErpProductListReqVO {
@Schema(description = "产品规格", example = "红色")
private String standard;
@Schema(description = "是否样品", example = "true")
private Boolean isSample;
@Schema(description = "样品分类", example = "CUSTOMER_SAMPLE")
private String sampleCategory;
@Schema(description = "所属事业部", example = "BIT")
private String bizUnit;
@Schema(description = "客户ID", example = "1001")
private Long customerId;
@Schema(description = "客户名称", example = "ACME HK")
private String customerName;
@Schema(description = "产品 id 集合")
private List<Long> ids;
}

@ -33,6 +33,21 @@ public class ErpProductPageReqVO extends PageParam {
@Schema(description = "产品编码", example = "P-001")
private String barCode;
@Schema(description = "是否样品", example = "true")
private Boolean isSample;
@Schema(description = "样品分类", example = "CUSTOMER_SAMPLE")
private String sampleCategory;
@Schema(description = "所属事业部", example = "BIT")
private String bizUnit;
@Schema(description = "客户ID", example = "1001")
private Long customerId;
@Schema(description = "客户名称", example = "ACME HK")
private String customerName;
@Schema(description = "产品规格", example = "红色")
private String standard;

@ -31,6 +31,21 @@ public class ErpProductRespVO extends ErpProductDO {
@ExcelProperty("产品条码")
private String barCode;
@Schema(description = "是否样品", example = "true")
private Boolean isSample;
@Schema(description = "样品分类", example = "CUSTOMER_SAMPLE")
private String sampleCategory;
@Schema(description = "所属事业部", example = "BIT")
private String bizUnit;
@Schema(description = "客户ID", example = "1001")
private Long customerId;
@Schema(description = "客户名称", example = "ACME HK")
private String customerName;
@Schema(description = "产品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11161")
private Long categoryId;

@ -28,6 +28,21 @@ public class ProductSaveReqVO {
// @NotEmpty(message = "产品条码不能为空")
private String barCode;
@Schema(description = "是否样品", example = "true")
private Boolean isSample;
@Schema(description = "样品分类", example = "CUSTOMER_SAMPLE")
private String sampleCategory;
@Schema(description = "所属事业部", example = "BIT")
private String bizUnit;
@Schema(description = "客户ID", example = "1001")
private Long customerId;
@Schema(description = "客户名称", example = "ACME HK")
private String customerName;
@Schema(description = "产品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11161")
@NotNull(message = "产品分类编号不能为空")
private Long categoryId;

@ -48,10 +48,22 @@ public class ErpStockMoveSaveReqVO {
@NotNull(message = "调出仓库编号不能为空")
private Long fromWarehouseId;
@Schema(description = "调出库区编号", example = "30")
private Long fromAreaId;
@Schema(description = "调出库区名称", example = "A1")
private String fromAreaName;
@Schema(description = "调入仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888")
@NotNull(message = "调入仓库编号不能为空")
private Long toWarehouseId;
@Schema(description = "调入库区编号", example = "31")
private Long toAreaId;
@Schema(description = "调入库区名称", example = "A2")
private String toAreaName;
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
@NotNull(message = "产品编号不能为空")
private Long productId;
@ -69,9 +81,10 @@ public class ErpStockMoveSaveReqVO {
@AssertTrue(message = "调出、调仓仓库不能相同")
@JsonIgnore
public boolean isWarehouseValid() {
return ObjectUtil.notEqual(fromWarehouseId, toWarehouseId);
return ObjectUtil.notEqual(fromWarehouseId, toWarehouseId)
|| ObjectUtil.notEqual(fromAreaId, toAreaId);
}
}
}
}

@ -31,6 +31,15 @@ public class ErpStockRecordPageReqVO extends PageParam {
@Schema(description = "业务类型", example = "10")
private Integer bizType;
@Schema(description = "是否样品", example = "true")
private Boolean isSample;
@Schema(description = "业务单位", example = "BIT")
private String bizUnit;
@Schema(description = "是否仅查询 BIT 样品 WMS 流水", example = "true")
private Boolean bitWmsOnly;
@Schema(description = "业务单号", example = "Z110")
private String bizNo;

@ -27,6 +27,12 @@ public class ErpStockPageReqVO extends PageParam {
@Schema(description = "产品分类类型", example = "1")
private Integer categoryType;
@Schema(description = "是否样品", example = "true")
private Boolean isSample;
@Schema(description = "业务单位", example = "BIT")
private String bizUnit;
@Schema(description = "id集合导出用")
private String ids;
}

@ -15,6 +15,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.common.enums.QrcodeBizTypeEnum;
import cn.iocoder.yudao.module.common.service.qrcordrecord.QrcodeRecordService;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@ -40,6 +42,9 @@ public class WarehouseLocationController {
@Resource
private WarehouseLocationService warehouseLocationService;
@Resource
private QrcodeRecordService qrcodeRecordService;
@PostMapping("/create")
@Operation(summary = "创建ERP 库位信息")
@PreAuthorize("@ss.hasPermission('erp:warehouse-location:create')")
@ -73,6 +78,19 @@ public class WarehouseLocationController {
return success(BeanUtils.toBean(warehouseLocation, WarehouseLocationRespVO.class));
}
@GetMapping("/print-data")
@Operation(summary = "获得库位打印数据")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('erp:bit-wms-location:print')")
public CommonResult<WarehouseLocationRespVO> getPrintData(@RequestParam("id") Long id) {
WarehouseLocationDO warehouseLocation = warehouseLocationService.getWarehouseLocation(id);
WarehouseLocationRespVO respVO = BeanUtils.toBean(warehouseLocation, WarehouseLocationRespVO.class);
String qrcodeUrl = qrcodeRecordService.selectQrcodeUrlByIdAndCode(
QrcodeBizTypeEnum.WAREHOUSE_LOCATION.getCode(), id, warehouseLocation.getCode());
respVO.setQrcodeUrl(qrcodeUrl);
return success(respVO);
}
@GetMapping("/page")
@Operation(summary = "获得ERP 库位信息分页")
@PreAuthorize("@ss.hasPermission('erp:warehouse-location:query')")

@ -65,8 +65,11 @@ public class WarehouseLocationRespVO {
@ExcelProperty("开启状态")
private Integer status;
@Schema(description = "二维码地址")
private String qrcodeUrl;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}
}

@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.erp.dal.dataobject.bitwms;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.math.BigDecimal;
/**
* BIT DO
*/
@TableName("erp_bit_wms_location_stock")
@KeySequence("erp_bit_wms_location_stock_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BitWmsLocationStockDO extends BaseDO {
@TableId
private Long id;
private Long productId;
private Long warehouseId;
private Long areaId;
private Long locationId;
private BigDecimal count;
private Long unitId;
}

@ -0,0 +1,59 @@
package cn.iocoder.yudao.module.erp.dal.dataobject.bitwms;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* BIT DO
*/
@TableName("erp_bit_wms_location_stock_record")
@KeySequence("erp_bit_wms_location_stock_record_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BitWmsLocationStockRecordDO extends BaseDO {
@TableId
private Long id;
private Long productId;
private Long warehouseId;
private Long areaId;
private Long locationId;
private BigDecimal count;
private BigDecimal totalCount;
private Long unitId;
private String bizDirection;
private String bizType;
private Long bizId;
private String bizNo;
private String remark;
private LocalDateTime recordTime;
}

@ -40,6 +40,26 @@ public class ErpProductDO extends BaseDO {
*
*/
private String barCode;
/**
*
*/
private Boolean isSample;
/**
*
*/
private String sampleCategory;
/**
*
*/
private String bizUnit;
/**
* ID
*/
private Long customerId;
/**
*
*/
private String customerName;
/**
*
*

@ -41,12 +41,28 @@ public class ErpStockMoveItemDO extends BaseDO {
* {@link ErpWarehouseDO#getId()}
*/
private Long fromWarehouseId;
/**
*
*/
private Long fromAreaId;
/**
*
*/
private String fromAreaName;
/**
*
*
* {@link ErpWarehouseDO#getId()}
*/
private Long toWarehouseId;
/**
*
*/
private Long toAreaId;
/**
*
*/
private String toAreaName;
/**
*
*
@ -76,4 +92,4 @@ public class ErpStockMoveItemDO extends BaseDO {
*/
private String remark;
}
}

@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.erp.dal.mysql.bitwms;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockPageReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.bitwms.BitWmsLocationStockDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.math.BigDecimal;
@Mapper
public interface BitWmsLocationStockMapper extends BaseMapperX<BitWmsLocationStockDO> {
default PageResult<BitWmsLocationStockDO> selectPage(BitWmsLocationStockPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<BitWmsLocationStockDO>()
.eqIfPresent(BitWmsLocationStockDO::getProductId, reqVO.getProductId())
.eqIfPresent(BitWmsLocationStockDO::getWarehouseId, reqVO.getWarehouseId())
.eqIfPresent(BitWmsLocationStockDO::getAreaId, reqVO.getAreaId())
.eqIfPresent(BitWmsLocationStockDO::getLocationId, reqVO.getLocationId())
.ne(BitWmsLocationStockDO::getCount, BigDecimal.ZERO)
.orderByDesc(BitWmsLocationStockDO::getId));
}
default BitWmsLocationStockDO selectByProductIdAndLocationId(Long productId, Long locationId) {
return selectOne(new LambdaQueryWrapperX<BitWmsLocationStockDO>()
.eq(BitWmsLocationStockDO::getProductId, productId)
.eq(BitWmsLocationStockDO::getLocationId, locationId));
}
default int updateCountIncrement(Long id, BigDecimal count, boolean negativeEnable) {
if (count == null || count.compareTo(BigDecimal.ZERO) == 0) {
return 0;
}
LambdaUpdateWrapper<BitWmsLocationStockDO> updateWrapper = new LambdaUpdateWrapper<BitWmsLocationStockDO>()
.eq(BitWmsLocationStockDO::getId, id);
if (count.compareTo(BigDecimal.ZERO) > 0) {
updateWrapper.setSql("count = count + " + count);
} else {
if (!negativeEnable) {
updateWrapper.ge(BitWmsLocationStockDO::getCount, count.abs());
}
updateWrapper.setSql("count = count - " + count.abs());
}
return update(null, updateWrapper);
}
}

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.erp.dal.mysql.bitwms;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRecordPageReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.bitwms.BitWmsLocationStockRecordDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BitWmsLocationStockRecordMapper extends BaseMapperX<BitWmsLocationStockRecordDO> {
default PageResult<BitWmsLocationStockRecordDO> selectPage(BitWmsLocationStockRecordPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<BitWmsLocationStockRecordDO>()
.eqIfPresent(BitWmsLocationStockRecordDO::getProductId, reqVO.getProductId())
.eqIfPresent(BitWmsLocationStockRecordDO::getWarehouseId, reqVO.getWarehouseId())
.eqIfPresent(BitWmsLocationStockRecordDO::getAreaId, reqVO.getAreaId())
.eqIfPresent(BitWmsLocationStockRecordDO::getLocationId, reqVO.getLocationId())
.likeIfPresent(BitWmsLocationStockRecordDO::getBizNo, reqVO.getBizNo())
.orderByDesc(BitWmsLocationStockRecordDO::getId));
}
}

@ -42,6 +42,10 @@ public interface ErpProductMapper extends BaseMapperX<ErpProductDO> {
return selectPage(reqVO, new LambdaQueryWrapperX<ErpProductDO>()
.likeIfPresent(ErpProductDO::getName, reqVO.getName())
.likeIfPresent(ErpProductDO::getBarCode, resolveCode(reqVO))
.eqIfPresent(ErpProductDO::getIsSample, reqVO.getIsSample())
.eqIfPresent(ErpProductDO::getBizUnit, reqVO.getBizUnit())
.eqIfPresent(ErpProductDO::getSampleCategory, reqVO.getSampleCategory())
.eqIfPresent(ErpProductDO::getCustomerId, reqVO.getCustomerId())
.eqIfPresent(ErpProductDO::getCategoryId, reqVO.getCategoryId())
.inIfPresent(ErpProductDO::getCategoryId, categoryIds)
.betweenIfPresent(ErpProductDO::getCreateTime, reqVO.getCreateTime())
@ -67,6 +71,10 @@ public interface ErpProductMapper extends BaseMapperX<ErpProductDO> {
.inIfPresent(ErpProductDO::getId, reqVO.getIds())
.likeIfPresent(ErpProductDO::getName, reqVO.getName())
.likeIfPresent(ErpProductDO::getBarCode, reqVO.getCode())
.eqIfPresent(ErpProductDO::getIsSample, reqVO.getIsSample())
.eqIfPresent(ErpProductDO::getBizUnit, reqVO.getBizUnit())
.eqIfPresent(ErpProductDO::getSampleCategory, reqVO.getSampleCategory())
.eqIfPresent(ErpProductDO::getCustomerId, reqVO.getCustomerId())
.eqIfPresent(ErpProductDO::getCategoryId, reqVO.getCategoryId())
.inIfPresent(ErpProductDO::getCategoryId, categoryIds)
.likeIfPresent(ErpProductDO::getStandard, reqVO.getStandard())

@ -48,11 +48,24 @@ public interface ErpStockMapper extends BaseMapperX<ErpStockDO> {
erpStockDOLambdaQueryWrapperX.in(ErpStockDO::getId, idList);
}
appendProductScope(erpStockDOLambdaQueryWrapperX, reqVO.getIsSample(), reqVO.getBizUnit());
return selectPage(reqVO,erpStockDOLambdaQueryWrapperX);
}
static void appendProductScope(LambdaQueryWrapperX<ErpStockDO> query, Boolean isSample, String bizUnit) {
if (isSample != null && StringUtils.isNotBlank(bizUnit)) {
query.apply("EXISTS (SELECT 1 FROM erp_product ep WHERE ep.id = product_id AND ep.deleted = 0 AND ep.is_sample = {0} AND ep.biz_unit = {1})",
isSample, bizUnit);
} else if (isSample != null) {
query.apply("EXISTS (SELECT 1 FROM erp_product ep WHERE ep.id = product_id AND ep.deleted = 0 AND ep.is_sample = {0})",
isSample);
} else if (StringUtils.isNotBlank(bizUnit)) {
query.apply("EXISTS (SELECT 1 FROM erp_product ep WHERE ep.id = product_id AND ep.deleted = 0 AND ep.biz_unit = {0})",
bizUnit);
}
}
default ErpStockDO selectByProductIdAndWarehouseId(Long productId, Long warehouseId) {
return selectByProductIdAndWarehouseIdAndAreaId(productId, warehouseId, null);
}

@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.record.ErpStockRecordPageReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockRecordDO;
import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum;
import com.alibaba.excel.util.StringUtils;
import org.apache.ibatis.annotations.Mapper;
@ -23,6 +24,10 @@ import java.util.stream.Collectors;
@Mapper
public interface ErpStockRecordMapper extends BaseMapperX<ErpStockRecordDO> {
String BIT_BIZ_UNIT = "BIT";
String SAMPLE_IN_TYPE = "样品入库";
String SAMPLE_OUT_TYPE = "样品出库";
default PageResult<ErpStockRecordDO> selectPage(ErpStockRecordPageReqVO reqVO) {
LambdaQueryWrapperX<ErpStockRecordDO> erpStockRecordDOLambdaQueryWrapperX = new LambdaQueryWrapperX<ErpStockRecordDO>()
@ -46,9 +51,50 @@ public interface ErpStockRecordMapper extends BaseMapperX<ErpStockRecordDO> {
erpStockRecordDOLambdaQueryWrapperX.in(ErpStockRecordDO::getId, idList);
}
if (Boolean.TRUE.equals(reqVO.getBitWmsOnly())) {
appendProductScope(erpStockRecordDOLambdaQueryWrapperX, true, BIT_BIZ_UNIT);
appendBitWmsRecordScope(erpStockRecordDOLambdaQueryWrapperX);
} else {
appendProductScope(erpStockRecordDOLambdaQueryWrapperX, reqVO.getIsSample(), reqVO.getBizUnit());
}
return selectPage(reqVO,erpStockRecordDOLambdaQueryWrapperX);
}
static void appendProductScope(LambdaQueryWrapperX<ErpStockRecordDO> query, Boolean isSample, String bizUnit) {
if (isSample != null && StringUtils.isNotBlank(bizUnit)) {
query.apply("EXISTS (SELECT 1 FROM erp_product ep WHERE ep.id = product_id AND ep.deleted = 0 AND ep.is_sample = {0} AND ep.biz_unit = {1})",
isSample, bizUnit);
} else if (isSample != null) {
query.apply("EXISTS (SELECT 1 FROM erp_product ep WHERE ep.id = product_id AND ep.deleted = 0 AND ep.is_sample = {0})",
isSample);
} else if (StringUtils.isNotBlank(bizUnit)) {
query.apply("EXISTS (SELECT 1 FROM erp_product ep WHERE ep.id = product_id AND ep.deleted = 0 AND ep.biz_unit = {0})",
bizUnit);
}
}
static void appendBitWmsRecordScope(LambdaQueryWrapperX<ErpStockRecordDO> query) {
query.apply("(" +
"((biz_type IS NULL OR biz_type IN ({0}, {1})) AND EXISTS (" +
"SELECT 1 FROM erp_stock_in si WHERE si.id = biz_id AND si.no = biz_no AND si.deleted = 0 AND si.in_type = {2}))" +
" OR ((biz_type IS NULL OR biz_type IN ({3}, {4})) AND EXISTS (" +
"SELECT 1 FROM erp_stock_out so WHERE so.id = biz_id AND so.no = biz_no AND so.deleted = 0 AND so.out_type = {5}))" +
" OR (biz_type IN ({6}, {7}, {8}, {9}) AND EXISTS (" +
"SELECT 1 FROM erp_stock_move sm WHERE sm.id = biz_id AND sm.no = biz_no AND sm.deleted = 0))" +
")",
ErpStockRecordBizTypeEnum.OTHER_IN.getType(),
ErpStockRecordBizTypeEnum.OTHER_IN_CANCEL.getType(),
SAMPLE_IN_TYPE,
ErpStockRecordBizTypeEnum.OTHER_OUT.getType(),
ErpStockRecordBizTypeEnum.OTHER_OUT_CANCEL.getType(),
SAMPLE_OUT_TYPE,
ErpStockRecordBizTypeEnum.MOVE_IN.getType(),
ErpStockRecordBizTypeEnum.MOVE_IN_CANCEL.getType(),
ErpStockRecordBizTypeEnum.MOVE_OUT.getType(),
ErpStockRecordBizTypeEnum.MOVE_OUT_CANCEL.getType());
}
default LocalDateTime selectLatestRecordTime(Long productId, Long warehouseId, Long areaId, boolean inbound) {
ErpStockRecordDO record = selectOne(new LambdaQueryWrapperX<ErpStockRecordDO>()
.eq(ErpStockRecordDO::getProductId, productId)

@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.erp.dal.dataobject.warehouselocation.WarehouseLocationDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import cn.iocoder.yudao.module.erp.controller.admin.warehouselocation.vo.*;
/**
@ -58,4 +59,6 @@ public interface WarehouseLocationMapper extends BaseMapperX<WarehouseLocationDO
default Long selectCountByWarehouseId(Long warehouseId) {
return selectCount(WarehouseLocationDO::getWarehouseId, warehouseId);
}
String selectPrintTemplate(@Param("templateType") Integer templateType);
}

@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.erp.handler;
import cn.iocoder.yudao.module.common.enums.QrcodeBizTypeEnum;
import cn.iocoder.yudao.module.common.handler.QrcodeBizHandler;
import cn.iocoder.yudao.module.erp.dal.mysql.warehouselocation.WarehouseLocationMapper;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class WarehouseLocationQrcodeBizHandler implements QrcodeBizHandler {
@Resource
private WarehouseLocationMapper warehouseLocationMapper;
@Override
public String getBizType() {
return QrcodeBizTypeEnum.WAREHOUSE_LOCATION.getCode();
}
@Override
public boolean exists(Long id) {
return warehouseLocationMapper.selectById(id) != null;
}
@Override
public String buildDeepLink(Long id) {
return "besure://warehouse-location/detail?id=" + id;
}
@Override
public String buildH5Path(Long id) {
return "/pages_function/pages/bitWms/stock?locationId=" + id;
}
}

@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.erp.service.bitwms;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsInboundReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLabelRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRecordPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRecordRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsMoveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsOutboundReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsScanRespVO;
import java.math.BigDecimal;
import javax.validation.Valid;
public interface BitWmsService {
Long inbound(@Valid BitWmsInboundReqVO reqVO);
Long outbound(@Valid BitWmsOutboundReqVO reqVO);
Long move(@Valid BitWmsMoveReqVO reqVO);
BitWmsScanRespVO scan(String code);
PageResult<BitWmsLocationStockRespVO> getLocationStockPage(@Valid BitWmsLocationStockPageReqVO pageReqVO);
PageResult<BitWmsLocationStockRecordRespVO> getLocationStockRecordPage(@Valid BitWmsLocationStockRecordPageReqVO pageReqVO);
BigDecimal getLocationStockCount(Long productId, Long locationId);
BitWmsLabelRespVO getSampleLabel(Long productId);
BitWmsLabelRespVO getLocationLabel(Long locationId);
}

@ -0,0 +1,607 @@
package cn.iocoder.yudao.module.erp.service.bitwms;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.common.enums.CodeTypeEnum;
import cn.iocoder.yudao.module.common.enums.QrcodeBizTypeEnum;
import cn.iocoder.yudao.module.common.service.qrcordrecord.QrcodeRecordService;
import cn.iocoder.yudao.module.erp.controller.admin.autocode.util.AutoCodeUtil;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsInboundReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLabelRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRecordPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRecordRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLocationStockRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsMoveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsOutboundReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsScanRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInAuditReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInSaveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveSaveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutAuditReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutSaveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutSaveReqVOItem;
import cn.iocoder.yudao.module.erp.dal.dataobject.bitwms.BitWmsLocationStockDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.bitwms.BitWmsLocationStockRecordDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductUnitDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockOutDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpWarehouseDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.warehousearea.WarehouseAreaDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.warehouselocation.WarehouseLocationDO;
import cn.iocoder.yudao.module.erp.dal.mysql.bitwms.BitWmsLocationStockMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.bitwms.BitWmsLocationStockRecordMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.warehouselocation.WarehouseLocationMapper;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
import cn.iocoder.yudao.module.erp.service.product.ErpProductUnitService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockInService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockMoveService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockOutService;
import cn.iocoder.yudao.module.erp.service.stock.ErpWarehouseService;
import cn.iocoder.yudao.module.erp.service.warehousearea.WarehouseAreaService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.io.UnsupportedEncodingException;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.PRODUCT_CODE_NOT_EXISTS;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.PRODUCT_NOT_EXISTS;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.PRODUCT_UNIT_NOT_EXISTS;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_COUNT_NEGATIVE2;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.WAREHOUSE_LOCATION_NOT_EXISTS;
@Service
@Validated
public class BitWmsServiceImpl implements BitWmsService {
private static final String BIT_BIZ_UNIT = "BIT";
private static final String SAMPLE_TYPE = "SAMPLE";
private static final String LOCATION_TYPE = "LOCATION";
private static final String SAMPLE_IN_TYPE = "样品入库";
private static final String SAMPLE_OUT_TYPE = "样品出库";
private static final String SAMPLE_MOVE_TYPE = "样品移库";
private static final String BIT_SAMPLE_CODE_RULE = "BIT_SAMPLE_CODE";
private static final String BIT_WAREHOUSE_LOCATION_CODE_RULE = "BIT_WAREHOUSE_LOCATION_CODE";
private static final int SAMPLE_PRINT_TEMPLATE_TYPE = 7;
private static final int LOCATION_PRINT_TEMPLATE_TYPE = 8;
private static final Pattern QRCODE_BIZ_KEY_PATTERN = Pattern.compile("^(.+)[-_](\\d+)$");
@Resource
private ErpProductService productService;
@Resource
private ErpProductUnitService productUnitService;
@Resource
private ErpProductMapper productMapper;
@Resource
private WarehouseLocationMapper warehouseLocationMapper;
@Resource
private ErpStockInService stockInService;
@Resource
private ErpStockOutService stockOutService;
@Resource
private ErpStockMoveService stockMoveService;
@Resource
private ErpWarehouseService warehouseService;
@Resource
private WarehouseAreaService warehouseAreaService;
@Resource
private BitWmsLocationStockMapper locationStockMapper;
@Resource
private BitWmsLocationStockRecordMapper locationStockRecordMapper;
@Resource
private QrcodeRecordService qrcodeRecordService;
@Resource
private AutoCodeUtil autoCodeUtil;
@Resource
private AdminUserApi adminUserApi;
@Override
@Transactional(rollbackFor = Exception.class)
public Long inbound(BitWmsInboundReqVO reqVO) {
ErpProductRespVO product = validateBitSample(reqVO.getProductId());
validateInboundUnit(reqVO, product);
WarehouseLocationDO location = resolveInboundLocation(reqVO);
Long warehouseId = location != null ? location.getWarehouseId() : reqVO.getWarehouseId();
Long areaId = location != null ? location.getAreaId() : reqVO.getAreaId();
ErpStockInSaveReqVO stockIn = new ErpStockInSaveReqVO();
stockIn.setSupplierId(reqVO.getSupplierId());
stockIn.setInTime(LocalDateTime.now());
stockIn.setInType(SAMPLE_IN_TYPE);
stockIn.setRemark(reqVO.getRemark());
stockIn.setWarehouseId(warehouseId);
ErpStockInSaveReqVO.Item item = new ErpStockInSaveReqVO.Item();
item.setProductId(reqVO.getProductId());
item.setWarehouseId(warehouseId);
item.setAreaId(areaId);
item.setCount(reqVO.getCount());
item.setProductPrice(product.getPurchasePrice() != null ? product.getPurchasePrice() : BigDecimal.ZERO);
item.setRelateTask(false);
item.setRemark(reqVO.getRemark());
stockIn.setItems(Collections.singletonList(item));
Long id = stockInService.createStockIn(stockIn);
ErpStockInAuditReqVO auditReqVO = new ErpStockInAuditReqVO();
auditReqVO.setId(id);
auditReqVO.setStatus(ErpAuditStatus.APPROVE.getStatus());
auditReqVO.setRemark(reqVO.getRemark() != null ? reqVO.getRemark() : "样品入库自动入库");
stockInService.auditStockIn(auditReqVO);
ErpStockInDO stockInDO = stockInService.getStockIn(id);
adjustLocationStock(product, location, reqVO.getCount(), "入库", SAMPLE_IN_TYPE, id,
stockInDO != null ? stockInDO.getNo() : null, reqVO.getRemark());
return id;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long outbound(BitWmsOutboundReqVO reqVO) {
ErpProductRespVO product = validateBitSample(reqVO.getProductId());
WarehouseLocationDO location = resolveOutboundLocation(reqVO);
Long warehouseId = location.getWarehouseId();
Long areaId = location.getAreaId();
validateLocationStockEnough(reqVO.getProductId(), location.getId(), reqVO.getCount());
ErpStockOutSaveReqVO stockOut = new ErpStockOutSaveReqVO();
stockOut.setOutType(SAMPLE_OUT_TYPE);
stockOut.setOutTime(LocalDateTime.now());
stockOut.setRemark(buildOutboundRemark(reqVO.getOutReason(), reqVO.getRemark()));
ErpStockOutSaveReqVOItem item = new ErpStockOutSaveReqVOItem();
item.setProductId(reqVO.getProductId());
item.setWarehouseId(warehouseId);
item.setAreaId(areaId);
item.setCount(reqVO.getCount());
item.setProductPrice(product.getPurchasePrice() != null ? product.getPurchasePrice() : BigDecimal.ZERO);
item.setRemark(reqVO.getRemark());
stockOut.setItems(Collections.singletonList(item));
Long id = stockOutService.createStockOut(stockOut);
ErpStockOutAuditReqVO auditReqVO = new ErpStockOutAuditReqVO();
auditReqVO.setId(id);
auditReqVO.setStatus(ErpAuditStatus.APPROVE.getStatus());
auditReqVO.setRemark(reqVO.getRemark() != null ? reqVO.getRemark() : "样品出库自动出库");
stockOutService.auditStockOut(auditReqVO);
ErpStockOutDO stockOutDO = stockOutService.getStockOut(id);
adjustLocationStock(product, location, reqVO.getCount().negate(), "出库", SAMPLE_OUT_TYPE, id,
stockOutDO != null ? stockOutDO.getNo() : null, buildOutboundRemark(reqVO.getOutReason(), reqVO.getRemark()));
return id;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long move(BitWmsMoveReqVO reqVO) {
ErpProductRespVO product = validateBitSample(reqVO.getProductId());
WarehouseLocationDO fromLocation = resolveLocation(reqVO.getFromLocationId());
WarehouseLocationDO toLocation = resolveLocation(reqVO.getToLocationId());
validateLocationStockEnough(reqVO.getProductId(), fromLocation.getId(), reqVO.getCount());
if (fromLocation.getId().equals(toLocation.getId())) {
throw exception(STOCK_COUNT_NEGATIVE2, reqVO.getProductId(), fromLocation.getId());
}
if (isSameWarehouseArea(fromLocation, toLocation)) {
String bizNo = buildLocationMoveNo();
Long recordId = adjustLocationStock(product, fromLocation, reqVO.getCount().negate(), "出库",
SAMPLE_MOVE_TYPE + "出", null, bizNo, reqVO.getRemark());
adjustLocationStock(product, toLocation, reqVO.getCount(), "入库", SAMPLE_MOVE_TYPE + "入", null,
bizNo, reqVO.getRemark());
return recordId;
}
ErpStockMoveSaveReqVO stockMove = new ErpStockMoveSaveReqVO();
stockMove.setMoveTime(LocalDateTime.now());
stockMove.setRemark(reqVO.getRemark() != null ? reqVO.getRemark() : SAMPLE_MOVE_TYPE);
ErpStockMoveSaveReqVO.Item item = new ErpStockMoveSaveReqVO.Item();
item.setProductId(reqVO.getProductId());
item.setFromWarehouseId(fromLocation.getWarehouseId());
item.setFromAreaId(fromLocation.getAreaId());
item.setToWarehouseId(toLocation.getWarehouseId());
item.setToAreaId(toLocation.getAreaId());
item.setCount(reqVO.getCount());
item.setProductPrice(product.getPurchasePrice() != null ? product.getPurchasePrice() : BigDecimal.ZERO);
item.setRemark(reqVO.getRemark());
stockMove.setItems(Collections.singletonList(item));
Long id = stockMoveService.createStockMove(stockMove);
stockMoveService.updateStockMoveStatus(id, ErpAuditStatus.APPROVE.getStatus());
ErpStockMoveDO stockMoveDO = stockMoveService.getStockMove(id);
String bizNo = stockMoveDO != null ? stockMoveDO.getNo() : null;
adjustLocationStock(product, fromLocation, reqVO.getCount().negate(), "出库", SAMPLE_MOVE_TYPE + "出", id,
bizNo, reqVO.getRemark());
adjustLocationStock(product, toLocation, reqVO.getCount(), "入库", SAMPLE_MOVE_TYPE + "入", id,
bizNo, reqVO.getRemark());
return id;
}
@Override
public BitWmsScanRespVO scan(String code) {
String scanCode = code != null ? code.trim() : "";
BitWmsScanRespVO qrcodeBizRespVO = scanByQrcodeBizKey(scanCode);
if (qrcodeBizRespVO != null) {
return qrcodeBizRespVO;
}
ErpProductDO product = productMapper.selectOne(ErpProductDO::getBarCode, scanCode);
if (product != null && Boolean.TRUE.equals(product.getIsSample()) && BIT_BIZ_UNIT.equals(product.getBizUnit())) {
return buildSampleScanResp(product);
}
WarehouseLocationDO location = warehouseLocationMapper.selectByNo(scanCode);
if (location != null) {
return buildLocationScanResp(location);
}
throw exception(PRODUCT_CODE_NOT_EXISTS);
}
private BitWmsScanRespVO scanByQrcodeBizKey(String code) {
Matcher matcher = QRCODE_BIZ_KEY_PATTERN.matcher(code);
if (!matcher.matches()) {
return null;
}
String bizType = matcher.group(1);
Long bizId = Long.valueOf(matcher.group(2));
if (QrcodeBizTypeEnum.PRODUCT.getCode().equalsIgnoreCase(bizType)) {
ErpProductDO product = productMapper.selectById(bizId);
if (product != null && Boolean.TRUE.equals(product.getIsSample()) && BIT_BIZ_UNIT.equals(product.getBizUnit())) {
return buildSampleScanResp(product);
}
throw exception(PRODUCT_CODE_NOT_EXISTS);
}
if (QrcodeBizTypeEnum.WAREHOUSE_LOCATION.getCode().equalsIgnoreCase(bizType)) {
WarehouseLocationDO location = warehouseLocationMapper.selectById(bizId);
if (location != null) {
return buildLocationScanResp(location);
}
throw exception(PRODUCT_CODE_NOT_EXISTS);
}
return null;
}
private BitWmsScanRespVO buildSampleScanResp(ErpProductDO product) {
BitWmsScanRespVO respVO = new BitWmsScanRespVO();
respVO.setType(SAMPLE_TYPE);
respVO.setId(product.getId());
respVO.setCode(product.getBarCode());
respVO.setName(product.getName());
respVO.setUnitId(product.getUnitId());
if (product.getUnitId() != null) {
ErpProductUnitDO unit = productUnitService.getProductUnit(product.getUnitId());
if (unit != null) {
respVO.setUnitName(unit.getName());
}
}
return respVO;
}
private BitWmsScanRespVO buildLocationScanResp(WarehouseLocationDO location) {
BitWmsScanRespVO respVO = new BitWmsScanRespVO();
respVO.setType(LOCATION_TYPE);
respVO.setId(location.getId());
respVO.setCode(location.getCode());
respVO.setName(location.getName());
return respVO;
}
@Override
public PageResult<BitWmsLocationStockRespVO> getLocationStockPage(BitWmsLocationStockPageReqVO pageReqVO) {
PageResult<BitWmsLocationStockDO> pageResult = locationStockMapper.selectPage(pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return PageResult.empty(pageResult.getTotal());
}
LookupContext context = buildLookupContext(pageResult.getList(), null);
return BeanUtils.toBean(pageResult, BitWmsLocationStockRespVO.class,
stock -> fillLocationStockResp(stock, context));
}
@Override
public PageResult<BitWmsLocationStockRecordRespVO> getLocationStockRecordPage(BitWmsLocationStockRecordPageReqVO pageReqVO) {
PageResult<BitWmsLocationStockRecordDO> pageResult = locationStockRecordMapper.selectPage(pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return PageResult.empty(pageResult.getTotal());
}
LookupContext context = buildLookupContext(null, pageResult.getList());
return BeanUtils.toBean(pageResult, BitWmsLocationStockRecordRespVO.class,
record -> fillLocationStockRecordResp(record, context));
}
@Override
public BigDecimal getLocationStockCount(Long productId, Long locationId) {
validateBitSample(productId);
resolveLocation(locationId);
BitWmsLocationStockDO stock = locationStockMapper.selectByProductIdAndLocationId(productId, locationId);
return stock != null && stock.getCount() != null ? stock.getCount() : BigDecimal.ZERO;
}
@Override
public BitWmsLabelRespVO getSampleLabel(Long productId) {
ErpProductRespVO product = validateBitSample(productId);
String qrcodeUrl = resolveCodeUrl(QrcodeBizTypeEnum.PRODUCT, product.getId(), product.getBarCode(),
autoCodeUtil.queryCodeType(BIT_SAMPLE_CODE_RULE));
Map<String, Object> printData = new LinkedHashMap<>();
printData.put("id", product.getId());
printData.put("code", product.getBarCode());
printData.put("name", product.getName());
printData.put("customerName", product.getCustomerName());
printData.put("sampleCategory", product.getSampleCategory());
printData.put("qrcodeUrl", qrcodeUrl);
return buildLabel(SAMPLE_TYPE, product.getId(), product.getBarCode(), product.getName(), qrcodeUrl,
productMapper.selectPrintTemplate(SAMPLE_PRINT_TEMPLATE_TYPE), printData);
}
@Override
public BitWmsLabelRespVO getLocationLabel(Long locationId) {
WarehouseLocationDO location = warehouseLocationMapper.selectById(locationId);
if (location == null) {
throw exception(WAREHOUSE_LOCATION_NOT_EXISTS);
}
String qrcodeUrl = resolveCodeUrl(QrcodeBizTypeEnum.WAREHOUSE_LOCATION, location.getId(), location.getCode(),
autoCodeUtil.queryCodeType(BIT_WAREHOUSE_LOCATION_CODE_RULE));
Map<String, Object> printData = new LinkedHashMap<>();
printData.put("id", location.getId());
printData.put("code", location.getCode());
printData.put("name", location.getName());
printData.put("warehouseId", location.getWarehouseId());
printData.put("areaId", location.getAreaId());
printData.put("qrcodeUrl", qrcodeUrl);
return buildLabel(LOCATION_TYPE, location.getId(), location.getCode(), location.getName(), qrcodeUrl,
warehouseLocationMapper.selectPrintTemplate(LOCATION_PRINT_TEMPLATE_TYPE), printData);
}
private ErpProductRespVO validateBitSample(Long productId) {
ErpProductRespVO product = productService.getProduct(productId);
if (product == null || !Boolean.TRUE.equals(product.getIsSample()) || !BIT_BIZ_UNIT.equals(product.getBizUnit())) {
throw exception(PRODUCT_NOT_EXISTS);
}
return product;
}
private void validateInboundUnit(BitWmsInboundReqVO reqVO, ErpProductRespVO product) {
if (reqVO.getUnitId() == null || product.getUnitId() == null || !reqVO.getUnitId().equals(product.getUnitId())) {
throw exception(PRODUCT_UNIT_NOT_EXISTS);
}
}
private WarehouseLocationDO resolveInboundLocation(BitWmsInboundReqVO reqVO) {
if (reqVO.getLocationId() == null) {
return null;
}
WarehouseLocationDO location = warehouseLocationMapper.selectById(reqVO.getLocationId());
if (location == null) {
throw exception(WAREHOUSE_LOCATION_NOT_EXISTS);
}
return location;
}
private WarehouseLocationDO resolveOutboundLocation(BitWmsOutboundReqVO reqVO) {
return resolveLocation(reqVO.getLocationId());
}
private WarehouseLocationDO resolveLocation(Long locationId) {
WarehouseLocationDO location = warehouseLocationMapper.selectById(locationId);
if (location == null) {
throw exception(WAREHOUSE_LOCATION_NOT_EXISTS);
}
return location;
}
private void validateLocationStockEnough(Long productId, Long locationId, BigDecimal count) {
BitWmsLocationStockDO stock = locationStockMapper.selectByProductIdAndLocationId(productId, locationId);
BigDecimal currentCount = stock != null && stock.getCount() != null ? stock.getCount() : BigDecimal.ZERO;
if (currentCount.compareTo(count) < 0) {
throw exception(STOCK_COUNT_NEGATIVE2, productId, locationId);
}
}
private Long adjustLocationStock(ErpProductRespVO product, WarehouseLocationDO location, BigDecimal count,
String direction, String bizType, Long bizId, String bizNo, String remark) {
BitWmsLocationStockDO stock = locationStockMapper.selectByProductIdAndLocationId(product.getId(), location.getId());
if (stock == null) {
if (count.compareTo(BigDecimal.ZERO) < 0) {
throw exception(STOCK_COUNT_NEGATIVE2, product.getId(), location.getId());
}
stock = new BitWmsLocationStockDO()
.setProductId(product.getId())
.setWarehouseId(location.getWarehouseId())
.setAreaId(location.getAreaId())
.setLocationId(location.getId())
.setCount(count)
.setUnitId(product.getUnitId());
locationStockMapper.insert(stock);
} else {
int updated = locationStockMapper.updateCountIncrement(stock.getId(), count, false);
if (updated == 0 && count.compareTo(BigDecimal.ZERO) < 0) {
throw exception(STOCK_COUNT_NEGATIVE2, product.getId(), location.getId());
}
stock = locationStockMapper.selectById(stock.getId());
}
BitWmsLocationStockRecordDO record = new BitWmsLocationStockRecordDO()
.setProductId(product.getId())
.setWarehouseId(location.getWarehouseId())
.setAreaId(location.getAreaId())
.setLocationId(location.getId())
.setCount(count.abs())
.setTotalCount(stock.getCount())
.setUnitId(product.getUnitId())
.setBizDirection(direction)
.setBizType(bizType)
.setBizId(bizId)
.setBizNo(bizNo)
.setRemark(remark)
.setRecordTime(LocalDateTime.now());
locationStockRecordMapper.insert(record);
return record.getId();
}
private boolean isSameWarehouseArea(WarehouseLocationDO fromLocation, WarehouseLocationDO toLocation) {
return fromLocation.getWarehouseId().equals(toLocation.getWarehouseId())
&& java.util.Objects.equals(fromLocation.getAreaId(), toLocation.getAreaId());
}
private String buildLocationMoveNo() {
return "KWYK" + System.currentTimeMillis();
}
private LookupContext buildLookupContext(List<BitWmsLocationStockDO> stocks,
List<BitWmsLocationStockRecordDO> records) {
Set<Long> productIds = new HashSet<>();
Set<Long> warehouseIds = new HashSet<>();
Set<Long> areaIds = new HashSet<>();
Set<Long> locationIds = new HashSet<>();
Set<Long> creatorIds = new HashSet<>();
if (stocks != null) {
for (BitWmsLocationStockDO stock : stocks) {
collectCommonIds(productIds, warehouseIds, areaIds, locationIds,
stock.getProductId(), stock.getWarehouseId(), stock.getAreaId(), stock.getLocationId());
}
}
if (records != null) {
for (BitWmsLocationStockRecordDO record : records) {
collectCommonIds(productIds, warehouseIds, areaIds, locationIds,
record.getProductId(), record.getWarehouseId(), record.getAreaId(), record.getLocationId());
if (record.getCreator() != null) {
creatorIds.add(Long.parseLong(record.getCreator()));
}
}
}
LookupContext context = new LookupContext();
context.productMap = productIds.isEmpty() ? Collections.emptyMap() : productService.getProductVOMap(productIds);
context.warehouseMap = warehouseIds.isEmpty() ? Collections.emptyMap() : warehouseService.getWarehouseMap(warehouseIds);
context.areaMap = areaIds.isEmpty() ? Collections.emptyMap() : warehouseAreaService.getWarehouseAreaMap(areaIds);
context.locationMap = locationIds.isEmpty() ? Collections.emptyMap()
: warehouseLocationMapper.selectBatchIds(locationIds).stream()
.collect(Collectors.toMap(WarehouseLocationDO::getId, item -> item, (a, b) -> a));
context.userMap = creatorIds.isEmpty() ? Collections.emptyMap() : adminUserApi.getUserMap(creatorIds);
return context;
}
private void collectCommonIds(Set<Long> productIds, Set<Long> warehouseIds, Set<Long> areaIds, Set<Long> locationIds,
Long productId, Long warehouseId, Long areaId, Long locationId) {
if (productId != null) {
productIds.add(productId);
}
if (warehouseId != null) {
warehouseIds.add(warehouseId);
}
if (areaId != null) {
areaIds.add(areaId);
}
if (locationId != null) {
locationIds.add(locationId);
}
}
private void fillLocationStockResp(BitWmsLocationStockRespVO respVO, LookupContext context) {
ErpProductRespVO product = context.productMap.get(respVO.getProductId());
if (product != null) {
respVO.setProductName(product.getName());
respVO.setBarCode(product.getBarCode());
respVO.setSampleCategory(product.getSampleCategory());
respVO.setCustomerName(product.getCustomerName());
respVO.setUnitName(product.getUnitName());
}
fillLocationInfo(respVO.getWarehouseId(), respVO.getAreaId(), respVO.getLocationId(), context,
respVO::setWarehouseName, respVO::setAreaName, respVO::setLocationCode, respVO::setLocationName);
}
private void fillLocationStockRecordResp(BitWmsLocationStockRecordRespVO respVO, LookupContext context) {
ErpProductRespVO product = context.productMap.get(respVO.getProductId());
if (product != null) {
respVO.setProductName(product.getName());
respVO.setBarCode(product.getBarCode());
respVO.setUnitName(product.getUnitName());
}
fillLocationInfo(respVO.getWarehouseId(), respVO.getAreaId(), respVO.getLocationId(), context,
respVO::setWarehouseName, respVO::setAreaName, respVO::setLocationCode, respVO::setLocationName);
if (respVO.getCreator() != null) {
AdminUserRespDTO user = context.userMap.get(Long.parseLong(respVO.getCreator()));
if (user != null) {
respVO.setCreatorName(user.getNickname());
}
}
}
private void fillLocationInfo(Long warehouseId, Long areaId, Long locationId, LookupContext context,
java.util.function.Consumer<String> warehouseSetter,
java.util.function.Consumer<String> areaSetter,
java.util.function.Consumer<String> locationCodeSetter,
java.util.function.Consumer<String> locationNameSetter) {
ErpWarehouseDO warehouse = context.warehouseMap.get(warehouseId);
if (warehouse != null) {
warehouseSetter.accept(warehouse.getName());
}
WarehouseAreaDO area = context.areaMap.get(areaId);
if (area != null) {
areaSetter.accept(area.getAreaName());
}
WarehouseLocationDO location = context.locationMap.get(locationId);
if (location != null) {
locationCodeSetter.accept(location.getCode());
locationNameSetter.accept(location.getName());
}
}
private static class LookupContext {
private Map<Long, ErpProductRespVO> productMap;
private Map<Long, ErpWarehouseDO> warehouseMap;
private Map<Long, WarehouseAreaDO> areaMap;
private Map<Long, WarehouseLocationDO> locationMap;
private Map<Long, AdminUserRespDTO> userMap;
}
private String buildOutboundRemark(String outReason, String remark) {
if (remark == null || remark.isEmpty()) {
return "出库原因:" + outReason;
}
return "出库原因:" + outReason + "" + remark;
}
private String resolveCodeUrl(QrcodeBizTypeEnum bizType, Long bizId, String bizCode, CodeTypeEnum codeType) {
if (codeType == null) {
return qrcodeRecordService.selectQrcodeUrlByIdAndCode(bizType.getCode(), bizId, bizCode);
}
String codeUrl = qrcodeRecordService.selectQrcodeUrlByIdAndCodeAndType(
bizType.getCode(), bizId, bizCode, codeType);
if (codeUrl != null) {
return codeUrl;
}
try {
qrcodeRecordService.generateOrRefresh(bizType, bizId, bizCode, "DETAIL", codeType);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
return qrcodeRecordService.selectQrcodeUrlByIdAndCodeAndType(
bizType.getCode(), bizId, bizCode, codeType);
}
private BitWmsLabelRespVO buildLabel(String type, Long id, String code, String name, String qrcodeUrl,
String templateJson, Map<String, Object> printData) {
BitWmsLabelRespVO respVO = new BitWmsLabelRespVO();
respVO.setType(type);
respVO.setId(id);
respVO.setCode(code);
respVO.setName(name);
respVO.setQrcodeUrl(qrcodeUrl);
respVO.setTemplateJson(templateJson);
respVO.setPrintData(printData);
return respVO;
}
}

@ -76,6 +76,7 @@ public interface ErpProductService {
List<ErpProductRespVO> getProductVOListByStatus(Integer status,Integer categoryId);
List<ErpProductRespVO> getProductVOListByCategory(Integer categoryId);
List<ErpProductRespVO> getProductVOListByCategory(Integer categoryId, Integer categoryType);
List<ErpProductRespVO> getProductVOList(ErpProductListReqVO reqVO);
List<ErpProductRespVO> buildProductVOList(List<ErpProductDO> list);
/**
* VO

@ -72,6 +72,9 @@ import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
@Slf4j
public class ErpProductServiceImpl implements ErpProductService {
private static final String PRODUCT_CODE_RULE = "PRODUCT_CODE_GENERATE";
private static final String BIT_SAMPLE_CODE_RULE = "BIT_SAMPLE_CODE";
@Resource
private ErpProductMapper productMapper;
@ -125,6 +128,8 @@ public class ErpProductServiceImpl implements ErpProductService {
String code = createReqVO.getBarCode();
boolean autoGeneratedCode = StringUtils.isBlank(code);
boolean sample = Boolean.TRUE.equals(createReqVO.getIsSample());
String ruleCode = sample ? BIT_SAMPLE_CODE_RULE : PRODUCT_CODE_RULE;
validateProductUnitRelation(createReqVO.getUnitId(), createReqVO.getPurchaseUnitId(), createReqVO.getPurchaseUnitConvertQuantity());
@ -137,7 +142,7 @@ public class ErpProductServiceImpl implements ErpProductService {
}
if (autoGeneratedCode) {
code = autoCodeUtil.genSerialCode("PRODUCT_CODE_GENERATE", null);
code = autoCodeUtil.genSerialCode(ruleCode, null);
} else {
if (productMapper.selectOne(Wrappers.<ErpProductDO>lambdaQuery()
.eq(ErpProductDO::getBarCode, code)) != null) {
@ -166,7 +171,7 @@ public class ErpProductServiceImpl implements ErpProductService {
// 自动生成才拼接 分类编码-流水号;手工输入则原样保存
if (autoGeneratedCode) {
product.setBarCode(productCategory.getCode() + "-" + code);
product.setBarCode(sample ? code : productCategory.getCode() + "-" + code);
} else {
product.setBarCode(code);
}
@ -214,9 +219,9 @@ public class ErpProductServiceImpl implements ErpProductService {
saveProductSuppliers(product.getId(), createReqVO.getSuppliers(), createReqVO.getDefaultSupplierId());
// 生成二维码
CodeTypeEnum codeType = autoCodeUtil.queryCodeType("PRODUCT_CODE_GENERATE");
CodeTypeEnum codeType = autoCodeUtil.queryCodeType(ruleCode);
if (codeType==null){
log.warn("[创建产品物料]未配置码类型,跳过生ruleCode={}","PRODUCT_CODE_GENERATE");
log.warn("[创建产品物料]未配置码类型,跳过生ruleCode={}", ruleCode);
return product.getId();
}
@ -390,8 +395,7 @@ public class ErpProductServiceImpl implements ErpProductService {
ErpProductCategoryDO erpProductCategoryDO = productCategoryMapper.selectOne(Wrappers.<ErpProductCategoryDO>lambdaQuery().eq(ErpProductCategoryDO::getId, product.getSubCategoryId()));
String qrcodeUrl = qrcodeService.selectQrcodeUrlByIdAndCode(
QrcodeBizTypeEnum.PRODUCT.getCode(), id, product.getBarCode());
String qrcodeUrl = resolveProductCodeUrl(product);
product.setQrcodeUrl(qrcodeUrl);
ErpProductRespVO respVO = BeanUtils.toBean(product, ErpProductRespVO.class);
@ -541,6 +545,11 @@ public class ErpProductServiceImpl implements ErpProductService {
ErpProductListReqVO reqVO = new ErpProductListReqVO();
reqVO.setCategoryId(categoryId == null ? null : categoryId.longValue());
reqVO.setCategoryType(categoryType);
return getProductVOList(reqVO);
}
@Override
public List<ErpProductRespVO> getProductVOList(ErpProductListReqVO reqVO) {
List<ErpProductDO> list = productMapper.selectList(reqVO);
return buildProductVOList(list);
}
@ -788,24 +797,53 @@ public class ErpProductServiceImpl implements ErpProductService {
@Override
public void regenerateCode(Long id, String code) throws UnsupportedEncodingException {
if(productMapper.selectById(id)==null){
ErpProductDO product = productMapper.selectById(id);
if(product == null){
throw exception(PRODUCT_NOT_EXISTS);
}
if(StringUtils.isBlank(code)){
throw exception(PRODUCT_CODE_NOT_EXISTS);
}
CodeTypeEnum moldCodeGenerate = autoCodeUtil.queryCodeType("PRODUCT_CODE_GENERATE");
//s
CodeTypeEnum codeType = autoCodeUtil.queryCodeType(resolveProductRuleCode(product));
if (codeType == null) {
log.warn("[刷新产品物料码图]未配置码类型跳过生成productId={}", id);
return;
}
qrcodeService.regenerateByCodeType(
QrcodeBizTypeEnum.PRODUCT,
id,
code,
"DETAIL",
moldCodeGenerate.getCode()
codeType.getCode()
);
}
private String resolveProductCodeUrl(ErpProductDO product) {
CodeTypeEnum codeType = autoCodeUtil.queryCodeType(resolveProductRuleCode(product));
if (codeType == null) {
return qrcodeService.selectQrcodeUrlByIdAndCode(
QrcodeBizTypeEnum.PRODUCT.getCode(), product.getId(), product.getBarCode());
}
String codeUrl = qrcodeService.selectQrcodeUrlByIdAndCodeAndType(
QrcodeBizTypeEnum.PRODUCT.getCode(), product.getId(), product.getBarCode(), codeType);
if (StrUtil.isNotBlank(codeUrl)) {
return codeUrl;
}
try {
qrcodeService.generateOrRefresh(QrcodeBizTypeEnum.PRODUCT, product.getId(), product.getBarCode(),
"DETAIL", codeType);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
return qrcodeService.selectQrcodeUrlByIdAndCodeAndType(
QrcodeBizTypeEnum.PRODUCT.getCode(), product.getId(), product.getBarCode(), codeType);
}
private String resolveProductRuleCode(ErpProductDO product) {
return Boolean.TRUE.equals(product.getIsSample()) ? BIT_SAMPLE_CODE_RULE : PRODUCT_CODE_RULE;
}
private ErpProductDO convertToProductDO(ErpProductImportExcelVO importProduct) {
ErpProductDO productDO = BeanUtils.toBean(importProduct, ErpProductDO.class);

@ -420,8 +420,13 @@ public class ErpStockInServiceImpl implements ErpStockInService {
if (bizType != null) {
return bizType;
}
return approve ? ErpStockRecordBizTypeEnum.getTypeByName(inType)
Integer recordBizType = approve ? ErpStockRecordBizTypeEnum.getTypeByName(inType)
: ErpStockRecordBizTypeEnum.getTypeByName(inType, 10);
if (recordBizType != null) {
return recordBizType;
}
return approve ? ErpStockRecordBizTypeEnum.OTHER_IN.getType()
: ErpStockRecordBizTypeEnum.OTHER_IN_CANCEL.getType();
}
private boolean itemNeedUpdateMoldStatus(MoldBrandDO moldDO) {

@ -131,11 +131,15 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
ErpProductDO productDO = productService.getProduct(stockMoveItem.getProductId());
stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
stockMoveItem.getProductId(),productDO.getCategoryId(), stockMoveItem.getFromWarehouseId(), fromCount,
fromBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo(),stockMove.getMoveTime()));
stockMoveItem.getProductId(), productDO.getCategoryId(), null,
stockMoveItem.getFromWarehouseId(), stockMoveItem.getFromAreaId(), stockMoveItem.getFromAreaName(),
fromCount, fromBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo(),
stockMove.getMoveTime()));
stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
stockMoveItem.getProductId(),productDO.getCategoryId(), stockMoveItem.getToWarehouseId(), toCount,
toBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo(),stockMove.getMoveTime()));
stockMoveItem.getProductId(), productDO.getCategoryId(), null,
stockMoveItem.getToWarehouseId(), stockMoveItem.getToAreaId(), stockMoveItem.getToAreaName(),
toCount, toBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo(),
stockMove.getMoveTime()));
});
}
@ -228,4 +232,4 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
return stockMoveItemMapper.selectListByMoveIds(moveIds);
}
}
}

@ -420,6 +420,10 @@ public class ErpStockOutServiceImpl implements ErpStockOutService {
Integer recordBizType = bizType != null ? bizType
: (approve ? ErpStockRecordBizTypeEnum.getTypeByName(stockOut.getOutType())
: ErpStockRecordBizTypeEnum.getTypeByName(stockOut.getOutType(), 10));
if (recordBizType == null) {
recordBizType = approve ? ErpStockRecordBizTypeEnum.OTHER_OUT.getType()
: ErpStockRecordBizTypeEnum.OTHER_OUT_CANCEL.getType();
}
stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
stockOutItem.getProductId(), product.getCategoryId(), product.getCategoryType(), stockOutItem.getWarehouseId(),
stockOutItem.getAreaId(), stockOutItem.getAreaName(), count,

@ -2,6 +2,9 @@ package cn.iocoder.yudao.module.erp.service.warehouselocation;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.common.enums.CodeTypeEnum;
import cn.iocoder.yudao.module.common.enums.QrcodeBizTypeEnum;
import cn.iocoder.yudao.module.common.service.qrcordrecord.QrcodeRecordService;
import cn.iocoder.yudao.module.erp.controller.admin.autocode.util.AutoCodeUtil;
import cn.iocoder.yudao.module.erp.controller.admin.warehouselocation.vo.WarehouseLocationPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.warehouselocation.vo.WarehouseLocationSaveReqVO;
@ -14,6 +17,7 @@ import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.io.UnsupportedEncodingException;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -26,6 +30,8 @@ import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.WAREHOUSE_LOC
@Validated
public class WarehouseLocationServiceImpl implements WarehouseLocationService {
private static final String BIT_WAREHOUSE_LOCATION_CODE_RULE = "BIT_WAREHOUSE_LOCATION_CODE";
@Resource
private WarehouseLocationMapper warehouseLocationMapper;
@ -35,6 +41,9 @@ public class WarehouseLocationServiceImpl implements WarehouseLocationService {
@Resource
private AutoCodeUtil autoCodeUtil;
@Resource
private QrcodeRecordService qrcodeRecordService;
@Override
public Long createWarehouseLocation(WarehouseLocationSaveReqVO createReqVO) {
validateWarehouseAreaMatch(createReqVO.getWarehouseId(), createReqVO.getAreaId());
@ -45,6 +54,7 @@ public class WarehouseLocationServiceImpl implements WarehouseLocationService {
throw exception(WAREHOUSE_LOCATION_CODE_EXISTS);
}
warehouseLocationMapper.insert(warehouseLocation);
generateWarehouseLocationQrcode(warehouseLocation.getId(), warehouseLocation.getCode());
return warehouseLocation.getId();
}
@ -52,7 +62,9 @@ public class WarehouseLocationServiceImpl implements WarehouseLocationService {
public void updateWarehouseLocation(WarehouseLocationSaveReqVO updateReqVO) {
validateWarehouseLocationExists(updateReqVO.getId());
validateWarehouseAreaMatch(updateReqVO.getWarehouseId(), updateReqVO.getAreaId());
warehouseLocationMapper.updateById(BeanUtils.toBean(updateReqVO, WarehouseLocationDO.class));
WarehouseLocationDO updateObj = BeanUtils.toBean(updateReqVO, WarehouseLocationDO.class);
warehouseLocationMapper.updateById(updateObj);
generateWarehouseLocationQrcode(updateObj.getId(), updateObj.getCode());
}
@Override
@ -92,4 +104,24 @@ public class WarehouseLocationServiceImpl implements WarehouseLocationService {
return warehouseLocationMapper.selectPage(pageReqVO);
}
private void generateWarehouseLocationQrcode(Long id, String code) {
if (StringUtils.isBlank(code)) {
return;
}
CodeTypeEnum codeType = autoCodeUtil.queryCodeType(BIT_WAREHOUSE_LOCATION_CODE_RULE);
if (codeType == null) {
return;
}
try {
qrcodeRecordService.generateOrRefresh(
QrcodeBizTypeEnum.WAREHOUSE_LOCATION,
id,
code,
"DETAIL",
codeType);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}

@ -9,4 +9,12 @@
文档可见https://www.iocoder.cn/MyBatis/x-plugins/
-->
</mapper>
<select id="selectPrintTemplate" resultType="java.lang.String">
select template_json
from mes_print_template
where deleted = 0
and template_type = #{templateType}
and template_biz_type = 1
</select>
</mapper>

@ -0,0 +1,424 @@
package cn.iocoder.yudao.module.erp.service.bitwms;
import cn.iocoder.yudao.framework.test.core.util.AssertUtils;
import cn.iocoder.yudao.module.common.enums.CodeTypeEnum;
import cn.iocoder.yudao.module.common.enums.QrcodeBizTypeEnum;
import cn.iocoder.yudao.module.common.service.qrcordrecord.QrcodeRecordService;
import cn.iocoder.yudao.module.erp.controller.admin.autocode.util.AutoCodeUtil;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsInboundReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLabelRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsMoveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsOutboundReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsScanRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInAuditReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInSaveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveSaveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutAuditReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductUnitDO;
import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.warehouselocation.WarehouseLocationMapper;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.warehouselocation.WarehouseLocationDO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
import cn.iocoder.yudao.module.erp.service.product.ErpProductUnitService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockInService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockMoveService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockOutService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockService;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.PRODUCT_CODE_NOT_EXISTS;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.PRODUCT_UNIT_NOT_EXISTS;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_COUNT_NEGATIVE2;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.WAREHOUSE_LOCATION_NOT_EXISTS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* {@link BitWmsServiceImpl}
*/
@ExtendWith(MockitoExtension.class)
class BitWmsServiceImplTest {
@InjectMocks
private BitWmsServiceImpl bitWmsService;
@Mock
private ErpProductService productService;
@Mock
private ErpProductUnitService productUnitService;
@Mock
private ErpProductMapper productMapper;
@Mock
private WarehouseLocationMapper warehouseLocationMapper;
@Mock
private ErpStockInService stockInService;
@Mock
private ErpStockOutService stockOutService;
@Mock
private ErpStockMoveService stockMoveService;
@Mock
private ErpStockService stockService;
@Mock
private QrcodeRecordService qrcodeRecordService;
@Mock
private AutoCodeUtil autoCodeUtil;
@Test
void scan_whenProductQrcodeBizKey_returnsSample() {
when(productMapper.selectById(332L)).thenReturn(buildBitSampleProductDO(332L));
when(productUnitService.getProductUnit(5L)).thenReturn(buildProductUnit());
BitWmsScanRespVO respVO = bitWmsService.scan(" PRODUCTMATERIAL-332 ");
assertEquals("SAMPLE", respVO.getType());
assertEquals(332L, respVO.getId());
assertEquals("SP001", respVO.getCode());
assertEquals("BIT 样品", respVO.getName());
assertEquals(5L, respVO.getUnitId());
assertEquals("个", respVO.getUnitName());
}
@Test
void scan_whenLocationQrcodeBizKey_returnsLocation() {
when(warehouseLocationMapper.selectById(5L)).thenReturn(new WarehouseLocationDO()
.setId(5L).setCode("KW001").setName("必硕三楼 A01"));
BitWmsScanRespVO respVO = bitWmsService.scan("warehouse_location_5");
assertEquals("LOCATION", respVO.getType());
assertEquals(5L, respVO.getId());
assertEquals("KW001", respVO.getCode());
assertEquals("必硕三楼 A01", respVO.getName());
}
@Test
void scan_whenProductQrcodeBizKeyIsNotBitSample_throwsException() {
ErpProductDO product = buildBitSampleProductDO(332L);
product.setIsSample(false);
when(productMapper.selectById(332L)).thenReturn(product);
AssertUtils.assertServiceException(() -> bitWmsService.scan("PRODUCTMATERIAL-332"), PRODUCT_CODE_NOT_EXISTS);
}
@Test
void scan_whenPlainSampleCode_returnsSample() {
when(productMapper.selectOne(org.mockito.ArgumentMatchers.<SFunction<ErpProductDO, ?>>any(), eq("SP001")))
.thenReturn(buildBitSampleProductDO(332L));
when(productUnitService.getProductUnit(5L)).thenReturn(buildProductUnit());
BitWmsScanRespVO respVO = bitWmsService.scan("SP001");
assertEquals("SAMPLE", respVO.getType());
assertEquals(332L, respVO.getId());
assertEquals("SP001", respVO.getCode());
assertEquals("BIT 样品", respVO.getName());
assertEquals(5L, respVO.getUnitId());
assertEquals("个", respVO.getUnitName());
}
@Test
void scan_whenPlainLocationCode_returnsLocation() {
when(warehouseLocationMapper.selectByNo("KW001")).thenReturn(new WarehouseLocationDO()
.setId(5L).setCode("KW001").setName("必硕三楼 A01"));
BitWmsScanRespVO respVO = bitWmsService.scan("KW001");
assertEquals("LOCATION", respVO.getType());
assertEquals(5L, respVO.getId());
assertEquals("KW001", respVO.getCode());
assertEquals("必硕三楼 A01", respVO.getName());
}
@Test
void inbound_createsSampleStockInWithBitType() {
BitWmsInboundReqVO reqVO = new BitWmsInboundReqVO();
reqVO.setProductId(10L);
reqVO.setWarehouseId(20L);
reqVO.setAreaId(30L);
reqVO.setCount(new BigDecimal("2"));
reqVO.setUnitId(5L);
reqVO.setRemark("first shelf");
ErpProductRespVO product = buildBitSampleProduct();
when(productService.getProduct(10L)).thenReturn(product);
when(stockInService.createStockIn(org.mockito.ArgumentMatchers.any(ErpStockInSaveReqVO.class))).thenReturn(100L);
Long id = bitWmsService.inbound(reqVO);
assertEquals(100L, id);
ArgumentCaptor<ErpStockInSaveReqVO> captor = ArgumentCaptor.forClass(ErpStockInSaveReqVO.class);
verify(stockInService).createStockIn(captor.capture());
ErpStockInSaveReqVO stockIn = captor.getValue();
assertEquals("样品入库", stockIn.getInType());
assertEquals(1, stockIn.getItems().size());
assertEquals(10L, stockIn.getItems().get(0).getProductId());
assertEquals(20L, stockIn.getItems().get(0).getWarehouseId());
assertEquals(30L, stockIn.getItems().get(0).getAreaId());
assertEquals(new BigDecimal("2"), stockIn.getItems().get(0).getCount());
assertEquals(false, stockIn.getItems().get(0).getRelateTask());
ArgumentCaptor<ErpStockInAuditReqVO> auditCaptor = ArgumentCaptor.forClass(ErpStockInAuditReqVO.class);
verify(stockInService).auditStockIn(auditCaptor.capture());
assertEquals(100L, auditCaptor.getValue().getId());
assertEquals(ErpAuditStatus.APPROVE.getStatus(), auditCaptor.getValue().getStatus());
assertEquals("first shelf", auditCaptor.getValue().getRemark());
}
@Test
void inbound_whenLocationSelected_usesLocationWarehouseAndArea() {
BitWmsInboundReqVO reqVO = new BitWmsInboundReqVO();
reqVO.setProductId(10L);
reqVO.setWarehouseId(999L);
reqVO.setAreaId(998L);
reqVO.setLocationId(88L);
reqVO.setCount(new BigDecimal("2"));
reqVO.setUnitId(5L);
when(productService.getProduct(10L)).thenReturn(buildBitSampleProduct());
when(warehouseLocationMapper.selectById(88L)).thenReturn(new WarehouseLocationDO()
.setId(88L).setWarehouseId(20L).setAreaId(30L));
when(stockInService.createStockIn(org.mockito.ArgumentMatchers.any(ErpStockInSaveReqVO.class))).thenReturn(100L);
bitWmsService.inbound(reqVO);
ArgumentCaptor<ErpStockInSaveReqVO> captor = ArgumentCaptor.forClass(ErpStockInSaveReqVO.class);
verify(stockInService).createStockIn(captor.capture());
ErpStockInSaveReqVO stockIn = captor.getValue();
assertEquals(20L, stockIn.getWarehouseId());
assertEquals(20L, stockIn.getItems().get(0).getWarehouseId());
assertEquals(30L, stockIn.getItems().get(0).getAreaId());
assertEquals(false, stockIn.getItems().get(0).getRelateTask());
}
@Test
void inbound_whenRemarkBlank_autoApprovesWithDefaultRemark() {
BitWmsInboundReqVO reqVO = new BitWmsInboundReqVO();
reqVO.setProductId(10L);
reqVO.setWarehouseId(20L);
reqVO.setAreaId(30L);
reqVO.setCount(new BigDecimal("2"));
reqVO.setUnitId(5L);
when(productService.getProduct(10L)).thenReturn(buildBitSampleProduct());
when(stockInService.createStockIn(org.mockito.ArgumentMatchers.any(ErpStockInSaveReqVO.class))).thenReturn(100L);
bitWmsService.inbound(reqVO);
ArgumentCaptor<ErpStockInAuditReqVO> auditCaptor = ArgumentCaptor.forClass(ErpStockInAuditReqVO.class);
verify(stockInService).auditStockIn(auditCaptor.capture());
assertEquals(100L, auditCaptor.getValue().getId());
assertEquals(ErpAuditStatus.APPROVE.getStatus(), auditCaptor.getValue().getStatus());
assertEquals("样品入库自动入库", auditCaptor.getValue().getRemark());
}
@Test
void inbound_whenUnitDoesNotMatchSampleUnit_throwsException() {
BitWmsInboundReqVO reqVO = new BitWmsInboundReqVO();
reqVO.setProductId(10L);
reqVO.setWarehouseId(20L);
reqVO.setAreaId(30L);
reqVO.setCount(new BigDecimal("2"));
reqVO.setUnitId(99L);
when(productService.getProduct(10L)).thenReturn(buildBitSampleProduct());
AssertUtils.assertServiceException(() -> bitWmsService.inbound(reqVO), PRODUCT_UNIT_NOT_EXISTS);
}
@Test
void outbound_rejectsCountGreaterThanLocationStock() {
BitWmsOutboundReqVO reqVO = new BitWmsOutboundReqVO();
reqVO.setProductId(10L);
reqVO.setWarehouseId(999L);
reqVO.setAreaId(998L);
reqVO.setLocationId(88L);
reqVO.setCount(new BigDecimal("999"));
reqVO.setOutReason("TEST");
when(productService.getProduct(10L)).thenReturn(buildBitSampleProduct());
when(warehouseLocationMapper.selectById(88L)).thenReturn(new WarehouseLocationDO()
.setId(88L).setWarehouseId(20L).setAreaId(30L));
when(stockService.getStock(10L, 20L, 30L)).thenReturn(new ErpStockDO().setCount(BigDecimal.ONE));
AssertUtils.assertServiceException(() -> bitWmsService.outbound(reqVO), STOCK_COUNT_NEGATIVE2, 10L, 20L);
}
@Test
void outbound_createsSampleStockOutAndAutoApproves() {
BitWmsOutboundReqVO reqVO = new BitWmsOutboundReqVO();
reqVO.setProductId(10L);
reqVO.setWarehouseId(999L);
reqVO.setAreaId(998L);
reqVO.setLocationId(88L);
reqVO.setCount(new BigDecimal("1"));
reqVO.setOutReason("TEST");
reqVO.setRemark("out remark");
when(productService.getProduct(10L)).thenReturn(buildBitSampleProduct());
when(warehouseLocationMapper.selectById(88L)).thenReturn(new WarehouseLocationDO()
.setId(88L).setWarehouseId(20L).setAreaId(30L));
when(stockService.getStock(10L, 20L, 30L)).thenReturn(new ErpStockDO().setCount(new BigDecimal("2")));
when(stockOutService.createStockOut(org.mockito.ArgumentMatchers.any(ErpStockOutSaveReqVO.class))).thenReturn(200L);
Long id = bitWmsService.outbound(reqVO);
assertEquals(200L, id);
ArgumentCaptor<ErpStockOutSaveReqVO> stockOutCaptor = ArgumentCaptor.forClass(ErpStockOutSaveReqVO.class);
verify(stockOutService).createStockOut(stockOutCaptor.capture());
ErpStockOutSaveReqVO stockOut = stockOutCaptor.getValue();
assertEquals("样品出库", stockOut.getOutType());
assertEquals("出库原因TESTout remark", stockOut.getRemark());
assertEquals(1, stockOut.getItems().size());
assertEquals(10L, stockOut.getItems().get(0).getProductId());
assertEquals(20L, stockOut.getItems().get(0).getWarehouseId());
assertEquals(30L, stockOut.getItems().get(0).getAreaId());
assertEquals(new BigDecimal("1"), stockOut.getItems().get(0).getCount());
ArgumentCaptor<ErpStockOutAuditReqVO> auditCaptor = ArgumentCaptor.forClass(ErpStockOutAuditReqVO.class);
verify(stockOutService).auditStockOut(auditCaptor.capture());
assertEquals(200L, auditCaptor.getValue().getId());
assertEquals(ErpAuditStatus.APPROVE.getStatus(), auditCaptor.getValue().getStatus());
assertEquals("out remark", auditCaptor.getValue().getRemark());
}
@Test
void outbound_whenLocationNotExists_throwsException() {
BitWmsOutboundReqVO reqVO = new BitWmsOutboundReqVO();
reqVO.setProductId(10L);
reqVO.setWarehouseId(20L);
reqVO.setAreaId(30L);
reqVO.setLocationId(88L);
reqVO.setCount(new BigDecimal("1"));
reqVO.setOutReason("TEST");
when(productService.getProduct(10L)).thenReturn(buildBitSampleProduct());
when(warehouseLocationMapper.selectById(88L)).thenReturn(null);
AssertUtils.assertServiceException(() -> bitWmsService.outbound(reqVO), WAREHOUSE_LOCATION_NOT_EXISTS);
}
@Test
void move_keepsTotalStockAndChangesLocation() {
BitWmsMoveReqVO reqVO = new BitWmsMoveReqVO();
reqVO.setProductId(10L);
reqVO.setFromWarehouseId(20L);
reqVO.setFromAreaId(30L);
reqVO.setToWarehouseId(20L);
reqVO.setToAreaId(31L);
reqVO.setCount(new BigDecimal("1"));
ErpProductRespVO product = buildBitSampleProduct();
when(productService.getProduct(10L)).thenReturn(product);
when(stockService.getStock(10L, 20L, 30L)).thenReturn(new ErpStockDO().setCount(new BigDecimal("2")));
when(stockMoveService.createStockMove(org.mockito.ArgumentMatchers.any(ErpStockMoveSaveReqVO.class))).thenReturn(300L);
Long id = bitWmsService.move(reqVO);
assertEquals(300L, id);
ArgumentCaptor<ErpStockMoveSaveReqVO> captor = ArgumentCaptor.forClass(ErpStockMoveSaveReqVO.class);
verify(stockMoveService).createStockMove(captor.capture());
ErpStockMoveSaveReqVO.Item item = captor.getValue().getItems().get(0);
assertEquals(20L, item.getFromWarehouseId());
assertEquals(30L, item.getFromAreaId());
assertEquals(20L, item.getToWarehouseId());
assertEquals(31L, item.getToAreaId());
assertEquals(new BigDecimal("1"), item.getCount());
verify(stockMoveService).updateStockMoveStatus(eq(300L), eq(ErpAuditStatus.APPROVE.getStatus()));
}
@Test
void getSampleLabel_whenRuleIsQr_returnsQrUrl() throws Exception {
ErpProductRespVO product = buildBitSampleProduct();
when(productService.getProduct(10L)).thenReturn(product);
when(autoCodeUtil.queryCodeType("BIT_SAMPLE_CODE")).thenReturn(CodeTypeEnum.QR);
when(qrcodeRecordService.selectQrcodeUrlByIdAndCodeAndType(
QrcodeBizTypeEnum.PRODUCT.getCode(), 10L, "SP001", CodeTypeEnum.QR))
.thenReturn("https://file/qr.png");
when(productMapper.selectPrintTemplate(7)).thenReturn("{\"panels\":[]}");
BitWmsLabelRespVO label = bitWmsService.getSampleLabel(10L);
assertEquals("https://file/qr.png", label.getQrcodeUrl());
assertEquals("https://file/qr.png", label.getPrintData().get("qrcodeUrl"));
verify(qrcodeRecordService, never()).generateOrRefresh(
QrcodeBizTypeEnum.PRODUCT, 10L, "SP001", "DETAIL", CodeTypeEnum.QR);
}
@Test
void getSampleLabel_whenRuleIsQrAndQrMissing_generatesQrThenReturnsUrl() throws Exception {
ErpProductRespVO product = buildBitSampleProduct();
when(productService.getProduct(10L)).thenReturn(product);
when(autoCodeUtil.queryCodeType("BIT_SAMPLE_CODE")).thenReturn(CodeTypeEnum.QR);
when(qrcodeRecordService.selectQrcodeUrlByIdAndCodeAndType(
QrcodeBizTypeEnum.PRODUCT.getCode(), 10L, "SP001", CodeTypeEnum.QR))
.thenReturn(null)
.thenReturn("https://file/qr.png");
BitWmsLabelRespVO label = bitWmsService.getSampleLabel(10L);
assertEquals("https://file/qr.png", label.getQrcodeUrl());
verify(qrcodeRecordService).generateOrRefresh(
QrcodeBizTypeEnum.PRODUCT, 10L, "SP001", "DETAIL", CodeTypeEnum.QR);
}
@Test
void getSampleLabel_whenRuleIsBarcode_returnsBarcodeUrl() throws Exception {
ErpProductRespVO product = buildBitSampleProduct();
when(productService.getProduct(10L)).thenReturn(product);
when(autoCodeUtil.queryCodeType("BIT_SAMPLE_CODE")).thenReturn(CodeTypeEnum.BARCODE);
when(qrcodeRecordService.selectQrcodeUrlByIdAndCodeAndType(
QrcodeBizTypeEnum.PRODUCT.getCode(), 10L, "SP001", CodeTypeEnum.BARCODE))
.thenReturn("https://file/barcode.png");
BitWmsLabelRespVO label = bitWmsService.getSampleLabel(10L);
assertEquals("https://file/barcode.png", label.getQrcodeUrl());
assertEquals("https://file/barcode.png", label.getPrintData().get("qrcodeUrl"));
}
private ErpProductRespVO buildBitSampleProduct() {
ErpProductRespVO product = new ErpProductRespVO();
product.setId(10L);
product.setName("BIT 样品");
product.setBarCode("SP001");
product.setIsSample(true);
product.setBizUnit("BIT");
product.setCategoryId(50L);
product.setUnitId(5L);
product.setUnitName("个");
return product;
}
private ErpProductDO buildBitSampleProductDO(Long id) {
return new ErpProductDO()
.setId(id)
.setName("BIT 样品")
.setBarCode("SP001")
.setIsSample(true)
.setBizUnit("BIT")
.setUnitId(5L);
}
private ErpProductUnitDO buildProductUnit() {
ErpProductUnitDO unit = new ErpProductUnitDO();
unit.setId(5L);
unit.setName("个");
return unit;
}
}

@ -0,0 +1,151 @@
package cn.iocoder.yudao.module.erp.service.product;
import cn.iocoder.yudao.module.common.enums.CodeTypeEnum;
import cn.iocoder.yudao.module.common.enums.QrcodeBizTypeEnum;
import cn.iocoder.yudao.module.common.service.qrcordrecord.QrcodeRecordService;
import cn.iocoder.yudao.module.erp.controller.admin.autocode.util.AutoCodeUtil;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ProductSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductCategoryDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductUnitDO;
import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductCategoryMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.productpackagingschemerel.ProductPackagingSchemeRelMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.productsupplierrel.ProductSupplierRelMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* {@link ErpProductServiceImpl}
*/
@ExtendWith(MockitoExtension.class)
class ErpProductServiceImplTest {
@InjectMocks
private ErpProductServiceImpl productService;
@Mock
private ErpProductMapper productMapper;
@Mock
private ErpProductCategoryMapper productCategoryMapper;
@Mock
private ErpProductUnitService productUnitService;
@Mock
private QrcodeRecordService qrcodeService;
@Mock
private AutoCodeUtil autoCodeUtil;
@Mock
private ProductPackagingSchemeRelMapper productPackagingSchemeRelMapper;
@Mock
private ProductSupplierRelMapper productSupplierRelMapper;
@Test
void createProduct_whenBitSampleAndNoCode_generatesSampleCodeAndStoresMetadata() throws Exception {
ProductSaveReqVO reqVO = new ProductSaveReqVO();
reqVO.setName("BIT 样品");
reqVO.setBarCode(null);
reqVO.setIsSample(true);
reqVO.setSampleCategory("CUSTOMER_SAMPLE");
reqVO.setBizUnit("BIT");
reqVO.setCustomerId(1001L);
reqVO.setCustomerName("ACME HK");
reqVO.setCategoryId(10L);
reqVO.setUnitId(20L);
reqVO.setStatus(0);
reqVO.setStandard("STD-1");
ErpProductUnitDO unit = new ErpProductUnitDO();
unit.setId(20L);
unit.setName("PCS");
when(productUnitService.getProductUnit(20L)).thenReturn(unit);
ErpProductCategoryDO category = new ErpProductCategoryDO();
category.setId(10L);
category.setName("测试分类");
category.setCode("CAT");
when(productCategoryMapper.selectById(10L)).thenReturn(category);
when(productMapper.selectProductExist(any(ErpProductDO.class))).thenReturn(false);
when(autoCodeUtil.genSerialCode("BIT_SAMPLE_CODE", null)).thenReturn("SP202606220001");
when(autoCodeUtil.queryCodeType("BIT_SAMPLE_CODE")).thenReturn(CodeTypeEnum.QR);
when(productMapper.insert(any(ErpProductDO.class))).thenAnswer(invocation -> {
ErpProductDO product = invocation.getArgument(0);
product.setId(100L);
return 1;
});
Long id = productService.createProduct(reqVO);
assertEquals(100L, id);
ArgumentCaptor<ErpProductDO> productCaptor = ArgumentCaptor.forClass(ErpProductDO.class);
verify(productMapper).insert(productCaptor.capture());
ErpProductDO product = productCaptor.getValue();
assertTrue(product.getIsSample());
assertEquals("CUSTOMER_SAMPLE", product.getSampleCategory());
assertEquals("BIT", product.getBizUnit());
assertEquals(1001L, product.getCustomerId());
assertEquals("ACME HK", product.getCustomerName());
assertEquals("SP202606220001", product.getBarCode());
verify(qrcodeService).generateOrRefresh(
eq(QrcodeBizTypeEnum.PRODUCT),
eq(100L),
eq("SP202606220001"),
eq("DETAIL"),
eq(CodeTypeEnum.QR));
}
@Test
void getProduct_whenBitSampleRuleIsQr_returnsQrUrl() {
ErpProductDO product = new ErpProductDO();
product.setId(100L);
product.setName("BIT 样品");
product.setBarCode("SP202606220001");
product.setIsSample(true);
when(productMapper.selectById(100L)).thenReturn(product);
when(autoCodeUtil.queryCodeType("BIT_SAMPLE_CODE")).thenReturn(CodeTypeEnum.QR);
when(qrcodeService.selectQrcodeUrlByIdAndCodeAndType(
QrcodeBizTypeEnum.PRODUCT.getCode(), 100L, "SP202606220001", CodeTypeEnum.QR))
.thenReturn("https://file/sample-qr.png");
when(productMapper.selectDevicesByProductId(100L)).thenReturn(Collections.emptyList());
when(productMapper.selectMoldsByProductId(100L)).thenReturn(Collections.emptyList());
when(productPackagingSchemeRelMapper.selectListByProductId(100L)).thenReturn(Collections.emptyList());
when(productSupplierRelMapper.selectListByProductId(100L)).thenReturn(Collections.emptyList());
assertEquals("https://file/sample-qr.png", productService.getProduct(100L).getQrcodeUrl());
}
@Test
void regenerateCode_whenBitSampleRuleIsQr_regeneratesQr() throws Exception {
ErpProductDO product = new ErpProductDO();
product.setId(100L);
product.setBarCode("SP202606220001");
product.setIsSample(true);
when(productMapper.selectById(100L)).thenReturn(product);
when(autoCodeUtil.queryCodeType("BIT_SAMPLE_CODE")).thenReturn(CodeTypeEnum.QR);
productService.regenerateCode(100L, "SP202606220001");
verify(qrcodeService).regenerateByCodeType(
QrcodeBizTypeEnum.PRODUCT,
100L,
"SP202606220001",
"DETAIL",
CodeTypeEnum.QR.getCode());
}
}

@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.erp.service.warehouselocation;
import cn.iocoder.yudao.module.common.enums.CodeTypeEnum;
import cn.iocoder.yudao.module.common.enums.QrcodeBizTypeEnum;
import cn.iocoder.yudao.module.common.service.qrcordrecord.QrcodeRecordService;
import cn.iocoder.yudao.module.erp.controller.admin.autocode.util.AutoCodeUtil;
import cn.iocoder.yudao.module.erp.controller.admin.warehouselocation.vo.WarehouseLocationSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.warehousearea.WarehouseAreaDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.warehouselocation.WarehouseLocationDO;
import cn.iocoder.yudao.module.erp.dal.mysql.warehousearea.WarehouseAreaMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.warehouselocation.WarehouseLocationMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* {@link WarehouseLocationServiceImpl}
*/
@ExtendWith(MockitoExtension.class)
class WarehouseLocationServiceImplTest {
@InjectMocks
private WarehouseLocationServiceImpl warehouseLocationService;
@Mock
private WarehouseLocationMapper warehouseLocationMapper;
@Mock
private WarehouseAreaMapper warehouseAreaMapper;
@Mock
private AutoCodeUtil autoCodeUtil;
@Mock
private QrcodeRecordService qrcodeRecordService;
@Test
void createWarehouseLocation_generatesQrcodeRecord() throws Exception {
WarehouseLocationSaveReqVO reqVO = new WarehouseLocationSaveReqVO();
reqVO.setCode("A1");
reqVO.setName("A1");
reqVO.setWarehouseId(1L);
reqVO.setAreaId(1L);
reqVO.setAllowProductMix(false);
reqVO.setAllowBatchMix(false);
reqVO.setStatus(0);
WarehouseAreaDO area = new WarehouseAreaDO();
area.setId(1L);
area.setWarehouseId(1L);
when(warehouseAreaMapper.selectById(1L)).thenReturn(area);
when(autoCodeUtil.queryCodeType("BIT_WAREHOUSE_LOCATION_CODE")).thenReturn(CodeTypeEnum.QR);
when(warehouseLocationMapper.insert(any(WarehouseLocationDO.class))).thenAnswer(invocation -> {
WarehouseLocationDO location = invocation.getArgument(0);
location.setId(100L);
return 1;
});
Long id = warehouseLocationService.createWarehouseLocation(reqVO);
assertEquals(100L, id);
verify(qrcodeRecordService).generateOrRefresh(
eq(QrcodeBizTypeEnum.WAREHOUSE_LOCATION),
eq(100L),
eq("A1"),
eq("DETAIL"),
eq(CodeTypeEnum.QR));
}
}

@ -1,12 +1,15 @@
package cn.iocoder.yudao.module.iot.controller.admin.device;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit.ErpProductUnitImportExcelVO;
import cn.iocoder.yudao.module.infra.service.job.JobService;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.*;
import cn.iocoder.yudao.module.iot.controller.admin.devicecontactmodel.vo.DeviceAttributeImportExcelVO;
import cn.iocoder.yudao.module.iot.controller.admin.devicecontactmodel.vo.DeviceContactModelPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceAttributeDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO;
@ -17,12 +20,14 @@ import cn.iocoder.yudao.module.iot.service.device.TDengineService;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.quartz.SchedulerException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
@ -357,6 +362,33 @@ public class DeviceController {
return success(deviceService.getDeviceAttribute(id));
}
@GetMapping( "/device-attribute/get-import-template")
@Operation(summary = "获得导入设备属性模板")
public void importTemplate(HttpServletResponse response) throws IOException {
// 手动创建导出 demo
List<DeviceAttributeImportExcelVO> list = Arrays.asList(
DeviceAttributeImportExcelVO.builder().attributeCode("StackCount").attributeName("堆叠数量").attributeType("产量统计").dataType("int")
.address("").dataUnit("kg").ratio(0.00).remark("").build()
);
// 输出
ExcelUtils.write(response, "设备点位导入模板.xls", "设备点位列表", DeviceAttributeImportExcelVO.class, list);
}
@PostMapping("/device-attribute/import")
@Operation(summary = "导入设备属性")
@Parameters({
@Parameter(name = "file", description = "Excel 文件", required = true),
@Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true")
})
//@PreAuthorize("@ss.hasPermission('')")
public CommonResult<String> importExcel(@RequestParam("file") MultipartFile file,
@RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport,
@RequestParam("deviceId") Long deviceId) throws Exception {
List<DeviceAttributeImportExcelVO> list = ExcelUtils.read(file, DeviceAttributeImportExcelVO.class);
deviceService.importDeviceAttributeList(list, updateSupport, deviceId);
return success("导入成功");
}
@GetMapping("/devicePointList")
@PreAuthorize("@ss.hasPermission('iot:device:query')")
public CommonResult<List<DevicePointRespVO>> devicePointList() {

@ -0,0 +1,79 @@
package cn.iocoder.yudao.module.iot.controller.admin.devicecontactmodel.vo;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.TableId;
import com.sun.xml.bind.v2.TODO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* Excel VO
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = false) // 设置 chain = false避免用户导入有问题
public class DeviceAttributeImportExcelVO {
/**
*
*/
@ExcelProperty("点位编码")
private String attributeCode;
/**
*
*/
@ExcelProperty("点位名称")
private String attributeName;
/**
*
*
* {@link TODO mes_data_type }
*/
@ExcelProperty("点位类型")
private String attributeType;
/**
*
*
* {@link TODO iot_device_data_type }
*/
@ExcelProperty(value ="数据类型", converter = DictConvert.class)
@DictFormat("iot_device_data_type")
private String dataType;
/**
*
*/
@ExcelProperty("寄存器地址")
private String address;
/**
*
*/
@ExcelProperty("单位")
private String dataUnit;
/**
*
*/
@ExcelProperty("倍率")
private Double ratio;
/**
*
*/
@ExcelProperty("备注")
private String remark;
/* *//**
* id
*//*
private Long deviceId;
*/
}

@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.*;
import cn.iocoder.yudao.module.iot.controller.admin.devicecontactmodel.vo.DeviceAttributeImportExcelVO;
import cn.iocoder.yudao.module.iot.controller.admin.devicecontactmodel.vo.DeviceContactModelPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceAttributeDO;
@ -165,4 +166,6 @@ public interface DeviceService {
List<Map<String, Object>> getDeviceAttributeGroupList(DeviceContactModelPageReqVO deviceModelAttributePageReqVO);
Map<String, Object> historyAnalyse(Long deviceId, String collectionStartTime, String collectionEndTime, List<String> attributeCodes);
void importDeviceAttributeList(List<DeviceAttributeImportExcelVO> list, Boolean updateSupport, Long deviceId);
}

@ -16,6 +16,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.scheduled.utils.CronE
import cn.iocoder.yudao.module.iot.controller.admin.device.scheduled.scheduler.TaskSchedulerManager;
import cn.iocoder.yudao.module.iot.controller.admin.device.utils.DataTypeParseUtil;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.*;
import cn.iocoder.yudao.module.iot.controller.admin.devicecontactmodel.vo.DeviceAttributeImportExcelVO;
import cn.iocoder.yudao.module.iot.controller.admin.devicecontactmodel.vo.DeviceContactModelPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.deviceoperationrecord.utils.TimeConverterUtil;
import cn.iocoder.yudao.module.iot.controller.admin.deviceoperationrecord.vo.DeviceTotalTimeRecordReqVO;
@ -50,6 +51,7 @@ import cn.iocoder.yudao.module.iot.framework.mqtt.consumer.IMqttservice;
import cn.iocoder.yudao.module.iot.util.MapListStatsCalculator;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@ -81,6 +83,7 @@ import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.IMPORT_LIST_IS_EMPTY;
/**
* Service
@ -1176,6 +1179,39 @@ public class DeviceServiceImpl implements DeviceService {
}
}
@Override
public void importDeviceAttributeList(List<DeviceAttributeImportExcelVO> list, Boolean updateSupport, Long deviceId) {
// 1.1 参数校验
if (CollUtil.isEmpty(list)) {
throw exception(IMPORT_LIST_IS_EMPTY);
}
// 1.2 查询主单位数据
QueryWrapper<ErpProductUnitDO> wrapper = new QueryWrapper<>();
wrapper.eq("primary_flag", "Y");
List<ErpProductUnitDO> erpProductUnitDOS = productUnitMapper.selectList(wrapper);
Map<String, List<ErpProductUnitDO>> map = erpProductUnitDOS.stream().filter(Objects::nonNull)
.collect(Collectors.groupingBy(
ErpProductUnitDO::getName,
Collectors.toList()
));
List<DeviceContactModelDO> erpDeviceContactDOs = new ArrayList<>();
list.stream().forEach(importDeviceAttribute -> {
/* if(importDeviceAttribute.getDataUnit()!=null){
List<ErpProductUnitDO> erpProductUnitDOS1 = map.get(importDeviceAttribute.getPrimaryName());
if (CollUtil.isEmpty(erpProductUnitDOS1)) {
importDeviceAttribute.setPrimaryId(null);
} else {
importDeviceAttribute.setPrimaryId(erpProductUnitDOS1.get(0).getId());
}
}*/
DeviceContactModelDO deviceAttribute = BeanUtils.toBean(importDeviceAttribute, DeviceContactModelDO.class);
deviceAttribute.setDeviceId(deviceId);
erpDeviceContactDOs.add(deviceAttribute);
});
CollUtil.isNotEmpty(erpDeviceContactDOs);deviceContactModelMapper.insertBatch(erpDeviceContactDOs);
}
@Override
public PageResult<Map<String, Object>> historyRecord(

@ -11,7 +11,10 @@ public enum PrintTemplateTypeEnum {
EQUIPMENT_LEDGER(2),
KEY_COMPONENT(3),
MOLD(4),
SPARE_PARTS(5);
SPARE_PARTS(5),
PRODUCTION_LINE(6),
BIT_SAMPLE(7),
BIT_WAREHOUSE_LOCATION(8);
private final Integer value;

@ -0,0 +1,273 @@
server:
port: 48081
--- #################### 数据库相关配置 ####################
spring:
# 数据源配置项
autoconfigure:
exclude:
- com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
- de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration
- de.codecentric.boot.admin.server.ui.config.AdminServerUiAutoConfiguration
- de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration
datasource:
druid:
web-stat-filter:
enabled: true
stat-view-servlet:
enabled: true
url-pattern: /druid/*
filter:
stat:
enabled: true
log-slow-sql: true
slow-sql-millis: 100
merge-sql: true
wall:
config:
multi-statement-allow: true
dynamic:
druid: # 全局Druid配置
initial-size: 1
min-idle: 1
max-active: 20
max-wait: 600000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
max-evictable-idle-time-millis: 900000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: false # 禁用预处理语句缓存
max-pool-prepared-statement-per-connection-size: -1
primary: master
strict: true # 禁止未配置的数据源回退到默认主库,避免 TDengine 调用误打到 MySQL
datasource:
master:
name: besure_bit_dev
url: jdbc:mysql://47.106.185.127:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: besure_bit_user
password: Besure@123456
driver-class-name: com.mysql.cj.jdbc.Driver
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis:
host: 47.106.185.127 # 地址
port: 6380 # 端口成
database: 0 # 数据库索引
#password: bkcaydy8ydhZZnS2 # 密码,建议生产环境开启
password: BesureBitPwd258
--- #################### 定时任务相关配置 ####################
# Quartz 配置项,对应 QuartzProperties 配置类
spring:
quartz:
auto-startup: true # 本地开发环境,尽量不要开启 Job
scheduler-name: schedulerName # Scheduler 名字。默认为 schedulerName
job-store-type: jdbc # Job 存储器类型。默认为 memory 表示内存,可选 jdbc 使用数据库。
wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true
properties: # 添加 Quartz Scheduler 附加属性,更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文档
org:
quartz:
# Scheduler 相关配置
scheduler:
instanceName: schedulerName
instanceId: AUTO # 自动生成 instance ID
# JobStore 相关配置
jobStore:
# JobStore 实现类。可见博客https://blog.csdn.net/weixin_42458219/article/details/122247162
class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
isClustered: true # 是集群模式
clusterCheckinInterval: 15000 # 集群检查频率,单位:毫秒。默认为 15000即 15 秒
misfireThreshold: 60000 # misfire 阀值,单位:毫秒。
# 线程池相关配置
threadPool:
threadCount: 25 # 线程池大小。默认为 10 。
threadPriority: 5 # 线程优先级
class: org.quartz.simpl.SimpleThreadPool # 线程池类型
jdbc: # 使用 JDBC 的 JobStore 的时候JDBC 的配置
initialize-schema: NEVER # 是否自动使用 SQL 初始化 Quartz 表结构。这里设置成 never ,我们手动创建表结构。
--- #################### 消息队列相关 ####################
# rocketmq 配置项,对应 RocketMQProperties 配置类
rocketmq:
name-server: 127.0.0.1:9876 # RocketMQ Namesrv
spring:
# RabbitMQ 配置项,对应 RabbitProperties 配置类
rabbitmq:
host: 127.0.0.1 # RabbitMQ 服务的地址
port: 5672 # RabbitMQ 服务的端口
username: rabbit # RabbitMQ 服务的账号
password: rabbit # RabbitMQ 服务的密码
# Kafka 配置项,对应 KafkaProperties 配置类
kafka:
bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔
--- #################### 服务保障相关配置 ####################
# Lock4j 配置项
lock4j:
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
--- #################### 监控相关配置 ####################
# Actuator 监控端点的配置项
management:
endpoints:
web:
base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
exposure:
include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
# Spring Boot Admin 配置项
spring:
boot:
admin:
# Spring Boot Admin Client 客户端的相关配置
client:
url: http://127.0.0.1:${server.port}/${spring.boot.admin.context-path} # 设置 Spring Boot Admin Server 地址
instance:
service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME]
# Spring Boot Admin Server 服务端的相关配置
context-path: /admin # 配置 Spring
# 日志文件配置
logging:
file:
name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
level:
# 配置自己写的 MyBatis Mapper 打印日志
cn.iocoder.yudao.module.bpm.dal.mysql: debug
cn.iocoder.yudao.module.infra.dal.mysql: debug
cn.iocoder.yudao.module.infra.dal.mysql.logger.ApiErrorLogMapper: INFO # 配置 ApiErrorLogMapper 的日志级别为 info避免和 GlobalExceptionHandler 重复打印
cn.iocoder.yudao.module.infra.dal.mysql.job.JobLogMapper: INFO # 配置 JobLogMapper 的日志级别为 info
cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper: INFO # 配置 FileConfigMapper 的日志级别为 info
cn.iocoder.yudao.module.pay.dal.mysql: debug
cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskMapper: INFO # 配置 PayNotifyTaskMapper 的日志级别为 info
cn.iocoder.yudao.module.system.dal.mysql: debug
cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper: INFO # 配置 SmsChannelMapper 的日志级别为 info
cn.iocoder.yudao.module.tool.dal.mysql: debug
cn.iocoder.yudao.module.member.dal.mysql: debug
cn.iocoder.yudao.module.trade.dal.mysql: debug
cn.iocoder.yudao.module.promotion.dal.mysql: debug
cn.iocoder.yudao.module.statistics.dal.mysql: debug
cn.iocoder.yudao.module.crm.dal.mysql: debug
cn.iocoder.yudao.module.erp.dal.mysql: debug
cn.iocoder.yudao.module.mes.dal.mysql: debug
cn.iocoder.yudao.module.iot.dal.mysql: debug
org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿先禁用Spring Boot 3.X 存在部分错误的 WARN 提示
debug: false
--- #################### 微信公众号、小程序相关配置 ####################
wx:
mp: # 公众号配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md 文档
# app-id: wx041349c6f39b268b # 测试号(牛希尧提供的)
# secret: 5abee519483bc9f8cb37ce280e814bd0
app-id: wx5b23ba7a5589ecbb # 测试号(自己的)
secret: 2a7b3b20c537e52e74afd395eb85f61f
# app-id: wxa69ab825b163be19 # 测试号Kongdy 提供的)
# secret: bd4f9fab889591b62aeac0d7b8d8b4a0
# 存储配置,解决 AccessToken 的跨节点的共享
config-storage:
type: RedisTemplate # 采用 RedisTemplate 操作 Redis会自动从 Spring 中获取
key-prefix: wx # Redis Key 的前缀
http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台
miniapp: # 小程序配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 文档
# appid: wx62056c0d5e8db250 # 测试号(牛希尧提供的)
# secret: 333ae72f41552af1e998fe1f54e1584a
appid: wx63c280fe3248a3e7 # wenhualian的接口测试号
secret: 6f270509224a7ae1296bbf1c8cb97aed
# appid: wxc4598c446f8a9cb3 # 测试号Kongdy 提供的)
# secret: 4a1a04e07f6a4a0751b39c3064a92c8b
config-storage:
type: RedisTemplate # 采用 RedisTemplate 操作 Redis会自动从 Spring 中获取
key-prefix: wa # Redis Key 的前缀
http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置
yudao:
tdengine:
enable: false
captcha:
enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试;
security:
mock-enable: true
xss:
enable: false
exclude-urls: # 如下两个 url仅仅是为了演示去掉配置也没关系
- ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
pay:
order-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址
refund-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
access-log: # 访问日志的配置项
enable: false
demo: false # 关闭演示模式
tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
qrcode:
scan-base-url: http://192.168.5.167:48081/admin-api/qrcode/record/scan/resolve-id
transit-url: http://10.10.10.20:8080/h5/qrcode/transit
fail-url: https://chat.baidu.com/search?extParams=%7B%22enter_type%22%3A%22home_operate%22%7D&isShowHello=1
width: 300
height: 300
default-bucket: common-qrcode
buckets:
mold: mold-qrcode
product: product-qrcode
equipment: equipment-qrcode
part: part-qrcode
spare: spare-qrcode
work-order: workorder-qrcode
justauth:
enabled: true
type:
DINGTALK: # 钉钉
client-id: dingvrnreaje3yqvzhxg
client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI
ignore-check-redirect-uri: true
WECHAT_ENTERPRISE: # 企业微信
client-id: wwd411c69a39ad2e54
client-secret: 1wTb7hYxnpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw
agent-id: 1000004
ignore-check-redirect-uri: true
# noinspection SpringBootApplicationYaml
WECHAT_MINI_APP: # 微信小程序
client-id: ${wx.miniapp.appid}
client-secret: ${wx.miniapp.secret}
ignore-check-redirect-uri: true
ignore-check-state: true # 微信小程序,不会使用到 state所以不进行校验
WECHAT_MP: # 微信公众号
client-id: ${wx.mp.app-id}
client-secret: ${wx.mp.secret}
ignore-check-redirect-uri: true
cache:
type: REDIS
prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE::
timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟
emqx:
is-enable: false # 是否启用 MQTT
broker: tcp://47.106.185.127:1883 # EMQX 服务器地址TCP 协议)
client-id: mqtt-client-besure_server-dev # 客户端ID
user-name: admin # 用户名
password: admin # 密码
clean-session: true # 是否清空 session
reconnect: true # 是否自动断线重连
timeout: 5 # 连接超时时间(秒)
keep-alive: 60 # 心跳间隔(秒)

@ -0,0 +1,273 @@
server:
port: 48081
--- #################### 数据库相关配置 ####################
spring:
# 数据源配置项
autoconfigure:
exclude:
- com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
- de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration
- de.codecentric.boot.admin.server.ui.config.AdminServerUiAutoConfiguration
- de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration
datasource:
druid:
web-stat-filter:
enabled: true
stat-view-servlet:
enabled: true
url-pattern: /druid/*
filter:
stat:
enabled: true
log-slow-sql: true
slow-sql-millis: 100
merge-sql: true
wall:
config:
multi-statement-allow: true
dynamic:
druid: # 全局Druid配置
initial-size: 1
min-idle: 1
max-active: 20
max-wait: 600000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
max-evictable-idle-time-millis: 900000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: false # 禁用预处理语句缓存
max-pool-prepared-statement-per-connection-size: -1
primary: master
strict: true # 禁止未配置的数据源回退到默认主库,避免 TDengine 调用误打到 MySQL
datasource:
master:
name: besure_bit_prod
url: jdbc:mysql://47.106.185.127:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: besure_bit_user
password: Besure@123456
driver-class-name: com.mysql.cj.jdbc.Driver
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis:
host: 47.106.185.127 # 地址
port: 6380 # 端口成
database: 0 # 数据库索引
#password: bkcaydy8ydhZZnS2 # 密码,建议生产环境开启
password: BesureBitPwd258
--- #################### 定时任务相关配置 ####################
# Quartz 配置项,对应 QuartzProperties 配置类
spring:
quartz:
auto-startup: true # 本地开发环境,尽量不要开启 Job
scheduler-name: schedulerName # Scheduler 名字。默认为 schedulerName
job-store-type: jdbc # Job 存储器类型。默认为 memory 表示内存,可选 jdbc 使用数据库。
wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true
properties: # 添加 Quartz Scheduler 附加属性,更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文档
org:
quartz:
# Scheduler 相关配置
scheduler:
instanceName: schedulerName
instanceId: AUTO # 自动生成 instance ID
# JobStore 相关配置
jobStore:
# JobStore 实现类。可见博客https://blog.csdn.net/weixin_42458219/article/details/122247162
class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
isClustered: true # 是集群模式
clusterCheckinInterval: 15000 # 集群检查频率,单位:毫秒。默认为 15000即 15 秒
misfireThreshold: 60000 # misfire 阀值,单位:毫秒。
# 线程池相关配置
threadPool:
threadCount: 25 # 线程池大小。默认为 10 。
threadPriority: 5 # 线程优先级
class: org.quartz.simpl.SimpleThreadPool # 线程池类型
jdbc: # 使用 JDBC 的 JobStore 的时候JDBC 的配置
initialize-schema: NEVER # 是否自动使用 SQL 初始化 Quartz 表结构。这里设置成 never ,我们手动创建表结构。
--- #################### 消息队列相关 ####################
# rocketmq 配置项,对应 RocketMQProperties 配置类
rocketmq:
name-server: 127.0.0.1:9876 # RocketMQ Namesrv
spring:
# RabbitMQ 配置项,对应 RabbitProperties 配置类
rabbitmq:
host: 127.0.0.1 # RabbitMQ 服务的地址
port: 5672 # RabbitMQ 服务的端口
username: rabbit # RabbitMQ 服务的账号
password: rabbit # RabbitMQ 服务的密码
# Kafka 配置项,对应 KafkaProperties 配置类
kafka:
bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔
--- #################### 服务保障相关配置 ####################
# Lock4j 配置项
lock4j:
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
--- #################### 监控相关配置 ####################
# Actuator 监控端点的配置项
management:
endpoints:
web:
base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
exposure:
include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
# Spring Boot Admin 配置项
spring:
boot:
admin:
# Spring Boot Admin Client 客户端的相关配置
client:
url: http://127.0.0.1:${server.port}/${spring.boot.admin.context-path} # 设置 Spring Boot Admin Server 地址
instance:
service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME]
# Spring Boot Admin Server 服务端的相关配置
context-path: /admin # 配置 Spring
# 日志文件配置
logging:
file:
name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
level:
# 配置自己写的 MyBatis Mapper 打印日志
cn.iocoder.yudao.module.bpm.dal.mysql: debug
cn.iocoder.yudao.module.infra.dal.mysql: debug
cn.iocoder.yudao.module.infra.dal.mysql.logger.ApiErrorLogMapper: INFO # 配置 ApiErrorLogMapper 的日志级别为 info避免和 GlobalExceptionHandler 重复打印
cn.iocoder.yudao.module.infra.dal.mysql.job.JobLogMapper: INFO # 配置 JobLogMapper 的日志级别为 info
cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper: INFO # 配置 FileConfigMapper 的日志级别为 info
cn.iocoder.yudao.module.pay.dal.mysql: debug
cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskMapper: INFO # 配置 PayNotifyTaskMapper 的日志级别为 info
cn.iocoder.yudao.module.system.dal.mysql: debug
cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper: INFO # 配置 SmsChannelMapper 的日志级别为 info
cn.iocoder.yudao.module.tool.dal.mysql: debug
cn.iocoder.yudao.module.member.dal.mysql: debug
cn.iocoder.yudao.module.trade.dal.mysql: debug
cn.iocoder.yudao.module.promotion.dal.mysql: debug
cn.iocoder.yudao.module.statistics.dal.mysql: debug
cn.iocoder.yudao.module.crm.dal.mysql: debug
cn.iocoder.yudao.module.erp.dal.mysql: debug
cn.iocoder.yudao.module.mes.dal.mysql: debug
cn.iocoder.yudao.module.iot.dal.mysql: debug
org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿先禁用Spring Boot 3.X 存在部分错误的 WARN 提示
debug: false
--- #################### 微信公众号、小程序相关配置 ####################
wx:
mp: # 公众号配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md 文档
# app-id: wx041349c6f39b268b # 测试号(牛希尧提供的)
# secret: 5abee519483bc9f8cb37ce280e814bd0
app-id: wx5b23ba7a5589ecbb # 测试号(自己的)
secret: 2a7b3b20c537e52e74afd395eb85f61f
# app-id: wxa69ab825b163be19 # 测试号Kongdy 提供的)
# secret: bd4f9fab889591b62aeac0d7b8d8b4a0
# 存储配置,解决 AccessToken 的跨节点的共享
config-storage:
type: RedisTemplate # 采用 RedisTemplate 操作 Redis会自动从 Spring 中获取
key-prefix: wx # Redis Key 的前缀
http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台
miniapp: # 小程序配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 文档
# appid: wx62056c0d5e8db250 # 测试号(牛希尧提供的)
# secret: 333ae72f41552af1e998fe1f54e1584a
appid: wx63c280fe3248a3e7 # wenhualian的接口测试号
secret: 6f270509224a7ae1296bbf1c8cb97aed
# appid: wxc4598c446f8a9cb3 # 测试号Kongdy 提供的)
# secret: 4a1a04e07f6a4a0751b39c3064a92c8b
config-storage:
type: RedisTemplate # 采用 RedisTemplate 操作 Redis会自动从 Spring 中获取
key-prefix: wa # Redis Key 的前缀
http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置
yudao:
tdengine:
enable: false
captcha:
enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试;
security:
mock-enable: true
xss:
enable: false
exclude-urls: # 如下两个 url仅仅是为了演示去掉配置也没关系
- ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
pay:
order-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址
refund-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
access-log: # 访问日志的配置项
enable: false
demo: false # 关闭演示模式
tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
qrcode:
scan-base-url: http://192.168.5.167:48081/admin-api/qrcode/record/scan/resolve-id
transit-url: http://10.10.10.20:8080/h5/qrcode/transit
fail-url: https://chat.baidu.com/search?extParams=%7B%22enter_type%22%3A%22home_operate%22%7D&isShowHello=1
width: 300
height: 300
default-bucket: common-qrcode
buckets:
mold: mold-qrcode
product: product-qrcode
equipment: equipment-qrcode
part: part-qrcode
spare: spare-qrcode
work-order: workorder-qrcode
justauth:
enabled: true
type:
DINGTALK: # 钉钉
client-id: dingvrnreaje3yqvzhxg
client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI
ignore-check-redirect-uri: true
WECHAT_ENTERPRISE: # 企业微信
client-id: wwd411c69a39ad2e54
client-secret: 1wTb7hYxnpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw
agent-id: 1000004
ignore-check-redirect-uri: true
# noinspection SpringBootApplicationYaml
WECHAT_MINI_APP: # 微信小程序
client-id: ${wx.miniapp.appid}
client-secret: ${wx.miniapp.secret}
ignore-check-redirect-uri: true
ignore-check-state: true # 微信小程序,不会使用到 state所以不进行校验
WECHAT_MP: # 微信公众号
client-id: ${wx.mp.app-id}
client-secret: ${wx.mp.secret}
ignore-check-redirect-uri: true
cache:
type: REDIS
prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE::
timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟
emqx:
is-enable: false # 是否启用 MQTT
broker: tcp://47.106.185.127:1883 # EMQX 服务器地址TCP 协议)
client-id: mqtt-client-besure_server-dev # 客户端ID
user-name: admin # 用户名
password: admin # 密码
clean-session: true # 是否清空 session
reconnect: true # 是否自动断线重连
timeout: 5 # 连接超时时间(秒)
keep-alive: 60 # 心跳间隔(秒)
Loading…
Cancel
Save