From 855642ea753dbdf162ac4a6cfa30ab24f1a6276c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=87=E7=9D=BF=20=E5=BC=A0?= Date: Thu, 17 Sep 2026 16:22:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B7=B2=E5=AE=8C=E6=88=90=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E4=BD=93=E7=B3=BB=E6=B8=85=E7=90=86=E5=92=8C=20Docker=20?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E6=94=B9=E9=80=A0=EF=BC=9A=20-=20?= =?UTF-8?q?=E6=96=B0=E7=89=88=20[init.sql](C:\\1111\\12.project\\VSCode\\w?= =?UTF-8?q?arehouse-mms\\scripts\\init.sql)=20=E7=9B=B4=E6=8E=A5=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E6=9C=80=E6=96=B0=E7=A9=BA=E5=BA=93=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E3=80=81=E9=BB=98=E8=AE=A4=E7=BB=84=E7=BB=87/=E6=9D=83?= =?UTF-8?q?=E9=99=90/=E4=BB=93=E5=BA=93/=E5=88=86=E7=B1=BB=E3=80=82=20-=20?= =?UTF-8?q?[docker-compose.yml](C:\\1111\\12.project\\VSCode\\warehouse-mm?= =?UTF-8?q?s\\docker-compose.yml)=20=E6=94=B9=E4=B8=BA=E7=94=B1=20MySQL=20?= =?UTF-8?q?=E5=9C=A8=E7=A9=BA=E6=95=B0=E6=8D=AE=E5=8D=B7=E9=A6=96=E6=AC=A1?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E6=97=B6=E8=87=AA=E5=8A=A8=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=20init.sql=E3=80=82=20-=20[entrypoint.sh](C:\\1111\\12.project?= =?UTF-8?q?\\VSCode\\warehouse-mms\\scripts\\entrypoint.sh)=20=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E9=87=8D=E5=A4=8D=E6=89=A7=E8=A1=8C=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E3=80=82=20-=20=E5=B7=B2=E5=88=A0=E9=99=A4=20scripts/migration?= =?UTF-8?q?s/=E3=80=81pkg/migration/=E3=80=81cmd/migrate/=20=E5=92=8C?= =?UTF-8?q?=E6=97=A7=E8=BF=81=E7=A7=BB=E8=AF=B4=E6=98=8E=E3=80=82=20-=20?= =?UTF-8?q?=E5=B7=B2=E6=89=A7=E8=A1=8C=20docker=20compose=20down=20-v=20--?= =?UTF-8?q?remove-orphans=EF=BC=9B=E8=BE=93=E5=87=BA=E7=A1=AE=E8=AE=A4?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E4=BA=86=20warehouse-mms=5Fuploads=5Fdata=20?= =?UTF-8?q?=E5=8D=B7=E3=80=82=20-=20go=20test=20./...=20=E5=85=A8=E9=83=A8?= =?UTF-8?q?=E9=80=9A=E8=BF=87=EF=BC=9Bgit=20diff=20--check=20=E4=B8=8E=20C?= =?UTF-8?q?ompose=20=E9=85=8D=E7=BD=AE=E6=A3=80=E6=9F=A5=E9=80=9A=E8=BF=87?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/migrate/main.go | 34 --- docker-compose.yml | 2 +- docs/phase1-migration.md | 31 -- pkg/migration/runner.go | 202 ------------- pkg/migration/runner_test.go | 136 --------- scripts/entrypoint.sh | 6 - scripts/init.sql | 279 +++--------------- scripts/migrations/001_phase1_auth_org.sql | 108 ------- scripts/migrations/002_phase1_master_data.sql | 100 ------- scripts/migrations/003_phase1_partners.sql | 52 ---- scripts/migrations/004_phase1_warehouses.sql | 43 --- .../migrations/005_phase1_inventory_split.sql | 36 --- scripts/migrations/006_phase1_backfill.sql | 31 -- .../006_phase1_drop_legacy_material_stock.sql | 3 - .../006_phase1_role_permission_defaults.sql | 31 -- .../006_phase1_user_multi_roles.sql | 1 - .../migrations/007_material_subcategories.sql | 96 ------ .../008_require_material_category.sql | 2 - 18 files changed, 46 insertions(+), 1147 deletions(-) delete mode 100644 cmd/migrate/main.go delete mode 100644 docs/phase1-migration.md delete mode 100644 pkg/migration/runner.go delete mode 100644 pkg/migration/runner_test.go delete mode 100644 scripts/migrations/001_phase1_auth_org.sql delete mode 100644 scripts/migrations/002_phase1_master_data.sql delete mode 100644 scripts/migrations/003_phase1_partners.sql delete mode 100644 scripts/migrations/004_phase1_warehouses.sql delete mode 100644 scripts/migrations/005_phase1_inventory_split.sql delete mode 100644 scripts/migrations/006_phase1_backfill.sql delete mode 100644 scripts/migrations/006_phase1_drop_legacy_material_stock.sql delete mode 100644 scripts/migrations/006_phase1_role_permission_defaults.sql delete mode 100644 scripts/migrations/006_phase1_user_multi_roles.sql delete mode 100644 scripts/migrations/007_material_subcategories.sql delete mode 100644 scripts/migrations/008_require_material_category.sql diff --git a/cmd/migrate/main.go b/cmd/migrate/main.go deleted file mode 100644 index 00a0fe76..00000000 --- a/cmd/migrate/main.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "context" - "database/sql" - "log" - "os" - - _ "github.com/go-sql-driver/mysql" - "github.com/wmms/wmms/pkg/migration" -) - -func main() { - dsn := os.Getenv("DB_DSN") - if dsn == "" { - log.Fatal("DB_DSN is required") - } - dir := os.Getenv("MIGRATIONS_DIR") - if dir == "" { - dir = "scripts/migrations" - } - db, err := sql.Open("mysql", dsn) - if err != nil { - log.Fatalf("open database: %v", err) - } - defer db.Close() - if err := db.PingContext(context.Background()); err != nil { - log.Fatalf("connect database: %v", err) - } - if err := migration.Run(context.Background(), db, dir); err != nil { - log.Fatalf("run migrations: %v", err) - } - log.Print("migrations applied successfully") -} diff --git a/docker-compose.yml b/docker-compose.yml index 4c9efbf4..eb704717 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,7 @@ services: - "3307:3306" volumes: - mysql_data:/var/lib/mysql + - ./scripts/init.sql:/docker-entrypoint-initdb.d/001-init.sql:ro command: > --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 @@ -35,7 +36,6 @@ services: GIN_MODE: release volumes: - uploads_data:/app/uploads - - ./scripts/migrations:/docker-entrypoint-initdb.d/migrations:ro depends_on: db: condition: service_healthy diff --git a/docs/phase1-migration.md b/docs/phase1-migration.md deleted file mode 100644 index 9c1f9f82..00000000 --- a/docs/phase1-migration.md +++ /dev/null @@ -1,31 +0,0 @@ -# 第一阶段数据库迁移 - -本阶段将公司组织、业务伙伴、仓库/库位、物料基础配置和库存余额拆分到独立表。旧的 -`materials.current_stock` 与 `materials.warning_stock` 暂时保留用于回滚观察,但应用代码不再读写。 - -## 执行顺序 - -1. 备份现有数据库,并暂停入库、出库和物料申请操作。 -2. 为当前 shell 设置 `DB_DSN`,不要把账号密码写入仓库。 -3. 在项目根目录运行: - - ```powershell - go run ./cmd/migrate - ``` - -4. 确认迁移 `001` 到 `006` 均为非 dirty 状态,并检查 `006` 最后的库存差异查询没有结果。 -5. 发布后端和前端,再恢复库存操作。 - -迁移器按文件名顺序执行并记录在 `schema_migrations`。MySQL DDL 可能隐式提交;如果某个版本为 -dirty,迁移器会停止,必须根据报错的语句编号人工核对并修复,不能直接假定事务已回滚。 - -## 核对重点 - -- 每个旧用户均已分配默认组织和默认仓库。 -- 旧物料数量与 `inventory_balances.on_hand` 汇总一致。 -- 旧预警值与 `stock_warning_rules.minimum_available` 一致。 -- `materials` API 响应不再包含库存数量和预警值。 -- 公司组织位于 `organizations`,外部公司位于 `business_partners`,合作关系位于 `organization_partners`。 - -不要在生产库直接试跑。应先在数据库副本验证一次;当前自动化测试只验证迁移执行器和应用查询, -不替代生产数据副本的迁移核对。 diff --git a/pkg/migration/runner.go b/pkg/migration/runner.go deleted file mode 100644 index f2828363..00000000 --- a/pkg/migration/runner.go +++ /dev/null @@ -1,202 +0,0 @@ -package migration - -import ( - "context" - "database/sql" - "fmt" - "os" - "path/filepath" - "sort" - "strings" -) - -const createMigrationsTable = `CREATE TABLE IF NOT EXISTS schema_migrations ( - version VARCHAR(100) PRIMARY KEY, - dirty BOOLEAN NOT NULL DEFAULT TRUE, - applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP - )` - -// Run applies .sql files in lexical filename order. -// MySQL 5.7 may implicitly commit DDL, so every migration is marked dirty -// before its first statement and only marked clean after all statements pass. -func Run(ctx context.Context, db *sql.DB, dir string) error { - if db == nil { - return fmt.Errorf("migration database is nil") - } - if _, err := db.ExecContext(ctx, createMigrationsTable); err != nil { - return fmt.Errorf("create schema_migrations: %w", err) - } - - var dirtyVersion string - err := db.QueryRowContext(ctx, `SELECT version FROM schema_migrations WHERE dirty = TRUE ORDER BY applied_at DESC LIMIT 1`).Scan(&dirtyVersion) - if err == nil { - return fmt.Errorf("migration %s is dirty; stop and perform manual repair before retrying", dirtyVersion) - } - if err != sql.ErrNoRows { - return fmt.Errorf("check dirty migration: %w", err) - } - - applied, err := loadApplied(ctx, db) - if err != nil { - return err - } - files, err := migrationFiles(dir) - if err != nil { - return err - } - - for _, path := range files { - version := filepath.Base(path) - if applied[version] { - continue - } - contents, err := os.ReadFile(path) - if err != nil { - return fmt.Errorf("read migration %s: %w", version, err) - } - statements, err := splitStatements(string(contents)) - if err != nil { - return fmt.Errorf("parse migration %s: %w", version, err) - } - if _, err := db.ExecContext(ctx, `INSERT INTO schema_migrations (version, dirty) VALUES (?, TRUE)`, version); err != nil { - return fmt.Errorf("mark migration %s dirty: %w", version, err) - } - for index, statement := range statements { - if _, err := db.ExecContext(ctx, statement); err != nil { - return fmt.Errorf("migration %s statement %d failed: %w; version remains dirty because MySQL DDL cannot be transactionally rolled back; perform manual repair", version, index+1, err) - } - } - if _, err := db.ExecContext(ctx, `UPDATE schema_migrations SET dirty = FALSE, applied_at = CURRENT_TIMESTAMP WHERE version = ?`, version); err != nil { - return fmt.Errorf("mark migration %s clean: %w; version remains dirty and requires manual repair", version, err) - } - } - return nil -} - -func loadApplied(ctx context.Context, db *sql.DB) (map[string]bool, error) { - rows, err := db.QueryContext(ctx, `SELECT version FROM schema_migrations WHERE dirty = FALSE`) - if err != nil { - return nil, fmt.Errorf("list applied migrations: %w", err) - } - defer rows.Close() - applied := make(map[string]bool) - for rows.Next() { - var version string - if err := rows.Scan(&version); err != nil { - return nil, fmt.Errorf("scan applied migration: %w", err) - } - applied[version] = true - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("iterate applied migrations: %w", err) - } - return applied, nil -} - -func migrationFiles(dir string) ([]string, error) { - entries, err := os.ReadDir(dir) - if err != nil { - return nil, fmt.Errorf("read migrations directory %s: %w", dir, err) - } - files := make([]string, 0, len(entries)) - for _, entry := range entries { - if entry.IsDir() || strings.ToLower(filepath.Ext(entry.Name())) != ".sql" { - continue - } - files = append(files, filepath.Join(dir, entry.Name())) - } - sort.Strings(files) - return files, nil -} - -func splitStatements(input string) ([]string, error) { - var statements []string - var current strings.Builder - var quote rune - escaped := false - lineComment := false - blockComment := false - runes := []rune(input) - - flush := func() { - statement := strings.TrimSpace(current.String()) - if statement != "" { - statements = append(statements, statement) - } - current.Reset() - } - - for i := 0; i < len(runes); i++ { - ch := runes[i] - next := rune(0) - if i+1 < len(runes) { - next = runes[i+1] - } - if lineComment { - if ch == '\n' { - lineComment = false - if current.Len() > 0 { - current.WriteRune(' ') - } - } - continue - } - if blockComment { - if ch == '*' && next == '/' { - blockComment = false - i++ - if current.Len() > 0 { - current.WriteRune(' ') - } - } - continue - } - if quote != 0 { - current.WriteRune(ch) - if escaped { - escaped = false - continue - } - if ch == '\\' { - escaped = true - continue - } - if ch == quote { - quote = 0 - } - continue - } - if ch == '-' && next == '-' { - lineComment = true - i++ - continue - } - if ch == '#' { - lineComment = true - continue - } - if ch == '/' && next == '*' { - blockComment = true - i++ - continue - } - if ch == '\'' || ch == '"' || ch == '`' { - quote = ch - current.WriteRune(ch) - continue - } - if ch == ';' { - flush() - continue - } - current.WriteRune(ch) - } - if quote != 0 { - return nil, fmt.Errorf("unterminated quoted string") - } - if blockComment { - return nil, fmt.Errorf("unterminated block comment") - } - flush() - return statements, nil -} diff --git a/pkg/migration/runner_test.go b/pkg/migration/runner_test.go deleted file mode 100644 index 0f19d568..00000000 --- a/pkg/migration/runner_test.go +++ /dev/null @@ -1,136 +0,0 @@ -package migration - -import ( - "context" - "errors" - "os" - "path/filepath" - "regexp" - "strings" - "testing" - - "github.com/DATA-DOG/go-sqlmock" -) - -const createMigrationsTableSQL = `CREATE TABLE IF NOT EXISTS schema_migrations ( - version VARCHAR(100) PRIMARY KEY, - dirty BOOLEAN NOT NULL DEFAULT TRUE, - applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP - )` - -func writeMigration(t *testing.T, name, sqlText string) string { - t.Helper() - dir := t.TempDir() - if err := os.WriteFile(filepath.Join(dir, name), []byte(sqlText), 0o600); err != nil { - t.Fatalf("write migration: %v", err) - } - return dir -} - -func expectCleanState(mock sqlmock.Sqlmock, applied ...string) { - mock.ExpectExec(regexp.QuoteMeta(createMigrationsTableSQL)).WillReturnResult(sqlmock.NewResult(0, 0)) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT version FROM schema_migrations WHERE dirty = TRUE ORDER BY applied_at DESC LIMIT 1`)). - WillReturnRows(sqlmock.NewRows([]string{"version"})) - rows := sqlmock.NewRows([]string{"version"}) - for _, version := range applied { - rows.AddRow(version) - } - mock.ExpectQuery(regexp.QuoteMeta(`SELECT version FROM schema_migrations WHERE dirty = FALSE`)).WillReturnRows(rows) -} - -func TestRunAppliesEachMigrationOnce(t *testing.T) { - dir := writeMigration(t, "001_create_widgets.sql", `CREATE TABLE widgets (id BIGINT PRIMARY KEY);`) - db, mock, err := sqlmock.New() - if err != nil { - t.Fatalf("open sqlmock: %v", err) - } - t.Cleanup(func() { db.Close() }) - - expectCleanState(mock) - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO schema_migrations (version, dirty) VALUES (?, TRUE)`)). - WithArgs("001_create_widgets.sql").WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(regexp.QuoteMeta(`CREATE TABLE widgets (id BIGINT PRIMARY KEY)`)). - WillReturnResult(sqlmock.NewResult(0, 0)) - mock.ExpectExec(regexp.QuoteMeta(`UPDATE schema_migrations SET dirty = FALSE, applied_at = CURRENT_TIMESTAMP WHERE version = ?`)). - WithArgs("001_create_widgets.sql").WillReturnResult(sqlmock.NewResult(0, 1)) - - if err := Run(context.Background(), db, dir); err != nil { - t.Fatalf("first run: %v", err) - } - - expectCleanState(mock, "001_create_widgets.sql") - if err := Run(context.Background(), db, dir); err != nil { - t.Fatalf("second run: %v", err) - } - if err := mock.ExpectationsWereMet(); err != nil { - t.Fatal(err) - } -} - -func TestRunLeavesFailedMigrationDirty(t *testing.T) { - dir := writeMigration(t, "002_broken.sql", `CREATE TABLE broken (id BIGINT); ALTER TABLE missing ADD value INT;`) - db, mock, err := sqlmock.New() - if err != nil { - t.Fatalf("open sqlmock: %v", err) - } - t.Cleanup(func() { db.Close() }) - - expectCleanState(mock) - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO schema_migrations (version, dirty) VALUES (?, TRUE)`)). - WithArgs("002_broken.sql").WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(regexp.QuoteMeta(`CREATE TABLE broken (id BIGINT)`)).WillReturnResult(sqlmock.NewResult(0, 0)) - mock.ExpectExec(regexp.QuoteMeta(`ALTER TABLE missing ADD value INT`)).WillReturnError(errors.New("missing table")) - - err = Run(context.Background(), db, dir) - if err == nil { - t.Fatal("expected migration failure") - } - for _, fragment := range []string{"002_broken.sql", "statement 2", "dirty", "manual repair"} { - if !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(fragment)) { - t.Fatalf("error %q does not contain %q", err, fragment) - } - } - if err := mock.ExpectationsWereMet(); err != nil { - t.Fatal(err) - } -} - -func TestRunStopsWhenDirtyMigrationExists(t *testing.T) { - dir := writeMigration(t, "003_should_not_run.sql", `CREATE TABLE should_not_run (id BIGINT);`) - db, mock, err := sqlmock.New() - if err != nil { - t.Fatalf("open sqlmock: %v", err) - } - t.Cleanup(func() { db.Close() }) - - mock.ExpectExec(regexp.QuoteMeta(createMigrationsTableSQL)).WillReturnResult(sqlmock.NewResult(0, 0)) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT version FROM schema_migrations WHERE dirty = TRUE ORDER BY applied_at DESC LIMIT 1`)). - WillReturnRows(sqlmock.NewRows([]string{"version"}).AddRow("002_broken.sql")) - - err = Run(context.Background(), db, dir) - if err == nil || !strings.Contains(err.Error(), "002_broken.sql") { - t.Fatalf("expected dirty migration error, got %v", err) - } - if err := mock.ExpectationsWereMet(); err != nil { - t.Fatal(err) - } -} - -func TestSplitStatementsPreservesSemicolonsInsideStrings(t *testing.T) { - statements, err := splitStatements("INSERT INTO notes(value) VALUES ('a;b'); -- comment\nUPDATE notes SET value = \"c;d\";") - if err != nil { - t.Fatalf("split statements: %v", err) - } - want := []string{ - "INSERT INTO notes(value) VALUES ('a;b')", - "UPDATE notes SET value = \"c;d\"", - } - if len(statements) != len(want) { - t.Fatalf("got %d statements, want %d: %#v", len(statements), len(want), statements) - } - for i := range want { - if statements[i] != want[i] { - t.Fatalf("statement %d = %q, want %q", i+1, statements[i], want[i]) - } - } -} diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index 04939784..3fcc1597 100644 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -7,11 +7,5 @@ until mysqladmin ping -h db -uroot -pwmms1234 --silent 2>/dev/null; do done echo "MySQL is ready." -echo "Running database migrations..." -for f in $(ls /docker-entrypoint-initdb.d/migrations/*.sql 2>/dev/null | sort); do - echo "Executing: $f" - mysql -h db -uroot -pwmms1234 wmms < "$f" 2>/dev/null && echo "OK: $(basename $f)" || echo "FAILED: $(basename $f)" -done - echo "Starting application..." exec "$@" diff --git a/scripts/init.sql b/scripts/init.sql index fc397cc2..bd12d656 100644 --- a/scripts/init.sql +++ b/scripts/init.sql @@ -1,237 +1,48 @@ --- ============================================ --- Warehouse Material Management System (WMMS) --- Database Initialization Script --- MySQL 5.7+ --- ============================================ - --- Create database -CREATE DATABASE IF NOT EXISTS wmms; +-- WMMS fresh schema for MySQL 8.0. Executed once for an empty Docker volume. +CREATE DATABASE IF NOT EXISTS wmms CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE wmms; --- ============================================ --- Table: materials --- ============================================ -CREATE TABLE IF NOT EXISTS materials ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - material_code VARCHAR(50) NOT NULL UNIQUE, - name VARCHAR(200) NOT NULL, - material_image VARCHAR(500), - spec VARCHAR(200), - category VARCHAR(100), - unit VARCHAR(20), - warning_stock INT DEFAULT 0, - current_stock INT DEFAULT 0, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - INDEX idx_material_code (material_code), - INDEX idx_category (category) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE users (id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL, description VARCHAR(500) NOT NULL DEFAULT '', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE organizations (id BIGINT AUTO_INCREMENT PRIMARY KEY, code VARCHAR(50) NOT NULL UNIQUE, name VARCHAR(200) NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'active', default_timezone VARCHAR(64) NOT NULL DEFAULT 'Asia/Shanghai', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE roles (id BIGINT AUTO_INCREMENT PRIMARY KEY, code VARCHAR(80) NOT NULL UNIQUE, name VARCHAR(100) NOT NULL, enabled BOOLEAN NOT NULL DEFAULT TRUE, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE permissions (id BIGINT AUTO_INCREMENT PRIMARY KEY, code VARCHAR(120) NOT NULL UNIQUE, name VARCHAR(150) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE role_permissions (role_id BIGINT NOT NULL, permission_id BIGINT NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(role_id,permission_id), FOREIGN KEY(role_id) REFERENCES roles(id) ON DELETE CASCADE, FOREIGN KEY(permission_id) REFERENCES permissions(id) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE user_roles (user_id BIGINT NOT NULL, role_id BIGINT NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(user_id,role_id), FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY(role_id) REFERENCES roles(id) ON DELETE RESTRICT) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE user_organizations (user_id BIGINT NOT NULL, organization_id BIGINT NOT NULL, is_default BOOLEAN NOT NULL DEFAULT FALSE, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(user_id,organization_id), FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY(organization_id) REFERENCES organizations(id) ON DELETE RESTRICT) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE revoked_tokens (jti VARCHAR(64) PRIMARY KEY, user_id BIGINT NOT NULL, expires_at DATETIME NOT NULL, revoked_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_revoked_tokens_expires_at(expires_at), FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE material_types (id BIGINT AUTO_INCREMENT PRIMARY KEY, organization_id BIGINT NOT NULL, code VARCHAR(50) NOT NULL, name VARCHAR(100) NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'active', UNIQUE KEY uk_material_types_org_code(organization_id,code), FOREIGN KEY(organization_id) REFERENCES organizations(id) ON DELETE RESTRICT) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE material_categories (id BIGINT AUTO_INCREMENT PRIMARY KEY, organization_id BIGINT NOT NULL, code VARCHAR(50) NOT NULL, name VARCHAR(100) NOT NULL, parent_id BIGINT NULL, status VARCHAR(20) NOT NULL DEFAULT 'active', UNIQUE KEY uk_material_categories_org_code(organization_id,code), FOREIGN KEY(organization_id) REFERENCES organizations(id) ON DELETE RESTRICT, FOREIGN KEY(parent_id) REFERENCES material_categories(id) ON DELETE RESTRICT) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE units (id BIGINT AUTO_INCREMENT PRIMARY KEY, organization_id BIGINT NOT NULL, code VARCHAR(50) NOT NULL, name VARCHAR(100) NOT NULL, precision_scale TINYINT NOT NULL DEFAULT 0, status VARCHAR(20) NOT NULL DEFAULT 'active', UNIQUE KEY uk_units_org_code(organization_id,code), FOREIGN KEY(organization_id) REFERENCES organizations(id) ON DELETE RESTRICT) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE materials (id BIGINT AUTO_INCREMENT PRIMARY KEY, organization_id BIGINT NOT NULL, material_code VARCHAR(50) NOT NULL, name VARCHAR(200) NOT NULL, material_image VARCHAR(500), spec VARCHAR(200), manufacturer_model VARCHAR(200), material_type_id BIGINT, category_id BIGINT NOT NULL, base_unit_id BIGINT, unit VARCHAR(20), status VARCHAR(20) NOT NULL DEFAULT 'active', version INT NOT NULL DEFAULT 1, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_materials_org_code(organization_id,material_code), KEY idx_materials_category(category_id), FOREIGN KEY(organization_id) REFERENCES organizations(id) ON DELETE RESTRICT, FOREIGN KEY(material_type_id) REFERENCES material_types(id) ON DELETE RESTRICT, FOREIGN KEY(category_id) REFERENCES material_categories(id) ON DELETE RESTRICT, FOREIGN KEY(base_unit_id) REFERENCES units(id) ON DELETE RESTRICT) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE code_rules (id BIGINT AUTO_INCREMENT PRIMARY KEY, organization_id BIGINT NOT NULL, category_id BIGINT NOT NULL, prefix VARCHAR(30) NOT NULL, sequence_length TINYINT NOT NULL, next_sequence BIGINT NOT NULL DEFAULT 1, status VARCHAR(20) NOT NULL DEFAULT 'active', UNIQUE KEY uk_code_rules_org_category(organization_id,category_id), FOREIGN KEY(organization_id) REFERENCES organizations(id), FOREIGN KEY(category_id) REFERENCES material_categories(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE material_unit_conversions (id BIGINT AUTO_INCREMENT PRIMARY KEY, organization_id BIGINT NOT NULL, material_id BIGINT NOT NULL, from_unit_id BIGINT NOT NULL, to_unit_id BIGINT NOT NULL, conversion_rate DECIMAL(20,6) NOT NULL, effective_from DATE NOT NULL, effective_to DATE NULL, KEY idx_material_unit_conversions_material(organization_id,material_id), FOREIGN KEY(organization_id) REFERENCES organizations(id), FOREIGN KEY(material_id) REFERENCES materials(id), FOREIGN KEY(from_unit_id) REFERENCES units(id), FOREIGN KEY(to_unit_id) REFERENCES units(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE tags (id BIGINT AUTO_INCREMENT PRIMARY KEY, material_id BIGINT NOT NULL, tag_code VARCHAR(100) NOT NULL UNIQUE, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_tags_material(material_id), FOREIGN KEY(material_id) REFERENCES materials(id) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE business_partners (id BIGINT AUTO_INCREMENT PRIMARY KEY, legal_name VARCHAR(200) NOT NULL, unified_credit_code VARCHAR(50) UNIQUE, contact_name VARCHAR(100), contact_phone VARCHAR(50), address VARCHAR(500), created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE organization_partners (id BIGINT AUTO_INCREMENT PRIMARY KEY, organization_id BIGINT NOT NULL, business_partner_id BIGINT NOT NULL, partner_code VARCHAR(50) NOT NULL, is_supplier BOOLEAN NOT NULL DEFAULT FALSE, is_customer BOOLEAN NOT NULL DEFAULT FALSE, supplier_status VARCHAR(20), customer_status VARCHAR(20), settlement_currency CHAR(3), payment_terms VARCHAR(200), created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_organization_partners_org_partner(organization_id,business_partner_id), UNIQUE KEY uk_organization_partners_org_code(organization_id,partner_code), FOREIGN KEY(organization_id) REFERENCES organizations(id), FOREIGN KEY(business_partner_id) REFERENCES business_partners(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE material_suppliers (id BIGINT AUTO_INCREMENT PRIMARY KEY, organization_id BIGINT NOT NULL, material_id BIGINT NOT NULL, organization_partner_id BIGINT NOT NULL, price DECIMAL(20,6) NOT NULL, currency CHAR(3) NOT NULL, minimum_order_quantity DECIMAL(20,6) NOT NULL DEFAULT 0, lead_time_days INT NOT NULL DEFAULT 0, is_preferred BOOLEAN NOT NULL DEFAULT FALSE, effective_from DATE NOT NULL, effective_to DATE, status VARCHAR(20) NOT NULL DEFAULT 'active', KEY idx_material_suppliers_material(organization_id,material_id), FOREIGN KEY(organization_id) REFERENCES organizations(id), FOREIGN KEY(material_id) REFERENCES materials(id), FOREIGN KEY(organization_partner_id) REFERENCES organization_partners(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE warehouses (id BIGINT AUTO_INCREMENT PRIMARY KEY, organization_id BIGINT NOT NULL, code VARCHAR(50) NOT NULL, name VARCHAR(150) NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'active', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_warehouses_org_code(organization_id,code), FOREIGN KEY(organization_id) REFERENCES organizations(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE warehouse_locations (id BIGINT AUTO_INCREMENT PRIMARY KEY, warehouse_id BIGINT NOT NULL, code VARCHAR(50) NOT NULL, name VARCHAR(150) NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'active', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_warehouse_locations_warehouse_code(warehouse_id,code), FOREIGN KEY(warehouse_id) REFERENCES warehouses(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE user_warehouses (user_id BIGINT NOT NULL, warehouse_id BIGINT NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(user_id,warehouse_id), FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY(warehouse_id) REFERENCES warehouses(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE inventory_balances (id BIGINT AUTO_INCREMENT PRIMARY KEY, organization_id BIGINT NOT NULL, warehouse_id BIGINT NOT NULL, location_id BIGINT NOT NULL, material_id BIGINT NOT NULL, on_hand DECIMAL(20,6) NOT NULL DEFAULT 0, reserved DECIMAL(20,6) NOT NULL DEFAULT 0, locked DECIMAL(20,6) NOT NULL DEFAULT 0, in_transit DECIMAL(20,6) NOT NULL DEFAULT 0, version INT NOT NULL DEFAULT 1, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_inventory_balance_dimension(organization_id,warehouse_id,location_id,material_id), KEY idx_inventory_balances_material(organization_id,material_id), FOREIGN KEY(organization_id) REFERENCES organizations(id), FOREIGN KEY(warehouse_id) REFERENCES warehouses(id), FOREIGN KEY(location_id) REFERENCES warehouse_locations(id), FOREIGN KEY(material_id) REFERENCES materials(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE stock_warning_rules (id BIGINT AUTO_INCREMENT PRIMARY KEY, organization_id BIGINT NOT NULL, warehouse_id BIGINT NOT NULL, material_id BIGINT NOT NULL, minimum_available DECIMAL(20,6) NOT NULL DEFAULT 0, enabled BOOLEAN NOT NULL DEFAULT TRUE, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_stock_warning_rules_dimension(organization_id,warehouse_id,material_id), FOREIGN KEY(organization_id) REFERENCES organizations(id), FOREIGN KEY(warehouse_id) REFERENCES warehouses(id), FOREIGN KEY(material_id) REFERENCES materials(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE inbound_orders (id BIGINT AUTO_INCREMENT PRIMARY KEY, organization_id BIGINT NOT NULL DEFAULT 1, warehouse_id BIGINT NOT NULL DEFAULT 1, location_id BIGINT NOT NULL DEFAULT 1, inbound_date DATETIME NOT NULL, handler VARCHAR(100) NOT NULL, supplier VARCHAR(200), created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_inbound_orders_date(inbound_date), FOREIGN KEY(organization_id) REFERENCES organizations(id), FOREIGN KEY(warehouse_id) REFERENCES warehouses(id), FOREIGN KEY(location_id) REFERENCES warehouse_locations(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE inbound_items (id BIGINT AUTO_INCREMENT PRIMARY KEY, inbound_order_id BIGINT NOT NULL, material_id BIGINT NOT NULL, quantity INT NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_inbound_items_order(inbound_order_id), FOREIGN KEY(inbound_order_id) REFERENCES inbound_orders(id) ON DELETE CASCADE, FOREIGN KEY(material_id) REFERENCES materials(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE outbound_orders (id BIGINT AUTO_INCREMENT PRIMARY KEY, organization_id BIGINT NOT NULL DEFAULT 1, warehouse_id BIGINT NOT NULL DEFAULT 1, location_id BIGINT NOT NULL DEFAULT 1, outbound_date DATETIME NOT NULL, handler VARCHAR(100) NOT NULL, receiver VARCHAR(100) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_outbound_orders_date(outbound_date), FOREIGN KEY(organization_id) REFERENCES organizations(id), FOREIGN KEY(warehouse_id) REFERENCES warehouses(id), FOREIGN KEY(location_id) REFERENCES warehouse_locations(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE outbound_items (id BIGINT AUTO_INCREMENT PRIMARY KEY, outbound_order_id BIGINT NOT NULL, material_id BIGINT NOT NULL, quantity INT NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_outbound_items_order(outbound_order_id), FOREIGN KEY(outbound_order_id) REFERENCES outbound_orders(id) ON DELETE CASCADE, FOREIGN KEY(material_id) REFERENCES materials(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE material_logs (id BIGINT AUTO_INCREMENT PRIMARY KEY, operation_type VARCHAR(50) NOT NULL, material_id BIGINT NOT NULL, old_value TEXT, new_value TEXT, operator VARCHAR(100), description TEXT, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_material_logs_material(material_id), FOREIGN KEY(material_id) REFERENCES materials(id) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE stock_logs (id BIGINT AUTO_INCREMENT PRIMARY KEY, material_id BIGINT NOT NULL, type ENUM('in','out') NOT NULL, quantity INT NOT NULL, related_order_type VARCHAR(50) NOT NULL, related_order_id BIGINT NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_stock_logs_material(material_id), KEY idx_stock_logs_related_order(related_order_type,related_order_id), FOREIGN KEY(material_id) REFERENCES materials(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE material_applications (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL, purpose VARCHAR(500) NOT NULL DEFAULT '', status ENUM('pending','approved','rejected','cancelled') NOT NULL DEFAULT 'pending', rejection_reason VARCHAR(500) NOT NULL DEFAULT '', approved_by BIGINT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY(user_id) REFERENCES users(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE application_items (id BIGINT AUTO_INCREMENT PRIMARY KEY, application_id BIGINT NOT NULL, material_id BIGINT NOT NULL, quantity INT NOT NULL, FOREIGN KEY(application_id) REFERENCES material_applications(id) ON DELETE CASCADE, FOREIGN KEY(material_id) REFERENCES materials(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; --- ============================================ --- Table: tags --- ============================================ -CREATE TABLE IF NOT EXISTS tags ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - material_id BIGINT NOT NULL, - tag_code VARCHAR(100) NOT NULL UNIQUE, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - INDEX idx_material_id (material_id), - INDEX idx_tag_code (tag_code), - CONSTRAINT fk_tags_material FOREIGN KEY (material_id) REFERENCES materials(id) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - --- ============================================ --- Table: apply_orders --- ============================================ -CREATE TABLE IF NOT EXISTS apply_orders ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - applicant VARCHAR(100) NOT NULL, - apply_date DATE NOT NULL, - status ENUM('draft', 'pending', 'approved', 'rejected') DEFAULT 'draft', - approval_comment TEXT, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - INDEX idx_applicant (applicant), - INDEX idx_status (status), - INDEX idx_apply_date (apply_date) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - --- ============================================ --- Table: apply_items --- ============================================ -CREATE TABLE IF NOT EXISTS apply_items ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - apply_order_id BIGINT NOT NULL, - material_id BIGINT NOT NULL, - quantity INT NOT NULL, - purpose TEXT, - INDEX idx_apply_order_id (apply_order_id), - INDEX idx_material_id (material_id), - CONSTRAINT fk_apply_items_order FOREIGN KEY (apply_order_id) REFERENCES apply_orders(id) ON DELETE CASCADE, - CONSTRAINT fk_apply_items_material FOREIGN KEY (material_id) REFERENCES materials(id) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - --- ============================================ --- Table: inbound_orders --- ============================================ -CREATE TABLE IF NOT EXISTS inbound_orders ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - inbound_date DATETIME NOT NULL, - handler VARCHAR(100) NOT NULL, - supplier VARCHAR(200), - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - INDEX idx_inbound_date (inbound_date), - INDEX idx_handler (handler) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - --- ============================================ --- Table: inbound_items --- ============================================ -CREATE TABLE IF NOT EXISTS inbound_items ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - inbound_order_id BIGINT NOT NULL, - material_id BIGINT NOT NULL, - quantity INT NOT NULL, - INDEX idx_inbound_order_id (inbound_order_id), - INDEX idx_material_id (material_id), - CONSTRAINT fk_inbound_items_order FOREIGN KEY (inbound_order_id) REFERENCES inbound_orders(id) ON DELETE CASCADE, - CONSTRAINT fk_inbound_items_material FOREIGN KEY (material_id) REFERENCES materials(id) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - --- ============================================ --- Table: outbound_orders --- ============================================ -CREATE TABLE IF NOT EXISTS outbound_orders ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - outbound_date DATETIME NOT NULL, - handler VARCHAR(100) NOT NULL, - receiver VARCHAR(100) NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - INDEX idx_outbound_date (outbound_date), - INDEX idx_handler (handler), - INDEX idx_receiver (receiver) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - --- ============================================ --- Table: outbound_items --- ============================================ -CREATE TABLE IF NOT EXISTS outbound_items ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - outbound_order_id BIGINT NOT NULL, - material_id BIGINT NOT NULL, - quantity INT NOT NULL, - INDEX idx_outbound_order_id (outbound_order_id), - INDEX idx_material_id (material_id), - CONSTRAINT fk_outbound_items_order FOREIGN KEY (outbound_order_id) REFERENCES outbound_orders(id) ON DELETE CASCADE, - CONSTRAINT fk_outbound_items_material FOREIGN KEY (material_id) REFERENCES materials(id) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - --- ============================================ --- Table: material_logs --- ============================================ -CREATE TABLE IF NOT EXISTS material_logs ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - operation_type VARCHAR(50) NOT NULL, - material_id BIGINT NOT NULL, - old_value TEXT, - new_value TEXT, - operator VARCHAR(100), - description TEXT, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - INDEX idx_operation_type (operation_type), - INDEX idx_material_id (material_id), - INDEX idx_created_at (created_at), - CONSTRAINT fk_material_logs_material FOREIGN KEY (material_id) REFERENCES materials(id) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - --- ============================================ --- Table: stock_logs --- ============================================ -CREATE TABLE IF NOT EXISTS stock_logs ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - material_id BIGINT NOT NULL, - type ENUM('in', 'out') NOT NULL, - quantity INT NOT NULL, - related_order_type VARCHAR(50) NOT NULL, - related_order_id BIGINT NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - INDEX idx_material_id (material_id), - INDEX idx_type (type), - INDEX idx_related_order (related_order_type, related_order_id), - CONSTRAINT fk_stock_logs_material FOREIGN KEY (material_id) REFERENCES materials(id) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - --- ============================================ --- Table: users --- ============================================ -CREATE TABLE IF NOT EXISTS users ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - username VARCHAR(50) NOT NULL UNIQUE, - password VARCHAR(255) NOT NULL, - role ENUM('admin','user') NOT NULL DEFAULT 'user', - description VARCHAR(500) DEFAULT '', - created_at DATETIME DEFAULT CURRENT_TIMESTAMP -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - --- ============================================ --- Category Code Reference --- A01-A07: 标准件, 成品件, 简易加工件, 精密加工件, 钣金件, 塑胶件, 其他 --- B01-B07: 成品件, 简易加工件, PCB硬板, FPC软板, 线束, 组装组件, 其他 --- C01-C03: 连接耗材, 密封耗材, 其他 --- D01-D04: 自研程序固件, 外购软件授权, 授权介质, 其他 --- E01-E02: 成品, 半成品 --- F01-F05: 资料文档, 标识标牌, 包装物料, 随机工具配件, 其他 --- ============================================ - --- ============================================ --- Table: materials_category (reference table) --- ============================================ -CREATE TABLE IF NOT EXISTS materials_category ( - category_code VARCHAR(10) PRIMARY KEY, - category_name VARCHAR(100) NOT NULL, - parent_category VARCHAR(10), - description VARCHAR(200) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - --- Insert category reference data -INSERT INTO materials_category (category_code, category_name, description) VALUES -('A01', '标准件', 'Standard parts'), -('A02', '成品件', 'Finished parts'), -('A03', '简易加工件', 'Simple machined parts'), -('A04', '精密加工件', 'Precision machined parts'), -('A05', '钣金件', 'Sheet metal parts'), -('A06', '塑胶件', 'Plastic parts'), -('A07', '其他', 'Other (A series)'), -('B01', '成品件', 'Finished parts'), -('B02', '简易加工件', 'Simple machined parts'), -('B03', 'PCB硬板', 'PCB hard board'), -('B04', 'FPC软板', 'FPC flexible board'), -('B05', '线束', 'Wire harness'), -('B06', '组装组件', 'Assembly component'), -('B07', '其他', 'Other (B series)'), -('C01', '连接耗材', 'Connection consumable'), -('C02', '密封耗材', 'Sealing consumable'), -('C03', '其他', 'Other (C series)'), -('D01', '自研程序固件', 'In-house firmware'), -('D02', '外购软件授权', 'Purchased software license'), -('D03', '授权介质', 'License media'), -('D04', '其他', 'Other (D series)'), -('E01', '成品', 'Finished product'), -('E02', '半成品', 'Semi-finished product'), -('F01', '资料文档', 'Documentation'), -('F02', '标识标牌', 'Label/signage'), -('F03', '包装物料', 'Packaging material'), -('F04', '随机工具配件', 'Bundled tools/accessories'), -('F05', '其他', 'Other (F series)'); - --- ============================================ --- Test Data: 5 Materials (using category codes) --- ============================================ -INSERT INTO materials (material_code, name, spec, category, unit, warning_stock, current_stock) VALUES -('MAT-001', 'Steel Plate', '1000x2000x5mm', 'A01', 'Sheet', 50, 200), -('MAT-002', 'Aluminum Rod', 'D20x1000mm', 'A01', 'Piece', 100, 500), -('MAT-003', 'Hex Bolt M10', 'M10x30mm SS304', 'A01', 'Box', 20, 80), -('MAT-004', 'Hydraulic Oil', 'ISO VG 46', 'C01', 'Barrel', 10, 25), -('MAT-005', 'Bearing 6205', '6205-2RS Deep Groove', 'A02', 'Piece', 30, 120); +INSERT INTO organizations (id,code,name,status) VALUES (1,'DEFAULT','默认公司组织','active'); +INSERT INTO roles (code,name) VALUES ('super_admin','超级管理员'),('warehouse_admin','仓库管理员'),('purchaser','采购人员'),('technician','技术人员'),('production','生产人员'),('sales','销售人员'); +INSERT INTO permissions (code,name) VALUES ('master.material.read','查看物料'),('master.material.write','维护物料'),('master.config.write','维护物料基础配置'),('organization.read','查看公司组织'),('organization.write','维护公司组织'),('organization.user.write','维护用户'),('organization.role.write','维护角色与权限'),('partner.supplier.read','查看供应商'),('partner.supplier.write','维护供应商'),('partner.customer.read','查看客户'),('partner.customer.write','维护客户'),('inventory.stock.read','查看库存'),('inventory.warning.write','维护库存预警'),('inventory.inbound.write','维护入库'),('inventory.outbound.write','维护出库'); +INSERT INTO role_permissions (role_id,permission_id) SELECT r.id,p.id FROM roles r CROSS JOIN permissions p WHERE r.code='super_admin'; +INSERT INTO material_types (organization_id,code,name) VALUES (1,'raw_material','原材料'),(1,'semi_finished','半成品'),(1,'finished_good','成品'),(1,'consumable','耗材'),(1,'service','服务'); +INSERT INTO material_categories (organization_id,code,name) VALUES (1,'A01','标准件'),(1,'A02','成品件'),(1,'A03','简易加工件'),(1,'A04','精密加工件'),(1,'A05','钣金件'),(1,'A06','塑胶件'),(1,'A07','其他'),(1,'B01','外购件类'),(1,'B02','简易加工件'),(1,'B03','PCB硬板'),(1,'B04','FPC软板'),(1,'B05','线束'),(1,'B06','组装组件'),(1,'B07','其他'),(1,'C01','连接耗材'),(1,'C02','密封耗材'),(1,'C03','其他'),(1,'D01','自研程序固件'),(1,'D02','外购软件授权'),(1,'D03','授权介质'),(1,'D04','其他'),(1,'E01','成品'),(1,'E02','半成品'),(1,'F01','资料文档'),(1,'F02','标识标牌'),(1,'F03','包装物料'),(1,'F04','随机工具配件'),(1,'F05','其他'); +INSERT INTO material_categories (organization_id,code,name,parent_id,status) +SELECT organization_id,CONCAT(code,'-01'),CONCAT(name,'·通用型'),id,status FROM material_categories WHERE parent_id IS NULL; +INSERT INTO material_categories (organization_id,code,name,parent_id,status) +SELECT organization_id,CONCAT(code,'-02'),CONCAT(name,'·专用型'),id,status FROM material_categories WHERE parent_id IS NULL; +INSERT INTO warehouses (id,organization_id,code,name,status) VALUES (1,1,'DEFAULT','默认仓库','active'); +INSERT INTO warehouse_locations (id,warehouse_id,code,name,status) VALUES (1,1,'MAIN','主库位','active'); diff --git a/scripts/migrations/001_phase1_auth_org.sql b/scripts/migrations/001_phase1_auth_org.sql deleted file mode 100644 index 6c7254ed..00000000 --- a/scripts/migrations/001_phase1_auth_org.sql +++ /dev/null @@ -1,108 +0,0 @@ -CREATE TABLE organizations ( - id BIGINT NOT NULL AUTO_INCREMENT, - code VARCHAR(50) NOT NULL, - name VARCHAR(200) NOT NULL, - status VARCHAR(20) NOT NULL DEFAULT 'active', - default_timezone VARCHAR(64) NOT NULL DEFAULT 'Asia/Shanghai', - 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_organizations_code (code) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -INSERT INTO organizations (id, code, name, status) -VALUES (1, 'DEFAULT', '默认公司组织', 'active'); - -CREATE TABLE roles ( - id BIGINT NOT NULL AUTO_INCREMENT, - code VARCHAR(80) NOT NULL, - name VARCHAR(100) NOT NULL, - enabled BOOLEAN NOT NULL DEFAULT TRUE, - 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_roles_code (code) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -CREATE TABLE permissions ( - id BIGINT NOT NULL AUTO_INCREMENT, - code VARCHAR(120) NOT NULL, - name VARCHAR(150) NOT NULL, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (id), - UNIQUE KEY uk_permissions_code (code) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -CREATE TABLE role_permissions ( - role_id BIGINT NOT NULL, - permission_id BIGINT NOT NULL, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (role_id, permission_id), - CONSTRAINT fk_role_permissions_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE, - CONSTRAINT fk_role_permissions_permission FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -CREATE TABLE user_roles ( - user_id BIGINT NOT NULL, - role_id BIGINT NOT NULL, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (user_id, role_id), - CONSTRAINT fk_user_roles_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - CONSTRAINT fk_user_roles_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -CREATE TABLE user_organizations ( - user_id BIGINT NOT NULL, - organization_id BIGINT NOT NULL, - is_default BOOLEAN NOT NULL DEFAULT FALSE, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (user_id, organization_id), - CONSTRAINT fk_user_organizations_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - CONSTRAINT fk_user_organizations_org FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -CREATE TABLE revoked_tokens ( - jti VARCHAR(64) NOT NULL, - user_id BIGINT NOT NULL, - expires_at DATETIME NOT NULL, - revoked_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (jti), - KEY idx_revoked_tokens_expires_at (expires_at), - CONSTRAINT fk_revoked_tokens_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -INSERT INTO roles (code, name) VALUES -('super_admin', '超级管理员'), -('warehouse_admin', '仓库管理员'), -('purchaser', '采购人员'), -('technician', '技术人员'), -('production', '生产人员'), -('sales', '销售人员'); - -INSERT INTO permissions (code, name) VALUES -('master.material.read', '查看物料'), -('master.material.write', '维护物料'), -('master.config.write', '维护物料基础配置'), -('organization.read', '查看公司组织'), -('organization.write', '维护公司组织'), -('organization.user.write', '维护用户'), -('organization.role.write', '维护角色与权限'), -('partner.supplier.read', '查看供应商'), -('partner.supplier.write', '维护供应商'), -('partner.customer.read', '查看客户'), -('partner.customer.write', '维护客户'), -('inventory.stock.read', '查看库存'), -('inventory.warning.write', '维护库存预警'), -('inventory.inbound.write', '维护入库'), -('inventory.outbound.write', '维护出库'); - -INSERT INTO role_permissions (role_id, permission_id) -SELECT r.id, p.id FROM roles r CROSS JOIN permissions p WHERE r.code = 'super_admin'; - -INSERT INTO user_organizations (user_id, organization_id, is_default) -SELECT id, 1, TRUE FROM users; - -INSERT INTO user_roles (user_id, role_id) -SELECT u.id, r.id -FROM users u -JOIN roles r ON r.code = CASE WHEN u.role = 'admin' THEN 'super_admin' ELSE 'warehouse_admin' END; diff --git a/scripts/migrations/002_phase1_master_data.sql b/scripts/migrations/002_phase1_master_data.sql deleted file mode 100644 index b34f2e6a..00000000 --- a/scripts/migrations/002_phase1_master_data.sql +++ /dev/null @@ -1,100 +0,0 @@ -CREATE TABLE material_types ( - id BIGINT NOT NULL AUTO_INCREMENT, - organization_id BIGINT NOT NULL, - code VARCHAR(50) NOT NULL, - name VARCHAR(100) NOT NULL, - status VARCHAR(20) NOT NULL DEFAULT 'active', - PRIMARY KEY (id), - UNIQUE KEY uk_material_types_org_code (organization_id, code), - CONSTRAINT fk_material_types_org FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -CREATE TABLE material_categories ( - id BIGINT NOT NULL AUTO_INCREMENT, - organization_id BIGINT NOT NULL, - code VARCHAR(50) NOT NULL, - name VARCHAR(100) NOT NULL, - parent_id BIGINT NULL, - status VARCHAR(20) NOT NULL DEFAULT 'active', - PRIMARY KEY (id), - UNIQUE KEY uk_material_categories_org_code (organization_id, code), - CONSTRAINT fk_material_categories_org FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE RESTRICT, - CONSTRAINT fk_material_categories_parent FOREIGN KEY (parent_id) REFERENCES material_categories(id) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -CREATE TABLE units ( - id BIGINT NOT NULL AUTO_INCREMENT, - organization_id BIGINT NOT NULL, - code VARCHAR(50) NOT NULL, - name VARCHAR(100) NOT NULL, - precision_scale TINYINT NOT NULL DEFAULT 0, - status VARCHAR(20) NOT NULL DEFAULT 'active', - PRIMARY KEY (id), - UNIQUE KEY uk_units_org_code (organization_id, code), - CONSTRAINT fk_units_org FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -CREATE TABLE code_rules ( - id BIGINT NOT NULL AUTO_INCREMENT, - organization_id BIGINT NOT NULL, - category_id BIGINT NOT NULL, - prefix VARCHAR(30) NOT NULL, - sequence_length TINYINT NOT NULL, - next_sequence BIGINT NOT NULL DEFAULT 1, - status VARCHAR(20) NOT NULL DEFAULT 'active', - PRIMARY KEY (id), - UNIQUE KEY uk_code_rules_org_category (organization_id, category_id), - CONSTRAINT fk_code_rules_org FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE RESTRICT, - CONSTRAINT fk_code_rules_category FOREIGN KEY (category_id) REFERENCES material_categories(id) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -CREATE TABLE material_unit_conversions ( - id BIGINT NOT NULL AUTO_INCREMENT, - organization_id BIGINT NOT NULL, - material_id BIGINT NOT NULL, - from_unit_id BIGINT NOT NULL, - to_unit_id BIGINT NOT NULL, - conversion_rate DECIMAL(20,6) NOT NULL, - effective_from DATE NOT NULL, - effective_to DATE NULL, - PRIMARY KEY (id), - KEY idx_material_unit_conversions_material (organization_id, material_id), - CONSTRAINT fk_material_unit_conversions_org FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE RESTRICT, - CONSTRAINT fk_material_unit_conversions_material FOREIGN KEY (material_id) REFERENCES materials(id) ON DELETE RESTRICT, - CONSTRAINT fk_material_unit_conversions_from_unit FOREIGN KEY (from_unit_id) REFERENCES units(id) ON DELETE RESTRICT, - CONSTRAINT fk_material_unit_conversions_to_unit FOREIGN KEY (to_unit_id) REFERENCES units(id) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -ALTER TABLE materials - ADD COLUMN organization_id BIGINT NOT NULL DEFAULT 1 AFTER id, - ADD COLUMN material_type_id BIGINT NULL AFTER category, - ADD COLUMN category_id BIGINT NULL AFTER material_type_id, - ADD COLUMN base_unit_id BIGINT NULL AFTER unit, - ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'active' AFTER base_unit_id, - ADD COLUMN version INT NOT NULL DEFAULT 1 AFTER status, - ADD COLUMN manufacturer_model VARCHAR(200) NULL AFTER spec, - ADD CONSTRAINT fk_materials_org FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE RESTRICT, - ADD CONSTRAINT fk_materials_type FOREIGN KEY (material_type_id) REFERENCES material_types(id) ON DELETE RESTRICT, - ADD CONSTRAINT fk_materials_category FOREIGN KEY (category_id) REFERENCES material_categories(id) ON DELETE RESTRICT, - ADD CONSTRAINT fk_materials_base_unit FOREIGN KEY (base_unit_id) REFERENCES units(id) ON DELETE RESTRICT; - -ALTER TABLE materials DROP INDEX material_code; -ALTER TABLE materials ADD UNIQUE KEY uk_materials_org_code (organization_id, material_code); - -INSERT INTO material_types (organization_id, code, name) VALUES -(1, 'raw_material', '原材料'), -(1, 'semi_finished', '半成品'), -(1, 'finished_good', '成品'), -(1, 'consumable', '耗材'), -(1, 'service', '服务'); - -INSERT INTO material_categories (organization_id, code, name) -SELECT 1, category_code, category_name FROM materials_category; - -INSERT INTO units (organization_id, code, name) -SELECT 1, unit, unit FROM materials WHERE unit IS NOT NULL AND unit <> '' GROUP BY unit; - -UPDATE materials m -LEFT JOIN material_categories c ON c.organization_id = m.organization_id AND c.code = m.category -LEFT JOIN units u ON u.organization_id = m.organization_id AND u.code = m.unit -SET m.category_id = c.id, m.base_unit_id = u.id; diff --git a/scripts/migrations/003_phase1_partners.sql b/scripts/migrations/003_phase1_partners.sql deleted file mode 100644 index c87b88e8..00000000 --- a/scripts/migrations/003_phase1_partners.sql +++ /dev/null @@ -1,52 +0,0 @@ -CREATE TABLE business_partners ( - id BIGINT NOT NULL AUTO_INCREMENT, - legal_name VARCHAR(200) NOT NULL, - unified_credit_code VARCHAR(50) NULL, - contact_name VARCHAR(100) NULL, - contact_phone VARCHAR(50) NULL, - address VARCHAR(500) NULL, - 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_business_partners_credit_code (unified_credit_code) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -CREATE TABLE organization_partners ( - id BIGINT NOT NULL AUTO_INCREMENT, - organization_id BIGINT NOT NULL, - business_partner_id BIGINT NOT NULL, - partner_code VARCHAR(50) NOT NULL, - is_supplier BOOLEAN NOT NULL DEFAULT FALSE, - is_customer BOOLEAN NOT NULL DEFAULT FALSE, - supplier_status VARCHAR(20) NULL, - customer_status VARCHAR(20) NULL, - settlement_currency CHAR(3) NULL, - payment_terms VARCHAR(200) NULL, - 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_organization_partners_org_partner (organization_id, business_partner_id), - UNIQUE KEY uk_organization_partners_org_code (organization_id, partner_code), - CONSTRAINT fk_organization_partners_org FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE RESTRICT, - CONSTRAINT fk_organization_partners_partner FOREIGN KEY (business_partner_id) REFERENCES business_partners(id) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -CREATE TABLE material_suppliers ( - id BIGINT NOT NULL AUTO_INCREMENT, - organization_id BIGINT NOT NULL, - material_id BIGINT NOT NULL, - organization_partner_id BIGINT NOT NULL, - price DECIMAL(20,6) NOT NULL, - currency CHAR(3) NOT NULL, - minimum_order_quantity DECIMAL(20,6) NOT NULL DEFAULT 0, - lead_time_days INT NOT NULL DEFAULT 0, - is_preferred BOOLEAN NOT NULL DEFAULT FALSE, - effective_from DATE NOT NULL, - effective_to DATE NULL, - status VARCHAR(20) NOT NULL DEFAULT 'active', - PRIMARY KEY (id), - KEY idx_material_suppliers_material (organization_id, material_id), - CONSTRAINT fk_material_suppliers_org FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE RESTRICT, - CONSTRAINT fk_material_suppliers_material FOREIGN KEY (material_id) REFERENCES materials(id) ON DELETE RESTRICT, - CONSTRAINT fk_material_suppliers_partner FOREIGN KEY (organization_partner_id) REFERENCES organization_partners(id) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/scripts/migrations/004_phase1_warehouses.sql b/scripts/migrations/004_phase1_warehouses.sql deleted file mode 100644 index 44612b86..00000000 --- a/scripts/migrations/004_phase1_warehouses.sql +++ /dev/null @@ -1,43 +0,0 @@ -CREATE TABLE warehouses ( - id BIGINT NOT NULL AUTO_INCREMENT, - organization_id BIGINT NOT NULL, - code VARCHAR(50) NOT NULL, - name VARCHAR(150) NOT NULL, - status VARCHAR(20) NOT NULL DEFAULT 'active', - 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_warehouses_org_code (organization_id, code), - CONSTRAINT fk_warehouses_org FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -CREATE TABLE warehouse_locations ( - id BIGINT NOT NULL AUTO_INCREMENT, - warehouse_id BIGINT NOT NULL, - code VARCHAR(50) NOT NULL, - name VARCHAR(150) NOT NULL, - status VARCHAR(20) NOT NULL DEFAULT 'active', - 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_warehouse_locations_warehouse_code (warehouse_id, code), - CONSTRAINT fk_warehouse_locations_warehouse FOREIGN KEY (warehouse_id) REFERENCES warehouses(id) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -CREATE TABLE user_warehouses ( - user_id BIGINT NOT NULL, - warehouse_id BIGINT NOT NULL, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (user_id, warehouse_id), - CONSTRAINT fk_user_warehouses_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - CONSTRAINT fk_user_warehouses_warehouse FOREIGN KEY (warehouse_id) REFERENCES warehouses(id) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -INSERT INTO warehouses (id, organization_id, code, name, status) -VALUES (1, 1, 'DEFAULT', '默认仓库', 'active'); - -INSERT INTO warehouse_locations (id, warehouse_id, code, name, status) -VALUES (1, 1, 'MAIN', '主库位', 'active'); - -INSERT INTO user_warehouses (user_id, warehouse_id) -SELECT id, 1 FROM users; diff --git a/scripts/migrations/005_phase1_inventory_split.sql b/scripts/migrations/005_phase1_inventory_split.sql deleted file mode 100644 index 2efc8b26..00000000 --- a/scripts/migrations/005_phase1_inventory_split.sql +++ /dev/null @@ -1,36 +0,0 @@ -CREATE TABLE inventory_balances ( - id BIGINT NOT NULL AUTO_INCREMENT, - organization_id BIGINT NOT NULL, - warehouse_id BIGINT NOT NULL, - location_id BIGINT NOT NULL, - material_id BIGINT NOT NULL, - on_hand DECIMAL(20,6) NOT NULL DEFAULT 0, - reserved DECIMAL(20,6) NOT NULL DEFAULT 0, - locked DECIMAL(20,6) NOT NULL DEFAULT 0, - in_transit DECIMAL(20,6) NOT NULL DEFAULT 0, - version INT NOT NULL DEFAULT 1, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (id), - UNIQUE KEY uk_inventory_balance_dimension (organization_id, warehouse_id, location_id, material_id), - KEY idx_inventory_balances_material (organization_id, material_id), - CONSTRAINT fk_inventory_balances_org FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE RESTRICT, - CONSTRAINT fk_inventory_balances_warehouse FOREIGN KEY (warehouse_id) REFERENCES warehouses(id) ON DELETE RESTRICT, - CONSTRAINT fk_inventory_balances_location FOREIGN KEY (location_id) REFERENCES warehouse_locations(id) ON DELETE RESTRICT, - CONSTRAINT fk_inventory_balances_material FOREIGN KEY (material_id) REFERENCES materials(id) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -CREATE TABLE stock_warning_rules ( - id BIGINT NOT NULL AUTO_INCREMENT, - organization_id BIGINT NOT NULL, - warehouse_id BIGINT NOT NULL, - material_id BIGINT NOT NULL, - minimum_available DECIMAL(20,6) NOT NULL DEFAULT 0, - enabled BOOLEAN NOT NULL DEFAULT TRUE, - 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_stock_warning_rules_dimension (organization_id, warehouse_id, material_id), - CONSTRAINT fk_stock_warning_rules_org FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE RESTRICT, - CONSTRAINT fk_stock_warning_rules_warehouse FOREIGN KEY (warehouse_id) REFERENCES warehouses(id) ON DELETE RESTRICT, - CONSTRAINT fk_stock_warning_rules_material FOREIGN KEY (material_id) REFERENCES materials(id) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/scripts/migrations/006_phase1_backfill.sql b/scripts/migrations/006_phase1_backfill.sql deleted file mode 100644 index 5f07c944..00000000 --- a/scripts/migrations/006_phase1_backfill.sql +++ /dev/null @@ -1,31 +0,0 @@ -INSERT INTO inventory_balances (organization_id, warehouse_id, location_id, material_id, on_hand) -SELECT 1, 1, 1, id, current_stock FROM materials -ON DUPLICATE KEY UPDATE material_id = VALUES(material_id); - -INSERT INTO stock_warning_rules (organization_id, warehouse_id, material_id, minimum_available, enabled) -SELECT 1, 1, id, warning_stock, TRUE FROM materials -ON DUPLICATE KEY UPDATE material_id = VALUES(material_id); - -ALTER TABLE inbound_orders - ADD COLUMN organization_id BIGINT NOT NULL DEFAULT 1 AFTER id, - ADD COLUMN warehouse_id BIGINT NOT NULL DEFAULT 1 AFTER organization_id, - ADD COLUMN location_id BIGINT NOT NULL DEFAULT 1 AFTER warehouse_id, - ADD CONSTRAINT fk_inbound_orders_org FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE RESTRICT, - ADD CONSTRAINT fk_inbound_orders_warehouse FOREIGN KEY (warehouse_id) REFERENCES warehouses(id) ON DELETE RESTRICT, - ADD CONSTRAINT fk_inbound_orders_location FOREIGN KEY (location_id) REFERENCES warehouse_locations(id) ON DELETE RESTRICT; - -ALTER TABLE outbound_orders - ADD COLUMN organization_id BIGINT NOT NULL DEFAULT 1 AFTER id, - ADD COLUMN warehouse_id BIGINT NOT NULL DEFAULT 1 AFTER organization_id, - ADD COLUMN location_id BIGINT NOT NULL DEFAULT 1 AFTER warehouse_id, - ADD CONSTRAINT fk_outbound_orders_org FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE RESTRICT, - ADD CONSTRAINT fk_outbound_orders_warehouse FOREIGN KEY (warehouse_id) REFERENCES warehouses(id) ON DELETE RESTRICT, - ADD CONSTRAINT fk_outbound_orders_location FOREIGN KEY (location_id) REFERENCES warehouse_locations(id) ON DELETE RESTRICT; - -SELECT m.id, m.material_code, m.current_stock AS legacy_stock, - COALESCE(SUM(ib.on_hand), 0) AS migrated_stock, - m.current_stock - COALESCE(SUM(ib.on_hand), 0) AS difference -FROM materials m -LEFT JOIN inventory_balances ib ON ib.material_id = m.id -GROUP BY m.id, m.material_code, m.current_stock -HAVING difference <> 0; diff --git a/scripts/migrations/006_phase1_drop_legacy_material_stock.sql b/scripts/migrations/006_phase1_drop_legacy_material_stock.sql deleted file mode 100644 index 7d54895e..00000000 --- a/scripts/migrations/006_phase1_drop_legacy_material_stock.sql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE materials - DROP COLUMN current_stock, - DROP COLUMN warning_stock; diff --git a/scripts/migrations/006_phase1_role_permission_defaults.sql b/scripts/migrations/006_phase1_role_permission_defaults.sql deleted file mode 100644 index 3fd2e1a8..00000000 --- a/scripts/migrations/006_phase1_role_permission_defaults.sql +++ /dev/null @@ -1,31 +0,0 @@ -INSERT IGNORE INTO role_permissions (role_id, permission_id) -SELECT r.id, p.id -FROM ( - SELECT 'warehouse_admin' AS role_code, 'master.material.read' AS permission_code - UNION ALL SELECT 'warehouse_admin', 'inventory.stock.read' - UNION ALL SELECT 'warehouse_admin', 'inventory.warning.write' - UNION ALL SELECT 'warehouse_admin', 'inventory.inbound.write' - UNION ALL SELECT 'warehouse_admin', 'inventory.outbound.write' - - UNION ALL SELECT 'purchaser', 'master.material.read' - UNION ALL SELECT 'purchaser', 'partner.supplier.read' - UNION ALL SELECT 'purchaser', 'partner.supplier.write' - UNION ALL SELECT 'purchaser', 'inventory.stock.read' - UNION ALL SELECT 'purchaser', 'inventory.warning.write' - - UNION ALL SELECT 'technician', 'master.material.read' - UNION ALL SELECT 'technician', 'master.material.write' - UNION ALL SELECT 'technician', 'master.config.write' - UNION ALL SELECT 'technician', 'inventory.stock.read' - - UNION ALL SELECT 'production', 'master.material.read' - UNION ALL SELECT 'production', 'inventory.stock.read' - UNION ALL SELECT 'production', 'inventory.outbound.write' - - UNION ALL SELECT 'sales', 'master.material.read' - UNION ALL SELECT 'sales', 'partner.customer.read' - UNION ALL SELECT 'sales', 'partner.customer.write' - UNION ALL SELECT 'sales', 'inventory.stock.read' -) AS defaults -JOIN roles r ON r.code = defaults.role_code -JOIN permissions p ON p.code = defaults.permission_code; diff --git a/scripts/migrations/006_phase1_user_multi_roles.sql b/scripts/migrations/006_phase1_user_multi_roles.sql deleted file mode 100644 index 103bc7f8..00000000 --- a/scripts/migrations/006_phase1_user_multi_roles.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE users DROP COLUMN role; diff --git a/scripts/migrations/007_material_subcategories.sql b/scripts/migrations/007_material_subcategories.sql deleted file mode 100644 index d2fadf8a..00000000 --- a/scripts/migrations/007_material_subcategories.sql +++ /dev/null @@ -1,96 +0,0 @@ -UPDATE material_categories -SET name = '外购件类' -WHERE code = 'B01' AND parent_id IS NULL; - -INSERT INTO material_categories (organization_id, code, name, parent_id, status) -SELECT parent.organization_id, - CONCAT(parent.code, '-01'), - CASE parent.code - WHEN 'A01' THEN '紧固件类' - WHEN 'A02' THEN '轴承类' - WHEN 'A03' THEN '轴套法兰类' - WHEN 'A04' THEN '导轨丝杠类' - WHEN 'A05' THEN '机柜钣金类' - WHEN 'A06' THEN '管槽塑胶类' - WHEN 'B01' THEN '电子类' - WHEN 'B03' THEN '控制板类' - WHEN 'B04' THEN '排线类' - WHEN 'B05' THEN '电源线束类' - WHEN 'C01' THEN '接线辅材类' - WHEN 'C02' THEN '密封件类' - WHEN 'D01' THEN '控制固件类' - WHEN 'D02' THEN '工业软件类' - WHEN 'E01' THEN '电气设备类' - WHEN 'F01' THEN '技术资料类' - WHEN 'F02' THEN '设备标识类' - WHEN 'F03' THEN '包装容器类' - WHEN 'F04' THEN '手动工具类' - ELSE CONCAT(parent.name, '·通用型') - END, - parent.id, - parent.status -FROM material_categories parent -WHERE parent.parent_id IS NULL - AND NOT EXISTS ( - SELECT 1 FROM material_categories child - WHERE child.organization_id = parent.organization_id - AND child.parent_id = parent.id - ); - -INSERT INTO material_categories (organization_id, code, name, parent_id, status) -SELECT parent.organization_id, - CONCAT(parent.code, '-02'), - CASE parent.code - WHEN 'A01' THEN '传动连接类' - WHEN 'A02' THEN '其他成品件类' - WHEN 'A03' THEN '连接加工件类' - WHEN 'A04' THEN '齿轮传动类' - WHEN 'A05' THEN '管槽接头类' - WHEN 'A06' THEN '密封防护类' - WHEN 'B01' THEN '机电类' - WHEN 'B03' THEN '功能板类' - WHEN 'B04' THEN '柔性电缆类' - WHEN 'B05' THEN '信号线束类' - WHEN 'C01' THEN '包装连接类' - WHEN 'C02' THEN '密封材料类' - WHEN 'D01' THEN '升级包类' - WHEN 'D02' THEN '设计软件类' - WHEN 'E01' THEN '操作设备类' - WHEN 'F01' THEN '管理资料类' - WHEN 'F02' THEN '包装标识类' - WHEN 'F03' THEN '缓冲防护类' - WHEN 'F04' THEN '仪器仪表类' - ELSE CONCAT(parent.name, '·专用型') - END, - parent.id, - parent.status -FROM material_categories parent -WHERE parent.parent_id IS NULL - AND EXISTS ( - SELECT 1 FROM material_categories first_child - WHERE first_child.organization_id = parent.organization_id - AND first_child.parent_id = parent.id - AND first_child.code = CONCAT(parent.code, '-01') - ) - AND NOT EXISTS ( - SELECT 1 FROM material_categories child - WHERE child.organization_id = parent.organization_id - AND child.code = CONCAT(parent.code, '-02') - ); - -UPDATE materials material -JOIN material_categories parent - ON parent.id = material.category_id - AND parent.organization_id = material.organization_id - AND parent.parent_id IS NULL -JOIN material_categories child - ON child.id = ( - SELECT CASE - WHEN MOD(CRC32(material.material_code), 2) = 0 THEN MIN(candidate.id) - ELSE MAX(candidate.id) - END - FROM material_categories candidate - WHERE candidate.organization_id = parent.organization_id - AND candidate.parent_id = parent.id - ) -SET material.category_id = child.id; diff --git a/scripts/migrations/008_require_material_category.sql b/scripts/migrations/008_require_material_category.sql deleted file mode 100644 index 0305b1ae..00000000 --- a/scripts/migrations/008_require_material_category.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE materials - MODIFY COLUMN category_id BIGINT NOT NULL;