chore: sync codebase with upstream

Align origin/main with upstream/main (GitHub). The two branches
diverged due to pre-rebase vs post-rebase merge commits for the
k8s-amd64-dockerfiles feature.

Includes: multi-stage Go compilation Dockerfiles, pan-bolt
inventory features, CRM relations, Excel import tooling,
proto/gRPC updates, migration scripts, and tools/ directory.

Excludes deploy/bin/ pre-compiled binaries (arm64, not needed
for amd64 K3s cluster; builds use multi-stage Dockerfiles now).
This commit is contained in:
Chever John 2026-06-14 15:24:02 +08:00
parent c5ab2279c0
commit 379fb28d92
95 changed files with 6859 additions and 2164 deletions

30
.gitignore vendored
View File

@ -1,6 +1,6 @@
# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
# Created by https://www.toptal.com/developers/gitignore/api/go,macos,visualstudiocode
# Edit at https://www.toptal.com/developers/gitignore?templates=go,macos,visualstudiocode
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,linux,go,macos
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,linux,go,macos
### Go ###
# If you prefer the allow list template instead of the deny list, see community template:
@ -25,6 +25,21 @@
# Go workspace file
go.work
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
# General
.DS_Store
@ -77,16 +92,9 @@ Temporary Items
.history
.ionide
# End of https://www.toptal.com/developers/gitignore/api/go,macos,visualstudiocode
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,linux,go,macos
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
# MySQL data directory (contains binaries, certs, and runtime state)
# Local runtime data and certificates
data/
# Environment and secrets
.env
.env.*
*.pem
deploy/bin/
worker/graphsync/go.sum

View File

@ -11,5 +11,6 @@ RUN apk add --no-cache ca-certificates tzdata
WORKDIR /app
COPY --from=builder /server ./gateway
COPY deploy/etc/gateway.yaml etc/gateway.yaml
COPY deploy/tools/ tools/
EXPOSE 8888
CMD ["./gateway", "-f", "etc/gateway.yaml"]

View File

@ -62,10 +62,10 @@ services:
- --name=default
- --data-dir=/etcd-data
- --listen-client-urls=http://0.0.0.0:2379
- --advertise-client-urls=http://etcd:2379
- --advertise-client-urls=http://127.0.0.1:2379
- --listen-peer-urls=http://0.0.0.0:2380
- --initial-advertise-peer-urls=http://etcd:2380
- --initial-cluster=default=http://etcd:2380
- --initial-advertise-peer-urls=http://127.0.0.1:2380
- --initial-cluster=default=http://127.0.0.1:2380
- --initial-cluster-state=new
ports:
- "2379:2379"

View File

@ -1,6 +1,7 @@
Name: gateway-api
Host: 0.0.0.0
Port: 8888
Timeout: 120000
Auth:
AccessSecret: muyu-wms-jwt-secret-key-2026
@ -23,6 +24,7 @@ InventoryRpc:
- etcd:2379
Key: inventory.rpc
NonBlock: true
Timeout: 60000
Log:
ServiceName: gateway-api

View File

@ -334,27 +334,27 @@ CREATE TABLE `casbin_rule` (
-- ==================== Initial Data ====================
-- Default admin user (password: admin123, bcrypt hashed)
INSERT INTO `sys_user` (`user_id`, `username`, `password`, `salt`, `real_name`, `status`)
INSERT IGNORE INTO `sys_user` (`user_id`, `username`, `password`, `salt`, `real_name`, `status`)
VALUES ('u_admin_001', 'admin', '$2a$10$S5XPWRXUnexAt9KJw2CefeHMa6aude7j5dm5qgWM1YMQAAwRgsGXa', 'bcrypt', 'System Admin', 1);
-- Default tenant and mapping
INSERT INTO `crm_tenant` (`tenant_id`, `tenant_name`, `tenant_code`, `status`)
INSERT IGNORE INTO `crm_tenant` (`tenant_id`, `tenant_name`, `tenant_code`, `status`)
VALUES ('t_default_001', '默认租户', 'DEFAULT', 1);
INSERT INTO `crm_tenant_user` (`tenant_user_id`, `tenant_id`, `user_id`, `is_primary`, `status`)
INSERT IGNORE INTO `crm_tenant_user` (`tenant_user_id`, `tenant_id`, `user_id`, `is_primary`, `status`)
VALUES ('tu_001', 't_default_001', 'u_admin_001', 1, 1);
-- Default roles
INSERT INTO `sys_role` (`role_id`, `role_name`, `role_key`, `role_desc`, `sort_order`, `status`) VALUES
INSERT IGNORE INTO `sys_role` (`role_id`, `role_name`, `role_key`, `role_desc`, `sort_order`, `status`) VALUES
('r_001', '系统管理员', 'admin', 'Full system access', 1, 1),
('r_002', '仓库管理员', 'warehouse', 'Warehouse management access', 2, 1),
('r_003', '普通用户', 'user', 'Basic read access', 3, 1);
-- Assign admin role to admin user
INSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES ('u_admin_001', 'r_001');
INSERT IGNORE INTO `sys_user_role` (`user_id`, `role_id`) VALUES ('u_admin_001', 'r_001');
-- Default menus
INSERT INTO `sys_menu` (`menu_id`, `parent_id`, `menu_name`, `menu_type`, `path`, `component`, `permission`, `icon`, `sort_order`, `visible`, `status`) VALUES
INSERT IGNORE INTO `sys_menu` (`menu_id`, `parent_id`, `menu_name`, `menu_type`, `path`, `component`, `permission`, `icon`, `sort_order`, `visible`, `status`) VALUES
-- Dashboard
('m_001', '', '首页', 1, '/dashboard', './Dashboard', '', 'DashboardOutlined', 1, 1, 1),
-- System Management
@ -374,26 +374,26 @@ INSERT INTO `sys_menu` (`menu_id`, `parent_id`, `menu_name`, `menu_type`, `path`
('m_206', 'm_200', 'Excel导入', 2, '/inventory/import', './Inventory/Import', 'inventory:import:exec', 'ImportOutlined', 6, 1, 1);
-- CRM menus
INSERT INTO `sys_menu` (`menu_id`, `parent_id`, `menu_name`, `menu_type`, `path`, `component`, `permission`, `icon`, `sort_order`, `visible`, `status`) VALUES
INSERT IGNORE INTO `sys_menu` (`menu_id`, `parent_id`, `menu_name`, `menu_type`, `path`, `component`, `permission`, `icon`, `sort_order`, `visible`, `status`) VALUES
('m_300', '', '客户关系', 1, '/crm', '', '', 'NodeIndexOutlined', 300, 1, 1),
('m_301', 'm_300', '上下游关系', 2, '/crm/relations', './CRM/Relations', 'crm:relation:list', 'ApartmentOutlined', 1, 1, 1);
-- Assign all menus to admin role
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
INSERT IGNORE INTO `sys_role_menu` (`role_id`, `menu_id`)
SELECT 'r_001', `menu_id` FROM `sys_menu`;
-- Assign inventory menus to warehouse role
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES
INSERT IGNORE INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES
('r_002', 'm_001'),
('r_002', 'm_200'), ('r_002', 'm_201'), ('r_002', 'm_202'),
('r_002', 'm_203'), ('r_002', 'm_204'), ('r_002', 'm_205'), ('r_002', 'm_206');
-- Assign CRM menus to admin role
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES
INSERT IGNORE INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES
('r_001', 'm_300'), ('r_001', 'm_301');
-- Default Casbin policies
INSERT INTO `casbin_rule` (`ptype`, `v0`, `v1`, `v2`) VALUES
INSERT IGNORE INTO `casbin_rule` (`ptype`, `v0`, `v1`, `v2`) VALUES
('p', 'admin', '/api/v1/*', '*'),
('p', 'warehouse', '/api/v1/auth/*', '*'),
('p', 'warehouse', '/api/v1/inventory/*', '*'),
@ -404,7 +404,7 @@ INSERT INTO `casbin_rule` (`ptype`, `v0`, `v1`, `v2`) VALUES
('p', 'user', '/api/v1/inventory/stocks/*', 'GET');
-- Default system configs
INSERT INTO `sys_config` (`config_id`, `config_key`, `config_value`, `config_name`, `config_group`, `remark`) VALUES
INSERT IGNORE INTO `sys_config` (`config_id`, `config_key`, `config_value`, `config_name`, `config_group`, `remark`) VALUES
('c_001', 'sys_name', 'muyuqingfeng仓储管理系统', 'System Name', 'basic', 'System display name'),
('c_002', 'sys_logo', '', 'System Logo', 'basic', 'System logo URL'),
('c_003', 'upload_max_size', '10', 'Upload Max Size (MB)', 'upload', 'Maximum file upload size'),

View File

@ -0,0 +1,44 @@
-- Three-layer inventory model: Product -> Pan(盘) -> Bolt(匹)
-- Pan table: each row = one physical pan/roll, belongs to a product
CREATE TABLE IF NOT EXISTS `inv_product_pan` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`pan_id` VARCHAR(32) NOT NULL,
`product_id` VARCHAR(32) NOT NULL,
`name` VARCHAR(50) NOT NULL DEFAULT '' COMMENT 'pan label: A, B, C...',
`position` VARCHAR(100) NOT NULL DEFAULT '' COMMENT 'physical location: Shanghai, Nanjing...',
`sort_order` INT NOT NULL DEFAULT 0,
`tenant_id` VARCHAR(32) NOT NULL DEFAULT '',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_pan_id` (`pan_id`),
KEY `idx_product_id` (`product_id`),
KEY `idx_tenant_id` (`tenant_id`),
KEY `idx_position` (`position`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Bolt table: each row = one physical bolt/piece, belongs to a pan
CREATE TABLE IF NOT EXISTS `inv_product_bolt` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`bolt_id` VARCHAR(32) NOT NULL,
`pan_id` VARCHAR(32) NOT NULL COMMENT 'FK to inv_product_pan',
`length_m` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 'length in meters',
`sort_order` INT NOT NULL DEFAULT 0,
`tenant_id` VARCHAR(32) NOT NULL DEFAULT '',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_bolt_id` (`bolt_id`),
KEY `idx_pan_id` (`pan_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Add composite index for product summary aggregation (panel 1)
-- Use PREPARE/EXECUTE to skip if index already exists (MySQL has no CREATE INDEX IF NOT EXISTS).
SET @ix = (SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'inv_product' AND INDEX_NAME = 'idx_product_name_tenant');
SET @s = IF(@ix = 0,
'ALTER TABLE `inv_product` ADD KEY `idx_product_name_tenant` (`product_name`, `tenant_id`)',
'SELECT 1');
PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt;

View File

@ -0,0 +1,65 @@
-- Data migration: split existing inv_product flat data into pan + bolt records.
-- Run AFTER 20260403_add_pan_and_bolt.sql but BEFORE 20260403_drop_legacy_columns.sql.
-- Wrapped in a stored procedure so re-runs on an already-migrated database
-- (where `location` was already dropped) silently succeed rather than error.
-- MySQL validates column names at call time, not at CREATE PROCEDURE time,
-- so the IF guard prevents the INSERTs from running when the column is absent.
DROP PROCEDURE IF EXISTS _migrate_inv_pan_bolt;
DELIMITER $$
CREATE PROCEDURE _migrate_inv_pan_bolt()
BEGIN
DECLARE v_has_location TINYINT DEFAULT 0;
SELECT COUNT(*) INTO v_has_location
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'inv_product'
AND COLUMN_NAME = 'location';
IF v_has_location = 1 THEN
-- Step 1: one default pan per product, position taken from legacy `location`
INSERT INTO inv_product_pan (pan_id, product_id, name, position, sort_order, tenant_id)
SELECT
CONCAT('pan_mig_', product_id),
product_id,
'A',
COALESCE(location, ''),
1,
COALESCE(tenant_id, '')
FROM inv_product
WHERE deleted_at IS NULL
AND product_id NOT IN (SELECT DISTINCT product_id FROM inv_product_pan);
-- Step 2: bolt records, one per unit_piece (or one if unit_pieces = 0)
INSERT INTO inv_product_bolt (bolt_id, pan_id, length_m, sort_order, tenant_id)
SELECT
CONCAT('bolt_mig_', product_id, '_', seq.n),
CONCAT('pan_mig_', product_id),
CASE
WHEN unit_pieces > 0 THEN ROUND(stock_quantity / unit_pieces, 2)
ELSE stock_quantity
END,
seq.n,
COALESCE(tenant_id, '')
FROM inv_product
CROSS JOIN (
SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5
UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10
UNION ALL SELECT 11 UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15
UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18 UNION ALL SELECT 19 UNION ALL SELECT 20
) seq
WHERE deleted_at IS NULL
AND stock_quantity > 0
AND seq.n <= GREATEST(unit_pieces, 1)
AND CONCAT('pan_mig_', product_id) IN (SELECT pan_id FROM inv_product_pan);
END IF;
END$$
DELIMITER ;
CALL _migrate_inv_pan_bolt();
DROP PROCEDURE IF EXISTS _migrate_inv_pan_bolt;
-- Verification queries (run manually to check)
-- SELECT COUNT(*) AS pan_count FROM inv_product_pan WHERE pan_id LIKE 'pan_mig_%';
-- SELECT COUNT(*) AS bolt_count FROM inv_product_bolt WHERE bolt_id LIKE 'bolt_mig_%';

View File

@ -0,0 +1,26 @@
-- Remove deprecated flat columns from inv_product.
-- Must run AFTER 20260403_data_migration.sql has migrated existing rows.
-- Uses PREPARE/EXECUTE to skip each column that was already dropped
-- (MySQL has no DROP COLUMN IF EXISTS; that is a MariaDB-only extension).
SET @db = DATABASE();
SET @c = (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='inv_product' AND COLUMN_NAME='unit_pieces');
SET @s = IF(@c=1, 'ALTER TABLE `inv_product` DROP COLUMN `unit_pieces`', 'SELECT 1');
PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @c = (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='inv_product' AND COLUMN_NAME='unit_rolls');
SET @s = IF(@c=1, 'ALTER TABLE `inv_product` DROP COLUMN `unit_rolls`', 'SELECT 1');
PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @c = (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='inv_product' AND COLUMN_NAME='stock_quantity');
SET @s = IF(@c=1, 'ALTER TABLE `inv_product` DROP COLUMN `stock_quantity`', 'SELECT 1');
PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @c = (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='inv_product' AND COLUMN_NAME='location');
SET @s = IF(@c=1, 'ALTER TABLE `inv_product` DROP COLUMN `location`', 'SELECT 1');
PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @c = (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='inv_product' AND COLUMN_NAME='cost_price');
SET @s = IF(@c=1, 'ALTER TABLE `inv_product` DROP COLUMN `cost_price`', 'SELECT 1');
PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt;

View File

@ -0,0 +1,9 @@
-- Fix double-encoded (CP1252→UTF-8) menu_name for pans/bolts entries
-- Root cause: these records were inserted through a connection that
-- misinterpreted UTF-8 bytes as CP1252, causing mojibake.
SET NAMES utf8mb4;
USE `muyu_wms`;
UPDATE `sys_menu` SET `menu_name` = '盘库存' WHERE `menu_id` = 'm_207' AND `path` = '/inventory/pans';
UPDATE `sys_menu` SET `menu_name` = '匹库存' WHERE `menu_id` = 'm_208' AND `path` = '/inventory/bolts';

View File

@ -11,6 +11,9 @@ server {
try_files $uri $uri/ /index.html;
}
# Allow large file uploads (e.g. Excel imports up to 100MB)
client_max_body_size 100m;
# API proxy
location /api/ {
# Use Docker DNS dynamic resolution to avoid stale container IP after recreate.
@ -29,7 +32,15 @@ server {
add_header Cache-Control "public, immutable";
}
# Static assets cache
# Do not aggressively cache entry bundles because file names are stable
# (for example umi.js / umi.css) and stale cache can cause a blank page
# after deployment updates.
location ~* ^/(umi\.js|umi\.css|preload_helper\.js)$ {
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# Cache other static assets that are typically fingerprinted/chunked.
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";

119
deploy/tools/package-lock.json generated Normal file
View File

@ -0,0 +1,119 @@
{
"name": "tools",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tools",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"xlsx": "^0.18.5"
}
},
"node_modules/adler-32": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
"integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/cfb": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
"integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
"license": "Apache-2.0",
"dependencies": {
"adler-32": "~1.3.0",
"crc-32": "~1.2.0"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/codepage": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/crc-32": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"license": "Apache-2.0",
"bin": {
"crc32": "bin/crc32.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/frac": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/ssf": {
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
"license": "Apache-2.0",
"dependencies": {
"frac": "~1.1.2"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/wmf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
"integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/word": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
"integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/xlsx": {
"version": "0.18.5",
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
"license": "Apache-2.0",
"dependencies": {
"adler-32": "~1.3.0",
"cfb": "~1.2.1",
"codepage": "~1.15.0",
"crc-32": "~1.2.1",
"ssf": "~0.11.2",
"wmf": "~1.0.1",
"word": "~0.3.0"
},
"bin": {
"xlsx": "bin/xlsx.njs"
},
"engines": {
"node": ">=0.8"
}
}
}
}

15
deploy/tools/package.json Normal file
View File

@ -0,0 +1,15 @@
{
"name": "tools",
"version": "1.0.0",
"main": "xls2json.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"xlsx": "^0.18.5"
}
}

21
deploy/tools/xls2json.js Normal file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env node
// Converts .xls/.xlsx to JSON on stdout.
// Usage: node xls2json.js <filepath>
const XLSX = require(__dirname + '/node_modules/xlsx');
const path = require('path');
const file = process.argv[2];
if (!file) {
process.stderr.write('usage: xls2json.js <file>\n');
process.exit(1);
}
try {
const wb = XLSX.readFile(file);
const ws = wb.Sheets[wb.SheetNames[0]];
const rows = XLSX.utils.sheet_to_json(ws, { header: 1, defval: '' });
process.stdout.write(JSON.stringify(rows));
} catch (e) {
process.stderr.write('xls2json error: ' + e.message + '\n');
process.exit(2);
}

View File

@ -8,3 +8,23 @@ Auth:
DefaultTenantId: t_default_001
DataSource: root:muyu2026@tcp(127.0.0.1:3306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
PostgresDataSource: "postgres://muyu:muyu2026@127.0.0.1:5432/muyu_crm?sslmode=disable"
SystemRpc:
Etcd:
Hosts:
- 127.0.0.1:2379
Key: system.rpc
NonBlock: true
InventoryRpc:
Etcd:
Hosts:
- 127.0.0.1:2379
Key: inventory.rpc
NonBlock: true
Log:
ServiceName: gateway-api
Mode: console
Level: info

View File

@ -219,47 +219,33 @@ type (
// ==================== Product Types ====================
type (
ProductInfo {
ProductId string `json:"productId"`
ProductName string `json:"productName"`
ImageUrl string `json:"imageUrl"`
Spec string `json:"spec"`
Color string `json:"color"`
UnitPieces int64 `json:"unitPieces"`
UnitRolls int64 `json:"unitRolls"`
StockQuantity string `json:"stockQuantity"`
Location string `json:"location"`
CostPrice string `json:"costPrice"`
SalesPrice string `json:"salesPrice"`
Remark string `json:"remark"`
Status int64 `json:"status"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
ProductId string `json:"productId"`
ProductName string `json:"productName"`
ImageUrl string `json:"imageUrl"`
Spec string `json:"spec"`
Color string `json:"color"`
SalesPrice string `json:"salesPrice"`
Remark string `json:"remark"`
Status int64 `json:"status"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
CreateProductReq {
ProductName string `json:"productName"`
ImageUrl string `json:"imageUrl,optional"`
Spec string `json:"spec,optional"`
Color string `json:"color,optional"`
UnitPieces int64 `json:"unitPieces,optional"`
UnitRolls int64 `json:"unitRolls,optional"`
StockQuantity string `json:"stockQuantity,optional"`
Location string `json:"location,optional"`
CostPrice string `json:"costPrice,optional"`
SalesPrice string `json:"salesPrice,optional"`
Remark string `json:"remark,optional"`
ProductName string `json:"productName"`
ImageUrl string `json:"imageUrl,optional"`
Spec string `json:"spec,optional"`
Color string `json:"color,optional"`
SalesPrice string `json:"salesPrice,optional"`
Remark string `json:"remark,optional"`
Pans []PanInput `json:"pans,optional"`
}
UpdateProductReq {
ProductName string `json:"productName,optional"`
ImageUrl string `json:"imageUrl,optional"`
Spec string `json:"spec,optional"`
Color string `json:"color,optional"`
UnitPieces int64 `json:"unitPieces,optional"`
UnitRolls int64 `json:"unitRolls,optional"`
StockQuantity string `json:"stockQuantity,optional"`
Location string `json:"location,optional"`
CostPrice string `json:"costPrice,optional"`
SalesPrice string `json:"salesPrice,optional"`
Remark string `json:"remark,optional"`
ProductName string `json:"productName,optional"`
ImageUrl string `json:"imageUrl,optional"`
Spec string `json:"spec,optional"`
Color string `json:"color,optional"`
SalesPrice string `json:"salesPrice,optional"`
Remark string `json:"remark,optional"`
}
ListProductReq {
Page int64 `form:"page,default=1"`
@ -267,7 +253,6 @@ type (
ProductName string `form:"productName,optional"`
Spec string `form:"spec,optional"`
Color string `form:"color,optional"`
Location string `form:"location,optional"`
Status int64 `form:"status,optional,default=-1"`
}
ListProductResp {
@ -283,13 +268,92 @@ type (
}
)
// ==================== Pan / Bolt Types ====================
type (
BoltInfo {
BoltId string `json:"boltId"`
PanId string `json:"panId"`
LengthM string `json:"lengthM"`
SortOrder int64 `json:"sortOrder"`
}
PanInfo {
PanId string `json:"panId"`
ProductId string `json:"productId"`
Name string `json:"name"`
Position string `json:"position"`
SortOrder int64 `json:"sortOrder"`
Bolts []BoltInfo `json:"bolts"`
BoltCount int64 `json:"boltCount"`
TotalLength string `json:"totalLength"`
}
PanInput {
Name string `json:"name"`
Position string `json:"position,optional"`
BoltLengths []string `json:"boltLengths,optional"`
}
ListPanResp {
List []PanInfo `json:"list"`
}
SavePansReq {
Pans []PanInput `json:"pans"`
}
ListBoltResp {
List []BoltInfo `json:"list"`
}
SaveBoltsReq {
Lengths []string `json:"lengths"`
}
ProductTreeResp {
Product ProductInfo `json:"product"`
Pans []PanInfo `json:"pans"`
TotalPanCount int64 `json:"totalPanCount"`
TotalBoltCount int64 `json:"totalBoltCount"`
TotalLengthM string `json:"totalLengthM"`
}
)
// ==================== Panel Types ====================
type (
ProductSummaryItem {
ProductName string `json:"productName"`
Spec string `json:"spec"`
ColorCount int64 `json:"colorCount"`
TotalPanCount int64 `json:"totalPanCount"`
TotalBoltCount int64 `json:"totalBoltCount"`
TotalLengthM string `json:"totalLengthM"`
Colors string `json:"colors"`
Locations string `json:"locations"`
}
ProductSummaryResp {
List []ProductSummaryItem `json:"list"`
}
ColorDetailItem {
ProductId string `json:"productId"`
ProductName string `json:"productName"`
Color string `json:"color"`
Spec string `json:"spec"`
ImageUrl string `json:"imageUrl"`
PanCount int64 `json:"panCount"`
BoltCount int64 `json:"boltCount"`
TotalLengthM string `json:"totalLengthM"`
Locations string `json:"locations"`
SalesPrice string `json:"salesPrice"`
}
ListColorDetailReq {
ProductName string `form:"productName"`
}
ColorDetailResp {
List []ColorDetailItem `json:"list"`
}
)
// ==================== Stock Statistics Types ====================
type (
StockSummaryResp {
ProductCount int64 `json:"productCount"`
TotalPieces int64 `json:"totalPieces"`
TotalRolls int64 `json:"totalRolls"`
TotalCostValue string `json:"totalCostValue"`
TotalPans int64 `json:"totalPans"`
TotalLengthM string `json:"totalLengthM"`
TotalSalesValue string `json:"totalSalesValue"`
}
StockGroupItem {
@ -644,6 +708,36 @@ service gateway-api {
@handler ExportProductHandler
get /products/export
@handler GetProductSummaryHandler
get /products/summary returns (ProductSummaryResp)
@handler GetColorDetailHandler
get /products/colors (ListColorDetailReq) returns (ColorDetailResp)
@handler GetProductTreeHandler
get /products/:id/tree returns (ProductTreeResp)
@handler ListPansHandler
get /products/:id/pans returns (ListPanResp)
@handler SavePansHandler
put /products/:id/pans (SavePansReq) returns (IdResp)
}
// Inventory - Pan/Bolt Management
@server (
prefix: /api/v1/inventory
group: inventory/panbolt
jwt: Auth
middleware: AuthorityMiddleware
)
service gateway-api {
@handler ListBoltsHandler
get /pans/:id/bolts returns (ListBoltResp)
@handler SaveBoltsHandler
put /pans/:id/bolts (SaveBoltsReq) returns (IdResp)
}
// Inventory - Stock Query & Statistics

View File

@ -50,7 +50,7 @@ func main() {
if ok {
return http.StatusOK, &Response{Code: int(s.Code()), Msg: s.Message()}
}
return http.StatusOK, &Response{Code: 1001, Msg: err.Error()}
return http.StatusBadRequest, &Response{Code: 1001, Msg: err.Error()}
})
httpx.SetOkHandler(func(ctx context.Context, a interface{}) interface{} {

View File

@ -0,0 +1,29 @@
package relation
import (
"net/http"
"strconv"
"muyu-apiserver/gateway/internal/svc"
"github.com/zeromicro/go-zero/rest/httpx"
)
func SearchTenantsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
keyword := r.URL.Query().Get("keyword")
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
if limit <= 0 {
limit = 20
}
tenants, err := svcCtx.CrmRepo.SearchTenants(r.Context(), keyword, limit)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
httpx.OkJsonCtx(r.Context(), w, map[string]interface{}{
"list": tenants,
})
}
}

View File

@ -0,0 +1,27 @@
package panbolt
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
logic "muyu-apiserver/gateway/internal/logic/inventory/panbolt"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func ListBoltViewHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ListBoltViewReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := logic.NewListBoltViewLogic(r.Context(), svcCtx)
resp, err := l.ListBoltView(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -0,0 +1,24 @@
package panbolt
import (
"context"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
logic "muyu-apiserver/gateway/internal/logic/inventory/panbolt"
"muyu-apiserver/gateway/internal/svc"
)
func ListBoltsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"])
l := logic.NewListBoltsLogic(ctx, svcCtx)
resp, err := l.ListBolts()
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -0,0 +1,27 @@
package panbolt
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
logic "muyu-apiserver/gateway/internal/logic/inventory/panbolt"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func ListPanViewHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ListPanViewReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := logic.NewListPanViewLogic(r.Context(), svcCtx)
resp, err := l.ListPanView(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -0,0 +1,30 @@
package panbolt
import (
"context"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
logic "muyu-apiserver/gateway/internal/logic/inventory/panbolt"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func SaveBoltsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.SaveBoltsReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"])
l := logic.NewSaveBoltsLogic(ctx, svcCtx)
resp, err := l.SaveBolts(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -0,0 +1,27 @@
package product
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
logic "muyu-apiserver/gateway/internal/logic/inventory/product"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func GetColorDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ListColorDetailReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := logic.NewGetColorDetailLogic(r.Context(), svcCtx)
resp, err := l.GetColorDetail(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -0,0 +1,21 @@
package product
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
logic "muyu-apiserver/gateway/internal/logic/inventory/product"
"muyu-apiserver/gateway/internal/svc"
)
func GetProductSummaryHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := logic.NewGetProductSummaryLogic(r.Context(), svcCtx)
resp, err := l.GetProductSummary()
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -0,0 +1,24 @@
package product
import (
"context"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
logic "muyu-apiserver/gateway/internal/logic/inventory/product"
"muyu-apiserver/gateway/internal/svc"
)
func GetProductTreeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"])
l := logic.NewGetProductTreeLogic(ctx, svcCtx)
resp, err := l.GetProductTree()
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,20 +1,46 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.2
package product
import (
"io"
"net/http"
"os"
"path/filepath"
"github.com/zeromicro/go-zero/rest/httpx"
"muyu-apiserver/gateway/internal/logic/inventory/product"
"muyu-apiserver/gateway/internal/svc"
)
const maxUploadSize = 50 << 20 // 50 MB
func ImportProductHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)
file, header, err := r.FormFile("file")
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
defer file.Close()
ext := filepath.Ext(header.Filename)
tmpFile, err := os.CreateTemp("", "import-*"+ext)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
defer os.Remove(tmpFile.Name())
defer tmpFile.Close()
if _, err = io.Copy(tmpFile, file); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
tmpFile.Close()
l := product.NewImportProductLogic(r.Context(), svcCtx)
resp, err := l.ImportProduct()
resp, err := l.ImportProduct(tmpFile.Name(), header.Filename)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {

View File

@ -0,0 +1,24 @@
package product
import (
"context"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
logic "muyu-apiserver/gateway/internal/logic/inventory/product"
"muyu-apiserver/gateway/internal/svc"
)
func ListPansHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"])
l := logic.NewListPansLogic(ctx, svcCtx)
resp, err := l.ListPans()
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -0,0 +1,30 @@
package product
import (
"context"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
logic "muyu-apiserver/gateway/internal/logic/inventory/product"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func SavePansHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.SavePansReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"])
l := logic.NewSavePansLogic(ctx, svcCtx)
resp, err := l.SavePans(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -10,6 +10,7 @@ import (
crmrelation "muyu-apiserver/gateway/internal/handler/crm/relation"
inventoryadjust "muyu-apiserver/gateway/internal/handler/inventory/adjust"
inventorycheck "muyu-apiserver/gateway/internal/handler/inventory/check"
inventorypanbolt "muyu-apiserver/gateway/internal/handler/inventory/panbolt"
inventoryproduct "muyu-apiserver/gateway/internal/handler/inventory/product"
inventorystock "muyu-apiserver/gateway/internal/handler/inventory/stock"
systemconfig "muyu-apiserver/gateway/internal/handler/system/config"
@ -34,6 +35,17 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
rest.WithPrefix("/api/v1/auth"),
)
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodGet,
Path: "/tenants/search",
Handler: crmrelation.SearchTenantsHandler(serverCtx),
},
},
rest.WithPrefix("/api/v1/public"),
)
server.AddRoutes(
[]rest.Route{
{
@ -175,6 +187,21 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/products",
Handler: inventoryproduct.CreateProductHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/products/summary",
Handler: inventoryproduct.GetProductSummaryHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/products/colors",
Handler: inventoryproduct.GetColorDetailHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/products/export",
Handler: inventoryproduct.ExportProductHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/products/:id",
@ -192,9 +219,29 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
},
{
Method: http.MethodGet,
Path: "/products/export",
Handler: inventoryproduct.ExportProductHandler(serverCtx),
Path: "/products/:id/tree",
Handler: inventoryproduct.GetProductTreeHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/products/:id/pans",
Handler: inventoryproduct.ListPansHandler(serverCtx),
},
{
Method: http.MethodPut,
Path: "/products/:id/pans",
Handler: inventoryproduct.SavePansHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/inventory"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthorityMiddleware},
[]rest.Route{
{
Method: http.MethodPost,
Path: "/products/import",
@ -204,6 +251,37 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/inventory"),
rest.WithMaxBytes(50<<20),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthorityMiddleware},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/pans",
Handler: inventorypanbolt.ListPanViewHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/bolts",
Handler: inventorypanbolt.ListBoltViewHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/pans/:id/bolts",
Handler: inventorypanbolt.ListBoltsHandler(serverCtx),
},
{
Method: http.MethodPut,
Path: "/pans/:id/bolts",
Handler: inventorypanbolt.SaveBoltsHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/inventory"),
)
server.AddRoutes(

View File

@ -0,0 +1,53 @@
package panbolt
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type ListBoltViewLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewListBoltViewLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListBoltViewLogic {
return &ListBoltViewLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ListBoltViewLogic) ListBoltView(req *types.ListBoltViewReq) (resp *types.ListBoltViewResp, err error) {
rpcResp, err := l.svcCtx.InventoryRpc.ListBoltView(l.ctx, &pb.ListBoltViewReq{
Page: req.Page,
PageSize: req.PageSize,
ProductName: req.ProductName,
Position: req.Position,
})
if err != nil {
return nil, err
}
list := make([]types.BoltListItem, 0, len(rpcResp.List))
for _, item := range rpcResp.List {
list = append(list, types.BoltListItem{
BoltId: item.BoltId,
PanId: item.PanId,
LengthM: item.LengthM,
Color: item.Color,
SortOrder: item.SortOrder,
PanName: item.PanName,
Position: item.Position,
ProductId: item.ProductId,
ProductName: item.ProductName,
})
}
return &types.ListBoltViewResp{Total: rpcResp.Total, List: list}, nil
}

View File

@ -0,0 +1,44 @@
package panbolt
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type ListBoltsLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewListBoltsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListBoltsLogic {
return &ListBoltsLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ListBoltsLogic) ListBolts() (resp *types.ListBoltResp, err error) {
panId := ctxdata.GetPathId(l.ctx)
rpcResp, err := l.svcCtx.InventoryRpc.ListBolts(l.ctx, &pb.ListBoltReq{PanId: panId})
if err != nil {
return nil, err
}
list := make([]types.BoltInfo, 0, len(rpcResp.List))
for _, b := range rpcResp.List {
list = append(list, types.BoltInfo{
BoltId: b.BoltId,
PanId: b.PanId,
LengthM: b.LengthM,
SortOrder: b.SortOrder,
})
}
return &types.ListBoltResp{List: list}, nil
}

View File

@ -0,0 +1,52 @@
package panbolt
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type ListPanViewLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewListPanViewLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListPanViewLogic {
return &ListPanViewLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ListPanViewLogic) ListPanView(req *types.ListPanViewReq) (resp *types.ListPanViewResp, err error) {
rpcResp, err := l.svcCtx.InventoryRpc.ListPanView(l.ctx, &pb.ListPanViewReq{
Page: req.Page,
PageSize: req.PageSize,
ProductName: req.ProductName,
Position: req.Position,
})
if err != nil {
return nil, err
}
list := make([]types.PanListItem, 0, len(rpcResp.List))
for _, item := range rpcResp.List {
list = append(list, types.PanListItem{
PanId: item.PanId,
ProductId: item.ProductId,
Name: item.Name,
Position: item.Position,
ProductName: item.ProductName,
Colors: item.Colors,
BoltCount: item.BoltCount,
TotalLengthM: item.TotalLengthM,
})
}
return &types.ListPanViewResp{Total: rpcResp.Total, List: list}, nil
}

View File

@ -0,0 +1,38 @@
package panbolt
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type SaveBoltsLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewSaveBoltsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SaveBoltsLogic {
return &SaveBoltsLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *SaveBoltsLogic) SaveBolts(req *types.SaveBoltsReq) (resp *types.IdResp, err error) {
panId := ctxdata.GetPathId(l.ctx)
_, err = l.svcCtx.InventoryRpc.SaveBolts(l.ctx, &pb.SaveBoltsReq{
PanId: panId,
Lengths: req.Lengths,
})
if err != nil {
return nil, err
}
return &types.IdResp{Id: panId}, nil
}

View File

@ -26,17 +26,13 @@ func NewCreateProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Cre
func (l *CreateProductLogic) CreateProduct(req *types.CreateProductReq) (resp *types.IdResp, err error) {
rpcResp, err := l.svcCtx.InventoryRpc.CreateProduct(l.ctx, &pb.CreateProductReq{
ProductName: req.ProductName,
ImageUrl: req.ImageUrl,
Spec: req.Spec,
Color: req.Color,
UnitPieces: req.UnitPieces,
UnitRolls: req.UnitRolls,
StockQuantity: req.StockQuantity,
Location: req.Location,
CostPrice: req.CostPrice,
SalesPrice: req.SalesPrice,
Remark: req.Remark,
ProductName: req.ProductName,
ImageUrl: req.ImageUrl,
Spec: req.Spec,
Color: req.Color,
SalesPrice: req.SalesPrice,
Remark: req.Remark,
Pans: panInputsToPb(req.Pans),
})
if err != nil {
return nil, err
@ -44,3 +40,18 @@ func (l *CreateProductLogic) CreateProduct(req *types.CreateProductReq) (resp *t
return &types.IdResp{Id: rpcResp.Id}, nil
}
func panInputsToPb(pans []types.PanInput) []*pb.PanInput {
if len(pans) == 0 {
return nil
}
out := make([]*pb.PanInput, 0, len(pans))
for _, p := range pans {
out = append(out, &pb.PanInput{
Name: p.Name,
Position: p.Position,
BoltLengths: p.BoltLengths,
})
}
return out
}

View File

@ -0,0 +1,50 @@
package product
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type GetColorDetailLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetColorDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetColorDetailLogic {
return &GetColorDetailLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetColorDetailLogic) GetColorDetail(req *types.ListColorDetailReq) (resp *types.ColorDetailResp, err error) {
rpcResp, err := l.svcCtx.InventoryRpc.ListColorDetail(l.ctx, &pb.ListColorDetailReq{
ProductName: req.ProductName,
})
if err != nil {
return nil, err
}
list := make([]types.ColorDetailItem, 0, len(rpcResp.List))
for _, it := range rpcResp.List {
list = append(list, types.ColorDetailItem{
ProductId: it.ProductId,
ProductName: it.ProductName,
Color: it.Color,
Spec: it.Spec,
ImageUrl: it.ImageUrl,
PanCount: it.PanCount,
BoltCount: it.BoltCount,
TotalLengthM: it.TotalLengthM,
Locations: it.Locations,
SalesPrice: it.SalesPrice,
})
}
return &types.ColorDetailResp{List: list}, nil
}

View File

@ -35,21 +35,6 @@ func (l *GetProductLogic) GetProduct() (resp *types.ProductInfo, err error) {
return nil, err
}
return &types.ProductInfo{
ProductId: rpcResp.ProductId,
ProductName: rpcResp.ProductName,
ImageUrl: rpcResp.ImageUrl,
Spec: rpcResp.Spec,
Color: rpcResp.Color,
UnitPieces: rpcResp.UnitPieces,
UnitRolls: rpcResp.UnitRolls,
StockQuantity: rpcResp.StockQuantity,
Location: rpcResp.Location,
CostPrice: rpcResp.CostPrice,
SalesPrice: rpcResp.SalesPrice,
Remark: rpcResp.Remark,
Status: rpcResp.Status,
CreatedAt: rpcResp.CreatedAt,
UpdatedAt: rpcResp.UpdatedAt,
}, nil
pi := productInfoFromPB(rpcResp)
return &pi, nil
}

View File

@ -0,0 +1,46 @@
package product
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type GetProductSummaryLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetProductSummaryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetProductSummaryLogic {
return &GetProductSummaryLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetProductSummaryLogic) GetProductSummary() (resp *types.ProductSummaryResp, err error) {
rpcResp, err := l.svcCtx.InventoryRpc.ListProductSummary(l.ctx, &pb.ListProductSummaryReq{})
if err != nil {
return nil, err
}
list := make([]types.ProductSummaryItem, 0, len(rpcResp.List))
for _, it := range rpcResp.List {
list = append(list, types.ProductSummaryItem{
ProductName: it.ProductName,
Spec: it.Spec,
ColorCount: it.ColorCount,
TotalPanCount: it.TotalPanCount,
TotalBoltCount: it.TotalBoltCount,
TotalLengthM: it.TotalLengthM,
Colors: it.Colors,
Locations: it.Locations,
})
}
return &types.ProductSummaryResp{List: list}, nil
}

View File

@ -0,0 +1,47 @@
package product
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type GetProductTreeLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetProductTreeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetProductTreeLogic {
return &GetProductTreeLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetProductTreeLogic) GetProductTree() (resp *types.ProductTreeResp, err error) {
id := ctxdata.GetPathId(l.ctx)
rpcResp, err := l.svcCtx.InventoryRpc.GetProductTree(l.ctx, &pb.GetProductReq{
ProductId: id,
})
if err != nil {
return nil, err
}
pans := make([]types.PanInfo, 0, len(rpcResp.Pans))
for _, p := range rpcResp.Pans {
pans = append(pans, panInfoFromPB(p))
}
return &types.ProductTreeResp{
Product: productInfoFromPB(rpcResp.Product),
Pans: pans,
TotalPanCount: rpcResp.TotalPanCount,
TotalBoltCount: rpcResp.TotalBoltCount,
TotalLengthM: rpcResp.TotalLengthM,
}, nil
}

View File

@ -2,10 +2,13 @@ package product
import (
"context"
"errors"
"fmt"
"strconv"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/excelparse"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
@ -24,6 +27,50 @@ func NewImportProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Imp
}
}
func (l *ImportProductLogic) ImportProduct() (resp *types.ImportProductResp, err error) {
return nil, errors.New("import product requires file upload handling, will be implemented in handler layer")
func (l *ImportProductLogic) ImportProduct(filePath, fileName string) (resp *types.ImportProductResp, err error) {
result, err := excelparse.ParseFile(filePath)
if err != nil {
return nil, fmt.Errorf("解析Excel失败: %w", err)
}
l.Infof("excel parsed: format=%s, products=%d, skipped=%d",
result.ParserName, len(result.Products), result.SkippedRows)
products := make([]*pb.CreateProductReq, 0, len(result.Products))
for _, p := range result.Products {
products = append(products, &pb.CreateProductReq{
ProductName: p.ProductName,
Spec: p.Spec,
Color: p.Color,
SalesPrice: p.SalesPrice,
Remark: p.Remark,
})
}
rpcResp, err := l.svcCtx.InventoryRpc.ImportProducts(l.ctx, &pb.ImportProductReq{
Products: products,
FileName: fileName + " (" + result.ParserName + "格式)",
Operator: operatorFromCtx(l.ctx),
})
if err != nil {
return nil, err
}
return &types.ImportProductResp{
TotalCount: rpcResp.TotalCount,
SuccessCount: rpcResp.SuccessCount,
FailCount: rpcResp.FailCount,
ErrorMsg: rpcResp.ErrorMsg,
ImportId: rpcResp.ImportId,
}, nil
}
func operatorFromCtx(ctx context.Context) string {
if uid, ok := ctx.Value("userId").(string); ok {
return uid
}
if uid, ok := ctx.Value("userId").(int64); ok {
return strconv.FormatInt(uid, 10)
}
return "system"
}

View File

@ -0,0 +1,85 @@
package product
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type ListPansLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewListPansLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListPansLogic {
return &ListPansLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ListPansLogic) ListPans() (resp *types.ListPanResp, err error) {
id := ctxdata.GetPathId(l.ctx)
rpcResp, err := l.svcCtx.InventoryRpc.ListPans(l.ctx, &pb.ListPanReq{
ProductId: id,
})
if err != nil {
return nil, err
}
list := make([]types.PanInfo, 0, len(rpcResp.List))
for _, p := range rpcResp.List {
list = append(list, panInfoFromPB(p))
}
return &types.ListPanResp{List: list}, nil
}
func boltInfoFromPB(b *pb.BoltInfo) types.BoltInfo {
return types.BoltInfo{
BoltId: b.BoltId,
PanId: b.PanId,
LengthM: b.LengthM,
SortOrder: b.SortOrder,
}
}
func panInfoFromPB(p *pb.PanInfo) types.PanInfo {
bolts := make([]types.BoltInfo, 0, len(p.Bolts))
for _, b := range p.Bolts {
bolts = append(bolts, boltInfoFromPB(b))
}
return types.PanInfo{
PanId: p.PanId,
ProductId: p.ProductId,
Name: p.Name,
Position: p.Position,
SortOrder: p.SortOrder,
Bolts: bolts,
BoltCount: p.BoltCount,
TotalLength: p.TotalLength,
}
}
func productInfoFromPB(p *pb.ProductInfo) types.ProductInfo {
if p == nil {
return types.ProductInfo{}
}
return types.ProductInfo{
ProductId: p.ProductId,
ProductName: p.ProductName,
ImageUrl: p.ImageUrl,
Spec: p.Spec,
Color: p.Color,
SalesPrice: p.SalesPrice,
Remark: p.Remark,
Status: p.Status,
CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt,
}
}

View File

@ -31,7 +31,6 @@ func (l *ListProductLogic) ListProduct(req *types.ListProductReq) (resp *types.L
ProductName: req.ProductName,
Spec: req.Spec,
Color: req.Color,
Location: req.Location,
Status: req.Status,
})
if err != nil {
@ -40,23 +39,7 @@ func (l *ListProductLogic) ListProduct(req *types.ListProductReq) (resp *types.L
list := make([]types.ProductInfo, 0, len(rpcResp.List))
for _, p := range rpcResp.List {
list = append(list, types.ProductInfo{
ProductId: p.ProductId,
ProductName: p.ProductName,
ImageUrl: p.ImageUrl,
Spec: p.Spec,
Color: p.Color,
UnitPieces: p.UnitPieces,
UnitRolls: p.UnitRolls,
StockQuantity: p.StockQuantity,
Location: p.Location,
CostPrice: p.CostPrice,
SalesPrice: p.SalesPrice,
Remark: p.Remark,
Status: p.Status,
CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt,
})
list = append(list, productInfoFromPB(p))
}
return &types.ListProductResp{

View File

@ -0,0 +1,38 @@
package product
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type SavePansLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewSavePansLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SavePansLogic {
return &SavePansLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *SavePansLogic) SavePans(req *types.SavePansReq) (resp *types.IdResp, err error) {
id := ctxdata.GetPathId(l.ctx)
_, err = l.svcCtx.InventoryRpc.SavePans(l.ctx, &pb.SavePansReq{
ProductId: id,
Pans: panInputsToPb(req.Pans),
})
if err != nil {
return nil, err
}
return &types.IdResp{Id: id}, nil
}

View File

@ -29,18 +29,13 @@ func (l *UpdateProductLogic) UpdateProduct(req *types.UpdateProductReq) (resp *t
id := ctxdata.GetPathId(l.ctx)
_, err = l.svcCtx.InventoryRpc.UpdateProduct(l.ctx, &pb.UpdateProductReq{
ProductId: id,
ProductName: req.ProductName,
ImageUrl: req.ImageUrl,
Spec: req.Spec,
Color: req.Color,
UnitPieces: req.UnitPieces,
UnitRolls: req.UnitRolls,
StockQuantity: req.StockQuantity,
Location: req.Location,
CostPrice: req.CostPrice,
SalesPrice: req.SalesPrice,
Remark: req.Remark,
ProductId: id,
ProductName: req.ProductName,
ImageUrl: req.ImageUrl,
Spec: req.Spec,
Color: req.Color,
SalesPrice: req.SalesPrice,
Remark: req.Remark,
})
if err != nil {
return nil, err

View File

@ -33,8 +33,8 @@ func (l *GetStockSummaryLogic) GetStockSummary() (resp *types.StockSummaryResp,
return &types.StockSummaryResp{
ProductCount: rpcResp.ProductCount,
TotalPieces: rpcResp.TotalPieces,
TotalRolls: rpcResp.TotalRolls,
TotalCostValue: rpcResp.TotalCostValue,
TotalPans: rpcResp.TotalPans,
TotalLengthM: rpcResp.TotalLengthM,
TotalSalesValue: rpcResp.TotalSalesValue,
}, nil
}

View File

@ -31,7 +31,6 @@ func (l *ListStockLogic) ListStock(req *types.ListProductReq) (resp *types.ListP
ProductName: req.ProductName,
Spec: req.Spec,
Color: req.Color,
Location: req.Location,
Status: req.Status,
})
if err != nil {
@ -41,21 +40,16 @@ func (l *ListStockLogic) ListStock(req *types.ListProductReq) (resp *types.ListP
list := make([]types.ProductInfo, 0, len(rpcResp.List))
for _, p := range rpcResp.List {
list = append(list, types.ProductInfo{
ProductId: p.ProductId,
ProductName: p.ProductName,
ImageUrl: p.ImageUrl,
Spec: p.Spec,
Color: p.Color,
UnitPieces: p.UnitPieces,
UnitRolls: p.UnitRolls,
StockQuantity: p.StockQuantity,
Location: p.Location,
CostPrice: p.CostPrice,
SalesPrice: p.SalesPrice,
Remark: p.Remark,
Status: p.Status,
CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt,
ProductId: p.ProductId,
ProductName: p.ProductName,
ImageUrl: p.ImageUrl,
Spec: p.Spec,
Color: p.Color,
SalesPrice: p.SalesPrice,
Remark: p.Remark,
Status: p.Status,
CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt,
})
}

View File

@ -5,6 +5,7 @@ import (
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/system/pb"
"github.com/zeromicro/go-zero/core/logx"
@ -37,5 +38,9 @@ func (l *CreateUserLogic) CreateUser(req *types.CreateUserReq) (resp *types.IdRe
return nil, err
}
if tenantId := ctxdata.GetTenantId(l.ctx); tenantId != "" {
_ = l.svcCtx.CrmRepo.AddUserToTenant(l.ctx, rpcResp.Id, tenantId)
}
return &types.IdResp{Id: rpcResp.Id}, nil
}

View File

@ -63,6 +63,36 @@ func NewCrmRepo(conn sqlx.SqlConn) *CrmRepo {
return &CrmRepo{conn: conn}
}
func (r *CrmRepo) SearchTenants(ctx context.Context, keyword string, limit int) ([]GraphNode, error) {
if limit <= 0 || limit > 50 {
limit = 20
}
nodes := make([]GraphNode, 0)
if keyword == "" {
if err := r.conn.QueryRowsCtx(ctx, &nodes,
"SELECT tenant_id, tenant_name FROM crm_tenant WHERE status = 1 ORDER BY tenant_name LIMIT $1", limit); err != nil {
return nil, err
}
} else {
if err := r.conn.QueryRowsCtx(ctx, &nodes,
"SELECT tenant_id, tenant_name FROM crm_tenant WHERE status = 1 AND tenant_name LIKE $1 ORDER BY tenant_name LIMIT $2",
"%"+keyword+"%", limit); err != nil {
return nil, err
}
}
return nodes, nil
}
func (r *CrmRepo) AddUserToTenant(ctx context.Context, userId, tenantId string) error {
_, err := r.conn.ExecCtx(ctx,
`INSERT INTO crm_tenant_user (user_id, tenant_id, is_primary, status)
VALUES ($1, $2, 1, 1)
ON CONFLICT (user_id, tenant_id) DO NOTHING`,
userId, tenantId,
)
return err
}
func (r *CrmRepo) VerifyUserTenant(ctx context.Context, userId, tenantId string) (bool, error) {
var count int64
err := r.conn.QueryRowCtx(ctx, &count,

View File

@ -34,17 +34,13 @@ type CreateMenuReq struct {
}
type CreateProductReq struct {
ProductName string `json:"productName"`
ImageUrl string `json:"imageUrl,optional"`
Spec string `json:"spec,optional"`
Color string `json:"color,optional"`
UnitPieces int64 `json:"unitPieces,optional"`
UnitRolls int64 `json:"unitRolls,optional"`
StockQuantity string `json:"stockQuantity,optional"`
Location string `json:"location,optional"`
CostPrice string `json:"costPrice,optional"`
SalesPrice string `json:"salesPrice,optional"`
Remark string `json:"remark,optional"`
ProductName string `json:"productName"`
ImageUrl string `json:"imageUrl,optional"`
Spec string `json:"spec,optional"`
Color string `json:"color,optional"`
SalesPrice string `json:"salesPrice,optional"`
Remark string `json:"remark,optional"`
Pans []PanInput `json:"pans,optional"`
}
type CreateRoleReq struct {
@ -118,7 +114,6 @@ type ListProductReq struct {
ProductName string `form:"productName,optional"`
Spec string `form:"spec,optional"`
Color string `form:"color,optional"`
Location string `form:"location,optional"`
Status int64 `form:"status,optional,default=-1"`
}
@ -230,21 +225,147 @@ type PageReq struct {
}
type ProductInfo struct {
ProductId string `json:"productId"`
ProductName string `json:"productName"`
ImageUrl string `json:"imageUrl"`
Spec string `json:"spec"`
Color string `json:"color"`
UnitPieces int64 `json:"unitPieces"`
UnitRolls int64 `json:"unitRolls"`
StockQuantity string `json:"stockQuantity"`
Location string `json:"location"`
CostPrice string `json:"costPrice"`
SalesPrice string `json:"salesPrice"`
Remark string `json:"remark"`
Status int64 `json:"status"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
ProductId string `json:"productId"`
ProductName string `json:"productName"`
ImageUrl string `json:"imageUrl"`
Spec string `json:"spec"`
Color string `json:"color"`
SalesPrice string `json:"salesPrice"`
Remark string `json:"remark"`
Status int64 `json:"status"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type BoltInfo struct {
BoltId string `json:"boltId"`
PanId string `json:"panId"`
LengthM string `json:"lengthM"`
SortOrder int64 `json:"sortOrder"`
}
type PanInfo struct {
PanId string `json:"panId"`
ProductId string `json:"productId"`
Name string `json:"name"`
Position string `json:"position"`
SortOrder int64 `json:"sortOrder"`
Bolts []BoltInfo `json:"bolts"`
BoltCount int64 `json:"boltCount"`
TotalLength string `json:"totalLength"`
}
type PanInput struct {
Name string `json:"name"`
Position string `json:"position,optional"`
BoltLengths []string `json:"boltLengths,optional"`
}
type ListPanResp struct {
List []PanInfo `json:"list"`
}
type SavePansReq struct {
Pans []PanInput `json:"pans"`
}
type ListBoltResp struct {
List []BoltInfo `json:"list"`
}
type SaveBoltsReq struct {
Lengths []string `json:"lengths"`
}
type ProductTreeResp struct {
Product ProductInfo `json:"product"`
Pans []PanInfo `json:"pans"`
TotalPanCount int64 `json:"totalPanCount"`
TotalBoltCount int64 `json:"totalBoltCount"`
TotalLengthM string `json:"totalLengthM"`
}
type ProductSummaryItem struct {
ProductName string `json:"productName"`
Spec string `json:"spec"`
ColorCount int64 `json:"colorCount"`
TotalPanCount int64 `json:"totalPanCount"`
TotalBoltCount int64 `json:"totalBoltCount"`
TotalLengthM string `json:"totalLengthM"`
Colors string `json:"colors"`
Locations string `json:"locations"`
}
type ProductSummaryResp struct {
List []ProductSummaryItem `json:"list"`
}
type ColorDetailItem struct {
ProductId string `json:"productId"`
ProductName string `json:"productName"`
Color string `json:"color"`
Spec string `json:"spec"`
ImageUrl string `json:"imageUrl"`
PanCount int64 `json:"panCount"`
BoltCount int64 `json:"boltCount"`
TotalLengthM string `json:"totalLengthM"`
Locations string `json:"locations"`
SalesPrice string `json:"salesPrice"`
}
type ListColorDetailReq struct {
ProductName string `form:"productName"`
}
type ColorDetailResp struct {
List []ColorDetailItem `json:"list"`
}
type PanListItem struct {
PanId string `json:"panId"`
ProductId string `json:"productId"`
Name string `json:"name"`
Position string `json:"position"`
ProductName string `json:"productName"`
Colors string `json:"colors"`
BoltCount int64 `json:"boltCount"`
TotalLengthM string `json:"totalLengthM"`
}
type ListPanViewReq struct {
Page int64 `form:"page,default=1"`
PageSize int64 `form:"pageSize,default=20"`
ProductName string `form:"productName,optional"`
Position string `form:"position,optional"`
}
type ListPanViewResp struct {
Total int64 `json:"total"`
List []PanListItem `json:"list"`
}
type BoltListItem struct {
BoltId string `json:"boltId"`
PanId string `json:"panId"`
LengthM string `json:"lengthM"`
Color string `json:"color"`
SortOrder int64 `json:"sortOrder"`
PanName string `json:"panName"`
Position string `json:"position"`
ProductId string `json:"productId"`
ProductName string `json:"productName"`
}
type ListBoltViewReq struct {
Page int64 `form:"page,default=1"`
PageSize int64 `form:"pageSize,default=20"`
ProductName string `form:"productName,optional"`
Position string `form:"position,optional"`
}
type ListBoltViewResp struct {
Total int64 `json:"total"`
List []BoltListItem `json:"list"`
}
type RoleInfo struct {
@ -337,8 +458,8 @@ type StockGroupResp struct {
type StockSummaryResp struct {
ProductCount int64 `json:"productCount"`
TotalPieces int64 `json:"totalPieces"`
TotalRolls int64 `json:"totalRolls"`
TotalCostValue string `json:"totalCostValue"`
TotalPans int64 `json:"totalPans"`
TotalLengthM string `json:"totalLengthM"`
TotalSalesValue string `json:"totalSalesValue"`
}
@ -359,17 +480,12 @@ type UpdateMenuReq struct {
}
type UpdateProductReq struct {
ProductName string `json:"productName,optional"`
ImageUrl string `json:"imageUrl,optional"`
Spec string `json:"spec,optional"`
Color string `json:"color,optional"`
UnitPieces int64 `json:"unitPieces,optional"`
UnitRolls int64 `json:"unitRolls,optional"`
StockQuantity string `json:"stockQuantity,optional"`
Location string `json:"location,optional"`
CostPrice string `json:"costPrice,optional"`
SalesPrice string `json:"salesPrice,optional"`
Remark string `json:"remark,optional"`
ProductName string `json:"productName,optional"`
ImageUrl string `json:"imageUrl,optional"`
Spec string `json:"spec,optional"`
Color string `json:"color,optional"`
SalesPrice string `json:"salesPrice,optional"`
Remark string `json:"remark,optional"`
}
type UpdateRoleReq struct {

10
go.mod
View File

@ -7,6 +7,8 @@ require (
github.com/casbin/gorm-adapter/v3 v3.28.0
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/lib/pq v1.12.0
github.com/prometheus/client_golang v1.23.2
github.com/xuri/excelize/v2 v2.10.1
github.com/zeromicro/go-zero v1.10.0
golang.org/x/crypto v0.48.0
google.golang.org/grpc v1.79.1
@ -68,14 +70,18 @@ require (
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/openzipkin/zipkin-go v0.4.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/redis/go-redis/v9 v9.17.3 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/richardlehane/mscfb v1.0.6 // indirect
github.com/richardlehane/msoleps v1.0.6 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/tiendc/go-deepcopy v1.7.2 // indirect
github.com/xuri/efp v0.0.1 // indirect
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
go.etcd.io/etcd/client/v3 v3.5.15 // indirect
@ -97,7 +103,7 @@ require (
go.uber.org/zap v1.24.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.41.0 // indirect

18
go.sum
View File

@ -223,6 +223,10 @@ github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1D
github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/richardlehane/mscfb v1.0.6 h1:eN3bvvZCp00bs7Zf52bxNwAx5lJDBK1tCuH19qq5aC8=
github.com/richardlehane/mscfb v1.0.6/go.mod h1:pe0+IUIc0AHh0+teNzBlJCtSyZdFOGgV4ZK9bsoV+Jo=
github.com/richardlehane/msoleps v1.0.6 h1:9BvkpjvD+iUBalUY4esMwv6uBkfOip/Lzvd93jvR9gg=
github.com/richardlehane/msoleps v1.0.6/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
@ -248,6 +252,14 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tiendc/go-deepcopy v1.7.2 h1:Ut2yYR7W9tWjTQitganoIue4UGxZwCcJy3orjrrIj44=
github.com/tiendc/go-deepcopy v1.7.2/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/excelize/v2 v2.10.1 h1:V62UlqopMqha3kOpnlHy2CcRVw1V8E63jFoWUmMzxN0=
github.com/xuri/excelize/v2 v2.10.1/go.mod h1:iG5tARpgaEeIhTqt3/fgXCGoBRt4hNXgCp3tfXKoOIc=
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@ -317,6 +329,8 @@ golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@ -348,8 +362,8 @@ golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

View File

@ -0,0 +1,146 @@
package model
import (
"context"
"fmt"
"strings"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var _ InvProductBoltModel = (*customInvProductBoltModel)(nil)
// BoltListItem is the flat view row for the bolt-dimension panel.
// Color comes from the bolt itself, spec is omitted (product-level only).
type BoltListItem struct {
BoltId string `db:"bolt_id"`
PanId string `db:"pan_id"`
LengthM float64 `db:"length_m"`
Color string `db:"color"`
SortOrder int64 `db:"sort_order"`
PanName string `db:"pan_name"`
Position string `db:"position"`
ProductId string `db:"product_id"`
ProductName string `db:"product_name"`
}
type (
InvProductBoltModel interface {
invProductBoltModel
FindByPanId(ctx context.Context, panId string) ([]*InvProductBolt, error)
FindByProductId(ctx context.Context, productId string) ([]*InvProductBolt, error)
DeleteByPanId(ctx context.Context, panId string) error
DeleteByProductId(ctx context.Context, productId string) error
BulkReplace(ctx context.Context, panId, tenantId string, bolts []*InvProductBolt) error
FindBoltList(ctx context.Context, tenantId string, page, pageSize int64, productName, position string) ([]*BoltListItem, int64, error)
}
customInvProductBoltModel struct {
*defaultInvProductBoltModel
}
)
func NewInvProductBoltModel(conn sqlx.SqlConn) InvProductBoltModel {
return &customInvProductBoltModel{
defaultInvProductBoltModel: newInvProductBoltModel(conn),
}
}
func (m *customInvProductBoltModel) FindByPanId(ctx context.Context, panId string) ([]*InvProductBolt, error) {
var list []*InvProductBolt
query := fmt.Sprintf("SELECT %s FROM %s WHERE `pan_id` = ? ORDER BY `sort_order`", invProductBoltRows, m.table)
err := m.conn.QueryRowsCtx(ctx, &list, query, panId)
if err != nil {
return nil, err
}
return list, nil
}
func (m *customInvProductBoltModel) FindByProductId(ctx context.Context, productId string) ([]*InvProductBolt, error) {
var list []*InvProductBolt
query := fmt.Sprintf(
"SELECT b.`id`, b.`bolt_id`, b.`pan_id`, b.`length_m`, b.`sort_order`, b.`tenant_id`, b.`created_at`, b.`updated_at` "+
"FROM %s b INNER JOIN `inv_product_pan` p ON b.`pan_id` = p.`pan_id` "+
"WHERE p.`product_id` = ? ORDER BY p.`sort_order`, b.`sort_order`", m.table)
err := m.conn.QueryRowsCtx(ctx, &list, query, productId)
if err != nil {
return nil, err
}
return list, nil
}
func (m *customInvProductBoltModel) DeleteByPanId(ctx context.Context, panId string) error {
query := fmt.Sprintf("DELETE FROM %s WHERE `pan_id` = ?", m.table)
_, err := m.conn.ExecCtx(ctx, query, panId)
return err
}
func (m *customInvProductBoltModel) DeleteByProductId(ctx context.Context, productId string) error {
query := fmt.Sprintf(
"DELETE b FROM %s b INNER JOIN `inv_product_pan` p ON b.`pan_id` = p.`pan_id` WHERE p.`product_id` = ?", m.table)
_, err := m.conn.ExecCtx(ctx, query, productId)
return err
}
func (m *customInvProductBoltModel) FindBoltList(ctx context.Context, tenantId string, page, pageSize int64, productName, position string) ([]*BoltListItem, int64, error) {
where := "WHERE b.tenant_id = ? AND p.deleted_at IS NULL"
args := []interface{}{tenantId}
if productName != "" {
where += " AND p.product_name LIKE ?"
args = append(args, "%"+productName+"%")
}
if position != "" {
where += " AND pan.position LIKE ?"
args = append(args, "%"+position+"%")
}
countQuery := fmt.Sprintf(`SELECT COUNT(*)
FROM %s b
JOIN inv_product_pan pan ON pan.pan_id = b.pan_id
JOIN inv_product p ON p.product_id = pan.product_id
%s`, m.table, where)
var total int64
if err := m.conn.QueryRowCtx(ctx, &total, countQuery, args...); err != nil {
return nil, 0, err
}
query := fmt.Sprintf(`SELECT b.bolt_id, b.pan_id, b.length_m, b.color, b.sort_order,
pan.name AS pan_name, pan.position,
p.product_id, p.product_name
FROM %s b
JOIN inv_product_pan pan ON pan.pan_id = b.pan_id
JOIN inv_product p ON p.product_id = pan.product_id
%s
ORDER BY b.created_at DESC
LIMIT ? OFFSET ?`, m.table, where)
args = append(args, pageSize, (page-1)*pageSize)
var list []*BoltListItem
if err := m.conn.QueryRowsCtx(ctx, &list, query, args...); err != nil {
return nil, 0, err
}
return list, total, nil
}
func (m *customInvProductBoltModel) BulkReplace(ctx context.Context, panId, tenantId string, bolts []*InvProductBolt) error {
return m.conn.TransactCtx(ctx, func(ctx context.Context, session sqlx.Session) error {
delQuery := fmt.Sprintf("DELETE FROM %s WHERE `pan_id` = ?", m.table)
if _, err := session.ExecCtx(ctx, delQuery, panId); err != nil {
return err
}
if len(bolts) == 0 {
return nil
}
placeholder := "(?, ?, ?, ?, ?, ?)"
placeholders := make([]string, len(bolts))
args := make([]interface{}, 0, len(bolts)*6)
for i, b := range bolts {
placeholders[i] = placeholder
args = append(args, b.BoltId, panId, b.LengthM, b.Color, b.SortOrder, tenantId)
}
insQuery := fmt.Sprintf("INSERT INTO %s (%s) VALUES %s", m.table, invProductBoltRowsExpectAutoSet, strings.Join(placeholders, ","))
_, err := session.ExecCtx(ctx, insQuery, args...)
return err
})
}

View File

@ -0,0 +1,95 @@
package model
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
"github.com/zeromicro/go-zero/core/stores/builder"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/zeromicro/go-zero/core/stringx"
)
var (
invProductBoltFieldNames = builder.RawFieldNames(&InvProductBolt{})
invProductBoltRows = strings.Join(invProductBoltFieldNames, ",")
invProductBoltRowsExpectAutoSet = strings.Join(stringx.Remove(invProductBoltFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), ",")
invProductBoltRowsWithPlaceHolder = strings.Join(stringx.Remove(invProductBoltFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), "=?,") + "=?"
)
type (
invProductBoltModel interface {
Insert(ctx context.Context, data *InvProductBolt) (sql.Result, error)
FindOne(ctx context.Context, id int64) (*InvProductBolt, error)
FindOneByBoltId(ctx context.Context, boltId string) (*InvProductBolt, error)
Update(ctx context.Context, data *InvProductBolt) error
Delete(ctx context.Context, id int64) error
}
defaultInvProductBoltModel struct {
conn sqlx.SqlConn
table string
}
InvProductBolt struct {
Id int64 `db:"id"`
BoltId string `db:"bolt_id"`
PanId string `db:"pan_id"`
LengthM float64 `db:"length_m"`
Color string `db:"color"`
SortOrder int64 `db:"sort_order"`
TenantId string `db:"tenant_id"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
)
func newInvProductBoltModel(conn sqlx.SqlConn) *defaultInvProductBoltModel {
return &defaultInvProductBoltModel{
conn: conn,
table: "`inv_product_bolt`",
}
}
func (m *defaultInvProductBoltModel) Insert(ctx context.Context, data *InvProductBolt) (sql.Result, error) {
query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (?, ?, ?, ?, ?, ?)", m.table, invProductBoltRowsExpectAutoSet)
return m.conn.ExecCtx(ctx, query, data.BoltId, data.PanId, data.LengthM, data.Color, data.SortOrder, data.TenantId)
}
func (m *defaultInvProductBoltModel) FindOne(ctx context.Context, id int64) (*InvProductBolt, error) {
query := fmt.Sprintf("SELECT %s FROM %s WHERE `id` = ? LIMIT 1", invProductBoltRows, m.table)
var resp InvProductBolt
err := m.conn.QueryRowCtx(ctx, &resp, query, id)
if err != nil {
return nil, err
}
return &resp, nil
}
func (m *defaultInvProductBoltModel) FindOneByBoltId(ctx context.Context, boltId string) (*InvProductBolt, error) {
query := fmt.Sprintf("SELECT %s FROM %s WHERE `bolt_id` = ? LIMIT 1", invProductBoltRows, m.table)
var resp InvProductBolt
err := m.conn.QueryRowCtx(ctx, &resp, query, boltId)
if err != nil {
return nil, err
}
return &resp, nil
}
func (m *defaultInvProductBoltModel) Update(ctx context.Context, data *InvProductBolt) error {
query := fmt.Sprintf("UPDATE %s SET %s WHERE `id` = ?", m.table, invProductBoltRowsWithPlaceHolder)
_, err := m.conn.ExecCtx(ctx, query, data.BoltId, data.PanId, data.LengthM, data.Color, data.SortOrder, data.TenantId, data.Id)
return err
}
func (m *defaultInvProductBoltModel) Delete(ctx context.Context, id int64) error {
query := fmt.Sprintf("DELETE FROM %s WHERE `id` = ?", m.table)
_, err := m.conn.ExecCtx(ctx, query, id)
return err
}
func (m *defaultInvProductBoltModel) tableName() string {
return m.table
}

View File

@ -3,6 +3,7 @@ package model
import (
"context"
"fmt"
"strings"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlx"
@ -10,19 +11,46 @@ import (
var _ InvProductModel = (*customInvProductModel)(nil)
// StockGroupResult is kept for GroupBy queries (color / location).
type StockGroupResult struct {
Name string `db:"name"`
Count int64 `db:"count"`
Quantity float64 `db:"quantity"`
}
type ProductSummary struct {
ProductName string `db:"product_name"`
Spec string `db:"spec"`
ColorCount int64 `db:"color_count"`
TotalPanCount int64 `db:"total_pan_count"`
TotalBoltCount int64 `db:"total_bolt_count"`
TotalLengthM float64 `db:"total_length_m"`
Colors string `db:"colors"`
Locations string `db:"locations"`
}
type ColorDetail struct {
ProductId string `db:"product_id"`
ProductName string `db:"product_name"`
Color string `db:"color"`
Spec string `db:"spec"`
ImageUrl string `db:"image_url"`
SalesPrice float64 `db:"sales_price"`
PanCount int64 `db:"pan_count"`
BoltCount int64 `db:"bolt_count"`
TotalLengthM float64 `db:"total_length_m"`
Locations string `db:"locations"`
}
type (
InvProductModel interface {
invProductModel
FindList(ctx context.Context, tenantId string, page, pageSize int64, productName, spec, color, location string, status int64) ([]*InvProduct, int64, error)
FindStockSummary(ctx context.Context, tenantId string) (productCount, totalPieces, totalRolls int64, totalCostValue, totalSalesValue float64, err error)
FindList(ctx context.Context, tenantId string, page, pageSize int64, productName, spec, color string, status int64) ([]*InvProduct, int64, error)
FindProductSummary(ctx context.Context, tenantId string) ([]*ProductSummary, error)
FindColorDetail(ctx context.Context, tenantId, productName string) ([]*ColorDetail, error)
FindGroupByColor(ctx context.Context, tenantId string) ([]StockGroupResult, error)
FindGroupByLocation(ctx context.Context, tenantId string) ([]StockGroupResult, error)
BulkInsert(ctx context.Context, products []*InvProduct) (int64, error)
}
customInvProductModel struct {
@ -36,7 +64,48 @@ func NewInvProductModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Opti
}
}
func (m *customInvProductModel) FindList(ctx context.Context, tenantId string, page, pageSize int64, productName, spec, color, location string, status int64) ([]*InvProduct, int64, error) {
const bulkBatchSize = 200
func (m *customInvProductModel) BulkInsert(ctx context.Context, products []*InvProduct) (int64, error) {
if len(products) == 0 {
return 0, nil
}
var totalAffected int64
for i := 0; i < len(products); i += bulkBatchSize {
end := i + bulkBatchSize
if end > len(products) {
end = len(products)
}
batch := products[i:end]
placeholder := "(" + strings.Repeat("?,", 9) + "?)"
placeholders := make([]string, len(batch))
args := make([]interface{}, 0, len(batch)*10)
for j, p := range batch {
placeholders[j] = placeholder
args = append(args,
p.ProductId, p.ProductName, p.ImageUrl, p.Spec, p.Color,
p.SalesPrice, p.Remark, p.Status, p.TenantId, p.DeletedAt,
)
}
query := fmt.Sprintf("INSERT INTO %s (%s) VALUES %s",
m.table, invProductRowsExpectAutoSet, strings.Join(placeholders, ","))
result, err := m.ExecNoCacheCtx(ctx, query, args...)
if err != nil {
return totalAffected, fmt.Errorf("batch %d-%d: %w", i, end-1, err)
}
n, _ := result.RowsAffected()
totalAffected += n
}
return totalAffected, nil
}
func (m *customInvProductModel) FindList(ctx context.Context, tenantId string, page, pageSize int64, productName, spec, color string, status int64) ([]*InvProduct, int64, error) {
where := "WHERE deleted_at IS NULL AND tenant_id = ?"
args := []interface{}{tenantId}
@ -52,10 +121,6 @@ func (m *customInvProductModel) FindList(ctx context.Context, tenantId string, p
where += " AND color = ?"
args = append(args, color)
}
if location != "" {
where += " AND location = ?"
args = append(args, location)
}
if status >= 0 {
where += " AND status = ?"
args = append(args, status)
@ -79,25 +144,37 @@ func (m *customInvProductModel) FindList(ctx context.Context, tenantId string, p
return list, total, nil
}
func (m *customInvProductModel) FindStockSummary(ctx context.Context, tenantId string) (productCount, totalPieces, totalRolls int64, totalCostValue, totalSalesValue float64, err error) {
var summary struct {
ProductCount int64 `db:"product_count"`
TotalPieces int64 `db:"total_pieces"`
TotalRolls int64 `db:"total_rolls"`
TotalCostValue float64 `db:"total_cost_value"`
TotalSalesValue float64 `db:"total_sales_value"`
}
query := fmt.Sprintf("SELECT COUNT(*) AS product_count, COALESCE(SUM(unit_pieces), 0) AS total_pieces, COALESCE(SUM(unit_rolls), 0) AS total_rolls, COALESCE(SUM(stock_quantity * cost_price), 0) AS total_cost_value, COALESCE(SUM(stock_quantity * sales_price), 0) AS total_sales_value FROM %s WHERE deleted_at IS NULL AND tenant_id = ?", m.table)
err = m.QueryRowNoCacheCtx(ctx, &summary, query, tenantId)
func (m *customInvProductModel) FindProductSummary(ctx context.Context, tenantId string) ([]*ProductSummary, error) {
var list []*ProductSummary
query := `SELECT p.product_name, p.spec,
COUNT(DISTINCT p.product_id) AS color_count,
COUNT(DISTINCT pan.pan_id) AS total_pan_count,
COUNT(bolt.bolt_id) AS total_bolt_count,
COALESCE(SUM(bolt.length_m), 0) AS total_length_m,
GROUP_CONCAT(DISTINCT p.color) AS colors,
GROUP_CONCAT(DISTINCT pan.position ORDER BY pan.position) AS locations
FROM ` + m.table + ` p
LEFT JOIN ` + "`inv_product_pan`" + ` pan ON pan.product_id = p.product_id
LEFT JOIN ` + "`inv_product_bolt`" + ` bolt ON bolt.pan_id = pan.pan_id
WHERE p.tenant_id = ? AND p.deleted_at IS NULL
GROUP BY p.product_name, p.spec
ORDER BY p.product_name`
err := m.QueryRowsNoCacheCtx(ctx, &list, query, tenantId)
if err != nil {
return 0, 0, 0, 0, 0, err
return nil, err
}
return summary.ProductCount, summary.TotalPieces, summary.TotalRolls, summary.TotalCostValue, summary.TotalSalesValue, nil
return list, nil
}
func (m *customInvProductModel) FindGroupByColor(ctx context.Context, tenantId string) ([]StockGroupResult, error) {
var list []StockGroupResult
query := fmt.Sprintf("SELECT color AS name, COUNT(*) AS count, COALESCE(SUM(stock_quantity), 0) AS quantity FROM %s WHERE deleted_at IS NULL AND tenant_id = ? GROUP BY color ORDER BY count DESC", m.table)
query := `SELECT p.color AS name, COUNT(DISTINCT p.product_id) AS count,
COALESCE(SUM(bolt.length_m), 0) AS quantity
FROM ` + m.table + ` p
LEFT JOIN ` + "`inv_product_pan`" + ` pan ON pan.product_id = p.product_id
LEFT JOIN ` + "`inv_product_bolt`" + ` bolt ON bolt.pan_id = pan.pan_id
WHERE p.tenant_id = ? AND p.deleted_at IS NULL
GROUP BY p.color ORDER BY count DESC`
err := m.QueryRowsNoCacheCtx(ctx, &list, query, tenantId)
if err != nil {
return nil, err
@ -107,10 +184,36 @@ func (m *customInvProductModel) FindGroupByColor(ctx context.Context, tenantId s
func (m *customInvProductModel) FindGroupByLocation(ctx context.Context, tenantId string) ([]StockGroupResult, error) {
var list []StockGroupResult
query := fmt.Sprintf("SELECT location AS name, COUNT(*) AS count, COALESCE(SUM(stock_quantity), 0) AS quantity FROM %s WHERE deleted_at IS NULL AND tenant_id = ? GROUP BY location ORDER BY count DESC", m.table)
query := `SELECT pan.position AS name, COUNT(DISTINCT p.product_id) AS count,
COALESCE(SUM(bolt.length_m), 0) AS quantity
FROM ` + m.table + ` p
JOIN ` + "`inv_product_pan`" + ` pan ON pan.product_id = p.product_id
LEFT JOIN ` + "`inv_product_bolt`" + ` bolt ON bolt.pan_id = pan.pan_id
WHERE p.tenant_id = ? AND p.deleted_at IS NULL AND pan.position != ''
GROUP BY pan.position ORDER BY count DESC`
err := m.QueryRowsNoCacheCtx(ctx, &list, query, tenantId)
if err != nil {
return nil, err
}
return list, nil
}
func (m *customInvProductModel) FindColorDetail(ctx context.Context, tenantId, productName string) ([]*ColorDetail, error) {
var list []*ColorDetail
query := `SELECT p.product_id, p.product_name, p.color, p.spec, p.image_url, p.sales_price,
COUNT(DISTINCT pan.pan_id) AS pan_count,
COUNT(bolt.bolt_id) AS bolt_count,
COALESCE(SUM(bolt.length_m), 0) AS total_length_m,
GROUP_CONCAT(DISTINCT pan.position ORDER BY pan.position) AS locations
FROM ` + m.table + ` p
LEFT JOIN ` + "`inv_product_pan`" + ` pan ON pan.product_id = p.product_id
LEFT JOIN ` + "`inv_product_bolt`" + ` bolt ON bolt.pan_id = pan.pan_id
WHERE p.tenant_id = ? AND p.deleted_at IS NULL AND p.product_name = ?
GROUP BY p.product_id
ORDER BY p.color`
err := m.QueryRowsNoCacheCtx(ctx, &list, query, tenantId, productName)
if err != nil {
return nil, err
}
return list, nil
}

View File

@ -45,23 +45,19 @@ type (
}
InvProduct struct {
Id int64 `db:"id"`
ProductId string `db:"product_id"`
ProductName string `db:"product_name"`
ImageUrl string `db:"image_url"`
Spec string `db:"spec"`
Color string `db:"color"`
UnitPieces int64 `db:"unit_pieces"`
UnitRolls int64 `db:"unit_rolls"`
StockQuantity float64 `db:"stock_quantity"`
Location string `db:"location"`
CostPrice float64 `db:"cost_price"`
SalesPrice float64 `db:"sales_price"`
Remark sql.NullString `db:"remark"`
Status int64 `db:"status"` // 1:enabled 0:disabled
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
DeletedAt sql.NullTime `db:"deleted_at"`
Id int64 `db:"id"`
ProductId string `db:"product_id"`
ProductName string `db:"product_name"`
ImageUrl string `db:"image_url"`
Spec string `db:"spec"`
Color string `db:"color"`
SalesPrice float64 `db:"sales_price"`
Remark sql.NullString `db:"remark"`
Status int64 `db:"status"`
TenantId string `db:"tenant_id"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
DeletedAt sql.NullTime `db:"deleted_at"`
}
)
@ -150,8 +146,8 @@ func (m *defaultInvProductModel) Insert(ctx context.Context, data *InvProduct) (
invProductProductIdKey := fmt.Sprintf("%s%v", cacheInvProductProductIdPrefix, data.ProductId)
invProductProductNameKey := fmt.Sprintf("%s%v", cacheInvProductProductNamePrefix, data.ProductName)
ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, invProductRowsExpectAutoSet)
return conn.ExecCtx(ctx, query, data.ProductId, data.ProductName, data.ImageUrl, data.Spec, data.Color, data.UnitPieces, data.UnitRolls, data.StockQuantity, data.Location, data.CostPrice, data.SalesPrice, data.Remark, data.Status, data.DeletedAt)
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, invProductRowsExpectAutoSet)
return conn.ExecCtx(ctx, query, data.ProductId, data.ProductName, data.ImageUrl, data.Spec, data.Color, data.SalesPrice, data.Remark, data.Status, data.TenantId, data.DeletedAt)
}, invProductIdKey, invProductProductIdKey, invProductProductNameKey)
return ret, err
}
@ -167,7 +163,7 @@ func (m *defaultInvProductModel) Update(ctx context.Context, newData *InvProduct
invProductProductNameKey := fmt.Sprintf("%s%v", cacheInvProductProductNamePrefix, data.ProductName)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, invProductRowsWithPlaceHolder)
return conn.ExecCtx(ctx, query, newData.ProductId, newData.ProductName, newData.ImageUrl, newData.Spec, newData.Color, newData.UnitPieces, newData.UnitRolls, newData.StockQuantity, newData.Location, newData.CostPrice, newData.SalesPrice, newData.Remark, newData.Status, newData.DeletedAt, newData.Id)
return conn.ExecCtx(ctx, query, newData.ProductId, newData.ProductName, newData.ImageUrl, newData.Spec, newData.Color, newData.SalesPrice, newData.Remark, newData.Status, newData.TenantId, newData.DeletedAt, newData.Id)
}, invProductIdKey, invProductProductIdKey, invProductProductNameKey)
return err
}

123
model/invproductpanmodel.go Normal file
View File

@ -0,0 +1,123 @@
package model
import (
"context"
"fmt"
"strings"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var _ InvProductPanModel = (*customInvProductPanModel)(nil)
// PanListItem is the aggregated view row for the pan-dimension panel.
// Colors is aggregated from the bolts on this pan; spec is omitted (product-level only).
type PanListItem struct {
PanId string `db:"pan_id"`
ProductId string `db:"product_id"`
Name string `db:"name"`
Position string `db:"position"`
ProductName string `db:"product_name"`
Colors string `db:"colors"`
BoltCount int64 `db:"bolt_count"`
TotalLengthM float64 `db:"total_length_m"`
}
type (
InvProductPanModel interface {
invProductPanModel
FindByProductId(ctx context.Context, productId string) ([]*InvProductPan, error)
DeleteByProductId(ctx context.Context, productId string) error
BulkReplace(ctx context.Context, productId, tenantId string, pans []*InvProductPan) error
FindPanList(ctx context.Context, tenantId string, page, pageSize int64, productName, position string) ([]*PanListItem, int64, error)
}
customInvProductPanModel struct {
*defaultInvProductPanModel
}
)
func NewInvProductPanModel(conn sqlx.SqlConn) InvProductPanModel {
return &customInvProductPanModel{
defaultInvProductPanModel: newInvProductPanModel(conn),
}
}
func (m *customInvProductPanModel) FindByProductId(ctx context.Context, productId string) ([]*InvProductPan, error) {
var list []*InvProductPan
query := fmt.Sprintf("SELECT %s FROM %s WHERE `product_id` = ? ORDER BY `sort_order`", invProductPanRows, m.table)
err := m.conn.QueryRowsCtx(ctx, &list, query, productId)
if err != nil {
return nil, err
}
return list, nil
}
func (m *customInvProductPanModel) DeleteByProductId(ctx context.Context, productId string) error {
query := fmt.Sprintf("DELETE FROM %s WHERE `product_id` = ?", m.table)
_, err := m.conn.ExecCtx(ctx, query, productId)
return err
}
func (m *customInvProductPanModel) FindPanList(ctx context.Context, tenantId string, page, pageSize int64, productName, position string) ([]*PanListItem, int64, error) {
where := "WHERE pan.tenant_id = ? AND p.deleted_at IS NULL"
args := []interface{}{tenantId}
if productName != "" {
where += " AND p.product_name LIKE ?"
args = append(args, "%"+productName+"%")
}
if position != "" {
where += " AND pan.position LIKE ?"
args = append(args, "%"+position+"%")
}
countQuery := fmt.Sprintf(`SELECT COUNT(DISTINCT pan.pan_id)
FROM %s pan JOIN inv_product p ON p.product_id = pan.product_id %s`, m.table, where)
var total int64
if err := m.conn.QueryRowCtx(ctx, &total, countQuery, args...); err != nil {
return nil, 0, err
}
query := fmt.Sprintf(`SELECT pan.pan_id, pan.product_id, pan.name, pan.position,
p.product_name,
COALESCE(GROUP_CONCAT(DISTINCT b.color ORDER BY b.color SEPARATOR ', '), '') AS colors,
COUNT(b.bolt_id) AS bolt_count,
COALESCE(SUM(b.length_m), 0) AS total_length_m
FROM %s pan
JOIN inv_product p ON p.product_id = pan.product_id
LEFT JOIN inv_product_bolt b ON b.pan_id = pan.pan_id
%s
GROUP BY pan.pan_id
ORDER BY pan.created_at DESC
LIMIT ? OFFSET ?`, m.table, where)
args = append(args, pageSize, (page-1)*pageSize)
var list []*PanListItem
if err := m.conn.QueryRowsCtx(ctx, &list, query, args...); err != nil {
return nil, 0, err
}
return list, total, nil
}
func (m *customInvProductPanModel) BulkReplace(ctx context.Context, productId, tenantId string, pans []*InvProductPan) error {
return m.conn.TransactCtx(ctx, func(ctx context.Context, session sqlx.Session) error {
delQuery := fmt.Sprintf("DELETE FROM %s WHERE `product_id` = ?", m.table)
if _, err := session.ExecCtx(ctx, delQuery, productId); err != nil {
return err
}
if len(pans) == 0 {
return nil
}
placeholder := "(?, ?, ?, ?, ?, ?)"
placeholders := make([]string, len(pans))
args := make([]interface{}, 0, len(pans)*6)
for i, p := range pans {
placeholders[i] = placeholder
args = append(args, p.PanId, productId, p.Name, p.Position, p.SortOrder, tenantId)
}
insQuery := fmt.Sprintf("INSERT INTO %s (%s) VALUES %s", m.table, invProductPanRowsExpectAutoSet, strings.Join(placeholders, ","))
_, err := session.ExecCtx(ctx, insQuery, args...)
return err
})
}

View File

@ -0,0 +1,95 @@
package model
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
"github.com/zeromicro/go-zero/core/stores/builder"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/zeromicro/go-zero/core/stringx"
)
var (
invProductPanFieldNames = builder.RawFieldNames(&InvProductPan{})
invProductPanRows = strings.Join(invProductPanFieldNames, ",")
invProductPanRowsExpectAutoSet = strings.Join(stringx.Remove(invProductPanFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), ",")
invProductPanRowsWithPlaceHolder = strings.Join(stringx.Remove(invProductPanFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), "=?,") + "=?"
)
type (
invProductPanModel interface {
Insert(ctx context.Context, data *InvProductPan) (sql.Result, error)
FindOne(ctx context.Context, id int64) (*InvProductPan, error)
FindOneByPanId(ctx context.Context, panId string) (*InvProductPan, error)
Update(ctx context.Context, data *InvProductPan) error
Delete(ctx context.Context, id int64) error
}
defaultInvProductPanModel struct {
conn sqlx.SqlConn
table string
}
InvProductPan struct {
Id int64 `db:"id"`
PanId string `db:"pan_id"`
ProductId string `db:"product_id"`
Name string `db:"name"`
Position string `db:"position"`
SortOrder int64 `db:"sort_order"`
TenantId string `db:"tenant_id"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
)
func newInvProductPanModel(conn sqlx.SqlConn) *defaultInvProductPanModel {
return &defaultInvProductPanModel{
conn: conn,
table: "`inv_product_pan`",
}
}
func (m *defaultInvProductPanModel) Insert(ctx context.Context, data *InvProductPan) (sql.Result, error) {
query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (?, ?, ?, ?, ?, ?)", m.table, invProductPanRowsExpectAutoSet)
return m.conn.ExecCtx(ctx, query, data.PanId, data.ProductId, data.Name, data.Position, data.SortOrder, data.TenantId)
}
func (m *defaultInvProductPanModel) FindOne(ctx context.Context, id int64) (*InvProductPan, error) {
query := fmt.Sprintf("SELECT %s FROM %s WHERE `id` = ? LIMIT 1", invProductPanRows, m.table)
var resp InvProductPan
err := m.conn.QueryRowCtx(ctx, &resp, query, id)
if err != nil {
return nil, err
}
return &resp, nil
}
func (m *defaultInvProductPanModel) FindOneByPanId(ctx context.Context, panId string) (*InvProductPan, error) {
query := fmt.Sprintf("SELECT %s FROM %s WHERE `pan_id` = ? LIMIT 1", invProductPanRows, m.table)
var resp InvProductPan
err := m.conn.QueryRowCtx(ctx, &resp, query, panId)
if err != nil {
return nil, err
}
return &resp, nil
}
func (m *defaultInvProductPanModel) Update(ctx context.Context, data *InvProductPan) error {
query := fmt.Sprintf("UPDATE %s SET %s WHERE `id` = ?", m.table, invProductPanRowsWithPlaceHolder)
_, err := m.conn.ExecCtx(ctx, query, data.PanId, data.ProductId, data.Name, data.Position, data.SortOrder, data.TenantId, data.Id)
return err
}
func (m *defaultInvProductPanModel) Delete(ctx context.Context, id int64) error {
query := fmt.Sprintf("DELETE FROM %s WHERE `id` = ?", m.table)
_, err := m.conn.ExecCtx(ctx, query, id)
return err
}
func (m *defaultInvProductPanModel) tableName() string {
return m.table
}

View File

@ -46,6 +46,7 @@ type (
SysRole struct {
Id int64 `db:"id"`
TenantId string `db:"tenant_id"`
RoleId string `db:"role_id"`
RoleName string `db:"role_name"`
RoleKey string `db:"role_key"`
@ -142,8 +143,8 @@ func (m *defaultSysRoleModel) Insert(ctx context.Context, data *SysRole) (sql.Re
sysRoleRoleIdKey := fmt.Sprintf("%s%v", cacheSysRoleRoleIdPrefix, data.RoleId)
sysRoleRoleKeyKey := fmt.Sprintf("%s%v", cacheSysRoleRoleKeyPrefix, data.RoleKey)
ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?)", m.table, sysRoleRowsExpectAutoSet)
return conn.ExecCtx(ctx, query, data.RoleId, data.RoleName, data.RoleKey, data.RoleDesc, data.SortOrder, data.Status)
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?)", m.table, sysRoleRowsExpectAutoSet)
return conn.ExecCtx(ctx, query, data.TenantId, data.RoleId, data.RoleName, data.RoleKey, data.RoleDesc, data.SortOrder, data.Status)
}, sysRoleIdKey, sysRoleRoleIdKey, sysRoleRoleKeyKey)
return ret, err
}
@ -159,7 +160,7 @@ func (m *defaultSysRoleModel) Update(ctx context.Context, newData *SysRole) erro
sysRoleRoleKeyKey := fmt.Sprintf("%s%v", cacheSysRoleRoleKeyPrefix, data.RoleKey)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, sysRoleRowsWithPlaceHolder)
return conn.ExecCtx(ctx, query, newData.RoleId, newData.RoleName, newData.RoleKey, newData.RoleDesc, newData.SortOrder, newData.Status, newData.Id)
return conn.ExecCtx(ctx, query, newData.TenantId, newData.RoleId, newData.RoleName, newData.RoleKey, newData.RoleDesc, newData.SortOrder, newData.Status, newData.Id)
}, sysRoleIdKey, sysRoleRoleIdKey, sysRoleRoleKeyKey)
return err
}

102
pkg/excelparse/miaozhang.go Normal file
View File

@ -0,0 +1,102 @@
package excelparse
import (
"strconv"
"strings"
)
// MiaozhangParser handles Excel exports from 秒账 (miaozhang.cn).
//
// Expected headers:
// 图片1 | 图片2 | 图片3 | 名称 | 规格型号 | 颜色 | 条形码 | 匹数 | 库存数量 | 成本均价 | 参考售价 | 备注 | 更新日期
type MiaozhangParser struct{}
func (p *MiaozhangParser) Name() string { return "秒账" }
func (p *MiaozhangParser) Match(headers []string) bool {
required := map[string]bool{"名称": false, "规格型号": false, "颜色": false, "匹数": false, "库存数量": false}
for _, h := range headers {
h = strings.TrimSpace(h)
if _, ok := required[h]; ok {
required[h] = true
}
}
for _, found := range required {
if !found {
return false
}
}
return true
}
func (p *MiaozhangParser) ParseRows(headers []string, rows [][]string) (*ParseResult, error) {
idx := buildIndex(headers)
var products []ParsedProduct
skipped := 0
for _, row := range rows {
name := colVal(row, idx["名称"])
if name == "" || strings.HasPrefix(name, "总") {
skipped++
continue
}
rolls, _ := strconv.ParseInt(colVal(row, idx["匹数"]), 10, 64)
products = append(products, ParsedProduct{
ProductName: name,
Spec: colVal(row, idx["规格型号"]),
Color: colVal(row, idx["颜色"]),
UnitRolls: rolls,
StockQuantity: stripUnit(colVal(row, idx["库存数量"])),
CostPrice: stripCurrency(colVal(row, idx["成本均价"])),
SalesPrice: stripCurrency(colVal(row, idx["参考售价"])),
Remark: colVal(row, idx["备注"]),
})
}
return &ParseResult{Products: products, SkippedRows: skipped}, nil
}
func buildIndex(headers []string) map[string]int {
m := make(map[string]int, len(headers))
for i, h := range headers {
m[strings.TrimSpace(h)] = i
}
return m
}
func colVal(row []string, idx int) string {
if idx < 0 || idx >= len(row) {
return ""
}
return strings.TrimSpace(row[idx])
}
// stripUnit removes trailing unit markers like "米", "kg", etc.
func stripUnit(s string) string {
s = strings.TrimSpace(s)
for _, suffix := range []string{"米", "m", "kg", "KG"} {
s = strings.TrimSuffix(s, suffix)
}
s = strings.TrimSpace(s)
if s == "" {
return "0"
}
return s
}
// stripCurrency removes leading ¥ / $ / ¥ symbols.
func stripCurrency(s string) string {
s = strings.TrimSpace(s)
for _, prefix := range []string{"¥", "¥", "$", ""} {
s = strings.TrimPrefix(s, prefix)
}
s = strings.TrimSpace(s)
if s == "" {
return "0"
}
return s
}

167
pkg/excelparse/parse.go Normal file
View File

@ -0,0 +1,167 @@
package excelparse
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/xuri/excelize/v2"
)
var parsers []FormatParser
func init() {
parsers = append(parsers, &MiaozhangParser{})
}
// RegisterParser adds a format parser at runtime (e.g. for plugins).
func RegisterParser(p FormatParser) {
parsers = append(parsers, p)
}
// xlsToolPath returns the path to the xls2json.js script.
// Checks XLS_TOOL_PATH env var first, then falls back to a path
// relative to the binary's directory so it works both locally and in Docker.
func xlsToolPath() (string, error) {
if p := os.Getenv("XLS_TOOL_PATH"); p != "" {
return p, nil
}
// Docker layout: /app/tools/xls2json.js
candidates := []string{
"/app/tools/xls2json.js",
}
// Local dev: look relative to the process working directory
if cwd, err := os.Getwd(); err == nil {
candidates = append(candidates,
filepath.Join(cwd, "deploy/tools/xls2json.js"),
filepath.Join(cwd, "../deploy/tools/xls2json.js"),
)
}
for _, p := range candidates {
if _, err := os.Stat(p); err == nil {
return p, nil
}
}
return "", fmt.Errorf("xls2json.js not found; set XLS_TOOL_PATH env var or deploy to /app/tools/")
}
// ParseFile reads an Excel file, auto-detects the competitor format, and returns parsed products.
func ParseFile(filePath string) (*ParseResult, error) {
ext := strings.ToLower(filepath.Ext(filePath))
var headers []string
var rows [][]string
var err error
switch ext {
case ".xlsx":
headers, rows, err = readXLSX(filePath)
case ".xls":
headers, rows, err = readXLS(filePath)
default:
return nil, ErrUnsupportedExt
}
if err != nil {
return nil, fmt.Errorf("read excel: %w", err)
}
if len(rows) == 0 {
return nil, ErrEmptyFile
}
for _, p := range parsers {
if p.Match(headers) {
result, err := p.ParseRows(headers, rows)
if err != nil {
return nil, fmt.Errorf("parse %s: %w", p.Name(), err)
}
result.ParserName = p.Name()
return result, nil
}
}
return nil, ErrUnknownFormat
}
func readXLSX(filePath string) ([]string, [][]string, error) {
f, err := excelize.OpenFile(filePath)
if err != nil {
return nil, nil, err
}
defer f.Close()
sheets := f.GetSheetList()
if len(sheets) == 0 {
return nil, nil, ErrEmptyFile
}
allRows, err := f.GetRows(sheets[0])
if err != nil {
return nil, nil, err
}
if len(allRows) < 2 {
return nil, nil, ErrEmptyFile
}
return allRows[0], allRows[1:], nil
}
// readXLS delegates to the xls2json.js Node.js script via subprocess.
// This handles legacy .xls (BIFF8) files including those with embedded images,
// which pure-Go libraries cannot reliably process.
func readXLS(filePath string) ([]string, [][]string, error) {
toolPath, err := xlsToolPath()
if err != nil {
return nil, nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
var stdout, stderr bytes.Buffer
cmd := exec.CommandContext(ctx, "node", toolPath, filePath)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
msg := strings.TrimSpace(stderr.String())
if msg == "" {
msg = err.Error()
}
return nil, nil, fmt.Errorf("xls2json: %s", msg)
}
// JSON format: [][]interface{} (first row = headers)
var raw [][]interface{}
if err := json.Unmarshal(stdout.Bytes(), &raw); err != nil {
return nil, nil, fmt.Errorf("xls2json output parse: %w", err)
}
if len(raw) < 2 {
return nil, nil, ErrEmptyFile
}
toStrings := func(row []interface{}) []string {
out := make([]string, len(row))
for i, v := range row {
if v == nil {
out[i] = ""
} else {
out[i] = strings.TrimSpace(fmt.Sprintf("%v", v))
}
}
return out
}
headers := toStrings(raw[0])
rows := make([][]string, 0, len(raw)-1)
for _, r := range raw[1:] {
rows = append(rows, toStrings(r))
}
return headers, rows, nil
}

34
pkg/excelparse/types.go Normal file
View File

@ -0,0 +1,34 @@
package excelparse
import "errors"
var (
ErrUnknownFormat = errors.New("unrecognized excel format; supported: 秒账, 领星")
ErrEmptyFile = errors.New("excel file has no data rows")
ErrUnsupportedExt = errors.New("unsupported file extension; use .xls or .xlsx")
)
type ParsedProduct struct {
ProductName string
Spec string
Color string
UnitRolls int64
StockQuantity string
CostPrice string
SalesPrice string
Remark string
}
type ParseResult struct {
Products []ParsedProduct
ParserName string
SkippedRows int
}
// FormatParser transforms raw spreadsheet rows into structured products.
// Each competitor format (秒账, 领星, etc.) implements this interface.
type FormatParser interface {
Name() string
Match(headers []string) bool
ParseRows(headers []string, rows [][]string) (*ParseResult, error)
}

View File

@ -0,0 +1,32 @@
package metrics
import (
"context"
"strconv"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
)
func UnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
start := time.Now()
resp, err := handler(ctx, req)
elapsed := time.Since(start).Seconds()
code := "OK"
if err != nil {
if st, ok := status.FromError(err); ok {
code = strconv.Itoa(int(st.Code()))
} else {
code = "Unknown"
}
}
RPCDurationSeconds.WithLabelValues(info.FullMethod).Observe(elapsed)
RPCRequestsTotal.WithLabelValues(info.FullMethod, code).Inc()
return resp, err
}
}

141
pkg/metrics/metrics.go Normal file
View File

@ -0,0 +1,141 @@
package metrics
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/zeromicro/go-zero/core/logx"
)
const namespace = "muyu"
const subsystem = "inventory"
// ---- Business Counters ----
var ProductCreatedTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace, Subsystem: subsystem,
Name: "product_created_total",
Help: "Total number of products created.",
}, []string{"tenant_id", "product_name", "spec", "color"})
var ProductDeletedTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace, Subsystem: subsystem,
Name: "product_deleted_total",
Help: "Total number of products deleted (soft delete).",
}, []string{"tenant_id"})
var ImportOperationsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace, Subsystem: subsystem,
Name: "import_operations_total",
Help: "Total number of import operations executed.",
}, []string{"tenant_id", "operator"})
var ImportProductsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace, Subsystem: subsystem,
Name: "import_products_total",
Help: "Total number of products processed during imports.",
}, []string{"tenant_id", "result"})
var InboundLengthMetersTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace, Subsystem: subsystem,
Name: "inbound_length_meters_total",
Help: "Total length (meters) of fabric added to inventory.",
}, []string{"tenant_id", "product_name", "color", "operation"})
var InboundBoltsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace, Subsystem: subsystem,
Name: "inbound_bolts_total",
Help: "Total number of bolt records added to inventory.",
}, []string{"tenant_id", "operation"})
var PansSavedTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace, Subsystem: subsystem,
Name: "pans_saved_total",
Help: "Total number of pan-save operations.",
}, []string{"tenant_id", "product_id"})
var StockCheckCreatedTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace, Subsystem: subsystem,
Name: "stock_check_created_total",
Help: "Total number of stock check records created.",
}, []string{"tenant_id"})
var StockCheckConfirmedTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace, Subsystem: subsystem,
Name: "stock_check_confirmed_total",
Help: "Total number of stock checks confirmed.",
}, []string{"tenant_id"})
var StockAdjustCreatedTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace, Subsystem: subsystem,
Name: "stock_adjust_created_total",
Help: "Total number of stock adjustments created.",
}, []string{"tenant_id", "reason"})
var StockAdjustApprovedTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace, Subsystem: subsystem,
Name: "stock_adjust_approved_total",
Help: "Total number of stock adjustments approved/rejected.",
}, []string{"tenant_id", "action"})
var StockAdjustQuantityMeters = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace, Subsystem: subsystem,
Name: "stock_adjust_quantity_meters_total",
Help: "Total adjusted quantity in meters (absolute value, split by direction).",
}, []string{"tenant_id", "direction"})
// ---- Histograms ----
var ImportBatchSize = prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: namespace, Subsystem: subsystem,
Name: "import_batch_size",
Help: "Number of products per import operation.",
Buckets: []float64{1, 5, 10, 50, 100, 200, 500, 1000},
}, []string{"tenant_id"})
// ---- RPC Operational Metrics ----
var RPCDurationSeconds = prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: namespace, Subsystem: subsystem,
Name: "rpc_duration_seconds",
Help: "Duration of RPC calls in seconds.",
Buckets: prometheus.DefBuckets,
}, []string{"method"})
var RPCRequestsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace, Subsystem: subsystem,
Name: "rpc_requests_total",
Help: "Total number of RPC requests.",
}, []string{"method", "code"})
func init() {
prometheus.MustRegister(
ProductCreatedTotal,
ProductDeletedTotal,
ImportOperationsTotal,
ImportProductsTotal,
InboundLengthMetersTotal,
InboundBoltsTotal,
PansSavedTotal,
StockCheckCreatedTotal,
StockCheckConfirmedTotal,
StockAdjustCreatedTotal,
StockAdjustApprovedTotal,
StockAdjustQuantityMeters,
ImportBatchSize,
RPCDurationSeconds,
RPCRequestsTotal,
)
}
func StartHTTP(addr string) {
mux := http.NewServeMux()
mux.Handle("/metrics", promhttp.Handler())
go func() {
logx.Infof("prometheus metrics listening on %s", addr)
if err := http.ListenAndServe(addr, mux); err != nil {
logx.Errorf("prometheus http server error: %v", err)
}
}()
}

View File

@ -0,0 +1,10 @@
version: v2
plugins:
- local: protoc-gen-go
out: pb
opt:
- paths=source_relative
- local: protoc-gen-go-grpc
out: pb
opt:
- paths=source_relative

9
rpc/inventory/buf.yaml Normal file
View File

@ -0,0 +1,9 @@
version: v2
modules:
- path: .
lint:
use:
- DEFAULT
breaking:
use:
- FILE

View File

@ -11,6 +11,8 @@ DataSource: root:muyu2026@tcp(127.0.0.1:3306)/muyu_wms?charset=utf8mb4&parseTime
Cache:
- Host: 127.0.0.1:6379
PrometheusAddr: :9092
Log:
ServiceName: inventory-rpc
Mode: console

View File

@ -7,6 +7,7 @@ import (
type Config struct {
zrpc.RpcServerConf
DataSource string
Cache cache.CacheConf
DataSource string
Cache cache.CacheConf
PrometheusAddr string `json:",default=:9092"`
}

View File

@ -3,6 +3,8 @@ package logic
import (
"context"
"muyu-apiserver/pkg/metrics"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
@ -39,29 +41,11 @@ func (l *ApproveStockAdjustLogic) ApproveStockAdjust(in *pb.ApproveStockAdjustRe
switch in.Action {
case 1: // approve
details, err := l.svcCtx.AdjustDetailModel.FindByAdjustId(l.ctx, in.AdjustId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
for _, d := range details {
product, err := l.svcCtx.ProductModel.FindOneByProductId(l.ctx, d.ProductId)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to find product %s: %v", d.ProductId, err)
}
product.StockQuantity = d.AfterQuantity
err = l.svcCtx.ProductModel.Update(l.ctx, product)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to update product %s stock: %v", d.ProductId, err)
}
}
// In the three-layer model, actual stock adjustments are done via
// SaveBolts/SavePans. Approving records the approval decision only.
adjust.Status = 1
case 2: // reject
adjust.Status = 2
default:
return nil, status.Errorf(codes.InvalidArgument, "unsupported action: %d", in.Action)
}
@ -71,5 +55,12 @@ func (l *ApproveStockAdjustLogic) ApproveStockAdjust(in *pb.ApproveStockAdjustRe
return nil, status.Error(codes.Internal, err.Error())
}
tenantId := tenantctx.ExtractTenantId(l.ctx)
actionLabel := "approve"
if in.Action == 2 {
actionLabel = "reject"
}
metrics.StockAdjustApprovedTotal.WithLabelValues(tenantId, actionLabel).Inc()
return &pb.Empty{}, nil
}

View File

@ -3,6 +3,8 @@ package logic
import (
"context"
"muyu-apiserver/pkg/metrics"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
@ -35,29 +37,17 @@ func (l *ConfirmStockCheckLogic) ConfirmStockCheck(in *pb.ConfirmStockCheckReq)
return nil, status.Error(codes.FailedPrecondition, "stock check cannot be confirmed in current status")
}
details, err := l.svcCtx.CheckDetailModel.FindByCheckId(l.ctx, in.CheckId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
for _, d := range details {
product, err := l.svcCtx.ProductModel.FindOneByProductId(l.ctx, d.ProductId)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to find product %s: %v", d.ProductId, err)
}
product.StockQuantity = d.ActualQuantity
err = l.svcCtx.ProductModel.Update(l.ctx, product)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to update product %s stock: %v", d.ProductId, err)
}
}
// In the three-layer model, stock is held in bolt records.
// Confirming a stock check marks it as completed; bolt-level adjustments
// are handled separately via the SaveBolts/SavePans endpoints.
check.Status = 2
err = l.svcCtx.StockCheckModel.Update(l.ctx, check)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
tenantId := tenantctx.ExtractTenantId(l.ctx)
metrics.StockCheckConfirmedTotal.WithLabelValues(tenantId).Inc()
return &pb.Empty{}, nil
}

View File

@ -6,6 +6,8 @@ import (
"strconv"
"muyu-apiserver/model"
"muyu-apiserver/pkg/metrics"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/pkg/uid"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
@ -31,25 +33,17 @@ func NewCreateProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Cre
func (l *CreateProductLogic) CreateProduct(in *pb.CreateProductReq) (*pb.IdResp, error) {
productId := uid.Generate()
stockQuantity, _ := strconv.ParseFloat(in.StockQuantity, 64)
costPrice, _ := strconv.ParseFloat(in.CostPrice, 64)
salesPrice, _ := strconv.ParseFloat(in.SalesPrice, 64)
product := &model.InvProduct{
ProductId: productId,
ProductName: in.ProductName,
ImageUrl: in.ImageUrl,
Spec: in.Spec,
Color: in.Color,
UnitPieces: in.UnitPieces,
UnitRolls: in.UnitRolls,
StockQuantity: stockQuantity,
Location: in.Location,
CostPrice: costPrice,
SalesPrice: salesPrice,
Remark: sql.NullString{String: in.Remark, Valid: in.Remark != ""},
Status: 1,
ProductId: productId,
ProductName: in.ProductName,
ImageUrl: in.ImageUrl,
Spec: in.Spec,
Color: in.Color,
SalesPrice: salesPrice,
Remark: sql.NullString{String: in.Remark, Valid: in.Remark != ""},
Status: 1,
}
_, err := l.svcCtx.ProductModel.Insert(l.ctx, product)
@ -57,5 +51,52 @@ func (l *CreateProductLogic) CreateProduct(in *pb.CreateProductReq) (*pb.IdResp,
return nil, status.Error(codes.Internal, err.Error())
}
tenantId := tenantctx.ExtractTenantId(l.ctx)
var totalLengthM float64
var totalBolts int
if len(in.Pans) > 0 {
pans := make([]*model.InvProductPan, 0, len(in.Pans))
for i, panInput := range in.Pans {
panId := uid.Generate()
pans = append(pans, &model.InvProductPan{
PanId: panId,
ProductId: productId,
Name: panInput.Name,
Position: panInput.Position,
SortOrder: int64(i + 1),
})
if len(panInput.BoltLengths) > 0 {
bolts := make([]*model.InvProductBolt, 0, len(panInput.BoltLengths))
for j, lengthStr := range panInput.BoltLengths {
lengthM, _ := strconv.ParseFloat(lengthStr, 64)
totalLengthM += lengthM
bolts = append(bolts, &model.InvProductBolt{
BoltId: uid.Generate(),
PanId: panId,
LengthM: lengthM,
SortOrder: int64(j + 1),
})
}
totalBolts += len(panInput.BoltLengths)
if err := l.svcCtx.BoltModel.BulkReplace(l.ctx, panId, "", bolts); err != nil {
l.Errorf("create bolts for pan %s failed: %v", panId, err)
}
}
}
if err := l.svcCtx.PanModel.BulkReplace(l.ctx, productId, "", pans); err != nil {
l.Errorf("create pans for product %s failed: %v", productId, err)
}
}
metrics.ProductCreatedTotal.WithLabelValues(tenantId, in.ProductName, in.Spec, in.Color).Inc()
if totalLengthM > 0 {
metrics.InboundLengthMetersTotal.WithLabelValues(tenantId, in.ProductName, in.Color, "create").Add(totalLengthM)
}
if totalBolts > 0 {
metrics.InboundBoltsTotal.WithLabelValues(tenantId, "create").Add(float64(totalBolts))
}
return &pb.IdResp{Id: productId}, nil
}

View File

@ -6,7 +6,11 @@ import (
"strconv"
"time"
"math"
"muyu-apiserver/model"
"muyu-apiserver/pkg/metrics"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/pkg/uid"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
@ -30,6 +34,19 @@ func NewCreateStockAdjustLogic(ctx context.Context, svcCtx *svc.ServiceContext)
}
}
// currentQty returns the total bolt length (meters) for a product.
func (l *CreateStockAdjustLogic) currentQty(productId string) float64 {
bolts, err := l.svcCtx.BoltModel.FindByProductId(l.ctx, productId)
if err != nil {
return 0
}
var total float64
for _, b := range bolts {
total += b.LengthM
}
return total
}
func (l *CreateStockAdjustLogic) CreateStockAdjust(in *pb.CreateStockAdjustReq) (*pb.IdResp, error) {
adjustId := uid.Generate()
adjustNo := generateNo("TZ")
@ -56,19 +73,20 @@ func (l *CreateStockAdjustLogic) CreateStockAdjust(in *pb.CreateStockAdjustReq)
details := make([]*model.InvStockAdjustDetail, 0, len(in.Details))
for _, d := range in.Details {
product, err := l.svcCtx.ProductModel.FindOneByProductId(l.ctx, d.ProductId)
_, err := l.svcCtx.ProductModel.FindOneByProductId(l.ctx, d.ProductId)
if err != nil {
return nil, status.Errorf(codes.NotFound, "product %s not found", d.ProductId)
}
beforeQty := l.currentQty(d.ProductId)
adjustQty, _ := strconv.ParseFloat(d.AdjustQuantity, 64)
afterQty := product.StockQuantity + adjustQty
afterQty := beforeQty + adjustQty
details = append(details, &model.InvStockAdjustDetail{
DetailId: uid.Generate(),
AdjustId: adjustId,
ProductId: d.ProductId,
BeforeQuantity: product.StockQuantity,
BeforeQuantity: beforeQty,
AdjustQuantity: adjustQty,
AfterQuantity: afterQty,
Remark: sql.NullString{String: d.Remark, Valid: d.Remark != ""},
@ -80,5 +98,15 @@ func (l *CreateStockAdjustLogic) CreateStockAdjust(in *pb.CreateStockAdjustReq)
return nil, status.Error(codes.Internal, err.Error())
}
tenantId := tenantctx.ExtractTenantId(l.ctx)
metrics.StockAdjustCreatedTotal.WithLabelValues(tenantId, in.AdjustReason).Inc()
for _, d := range details {
if d.AdjustQuantity >= 0 {
metrics.StockAdjustQuantityMeters.WithLabelValues(tenantId, "increase").Add(d.AdjustQuantity)
} else {
metrics.StockAdjustQuantityMeters.WithLabelValues(tenantId, "decrease").Add(math.Abs(d.AdjustQuantity))
}
}
return &pb.IdResp{Id: adjustId}, nil
}

View File

@ -8,6 +8,8 @@ import (
"time"
"muyu-apiserver/model"
"muyu-apiserver/pkg/metrics"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/pkg/uid"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
@ -36,6 +38,19 @@ func generateNo(prefix string) string {
return fmt.Sprintf("%s%s%04d", prefix, now.Format("20060102"), now.UnixNano()%10000)
}
// computeSystemQty returns the total bolt length (meters) for a product.
func (l *CreateStockCheckLogic) computeSystemQty(productId string) float64 {
bolts, err := l.svcCtx.BoltModel.FindByProductId(l.ctx, productId)
if err != nil {
return 0
}
var total float64
for _, b := range bolts {
total += b.LengthM
}
return total
}
func (l *CreateStockCheckLogic) CreateStockCheck(in *pb.CreateStockCheckReq) (*pb.IdResp, error) {
checkId := uid.Generate()
checkNo := generateNo("PD")
@ -66,15 +81,16 @@ func (l *CreateStockCheckLogic) CreateStockCheck(in *pb.CreateStockCheckReq) (*p
return nil, status.Errorf(codes.NotFound, "product %s not found", d.ProductId)
}
systemQty := l.computeSystemQty(d.ProductId)
actualQty, _ := strconv.ParseFloat(d.ActualQuantity, 64)
diffQty := actualQty - product.StockQuantity
diffAmount := diffQty * product.CostPrice
diffQty := actualQty - systemQty
diffAmount := diffQty * product.SalesPrice
details = append(details, &model.InvStockCheckDetail{
DetailId: uid.Generate(),
CheckId: checkId,
ProductId: d.ProductId,
SystemQuantity: product.StockQuantity,
SystemQuantity: systemQty,
ActualQuantity: actualQty,
DiffQuantity: diffQty,
DiffAmount: diffAmount,
@ -87,5 +103,8 @@ func (l *CreateStockCheckLogic) CreateStockCheck(in *pb.CreateStockCheckReq) (*p
return nil, status.Error(codes.Internal, err.Error())
}
tenantId := tenantctx.ExtractTenantId(l.ctx)
metrics.StockCheckCreatedTotal.WithLabelValues(tenantId).Inc()
return &pb.IdResp{Id: checkId}, nil
}

View File

@ -5,6 +5,8 @@ import (
"database/sql"
"time"
"muyu-apiserver/pkg/metrics"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
@ -41,5 +43,8 @@ func (l *DeleteProductLogic) DeleteProduct(in *pb.DeleteProductReq) (*pb.Empty,
return nil, status.Error(codes.Internal, err.Error())
}
tenantId := tenantctx.ExtractTenantId(l.ctx)
metrics.ProductDeletedTotal.WithLabelValues(tenantId).Inc()
return &pb.Empty{}, nil
}

View File

@ -33,20 +33,15 @@ func (l *GetProductLogic) GetProduct(in *pb.GetProductReq) (*pb.ProductInfo, err
}
return &pb.ProductInfo{
ProductId: product.ProductId,
ProductName: product.ProductName,
ImageUrl: product.ImageUrl,
Spec: product.Spec,
Color: product.Color,
UnitPieces: product.UnitPieces,
UnitRolls: product.UnitRolls,
StockQuantity: fmt.Sprintf("%.2f", product.StockQuantity),
Location: product.Location,
CostPrice: fmt.Sprintf("%.2f", product.CostPrice),
SalesPrice: fmt.Sprintf("%.2f", product.SalesPrice),
Remark: product.Remark.String,
Status: product.Status,
CreatedAt: product.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: product.UpdatedAt.Format("2006-01-02 15:04:05"),
ProductId: product.ProductId,
ProductName: product.ProductName,
ImageUrl: product.ImageUrl,
Spec: product.Spec,
Color: product.Color,
SalesPrice: fmt.Sprintf("%.2f", product.SalesPrice),
Remark: product.Remark.String,
Status: product.Status,
CreatedAt: product.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: product.UpdatedAt.Format("2006-01-02 15:04:05"),
}, nil
}

View File

@ -0,0 +1,95 @@
package logic
import (
"context"
"fmt"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type GetProductTreeLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewGetProductTreeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetProductTreeLogic {
return &GetProductTreeLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *GetProductTreeLogic) GetProductTree(in *pb.GetProductReq) (*pb.ProductTreeResp, error) {
product, err := l.svcCtx.ProductModel.FindOneByProductId(l.ctx, in.ProductId)
if err != nil {
return nil, status.Error(codes.NotFound, "product not found")
}
pans, err := l.svcCtx.PanModel.FindByProductId(l.ctx, in.ProductId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
var totalBoltCount int64
var totalLengthM float64
pbPans := make([]*pb.PanInfo, 0, len(pans))
for _, pan := range pans {
bolts, err := l.svcCtx.BoltModel.FindByPanId(l.ctx, pan.PanId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
var panLength float64
pbBolts := make([]*pb.BoltInfo, 0, len(bolts))
for _, b := range bolts {
pbBolts = append(pbBolts, &pb.BoltInfo{
BoltId: b.BoltId,
PanId: b.PanId,
LengthM: fmt.Sprintf("%.2f", b.LengthM),
SortOrder: b.SortOrder,
})
panLength += b.LengthM
}
totalBoltCount += int64(len(bolts))
totalLengthM += panLength
pbPans = append(pbPans, &pb.PanInfo{
PanId: pan.PanId,
ProductId: pan.ProductId,
Name: pan.Name,
Position: pan.Position,
SortOrder: pan.SortOrder,
Bolts: pbBolts,
BoltCount: int64(len(bolts)),
TotalLength: fmt.Sprintf("%.2f", panLength),
})
}
return &pb.ProductTreeResp{
Product: &pb.ProductInfo{
ProductId: product.ProductId,
ProductName: product.ProductName,
ImageUrl: product.ImageUrl,
Spec: product.Spec,
Color: product.Color,
SalesPrice: fmt.Sprintf("%.2f", product.SalesPrice),
Remark: product.Remark.String,
Status: product.Status,
CreatedAt: product.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: product.UpdatedAt.Format("2006-01-02 15:04:05"),
},
Pans: pbPans,
TotalPanCount: int64(len(pans)),
TotalBoltCount: totalBoltCount,
TotalLengthM: fmt.Sprintf("%.2f", totalLengthM),
}, nil
}

View File

@ -29,16 +29,26 @@ func NewGetStockSummaryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *G
func (l *GetStockSummaryLogic) GetStockSummary(in *pb.StockSummaryReq) (*pb.StockSummaryResp, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
productCount, totalPieces, totalRolls, totalCostValue, totalSalesValue, err := l.svcCtx.ProductModel.FindStockSummary(l.ctx, tenantId)
summaries, err := l.svcCtx.ProductModel.FindProductSummary(l.ctx, tenantId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
var productCount, totalPieces, totalPans int64
var totalLengthM, totalSalesValue float64
for _, s := range summaries {
productCount += s.ColorCount
totalPieces += s.TotalBoltCount
totalPans += s.TotalPanCount
totalLengthM += s.TotalLengthM
}
_ = totalSalesValue
return &pb.StockSummaryResp{
ProductCount: productCount,
TotalPieces: totalPieces,
TotalRolls: totalRolls,
TotalCostValue: fmt.Sprintf("%.2f", totalCostValue),
TotalSalesValue: fmt.Sprintf("%.2f", totalSalesValue),
TotalPans: totalPans,
TotalLengthM: fmt.Sprintf("%.2f", totalLengthM),
TotalSalesValue: "0.00",
}, nil
}

View File

@ -3,11 +3,12 @@ package logic
import (
"context"
"database/sql"
"fmt"
"strconv"
"strings"
"muyu-apiserver/model"
"muyu-apiserver/pkg/metrics"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/pkg/uid"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
@ -32,44 +33,35 @@ func NewImportProductsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Im
}
func (l *ImportProductsLogic) ImportProducts(in *pb.ImportProductReq) (*pb.ImportProductResp, error) {
var successCount, failCount int64
var errMsgs []string
products := make([]*model.InvProduct, 0, len(in.Products))
for i, p := range in.Products {
productId := uid.Generate()
stockQuantity, _ := strconv.ParseFloat(p.StockQuantity, 64)
costPrice, _ := strconv.ParseFloat(p.CostPrice, 64)
for _, p := range in.Products {
salesPrice, _ := strconv.ParseFloat(p.SalesPrice, 64)
product := &model.InvProduct{
ProductId: productId,
ProductName: p.ProductName,
ImageUrl: p.ImageUrl,
Spec: p.Spec,
Color: p.Color,
UnitPieces: p.UnitPieces,
UnitRolls: p.UnitRolls,
StockQuantity: stockQuantity,
Location: p.Location,
CostPrice: costPrice,
SalesPrice: salesPrice,
Remark: sql.NullString{String: p.Remark, Valid: p.Remark != ""},
Status: 1,
}
_, err := l.svcCtx.ProductModel.Insert(l.ctx, product)
if err != nil {
failCount++
errMsgs = append(errMsgs, fmt.Sprintf("row %d: %s", i+1, err.Error()))
} else {
successCount++
}
products = append(products, &model.InvProduct{
ProductId: uid.Generate(),
ProductName: p.ProductName,
ImageUrl: p.ImageUrl,
Spec: p.Spec,
Color: p.Color,
SalesPrice: salesPrice,
Remark: sql.NullString{String: p.Remark, Valid: p.Remark != ""},
Status: 1,
})
}
totalCount := int64(len(in.Products))
importId := uid.Generate()
errorMsg := strings.Join(errMsgs, "; ")
totalCount := int64(len(products))
affected, err := l.svcCtx.ProductModel.BulkInsert(l.ctx, products)
var errorMsg string
successCount := affected
failCount := totalCount - affected
if err != nil {
errorMsg = err.Error()
l.Errorf("bulk insert partial failure: %v", err)
}
importId := uid.Generate()
importLog := &model.InvStockImportLog{
ImportId: importId,
FileName: in.FileName,
@ -80,9 +72,23 @@ func (l *ImportProductsLogic) ImportProducts(in *pb.ImportProductReq) (*pb.Impor
Operator: in.Operator,
}
_, err := l.svcCtx.ImportLogModel.Insert(l.ctx, importLog)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
if _, logErr := l.svcCtx.ImportLogModel.Insert(l.ctx, importLog); logErr != nil {
l.Errorf("save import log failed: %v", logErr)
if err != nil {
return nil, status.Error(codes.Internal, strings.Join([]string{errorMsg, logErr.Error()}, "; "))
}
return nil, status.Error(codes.Internal, logErr.Error())
}
tenantId := tenantctx.ExtractTenantId(l.ctx)
metrics.ImportOperationsTotal.WithLabelValues(tenantId, in.Operator).Inc()
metrics.ImportProductsTotal.WithLabelValues(tenantId, "success").Add(float64(successCount))
if failCount > 0 {
metrics.ImportProductsTotal.WithLabelValues(tenantId, "fail").Add(float64(failCount))
}
metrics.ImportBatchSize.WithLabelValues(tenantId).Observe(float64(totalCount))
for _, p := range in.Products {
metrics.ProductCreatedTotal.WithLabelValues(tenantId, p.ProductName, p.Spec, p.Color).Inc()
}
return &pb.ImportProductResp{

View File

@ -0,0 +1,46 @@
package logic
import (
"context"
"fmt"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type ListBoltsLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewListBoltsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListBoltsLogic {
return &ListBoltsLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *ListBoltsLogic) ListBolts(in *pb.ListBoltReq) (*pb.ListBoltResp, error) {
bolts, err := l.svcCtx.BoltModel.FindByPanId(l.ctx, in.PanId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
pbBolts := make([]*pb.BoltInfo, 0, len(bolts))
for _, b := range bolts {
pbBolts = append(pbBolts, &pb.BoltInfo{
BoltId: b.BoltId,
PanId: b.PanId,
LengthM: fmt.Sprintf("%.2f", b.LengthM),
SortOrder: b.SortOrder,
})
}
return &pb.ListBoltResp{List: pbBolts}, nil
}

View File

@ -0,0 +1,53 @@
package logic
import (
"context"
"fmt"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type ListBoltViewLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewListBoltViewLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListBoltViewLogic {
return &ListBoltViewLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *ListBoltViewLogic) ListBoltView(in *pb.ListBoltViewReq) (*pb.ListBoltViewResp, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
list, total, err := l.svcCtx.BoltModel.FindBoltList(l.ctx, tenantId, in.Page, in.PageSize, in.ProductName, in.Position)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
pbList := make([]*pb.BoltListItem, 0, len(list))
for _, item := range list {
pbList = append(pbList, &pb.BoltListItem{
BoltId: item.BoltId,
PanId: item.PanId,
LengthM: fmt.Sprintf("%.2f", item.LengthM),
Color: item.Color,
SortOrder: item.SortOrder,
PanName: item.PanName,
Position: item.Position,
ProductId: item.ProductId,
ProductName: item.ProductName,
})
}
return &pb.ListBoltViewResp{Total: total, List: pbList}, nil
}

View File

@ -0,0 +1,54 @@
package logic
import (
"context"
"fmt"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type ListColorDetailLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewListColorDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListColorDetailLogic {
return &ListColorDetailLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *ListColorDetailLogic) ListColorDetail(in *pb.ListColorDetailReq) (*pb.ListColorDetailResp, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
list, err := l.svcCtx.ProductModel.FindColorDetail(l.ctx, tenantId, in.ProductName)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
pbList := make([]*pb.ColorDetailItem, 0, len(list))
for _, d := range list {
pbList = append(pbList, &pb.ColorDetailItem{
ProductId: d.ProductId,
ProductName: d.ProductName,
Color: d.Color,
Spec: d.Spec,
ImageUrl: d.ImageUrl,
PanCount: d.PanCount,
BoltCount: d.BoltCount,
TotalLengthM: fmt.Sprintf("%.2f", d.TotalLengthM),
Locations: d.Locations,
SalesPrice: fmt.Sprintf("%.2f", d.SalesPrice),
})
}
return &pb.ListColorDetailResp{List: pbList}, nil
}

View File

@ -0,0 +1,65 @@
package logic
import (
"context"
"fmt"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type ListPansLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewListPansLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListPansLogic {
return &ListPansLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *ListPansLogic) ListPans(in *pb.ListPanReq) (*pb.ListPanResp, error) {
pans, err := l.svcCtx.PanModel.FindByProductId(l.ctx, in.ProductId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
pbPans := make([]*pb.PanInfo, 0, len(pans))
for _, pan := range pans {
bolts, err := l.svcCtx.BoltModel.FindByPanId(l.ctx, pan.PanId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
var panLength float64
pbBolts := make([]*pb.BoltInfo, 0, len(bolts))
for _, b := range bolts {
pbBolts = append(pbBolts, &pb.BoltInfo{
BoltId: b.BoltId,
PanId: b.PanId,
LengthM: fmt.Sprintf("%.2f", b.LengthM),
SortOrder: b.SortOrder,
})
panLength += b.LengthM
}
pbPans = append(pbPans, &pb.PanInfo{
PanId: pan.PanId,
ProductId: pan.ProductId,
Name: pan.Name,
Position: pan.Position,
SortOrder: pan.SortOrder,
Bolts: pbBolts,
BoltCount: int64(len(bolts)),
TotalLength: fmt.Sprintf("%.2f", panLength),
})
}
return &pb.ListPanResp{List: pbPans}, nil
}

View File

@ -0,0 +1,52 @@
package logic
import (
"context"
"fmt"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type ListPanViewLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewListPanViewLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListPanViewLogic {
return &ListPanViewLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *ListPanViewLogic) ListPanView(in *pb.ListPanViewReq) (*pb.ListPanViewResp, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
list, total, err := l.svcCtx.PanModel.FindPanList(l.ctx, tenantId, in.Page, in.PageSize, in.ProductName, in.Position)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
pbList := make([]*pb.PanListItem, 0, len(list))
for _, item := range list {
pbList = append(pbList, &pb.PanListItem{
PanId: item.PanId,
ProductId: item.ProductId,
Name: item.Name,
Position: item.Position,
ProductName: item.ProductName,
Colors: item.Colors,
BoltCount: item.BoltCount,
TotalLengthM: fmt.Sprintf("%.2f", item.TotalLengthM),
})
}
return &pb.ListPanViewResp{Total: total, List: pbList}, nil
}

View File

@ -29,7 +29,7 @@ func NewListProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListP
func (l *ListProductLogic) ListProduct(in *pb.ListProductReq) (*pb.ListProductResp, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
list, total, err := l.svcCtx.ProductModel.FindList(l.ctx, tenantId, in.Page, in.PageSize, in.ProductName, in.Spec, in.Color, in.Location, in.Status)
list, total, err := l.svcCtx.ProductModel.FindList(l.ctx, tenantId, in.Page, in.PageSize, in.ProductName, in.Spec, in.Color, in.Status)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
@ -37,21 +37,16 @@ func (l *ListProductLogic) ListProduct(in *pb.ListProductReq) (*pb.ListProductRe
pbList := make([]*pb.ProductInfo, 0, len(list))
for _, p := range list {
pbList = append(pbList, &pb.ProductInfo{
ProductId: p.ProductId,
ProductName: p.ProductName,
ImageUrl: p.ImageUrl,
Spec: p.Spec,
Color: p.Color,
UnitPieces: p.UnitPieces,
UnitRolls: p.UnitRolls,
StockQuantity: fmt.Sprintf("%.2f", p.StockQuantity),
Location: p.Location,
CostPrice: fmt.Sprintf("%.2f", p.CostPrice),
SalesPrice: fmt.Sprintf("%.2f", p.SalesPrice),
Remark: p.Remark.String,
Status: p.Status,
CreatedAt: p.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: p.UpdatedAt.Format("2006-01-02 15:04:05"),
ProductId: p.ProductId,
ProductName: p.ProductName,
ImageUrl: p.ImageUrl,
Spec: p.Spec,
Color: p.Color,
SalesPrice: fmt.Sprintf("%.2f", p.SalesPrice),
Remark: p.Remark.String,
Status: p.Status,
CreatedAt: p.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: p.UpdatedAt.Format("2006-01-02 15:04:05"),
})
}

View File

@ -0,0 +1,52 @@
package logic
import (
"context"
"fmt"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type ListProductSummaryLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewListProductSummaryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListProductSummaryLogic {
return &ListProductSummaryLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *ListProductSummaryLogic) ListProductSummary(in *pb.ListProductSummaryReq) (*pb.ListProductSummaryResp, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
list, err := l.svcCtx.ProductModel.FindProductSummary(l.ctx, tenantId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
pbList := make([]*pb.ProductSummaryItem, 0, len(list))
for _, s := range list {
pbList = append(pbList, &pb.ProductSummaryItem{
ProductName: s.ProductName,
Spec: s.Spec,
ColorCount: s.ColorCount,
TotalPanCount: s.TotalPanCount,
TotalBoltCount: s.TotalBoltCount,
TotalLengthM: fmt.Sprintf("%.2f", s.TotalLengthM),
Colors: s.Colors,
Locations: s.Locations,
})
}
return &pb.ListProductSummaryResp{List: pbList}, nil
}

View File

@ -0,0 +1,59 @@
package logic
import (
"context"
"strconv"
"muyu-apiserver/model"
"muyu-apiserver/pkg/metrics"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/pkg/uid"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type SaveBoltsLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewSaveBoltsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SaveBoltsLogic {
return &SaveBoltsLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *SaveBoltsLogic) SaveBolts(in *pb.SaveBoltsReq) (*pb.Empty, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
var totalLengthM float64
bolts := make([]*model.InvProductBolt, 0, len(in.Lengths))
for i, lengthStr := range in.Lengths {
lengthM, _ := strconv.ParseFloat(lengthStr, 64)
totalLengthM += lengthM
bolts = append(bolts, &model.InvProductBolt{
BoltId: uid.Generate(),
PanId: in.PanId,
LengthM: lengthM,
SortOrder: int64(i + 1),
})
}
if err := l.svcCtx.BoltModel.BulkReplace(l.ctx, in.PanId, tenantId, bolts); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
metrics.InboundBoltsTotal.WithLabelValues(tenantId, "save_bolts").Add(float64(len(in.Lengths)))
if totalLengthM > 0 {
metrics.InboundLengthMetersTotal.WithLabelValues(tenantId, "", "", "save_bolts").Add(totalLengthM)
}
return &pb.Empty{}, nil
}

View File

@ -0,0 +1,105 @@
package logic
import (
"context"
"strconv"
"muyu-apiserver/model"
"muyu-apiserver/pkg/metrics"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/pkg/uid"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type SavePansLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewSavePansLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SavePansLogic {
return &SavePansLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *SavePansLogic) SavePans(in *pb.SavePansReq) (*pb.Empty, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
if err := l.svcCtx.BoltModel.DeleteByProductId(l.ctx, in.ProductId); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
var totalLengthM float64
var totalBolts int
pans := make([]*model.InvProductPan, 0, len(in.Pans))
for i, panInput := range in.Pans {
panId := uid.Generate()
pans = append(pans, &model.InvProductPan{
PanId: panId,
ProductId: in.ProductId,
Name: panInput.Name,
Position: panInput.Position,
SortOrder: int64(i + 1),
TenantId: tenantId,
})
if len(panInput.BoltLengths) > 0 {
bolts := make([]*model.InvProductBolt, 0, len(panInput.BoltLengths))
for j, lengthStr := range panInput.BoltLengths {
lengthM, _ := strconv.ParseFloat(lengthStr, 64)
totalLengthM += lengthM
bolts = append(bolts, &model.InvProductBolt{
BoltId: uid.Generate(),
PanId: panId,
LengthM: lengthM,
SortOrder: int64(j + 1),
})
}
totalBolts += len(panInput.BoltLengths)
if err := l.svcCtx.BoltModel.BulkReplace(l.ctx, panId, tenantId, bolts); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
}
}
if err := l.svcCtx.PanModel.BulkReplace(l.ctx, in.ProductId, tenantId, pans); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
metrics.PansSavedTotal.WithLabelValues(tenantId, in.ProductId).Inc()
if totalLengthM > 0 {
productName := l.resolveProductName(in.ProductId)
color := l.resolveProductColor(in.ProductId)
metrics.InboundLengthMetersTotal.WithLabelValues(tenantId, productName, color, "save_pans").Add(totalLengthM)
}
if totalBolts > 0 {
metrics.InboundBoltsTotal.WithLabelValues(tenantId, "save_pans").Add(float64(totalBolts))
}
return &pb.Empty{}, nil
}
func (l *SavePansLogic) resolveProductName(productId string) string {
p, err := l.svcCtx.ProductModel.FindOneByProductId(l.ctx, productId)
if err != nil {
return "unknown"
}
return p.ProductName
}
func (l *SavePansLogic) resolveProductColor(productId string) string {
p, err := l.svcCtx.ProductModel.FindOneByProductId(l.ctx, productId)
if err != nil {
return "unknown"
}
return p.Color
}

View File

@ -33,19 +33,12 @@ func (l *UpdateProductLogic) UpdateProduct(in *pb.UpdateProductReq) (*pb.Empty,
return nil, status.Error(codes.NotFound, "product not found")
}
stockQuantity, _ := strconv.ParseFloat(in.StockQuantity, 64)
costPrice, _ := strconv.ParseFloat(in.CostPrice, 64)
salesPrice, _ := strconv.ParseFloat(in.SalesPrice, 64)
product.ProductName = in.ProductName
product.ImageUrl = in.ImageUrl
product.Spec = in.Spec
product.Color = in.Color
product.UnitPieces = in.UnitPieces
product.UnitRolls = in.UnitRolls
product.StockQuantity = stockQuantity
product.Location = in.Location
product.CostPrice = costPrice
product.SalesPrice = salesPrice
product.Remark = sql.NullString{String: in.Remark, Valid: in.Remark != ""}

View File

@ -29,6 +29,18 @@ func NewUpdateStockCheckLogic(ctx context.Context, svcCtx *svc.ServiceContext) *
}
}
func (l *UpdateStockCheckLogic) systemQty(productId string) float64 {
bolts, err := l.svcCtx.BoltModel.FindByProductId(l.ctx, productId)
if err != nil {
return 0
}
var total float64
for _, b := range bolts {
total += b.LengthM
}
return total
}
func (l *UpdateStockCheckLogic) UpdateStockCheck(in *pb.UpdateStockCheckReq) (*pb.Empty, error) {
check, err := l.svcCtx.StockCheckModel.FindOneByCheckId(l.ctx, in.CheckId)
if err != nil {
@ -59,15 +71,16 @@ func (l *UpdateStockCheckLogic) UpdateStockCheck(in *pb.UpdateStockCheckReq) (*p
return nil, status.Errorf(codes.NotFound, "product %s not found", d.ProductId)
}
sysQty := l.systemQty(d.ProductId)
actualQty, _ := strconv.ParseFloat(d.ActualQuantity, 64)
diffQty := actualQty - product.StockQuantity
diffAmount := diffQty * product.CostPrice
diffQty := actualQty - sysQty
diffAmount := diffQty * product.SalesPrice
details = append(details, &model.InvStockCheckDetail{
DetailId: uid.Generate(),
CheckId: in.CheckId,
ProductId: d.ProductId,
SystemQuantity: product.StockQuantity,
SystemQuantity: sysQty,
ActualQuantity: actualQty,
DiffQuantity: diffQty,
DiffAmount: diffAmount,

View File

@ -23,7 +23,7 @@ func NewInventoryServiceServer(svcCtx *svc.ServiceContext) *InventoryServiceServ
}
}
// Product
// Product CRUD
func (s *InventoryServiceServer) CreateProduct(ctx context.Context, in *pb.CreateProductReq) (*pb.IdResp, error) {
l := logic.NewCreateProductLogic(ctx, s.svcCtx)
return l.CreateProduct(in)
@ -49,6 +49,57 @@ func (s *InventoryServiceServer) ListProduct(ctx context.Context, in *pb.ListPro
return l.ListProduct(in)
}
// Product Tree (panel 3)
func (s *InventoryServiceServer) GetProductTree(ctx context.Context, in *pb.GetProductReq) (*pb.ProductTreeResp, error) {
l := logic.NewGetProductTreeLogic(ctx, s.svcCtx)
return l.GetProductTree(in)
}
// Product Summary (panel 1)
func (s *InventoryServiceServer) ListProductSummary(ctx context.Context, in *pb.ListProductSummaryReq) (*pb.ListProductSummaryResp, error) {
l := logic.NewListProductSummaryLogic(ctx, s.svcCtx)
return l.ListProductSummary(in)
}
// Color Detail (panel 2)
func (s *InventoryServiceServer) ListColorDetail(ctx context.Context, in *pb.ListColorDetailReq) (*pb.ListColorDetailResp, error) {
l := logic.NewListColorDetailLogic(ctx, s.svcCtx)
return l.ListColorDetail(in)
}
// Pan / Bolt list views
func (s *InventoryServiceServer) ListPanView(ctx context.Context, in *pb.ListPanViewReq) (*pb.ListPanViewResp, error) {
l := logic.NewListPanViewLogic(ctx, s.svcCtx)
return l.ListPanView(in)
}
func (s *InventoryServiceServer) ListBoltView(ctx context.Context, in *pb.ListBoltViewReq) (*pb.ListBoltViewResp, error) {
l := logic.NewListBoltViewLogic(ctx, s.svcCtx)
return l.ListBoltView(in)
}
// Pan CRUD
func (s *InventoryServiceServer) ListPans(ctx context.Context, in *pb.ListPanReq) (*pb.ListPanResp, error) {
l := logic.NewListPansLogic(ctx, s.svcCtx)
return l.ListPans(in)
}
func (s *InventoryServiceServer) SavePans(ctx context.Context, in *pb.SavePansReq) (*pb.Empty, error) {
l := logic.NewSavePansLogic(ctx, s.svcCtx)
return l.SavePans(in)
}
// Bolt CRUD
func (s *InventoryServiceServer) ListBolts(ctx context.Context, in *pb.ListBoltReq) (*pb.ListBoltResp, error) {
l := logic.NewListBoltsLogic(ctx, s.svcCtx)
return l.ListBolts(in)
}
func (s *InventoryServiceServer) SaveBolts(ctx context.Context, in *pb.SaveBoltsReq) (*pb.Empty, error) {
l := logic.NewSaveBoltsLogic(ctx, s.svcCtx)
return l.SaveBolts(in)
}
// Import
func (s *InventoryServiceServer) ImportProducts(ctx context.Context, in *pb.ImportProductReq) (*pb.ImportProductResp, error) {
l := logic.NewImportProductsLogic(ctx, s.svcCtx)

View File

@ -10,6 +10,8 @@ import (
type ServiceContext struct {
Config config.Config
ProductModel model.InvProductModel
PanModel model.InvProductPanModel
BoltModel model.InvProductBoltModel
ImportLogModel model.InvStockImportLogModel
StockCheckModel model.InvStockCheckModel
CheckDetailModel model.InvStockCheckDetailModel
@ -22,6 +24,8 @@ func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
ProductModel: model.NewInvProductModel(conn, c.Cache),
PanModel: model.NewInvProductPanModel(conn),
BoltModel: model.NewInvProductBoltModel(conn),
ImportLogModel: model.NewInvStockImportLogModel(conn, c.Cache),
StockCheckModel: model.NewInvStockCheckModel(conn, c.Cache),
CheckDetailModel: model.NewInvStockCheckDetailModel(conn, c.Cache),

View File

@ -4,6 +4,7 @@ import (
"flag"
"fmt"
"muyu-apiserver/pkg/metrics"
"muyu-apiserver/rpc/inventory/internal/config"
"muyu-apiserver/rpc/inventory/internal/server"
"muyu-apiserver/rpc/inventory/internal/svc"
@ -25,6 +26,8 @@ func main() {
conf.MustLoad(*configFile, &c)
ctx := svc.NewServiceContext(c)
metrics.StartHTTP(c.PrometheusAddr)
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
pb.RegisterInventoryServiceServer(grpcServer, server.NewInventoryServiceServer(ctx))
@ -32,6 +35,7 @@ func main() {
reflection.Register(grpcServer)
}
})
s.AddUnaryInterceptors(metrics.UnaryServerInterceptor())
defer s.Stop()
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)

View File

@ -12,16 +12,11 @@ message ProductInfo {
string image_url = 3;
string spec = 4;
string color = 5;
int64 unit_pieces = 6;
int64 unit_rolls = 7;
string stock_quantity = 8;
string location = 9;
string cost_price = 10;
string sales_price = 11;
string remark = 12;
int64 status = 13;
string created_at = 14;
string updated_at = 15;
string sales_price = 6;
string remark = 7;
int64 status = 8;
string created_at = 9;
string updated_at = 10;
}
message CreateProductReq {
@ -29,13 +24,9 @@ message CreateProductReq {
string image_url = 2;
string spec = 3;
string color = 4;
int64 unit_pieces = 5;
int64 unit_rolls = 6;
string stock_quantity = 7;
string location = 8;
string cost_price = 9;
string sales_price = 10;
string remark = 11;
string sales_price = 5;
string remark = 6;
repeated PanInput pans = 7;
}
message UpdateProductReq {
@ -44,13 +35,8 @@ message UpdateProductReq {
string image_url = 3;
string spec = 4;
string color = 5;
int64 unit_pieces = 6;
int64 unit_rolls = 7;
string stock_quantity = 8;
string location = 9;
string cost_price = 10;
string sales_price = 11;
string remark = 12;
string sales_price = 6;
string remark = 7;
}
message DeleteProductReq {
@ -67,8 +53,7 @@ message ListProductReq {
string product_name = 3;
string spec = 4;
string color = 5;
string location = 6;
int64 status = 7;
int64 status = 6;
}
message ListProductResp {
@ -76,6 +61,161 @@ message ListProductResp {
repeated ProductInfo list = 2;
}
// ==================== Pan () ====================
message PanInfo {
string pan_id = 1;
string product_id = 2;
string name = 3;
string position = 4;
int64 sort_order = 5;
repeated BoltInfo bolts = 6;
int64 bolt_count = 7;
string total_length = 8;
}
message PanInput {
string name = 1;
string position = 2;
repeated string bolt_lengths = 3;
}
message ListPanReq {
string product_id = 1;
}
message ListPanResp {
repeated PanInfo list = 1;
}
message SavePansReq {
string product_id = 1;
repeated PanInput pans = 2;
}
// ==================== Bolt () ====================
message BoltInfo {
string bolt_id = 1;
string pan_id = 2;
string length_m = 3;
int64 sort_order = 4;
}
message ListBoltReq {
string pan_id = 1;
}
message ListBoltResp {
repeated BoltInfo list = 1;
}
message SaveBoltsReq {
string pan_id = 1;
repeated string lengths = 2;
}
// ==================== Product Tree () ====================
message ProductTreeResp {
ProductInfo product = 1;
repeated PanInfo pans = 2;
int64 total_pan_count = 3;
int64 total_bolt_count = 4;
string total_length_m = 5;
}
// ==================== Panel 1: Product Summary ====================
message ProductSummaryItem {
string product_name = 1;
string spec = 2;
int64 color_count = 3;
int64 total_pan_count = 4;
int64 total_bolt_count = 5;
string total_length_m = 6;
string colors = 7;
string locations = 8;
}
message ListProductSummaryReq {}
message ListProductSummaryResp {
repeated ProductSummaryItem list = 1;
}
// ==================== Panel 2: Color Detail ====================
message ColorDetailItem {
string product_id = 1;
string product_name = 2;
string color = 3;
string spec = 4;
string image_url = 5;
int64 pan_count = 6;
int64 bolt_count = 7;
string total_length_m = 8;
string locations = 9;
string sales_price = 10;
}
message ListColorDetailReq {
string product_name = 1;
}
message ListColorDetailResp {
repeated ColorDetailItem list = 1;
}
// ==================== Pan / Bolt List View ====================
message PanListItem {
string pan_id = 1;
string product_id = 2;
string name = 3;
string position = 4;
string product_name = 5;
string colors = 6; // aggregated from bolt.color, no spec
int64 bolt_count = 7;
string total_length_m = 8;
}
message ListPanViewReq {
int64 page = 1;
int64 page_size = 2;
string product_name = 3;
string position = 4;
}
message ListPanViewResp {
int64 total = 1;
repeated PanListItem list = 2;
}
message BoltListItem {
string bolt_id = 1;
string pan_id = 2;
string length_m = 3;
string color = 4; // bolt's own color, no spec
int64 sort_order = 5;
string pan_name = 6;
string position = 7;
string product_id = 8;
string product_name = 9;
}
message ListBoltViewReq {
int64 page = 1;
int64 page_size = 2;
string product_name = 3;
string position = 4;
}
message ListBoltViewResp {
int64 total = 1;
repeated BoltListItem list = 2;
}
// ==================== Stock Import ====================
message ImportProductReq {
@ -120,8 +260,8 @@ message StockSummaryReq {}
message StockSummaryResp {
int64 product_count = 1;
int64 total_pieces = 2;
int64 total_rolls = 3;
string total_cost_value = 4;
int64 total_pans = 3;
string total_length_m = 4;
string total_sales_value = 5;
}
@ -284,13 +424,34 @@ message Empty {}
// ==================== Service ====================
service InventoryService {
// Product
// Product CRUD
rpc CreateProduct(CreateProductReq) returns (IdResp);
rpc UpdateProduct(UpdateProductReq) returns (Empty);
rpc DeleteProduct(DeleteProductReq) returns (Empty);
rpc GetProduct(GetProductReq) returns (ProductInfo);
rpc ListProduct(ListProductReq) returns (ListProductResp);
// Product Tree (panel 3)
rpc GetProductTree(GetProductReq) returns (ProductTreeResp);
// Product Summary (panel 1)
rpc ListProductSummary(ListProductSummaryReq) returns (ListProductSummaryResp);
// Color Detail (panel 2)
rpc ListColorDetail(ListColorDetailReq) returns (ListColorDetailResp);
// Pan / Bolt list views (dimension panels)
rpc ListPanView(ListPanViewReq) returns (ListPanViewResp);
rpc ListBoltView(ListBoltViewReq) returns (ListBoltViewResp);
// Pan CRUD
rpc ListPans(ListPanReq) returns (ListPanResp);
rpc SavePans(SavePansReq) returns (Empty);
// Bolt CRUD
rpc ListBolts(ListBoltReq) returns (ListBoltResp);
rpc SaveBolts(SaveBoltsReq) returns (Empty);
// Import
rpc ImportProducts(ImportProductReq) returns (ImportProductResp);
rpc ListImportLog(ListImportLogReq) returns (ListImportLogResp);

View File

@ -14,50 +14,87 @@ import (
)
type (
ApproveStockAdjustReq = pb.ApproveStockAdjustReq
ConfirmStockCheckReq = pb.ConfirmStockCheckReq
CreateProductReq = pb.CreateProductReq
CreateStockAdjustReq = pb.CreateStockAdjustReq
CreateStockCheckReq = pb.CreateStockCheckReq
DeleteProductReq = pb.DeleteProductReq
Empty = pb.Empty
GetProductReq = pb.GetProductReq
GetStockAdjustReq = pb.GetStockAdjustReq
GetStockCheckReq = pb.GetStockCheckReq
IdResp = pb.IdResp
ImportLogInfo = pb.ImportLogInfo
ImportProductReq = pb.ImportProductReq
ImportProductResp = pb.ImportProductResp
ListImportLogReq = pb.ListImportLogReq
ListImportLogResp = pb.ListImportLogResp
ListProductReq = pb.ListProductReq
ListProductResp = pb.ListProductResp
ListStockAdjustReq = pb.ListStockAdjustReq
ListStockAdjustResp = pb.ListStockAdjustResp
ListStockCheckReq = pb.ListStockCheckReq
ListStockCheckResp = pb.ListStockCheckResp
ProductInfo = pb.ProductInfo
StockAdjustDetailInfo = pb.StockAdjustDetailInfo
StockAdjustDetailReq = pb.StockAdjustDetailReq
StockAdjustInfo = pb.StockAdjustInfo
StockCheckDetailInfo = pb.StockCheckDetailInfo
StockCheckDetailReq = pb.StockCheckDetailReq
StockCheckInfo = pb.StockCheckInfo
StockGroupItem = pb.StockGroupItem
StockGroupReq = pb.StockGroupReq
StockGroupResp = pb.StockGroupResp
StockSummaryReq = pb.StockSummaryReq
StockSummaryResp = pb.StockSummaryResp
UpdateProductReq = pb.UpdateProductReq
UpdateStockCheckReq = pb.UpdateStockCheckReq
ApproveStockAdjustReq = pb.ApproveStockAdjustReq
BoltListItem = pb.BoltListItem
BoltInfo = pb.BoltInfo
ColorDetailItem = pb.ColorDetailItem
ConfirmStockCheckReq = pb.ConfirmStockCheckReq
CreateProductReq = pb.CreateProductReq
CreateStockAdjustReq = pb.CreateStockAdjustReq
CreateStockCheckReq = pb.CreateStockCheckReq
DeleteProductReq = pb.DeleteProductReq
Empty = pb.Empty
GetProductReq = pb.GetProductReq
GetStockAdjustReq = pb.GetStockAdjustReq
GetStockCheckReq = pb.GetStockCheckReq
IdResp = pb.IdResp
ImportLogInfo = pb.ImportLogInfo
ImportProductReq = pb.ImportProductReq
ImportProductResp = pb.ImportProductResp
ListBoltReq = pb.ListBoltReq
ListBoltResp = pb.ListBoltResp
ListBoltViewReq = pb.ListBoltViewReq
ListBoltViewResp = pb.ListBoltViewResp
ListColorDetailReq = pb.ListColorDetailReq
ListPanViewReq = pb.ListPanViewReq
ListPanViewResp = pb.ListPanViewResp
PanListItem = pb.PanListItem
ListColorDetailResp = pb.ListColorDetailResp
ListImportLogReq = pb.ListImportLogReq
ListImportLogResp = pb.ListImportLogResp
ListPanReq = pb.ListPanReq
ListPanResp = pb.ListPanResp
ListProductReq = pb.ListProductReq
ListProductResp = pb.ListProductResp
ListProductSummaryReq = pb.ListProductSummaryReq
ListProductSummaryResp = pb.ListProductSummaryResp
ListStockAdjustReq = pb.ListStockAdjustReq
ListStockAdjustResp = pb.ListStockAdjustResp
ListStockCheckReq = pb.ListStockCheckReq
ListStockCheckResp = pb.ListStockCheckResp
PanInfo = pb.PanInfo
PanInput = pb.PanInput
ProductInfo = pb.ProductInfo
ProductSummaryItem = pb.ProductSummaryItem
ProductTreeResp = pb.ProductTreeResp
SaveBoltsReq = pb.SaveBoltsReq
SavePansReq = pb.SavePansReq
StockAdjustDetailInfo = pb.StockAdjustDetailInfo
StockAdjustDetailReq = pb.StockAdjustDetailReq
StockAdjustInfo = pb.StockAdjustInfo
StockCheckDetailInfo = pb.StockCheckDetailInfo
StockCheckDetailReq = pb.StockCheckDetailReq
StockCheckInfo = pb.StockCheckInfo
StockGroupItem = pb.StockGroupItem
StockGroupReq = pb.StockGroupReq
StockGroupResp = pb.StockGroupResp
StockSummaryReq = pb.StockSummaryReq
StockSummaryResp = pb.StockSummaryResp
UpdateProductReq = pb.UpdateProductReq
UpdateStockCheckReq = pb.UpdateStockCheckReq
InventoryService interface {
// Product
// Product CRUD
CreateProduct(ctx context.Context, in *CreateProductReq, opts ...grpc.CallOption) (*IdResp, error)
UpdateProduct(ctx context.Context, in *UpdateProductReq, opts ...grpc.CallOption) (*Empty, error)
DeleteProduct(ctx context.Context, in *DeleteProductReq, opts ...grpc.CallOption) (*Empty, error)
GetProduct(ctx context.Context, in *GetProductReq, opts ...grpc.CallOption) (*ProductInfo, error)
ListProduct(ctx context.Context, in *ListProductReq, opts ...grpc.CallOption) (*ListProductResp, error)
// Product Tree (panel 3)
GetProductTree(ctx context.Context, in *GetProductReq, opts ...grpc.CallOption) (*ProductTreeResp, error)
// Product Summary (panel 1)
ListProductSummary(ctx context.Context, in *ListProductSummaryReq, opts ...grpc.CallOption) (*ListProductSummaryResp, error)
// Color Detail (panel 2)
ListColorDetail(ctx context.Context, in *ListColorDetailReq, opts ...grpc.CallOption) (*ListColorDetailResp, error)
// Pan / Bolt list views
ListPanView(ctx context.Context, in *ListPanViewReq, opts ...grpc.CallOption) (*ListPanViewResp, error)
ListBoltView(ctx context.Context, in *ListBoltViewReq, opts ...grpc.CallOption) (*ListBoltViewResp, error)
// Pan CRUD
ListPans(ctx context.Context, in *ListPanReq, opts ...grpc.CallOption) (*ListPanResp, error)
SavePans(ctx context.Context, in *SavePansReq, opts ...grpc.CallOption) (*Empty, error)
// Bolt CRUD
ListBolts(ctx context.Context, in *ListBoltReq, opts ...grpc.CallOption) (*ListBoltResp, error)
SaveBolts(ctx context.Context, in *SaveBoltsReq, opts ...grpc.CallOption) (*Empty, error)
// Import
ImportProducts(ctx context.Context, in *ImportProductReq, opts ...grpc.CallOption) (*ImportProductResp, error)
ListImportLog(ctx context.Context, in *ListImportLogReq, opts ...grpc.CallOption) (*ListImportLogResp, error)
@ -88,7 +125,7 @@ func NewInventoryService(cli zrpc.Client) InventoryService {
}
}
// Product
// Product CRUD
func (m *defaultInventoryService) CreateProduct(ctx context.Context, in *CreateProductReq, opts ...grpc.CallOption) (*IdResp, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.CreateProduct(ctx, in, opts...)
@ -114,6 +151,51 @@ func (m *defaultInventoryService) ListProduct(ctx context.Context, in *ListProdu
return client.ListProduct(ctx, in, opts...)
}
func (m *defaultInventoryService) GetProductTree(ctx context.Context, in *GetProductReq, opts ...grpc.CallOption) (*ProductTreeResp, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.GetProductTree(ctx, in, opts...)
}
func (m *defaultInventoryService) ListProductSummary(ctx context.Context, in *ListProductSummaryReq, opts ...grpc.CallOption) (*ListProductSummaryResp, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.ListProductSummary(ctx, in, opts...)
}
func (m *defaultInventoryService) ListColorDetail(ctx context.Context, in *ListColorDetailReq, opts ...grpc.CallOption) (*ListColorDetailResp, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.ListColorDetail(ctx, in, opts...)
}
func (m *defaultInventoryService) ListPanView(ctx context.Context, in *ListPanViewReq, opts ...grpc.CallOption) (*ListPanViewResp, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.ListPanView(ctx, in, opts...)
}
func (m *defaultInventoryService) ListBoltView(ctx context.Context, in *ListBoltViewReq, opts ...grpc.CallOption) (*ListBoltViewResp, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.ListBoltView(ctx, in, opts...)
}
func (m *defaultInventoryService) ListPans(ctx context.Context, in *ListPanReq, opts ...grpc.CallOption) (*ListPanResp, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.ListPans(ctx, in, opts...)
}
func (m *defaultInventoryService) SavePans(ctx context.Context, in *SavePansReq, opts ...grpc.CallOption) (*Empty, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.SavePans(ctx, in, opts...)
}
func (m *defaultInventoryService) ListBolts(ctx context.Context, in *ListBoltReq, opts ...grpc.CallOption) (*ListBoltResp, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.ListBolts(ctx, in, opts...)
}
func (m *defaultInventoryService) SaveBolts(ctx context.Context, in *SaveBoltsReq, opts ...grpc.CallOption) (*Empty, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.SaveBolts(ctx, in, opts...)
}
// Import
func (m *defaultInventoryService) ImportProducts(ctx context.Context, in *ImportProductReq, opts ...grpc.CallOption) (*ImportProductResp, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.20.3
// source: rpc/inventory/inventory.proto
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.3
// source: inventory.proto
package pb
@ -15,19 +15,64 @@ import (
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
InventoryService_CreateProduct_FullMethodName = "/inventory.InventoryService/CreateProduct"
InventoryService_UpdateProduct_FullMethodName = "/inventory.InventoryService/UpdateProduct"
InventoryService_DeleteProduct_FullMethodName = "/inventory.InventoryService/DeleteProduct"
InventoryService_GetProduct_FullMethodName = "/inventory.InventoryService/GetProduct"
InventoryService_ListProduct_FullMethodName = "/inventory.InventoryService/ListProduct"
InventoryService_GetProductTree_FullMethodName = "/inventory.InventoryService/GetProductTree"
InventoryService_ListProductSummary_FullMethodName = "/inventory.InventoryService/ListProductSummary"
InventoryService_ListColorDetail_FullMethodName = "/inventory.InventoryService/ListColorDetail"
InventoryService_ListPanView_FullMethodName = "/inventory.InventoryService/ListPanView"
InventoryService_ListBoltView_FullMethodName = "/inventory.InventoryService/ListBoltView"
InventoryService_ListPans_FullMethodName = "/inventory.InventoryService/ListPans"
InventoryService_SavePans_FullMethodName = "/inventory.InventoryService/SavePans"
InventoryService_ListBolts_FullMethodName = "/inventory.InventoryService/ListBolts"
InventoryService_SaveBolts_FullMethodName = "/inventory.InventoryService/SaveBolts"
InventoryService_ImportProducts_FullMethodName = "/inventory.InventoryService/ImportProducts"
InventoryService_ListImportLog_FullMethodName = "/inventory.InventoryService/ListImportLog"
InventoryService_GetStockSummary_FullMethodName = "/inventory.InventoryService/GetStockSummary"
InventoryService_GetStockGroup_FullMethodName = "/inventory.InventoryService/GetStockGroup"
InventoryService_CreateStockCheck_FullMethodName = "/inventory.InventoryService/CreateStockCheck"
InventoryService_UpdateStockCheck_FullMethodName = "/inventory.InventoryService/UpdateStockCheck"
InventoryService_ConfirmStockCheck_FullMethodName = "/inventory.InventoryService/ConfirmStockCheck"
InventoryService_GetStockCheck_FullMethodName = "/inventory.InventoryService/GetStockCheck"
InventoryService_ListStockCheck_FullMethodName = "/inventory.InventoryService/ListStockCheck"
InventoryService_CreateStockAdjust_FullMethodName = "/inventory.InventoryService/CreateStockAdjust"
InventoryService_ApproveStockAdjust_FullMethodName = "/inventory.InventoryService/ApproveStockAdjust"
InventoryService_GetStockAdjust_FullMethodName = "/inventory.InventoryService/GetStockAdjust"
InventoryService_ListStockAdjust_FullMethodName = "/inventory.InventoryService/ListStockAdjust"
)
// InventoryServiceClient is the client API for InventoryService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type InventoryServiceClient interface {
// Product
// Product CRUD
CreateProduct(ctx context.Context, in *CreateProductReq, opts ...grpc.CallOption) (*IdResp, error)
UpdateProduct(ctx context.Context, in *UpdateProductReq, opts ...grpc.CallOption) (*Empty, error)
DeleteProduct(ctx context.Context, in *DeleteProductReq, opts ...grpc.CallOption) (*Empty, error)
GetProduct(ctx context.Context, in *GetProductReq, opts ...grpc.CallOption) (*ProductInfo, error)
ListProduct(ctx context.Context, in *ListProductReq, opts ...grpc.CallOption) (*ListProductResp, error)
// Product Tree (panel 3)
GetProductTree(ctx context.Context, in *GetProductReq, opts ...grpc.CallOption) (*ProductTreeResp, error)
// Product Summary (panel 1)
ListProductSummary(ctx context.Context, in *ListProductSummaryReq, opts ...grpc.CallOption) (*ListProductSummaryResp, error)
// Color Detail (panel 2)
ListColorDetail(ctx context.Context, in *ListColorDetailReq, opts ...grpc.CallOption) (*ListColorDetailResp, error)
// Pan / Bolt list views (dimension panels)
ListPanView(ctx context.Context, in *ListPanViewReq, opts ...grpc.CallOption) (*ListPanViewResp, error)
ListBoltView(ctx context.Context, in *ListBoltViewReq, opts ...grpc.CallOption) (*ListBoltViewResp, error)
// Pan CRUD
ListPans(ctx context.Context, in *ListPanReq, opts ...grpc.CallOption) (*ListPanResp, error)
SavePans(ctx context.Context, in *SavePansReq, opts ...grpc.CallOption) (*Empty, error)
// Bolt CRUD
ListBolts(ctx context.Context, in *ListBoltReq, opts ...grpc.CallOption) (*ListBoltResp, error)
SaveBolts(ctx context.Context, in *SaveBoltsReq, opts ...grpc.CallOption) (*Empty, error)
// Import
ImportProducts(ctx context.Context, in *ImportProductReq, opts ...grpc.CallOption) (*ImportProductResp, error)
ListImportLog(ctx context.Context, in *ListImportLogReq, opts ...grpc.CallOption) (*ListImportLogResp, error)
@ -56,8 +101,9 @@ func NewInventoryServiceClient(cc grpc.ClientConnInterface) InventoryServiceClie
}
func (c *inventoryServiceClient) CreateProduct(ctx context.Context, in *CreateProductReq, opts ...grpc.CallOption) (*IdResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(IdResp)
err := c.cc.Invoke(ctx, "/inventory.InventoryService/CreateProduct", in, out, opts...)
err := c.cc.Invoke(ctx, InventoryService_CreateProduct_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -65,8 +111,9 @@ func (c *inventoryServiceClient) CreateProduct(ctx context.Context, in *CreatePr
}
func (c *inventoryServiceClient) UpdateProduct(ctx context.Context, in *UpdateProductReq, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
err := c.cc.Invoke(ctx, "/inventory.InventoryService/UpdateProduct", in, out, opts...)
err := c.cc.Invoke(ctx, InventoryService_UpdateProduct_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -74,8 +121,9 @@ func (c *inventoryServiceClient) UpdateProduct(ctx context.Context, in *UpdatePr
}
func (c *inventoryServiceClient) DeleteProduct(ctx context.Context, in *DeleteProductReq, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
err := c.cc.Invoke(ctx, "/inventory.InventoryService/DeleteProduct", in, out, opts...)
err := c.cc.Invoke(ctx, InventoryService_DeleteProduct_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -83,8 +131,9 @@ func (c *inventoryServiceClient) DeleteProduct(ctx context.Context, in *DeletePr
}
func (c *inventoryServiceClient) GetProduct(ctx context.Context, in *GetProductReq, opts ...grpc.CallOption) (*ProductInfo, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ProductInfo)
err := c.cc.Invoke(ctx, "/inventory.InventoryService/GetProduct", in, out, opts...)
err := c.cc.Invoke(ctx, InventoryService_GetProduct_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -92,8 +141,99 @@ func (c *inventoryServiceClient) GetProduct(ctx context.Context, in *GetProductR
}
func (c *inventoryServiceClient) ListProduct(ctx context.Context, in *ListProductReq, opts ...grpc.CallOption) (*ListProductResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListProductResp)
err := c.cc.Invoke(ctx, "/inventory.InventoryService/ListProduct", in, out, opts...)
err := c.cc.Invoke(ctx, InventoryService_ListProduct_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *inventoryServiceClient) GetProductTree(ctx context.Context, in *GetProductReq, opts ...grpc.CallOption) (*ProductTreeResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ProductTreeResp)
err := c.cc.Invoke(ctx, InventoryService_GetProductTree_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *inventoryServiceClient) ListProductSummary(ctx context.Context, in *ListProductSummaryReq, opts ...grpc.CallOption) (*ListProductSummaryResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListProductSummaryResp)
err := c.cc.Invoke(ctx, InventoryService_ListProductSummary_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *inventoryServiceClient) ListColorDetail(ctx context.Context, in *ListColorDetailReq, opts ...grpc.CallOption) (*ListColorDetailResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListColorDetailResp)
err := c.cc.Invoke(ctx, InventoryService_ListColorDetail_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *inventoryServiceClient) ListPanView(ctx context.Context, in *ListPanViewReq, opts ...grpc.CallOption) (*ListPanViewResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListPanViewResp)
err := c.cc.Invoke(ctx, InventoryService_ListPanView_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *inventoryServiceClient) ListBoltView(ctx context.Context, in *ListBoltViewReq, opts ...grpc.CallOption) (*ListBoltViewResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListBoltViewResp)
err := c.cc.Invoke(ctx, InventoryService_ListBoltView_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *inventoryServiceClient) ListPans(ctx context.Context, in *ListPanReq, opts ...grpc.CallOption) (*ListPanResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListPanResp)
err := c.cc.Invoke(ctx, InventoryService_ListPans_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *inventoryServiceClient) SavePans(ctx context.Context, in *SavePansReq, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
err := c.cc.Invoke(ctx, InventoryService_SavePans_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *inventoryServiceClient) ListBolts(ctx context.Context, in *ListBoltReq, opts ...grpc.CallOption) (*ListBoltResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListBoltResp)
err := c.cc.Invoke(ctx, InventoryService_ListBolts_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *inventoryServiceClient) SaveBolts(ctx context.Context, in *SaveBoltsReq, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
err := c.cc.Invoke(ctx, InventoryService_SaveBolts_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -101,8 +241,9 @@ func (c *inventoryServiceClient) ListProduct(ctx context.Context, in *ListProduc
}
func (c *inventoryServiceClient) ImportProducts(ctx context.Context, in *ImportProductReq, opts ...grpc.CallOption) (*ImportProductResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ImportProductResp)
err := c.cc.Invoke(ctx, "/inventory.InventoryService/ImportProducts", in, out, opts...)
err := c.cc.Invoke(ctx, InventoryService_ImportProducts_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -110,8 +251,9 @@ func (c *inventoryServiceClient) ImportProducts(ctx context.Context, in *ImportP
}
func (c *inventoryServiceClient) ListImportLog(ctx context.Context, in *ListImportLogReq, opts ...grpc.CallOption) (*ListImportLogResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListImportLogResp)
err := c.cc.Invoke(ctx, "/inventory.InventoryService/ListImportLog", in, out, opts...)
err := c.cc.Invoke(ctx, InventoryService_ListImportLog_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -119,8 +261,9 @@ func (c *inventoryServiceClient) ListImportLog(ctx context.Context, in *ListImpo
}
func (c *inventoryServiceClient) GetStockSummary(ctx context.Context, in *StockSummaryReq, opts ...grpc.CallOption) (*StockSummaryResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(StockSummaryResp)
err := c.cc.Invoke(ctx, "/inventory.InventoryService/GetStockSummary", in, out, opts...)
err := c.cc.Invoke(ctx, InventoryService_GetStockSummary_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -128,8 +271,9 @@ func (c *inventoryServiceClient) GetStockSummary(ctx context.Context, in *StockS
}
func (c *inventoryServiceClient) GetStockGroup(ctx context.Context, in *StockGroupReq, opts ...grpc.CallOption) (*StockGroupResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(StockGroupResp)
err := c.cc.Invoke(ctx, "/inventory.InventoryService/GetStockGroup", in, out, opts...)
err := c.cc.Invoke(ctx, InventoryService_GetStockGroup_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -137,8 +281,9 @@ func (c *inventoryServiceClient) GetStockGroup(ctx context.Context, in *StockGro
}
func (c *inventoryServiceClient) CreateStockCheck(ctx context.Context, in *CreateStockCheckReq, opts ...grpc.CallOption) (*IdResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(IdResp)
err := c.cc.Invoke(ctx, "/inventory.InventoryService/CreateStockCheck", in, out, opts...)
err := c.cc.Invoke(ctx, InventoryService_CreateStockCheck_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -146,8 +291,9 @@ func (c *inventoryServiceClient) CreateStockCheck(ctx context.Context, in *Creat
}
func (c *inventoryServiceClient) UpdateStockCheck(ctx context.Context, in *UpdateStockCheckReq, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
err := c.cc.Invoke(ctx, "/inventory.InventoryService/UpdateStockCheck", in, out, opts...)
err := c.cc.Invoke(ctx, InventoryService_UpdateStockCheck_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -155,8 +301,9 @@ func (c *inventoryServiceClient) UpdateStockCheck(ctx context.Context, in *Updat
}
func (c *inventoryServiceClient) ConfirmStockCheck(ctx context.Context, in *ConfirmStockCheckReq, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
err := c.cc.Invoke(ctx, "/inventory.InventoryService/ConfirmStockCheck", in, out, opts...)
err := c.cc.Invoke(ctx, InventoryService_ConfirmStockCheck_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -164,8 +311,9 @@ func (c *inventoryServiceClient) ConfirmStockCheck(ctx context.Context, in *Conf
}
func (c *inventoryServiceClient) GetStockCheck(ctx context.Context, in *GetStockCheckReq, opts ...grpc.CallOption) (*StockCheckInfo, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(StockCheckInfo)
err := c.cc.Invoke(ctx, "/inventory.InventoryService/GetStockCheck", in, out, opts...)
err := c.cc.Invoke(ctx, InventoryService_GetStockCheck_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -173,8 +321,9 @@ func (c *inventoryServiceClient) GetStockCheck(ctx context.Context, in *GetStock
}
func (c *inventoryServiceClient) ListStockCheck(ctx context.Context, in *ListStockCheckReq, opts ...grpc.CallOption) (*ListStockCheckResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListStockCheckResp)
err := c.cc.Invoke(ctx, "/inventory.InventoryService/ListStockCheck", in, out, opts...)
err := c.cc.Invoke(ctx, InventoryService_ListStockCheck_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -182,8 +331,9 @@ func (c *inventoryServiceClient) ListStockCheck(ctx context.Context, in *ListSto
}
func (c *inventoryServiceClient) CreateStockAdjust(ctx context.Context, in *CreateStockAdjustReq, opts ...grpc.CallOption) (*IdResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(IdResp)
err := c.cc.Invoke(ctx, "/inventory.InventoryService/CreateStockAdjust", in, out, opts...)
err := c.cc.Invoke(ctx, InventoryService_CreateStockAdjust_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -191,8 +341,9 @@ func (c *inventoryServiceClient) CreateStockAdjust(ctx context.Context, in *Crea
}
func (c *inventoryServiceClient) ApproveStockAdjust(ctx context.Context, in *ApproveStockAdjustReq, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
err := c.cc.Invoke(ctx, "/inventory.InventoryService/ApproveStockAdjust", in, out, opts...)
err := c.cc.Invoke(ctx, InventoryService_ApproveStockAdjust_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -200,8 +351,9 @@ func (c *inventoryServiceClient) ApproveStockAdjust(ctx context.Context, in *App
}
func (c *inventoryServiceClient) GetStockAdjust(ctx context.Context, in *GetStockAdjustReq, opts ...grpc.CallOption) (*StockAdjustInfo, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(StockAdjustInfo)
err := c.cc.Invoke(ctx, "/inventory.InventoryService/GetStockAdjust", in, out, opts...)
err := c.cc.Invoke(ctx, InventoryService_GetStockAdjust_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -209,8 +361,9 @@ func (c *inventoryServiceClient) GetStockAdjust(ctx context.Context, in *GetStoc
}
func (c *inventoryServiceClient) ListStockAdjust(ctx context.Context, in *ListStockAdjustReq, opts ...grpc.CallOption) (*ListStockAdjustResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListStockAdjustResp)
err := c.cc.Invoke(ctx, "/inventory.InventoryService/ListStockAdjust", in, out, opts...)
err := c.cc.Invoke(ctx, InventoryService_ListStockAdjust_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -219,14 +372,29 @@ func (c *inventoryServiceClient) ListStockAdjust(ctx context.Context, in *ListSt
// InventoryServiceServer is the server API for InventoryService service.
// All implementations must embed UnimplementedInventoryServiceServer
// for forward compatibility
// for forward compatibility.
type InventoryServiceServer interface {
// Product
// Product CRUD
CreateProduct(context.Context, *CreateProductReq) (*IdResp, error)
UpdateProduct(context.Context, *UpdateProductReq) (*Empty, error)
DeleteProduct(context.Context, *DeleteProductReq) (*Empty, error)
GetProduct(context.Context, *GetProductReq) (*ProductInfo, error)
ListProduct(context.Context, *ListProductReq) (*ListProductResp, error)
// Product Tree (panel 3)
GetProductTree(context.Context, *GetProductReq) (*ProductTreeResp, error)
// Product Summary (panel 1)
ListProductSummary(context.Context, *ListProductSummaryReq) (*ListProductSummaryResp, error)
// Color Detail (panel 2)
ListColorDetail(context.Context, *ListColorDetailReq) (*ListColorDetailResp, error)
// Pan / Bolt list views (dimension panels)
ListPanView(context.Context, *ListPanViewReq) (*ListPanViewResp, error)
ListBoltView(context.Context, *ListBoltViewReq) (*ListBoltViewResp, error)
// Pan CRUD
ListPans(context.Context, *ListPanReq) (*ListPanResp, error)
SavePans(context.Context, *SavePansReq) (*Empty, error)
// Bolt CRUD
ListBolts(context.Context, *ListBoltReq) (*ListBoltResp, error)
SaveBolts(context.Context, *SaveBoltsReq) (*Empty, error)
// Import
ImportProducts(context.Context, *ImportProductReq) (*ImportProductResp, error)
ListImportLog(context.Context, *ListImportLogReq) (*ListImportLogResp, error)
@ -247,9 +415,12 @@ type InventoryServiceServer interface {
mustEmbedUnimplementedInventoryServiceServer()
}
// UnimplementedInventoryServiceServer must be embedded to have forward compatible implementations.
type UnimplementedInventoryServiceServer struct {
}
// UnimplementedInventoryServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedInventoryServiceServer struct{}
func (UnimplementedInventoryServiceServer) CreateProduct(context.Context, *CreateProductReq) (*IdResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateProduct not implemented")
@ -266,6 +437,33 @@ func (UnimplementedInventoryServiceServer) GetProduct(context.Context, *GetProdu
func (UnimplementedInventoryServiceServer) ListProduct(context.Context, *ListProductReq) (*ListProductResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListProduct not implemented")
}
func (UnimplementedInventoryServiceServer) GetProductTree(context.Context, *GetProductReq) (*ProductTreeResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetProductTree not implemented")
}
func (UnimplementedInventoryServiceServer) ListProductSummary(context.Context, *ListProductSummaryReq) (*ListProductSummaryResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListProductSummary not implemented")
}
func (UnimplementedInventoryServiceServer) ListColorDetail(context.Context, *ListColorDetailReq) (*ListColorDetailResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListColorDetail not implemented")
}
func (UnimplementedInventoryServiceServer) ListPanView(context.Context, *ListPanViewReq) (*ListPanViewResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListPanView not implemented")
}
func (UnimplementedInventoryServiceServer) ListBoltView(context.Context, *ListBoltViewReq) (*ListBoltViewResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListBoltView not implemented")
}
func (UnimplementedInventoryServiceServer) ListPans(context.Context, *ListPanReq) (*ListPanResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListPans not implemented")
}
func (UnimplementedInventoryServiceServer) SavePans(context.Context, *SavePansReq) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SavePans not implemented")
}
func (UnimplementedInventoryServiceServer) ListBolts(context.Context, *ListBoltReq) (*ListBoltResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListBolts not implemented")
}
func (UnimplementedInventoryServiceServer) SaveBolts(context.Context, *SaveBoltsReq) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SaveBolts not implemented")
}
func (UnimplementedInventoryServiceServer) ImportProducts(context.Context, *ImportProductReq) (*ImportProductResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ImportProducts not implemented")
}
@ -306,6 +504,7 @@ func (UnimplementedInventoryServiceServer) ListStockAdjust(context.Context, *Lis
return nil, status.Errorf(codes.Unimplemented, "method ListStockAdjust not implemented")
}
func (UnimplementedInventoryServiceServer) mustEmbedUnimplementedInventoryServiceServer() {}
func (UnimplementedInventoryServiceServer) testEmbeddedByValue() {}
// UnsafeInventoryServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to InventoryServiceServer will
@ -315,6 +514,13 @@ type UnsafeInventoryServiceServer interface {
}
func RegisterInventoryServiceServer(s grpc.ServiceRegistrar, srv InventoryServiceServer) {
// If the following call pancis, it indicates UnimplementedInventoryServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&InventoryService_ServiceDesc, srv)
}
@ -328,7 +534,7 @@ func _InventoryService_CreateProduct_Handler(srv interface{}, ctx context.Contex
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/inventory.InventoryService/CreateProduct",
FullMethod: InventoryService_CreateProduct_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).CreateProduct(ctx, req.(*CreateProductReq))
@ -346,7 +552,7 @@ func _InventoryService_UpdateProduct_Handler(srv interface{}, ctx context.Contex
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/inventory.InventoryService/UpdateProduct",
FullMethod: InventoryService_UpdateProduct_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).UpdateProduct(ctx, req.(*UpdateProductReq))
@ -364,7 +570,7 @@ func _InventoryService_DeleteProduct_Handler(srv interface{}, ctx context.Contex
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/inventory.InventoryService/DeleteProduct",
FullMethod: InventoryService_DeleteProduct_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).DeleteProduct(ctx, req.(*DeleteProductReq))
@ -382,7 +588,7 @@ func _InventoryService_GetProduct_Handler(srv interface{}, ctx context.Context,
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/inventory.InventoryService/GetProduct",
FullMethod: InventoryService_GetProduct_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).GetProduct(ctx, req.(*GetProductReq))
@ -400,7 +606,7 @@ func _InventoryService_ListProduct_Handler(srv interface{}, ctx context.Context,
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/inventory.InventoryService/ListProduct",
FullMethod: InventoryService_ListProduct_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).ListProduct(ctx, req.(*ListProductReq))
@ -408,6 +614,168 @@ func _InventoryService_ListProduct_Handler(srv interface{}, ctx context.Context,
return interceptor(ctx, in, info, handler)
}
func _InventoryService_GetProductTree_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetProductReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InventoryServiceServer).GetProductTree(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: InventoryService_GetProductTree_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).GetProductTree(ctx, req.(*GetProductReq))
}
return interceptor(ctx, in, info, handler)
}
func _InventoryService_ListProductSummary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListProductSummaryReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InventoryServiceServer).ListProductSummary(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: InventoryService_ListProductSummary_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).ListProductSummary(ctx, req.(*ListProductSummaryReq))
}
return interceptor(ctx, in, info, handler)
}
func _InventoryService_ListColorDetail_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListColorDetailReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InventoryServiceServer).ListColorDetail(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: InventoryService_ListColorDetail_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).ListColorDetail(ctx, req.(*ListColorDetailReq))
}
return interceptor(ctx, in, info, handler)
}
func _InventoryService_ListPanView_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListPanViewReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InventoryServiceServer).ListPanView(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: InventoryService_ListPanView_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).ListPanView(ctx, req.(*ListPanViewReq))
}
return interceptor(ctx, in, info, handler)
}
func _InventoryService_ListBoltView_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListBoltViewReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InventoryServiceServer).ListBoltView(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: InventoryService_ListBoltView_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).ListBoltView(ctx, req.(*ListBoltViewReq))
}
return interceptor(ctx, in, info, handler)
}
func _InventoryService_ListPans_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListPanReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InventoryServiceServer).ListPans(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: InventoryService_ListPans_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).ListPans(ctx, req.(*ListPanReq))
}
return interceptor(ctx, in, info, handler)
}
func _InventoryService_SavePans_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SavePansReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InventoryServiceServer).SavePans(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: InventoryService_SavePans_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).SavePans(ctx, req.(*SavePansReq))
}
return interceptor(ctx, in, info, handler)
}
func _InventoryService_ListBolts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListBoltReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InventoryServiceServer).ListBolts(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: InventoryService_ListBolts_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).ListBolts(ctx, req.(*ListBoltReq))
}
return interceptor(ctx, in, info, handler)
}
func _InventoryService_SaveBolts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SaveBoltsReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InventoryServiceServer).SaveBolts(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: InventoryService_SaveBolts_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).SaveBolts(ctx, req.(*SaveBoltsReq))
}
return interceptor(ctx, in, info, handler)
}
func _InventoryService_ImportProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ImportProductReq)
if err := dec(in); err != nil {
@ -418,7 +786,7 @@ func _InventoryService_ImportProducts_Handler(srv interface{}, ctx context.Conte
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/inventory.InventoryService/ImportProducts",
FullMethod: InventoryService_ImportProducts_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).ImportProducts(ctx, req.(*ImportProductReq))
@ -436,7 +804,7 @@ func _InventoryService_ListImportLog_Handler(srv interface{}, ctx context.Contex
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/inventory.InventoryService/ListImportLog",
FullMethod: InventoryService_ListImportLog_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).ListImportLog(ctx, req.(*ListImportLogReq))
@ -454,7 +822,7 @@ func _InventoryService_GetStockSummary_Handler(srv interface{}, ctx context.Cont
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/inventory.InventoryService/GetStockSummary",
FullMethod: InventoryService_GetStockSummary_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).GetStockSummary(ctx, req.(*StockSummaryReq))
@ -472,7 +840,7 @@ func _InventoryService_GetStockGroup_Handler(srv interface{}, ctx context.Contex
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/inventory.InventoryService/GetStockGroup",
FullMethod: InventoryService_GetStockGroup_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).GetStockGroup(ctx, req.(*StockGroupReq))
@ -490,7 +858,7 @@ func _InventoryService_CreateStockCheck_Handler(srv interface{}, ctx context.Con
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/inventory.InventoryService/CreateStockCheck",
FullMethod: InventoryService_CreateStockCheck_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).CreateStockCheck(ctx, req.(*CreateStockCheckReq))
@ -508,7 +876,7 @@ func _InventoryService_UpdateStockCheck_Handler(srv interface{}, ctx context.Con
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/inventory.InventoryService/UpdateStockCheck",
FullMethod: InventoryService_UpdateStockCheck_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).UpdateStockCheck(ctx, req.(*UpdateStockCheckReq))
@ -526,7 +894,7 @@ func _InventoryService_ConfirmStockCheck_Handler(srv interface{}, ctx context.Co
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/inventory.InventoryService/ConfirmStockCheck",
FullMethod: InventoryService_ConfirmStockCheck_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).ConfirmStockCheck(ctx, req.(*ConfirmStockCheckReq))
@ -544,7 +912,7 @@ func _InventoryService_GetStockCheck_Handler(srv interface{}, ctx context.Contex
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/inventory.InventoryService/GetStockCheck",
FullMethod: InventoryService_GetStockCheck_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).GetStockCheck(ctx, req.(*GetStockCheckReq))
@ -562,7 +930,7 @@ func _InventoryService_ListStockCheck_Handler(srv interface{}, ctx context.Conte
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/inventory.InventoryService/ListStockCheck",
FullMethod: InventoryService_ListStockCheck_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).ListStockCheck(ctx, req.(*ListStockCheckReq))
@ -580,7 +948,7 @@ func _InventoryService_CreateStockAdjust_Handler(srv interface{}, ctx context.Co
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/inventory.InventoryService/CreateStockAdjust",
FullMethod: InventoryService_CreateStockAdjust_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).CreateStockAdjust(ctx, req.(*CreateStockAdjustReq))
@ -598,7 +966,7 @@ func _InventoryService_ApproveStockAdjust_Handler(srv interface{}, ctx context.C
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/inventory.InventoryService/ApproveStockAdjust",
FullMethod: InventoryService_ApproveStockAdjust_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).ApproveStockAdjust(ctx, req.(*ApproveStockAdjustReq))
@ -616,7 +984,7 @@ func _InventoryService_GetStockAdjust_Handler(srv interface{}, ctx context.Conte
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/inventory.InventoryService/GetStockAdjust",
FullMethod: InventoryService_GetStockAdjust_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).GetStockAdjust(ctx, req.(*GetStockAdjustReq))
@ -634,7 +1002,7 @@ func _InventoryService_ListStockAdjust_Handler(srv interface{}, ctx context.Cont
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/inventory.InventoryService/ListStockAdjust",
FullMethod: InventoryService_ListStockAdjust_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).ListStockAdjust(ctx, req.(*ListStockAdjustReq))
@ -669,6 +1037,42 @@ var InventoryService_ServiceDesc = grpc.ServiceDesc{
MethodName: "ListProduct",
Handler: _InventoryService_ListProduct_Handler,
},
{
MethodName: "GetProductTree",
Handler: _InventoryService_GetProductTree_Handler,
},
{
MethodName: "ListProductSummary",
Handler: _InventoryService_ListProductSummary_Handler,
},
{
MethodName: "ListColorDetail",
Handler: _InventoryService_ListColorDetail_Handler,
},
{
MethodName: "ListPanView",
Handler: _InventoryService_ListPanView_Handler,
},
{
MethodName: "ListBoltView",
Handler: _InventoryService_ListBoltView_Handler,
},
{
MethodName: "ListPans",
Handler: _InventoryService_ListPans_Handler,
},
{
MethodName: "SavePans",
Handler: _InventoryService_SavePans_Handler,
},
{
MethodName: "ListBolts",
Handler: _InventoryService_ListBolts_Handler,
},
{
MethodName: "SaveBolts",
Handler: _InventoryService_SaveBolts_Handler,
},
{
MethodName: "ImportProducts",
Handler: _InventoryService_ImportProducts_Handler,
@ -723,5 +1127,5 @@ var InventoryService_ServiceDesc = grpc.ServiceDesc{
},
},
Streams: []grpc.StreamDesc{},
Metadata: "rpc/inventory/inventory.proto",
Metadata: "inventory.proto",
}

View File

@ -4,6 +4,7 @@ import (
"context"
"muyu-apiserver/model"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/pkg/uid"
"muyu-apiserver/rpc/system/internal/svc"
"muyu-apiserver/rpc/system/pb"
@ -28,9 +29,11 @@ func NewCreateRoleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Create
}
func (l *CreateRoleLogic) CreateRole(in *pb.CreateRoleReq) (*pb.IdResp, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
roleId := uid.Generate()
_, err := l.svcCtx.RoleModel.Insert(l.ctx, &model.SysRole{
TenantId: tenantId,
RoleId: roleId,
RoleName: in.RoleName,
RoleKey: in.RoleKey,

96
worker/graphsync/go.sum Normal file
View File

@ -0,0 +1,96 @@
github.com/IBM/sarama v1.43.3 h1:Yj6L2IaNvb2mRBop39N7mmJAHBVY3dTPncr3qGVkxPA=
github.com/IBM/sarama v1.43.3/go.mod h1:FVIRaLrhK3Cla/9FfRF5X9Zua2KpS3SYIXxhac1H+FQ=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=
github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/neo4j/neo4j-go-driver/v5 v5.28.0 h1:chDT68PHNa8JZRmjSkGzAbk1weLWo4rMtDvccvpobg0=
github.com/neo4j/neo4j-go-driver/v5 v5.28.0/go.mod h1:Vff8OwT7QpLm7L2yYr85XNWe9Rbqlbeb9asNXJTHO4k=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=