openclaw/projects/glass-v2-migration-detailed-plan.md

581 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# glass-v2 迁移详细计划
## 📋 概述
将 glass-v2 的 5 个功能模块迁移到 glass 项目,同时适配:
1. 多租户架构team_id 隔离)
2. Spatie Permission 权限系统
3. Filament 4.x API
---
## 🎯 模块清单
### 1. 产品分类管理ProductCategory
#### 数据库
**状态**: glass 项目已有 `categories` 表,需要增强
**已创建的迁移**:
-`2026_02_02_140000_add_icon_and_color_to_categories_table.php`
#### 模型适配
**需要**: 创建或更新 `app/Models/Category.php`
```php
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Category extends Model
{
use BelongsToTenant;
protected $fillable = [
'name',
'name_en',
'slug',
'icon',
'color',
'description',
'sort_order',
'status', // 'active' or 'inactive' (改为 is_active)
];
protected $casts = [
'status' => 'boolean',
'sort_order' => 'integer',
];
// 添加字段以兼容 v2
protected $appends = ['is_active'];
public function getIsActiveAttribute(): bool
{
return $this->status === 'active';
}
public function products(): HasMany
{
return $this->hasMany(Product::class);
}
protected static function boot()
{
parent::boot();
static::creating(function ($category) {
if (empty($category->slug)) {
$category->slug = \Illuminate\Support\Str::slug($category->name);
}
});
}
}
```
#### Filament 资源
**需要创建**: `app/Filament/Resources/Categories/CategoryResource.php`
**来源**: v2 的 `ProductCategoryResource.php`
**适配点**:
1. 修改命名空间:`ProductCategories` → `Categories`
2. 修改模型:`ProductCategory` → `Category`
3. 修改权限检查:
```php
// v2: auth()->user()->is_admin
// glass: 使用 Gate 或 Policy
public static function canViewAny(): bool
{
return auth()->user()->can('view_any_categories');
}
```
4. 修改导航组:'库存与供应链' → 保持
5. 添加团队范围:默认会应用 BelongsToTenant
#### Seeder
**需要创建**: `database/seeders/CategorySeeder.php`
**预置数据**:
- 镜架 (heroicon-o-eye, 蓝色 #3b82f6)
- 镜片 (heroicon-o-circle-stack, 绿色 #10b981)
- 隐形眼镜 (heroicon-o-eye-dropper, 紫色 #8b5cf6)
- 护理液 (heroicon-o-beaker, 青色 #06b6d4)
- 配件 (heroicon-o-wrench-screwdriver, 橙色 #f59e0b)
---
### 2. 品牌管理Brand
#### 数据库
**状态**: glass 项目已有 `brands` 表,需要增强
**已创建的迁移**:
-`2026_02_02_140100_add_slug_and_sort_order_to_brands_table.php`
#### 模型适配
**需要**: 创建或更新 `app/Models/Brand.php`
```php
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Brand extends Model
{
use BelongsToTenant;
protected $fillable = [
'name',
'name_en',
'slug',
'logo',
'description',
'website',
'country',
'sort_order',
'status',
];
protected $casts = [
'status' => 'boolean',
'sort_order' => 'integer',
];
protected $appends = ['is_active'];
public function getIsActiveAttribute(): bool
{
return $this->status === 'active';
}
public function products(): HasMany
{
return $this->hasMany(Product::class);
}
protected static function boot()
{
parent::boot();
static::creating(function ($brand) {
if (empty($brand->slug)) {
$brand->slug = \Illuminate\Support\Str::slug($brand->name);
}
});
}
}
```
#### Filament 资源
**需要创建**: `app/Filament/Resources/Brands/BrandResource.php`
**来源**: v2 的 `BrandResource.php`
**适配点**:
1. 模型名称保持不变
2. 修改权限检查(同上)
3. 确保 logo 上传目录正确:`brands/`
#### Seeder
**需要创建**: `database/seeders/BrandSeeder.php`
**预置数据**:
- 雷朋 (Ray-Ban, 意大利)
- 暴龙 (Bolon, 中国)
- 蔡司 (Zeiss, 德国)
- 依视路 (Essilor, 法国)
- 强生 (Johnson, 美国)
- 博士伦 (Bausch & Lomb, 美国)
- 海昌 (Hydron, 中国)
---
### 3. 采购订单管理PurchaseOrder
#### 数据库
**状态**: glass 项目没有此表
**已创建的迁移**:
-`2026_02_02_140200_create_purchase_orders_table.php`
-`2026_02_02_140300_create_purchase_order_items_table.php`
#### 模型
**需要创建**: `app/Models/PurchaseOrder.php``app/Models/PurchaseOrderItem.php`
**来源**: v2 的模型
**适配点**:
1. 添加 `BelongsToTenant` trait
2. 确保所有关系正确
3. 添加团队范围查询
**PurchaseOrderItem**:
```php
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class PurchaseOrderItem extends Model
{
use BelongsToTenant;
protected $fillable = [
'purchase_order_id',
'product_id',
'quantity',
'received_quantity',
'unit_cost',
'total_cost',
'note',
];
protected $casts = [
'quantity' => 'integer',
'received_quantity' => 'integer',
'unit_cost' => 'decimal:2',
'total_cost' => 'decimal:2',
];
public function purchaseOrder(): BelongsTo
{
return $this->belongsTo(PurchaseOrder::class);
}
public function product(): BelongsTo
{
return $this->belongsTo(Product::class);
}
public function getPendingQuantityAttribute(): int
{
return $this->quantity - $this->received_quantity;
}
}
```
#### Filament 资源
**需要创建**: `app/Filament/Resources/PurchaseOrders/PurchaseOrderResource.php`
**来源**: v2 的 `PurchaseOrderResource.php`
**适配点**:
1. 修改权限检查
2. 确保关联关系正确加载(`with()`
3. 状态流转逻辑保持不变
#### 权限配置
**需要添加到 RoleAndPermissionSeeder**:
```php
$purchasePermissions = [
'view_any_purchase_orders',
'view_purchase_orders',
'create_purchase_orders',
'update_purchase_orders',
'delete_purchase_orders',
'receive_purchase_orders',
'stock_in_purchase_orders',
];
// 分配角色
$manager->givePermissionTo($purchasePermissions);
$warehouseStaff->givePermissionTo(['view_any_purchase_orders', 'view_purchase_orders', 'create_purchase_orders', 'receive_purchase_orders']);
```
---
### 4. 库存流水管理InventoryTransaction
#### 数据库
**状态**: glass 项目没有此表
**已创建的迁移**:
-`2026_02_02_140400_create_inventory_transactions_table.php`
#### 模型
**需要创建**: `app/Models/InventoryTransaction.php`
**来源**: v2 的模型
**适配点**:
1. 添加 `BelongsToTenant` trait
2. 确保多态关联正确
```php
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class InventoryTransaction extends Model
{
use BelongsToTenant;
protected $fillable = [
'product_id',
'type', // 'in', 'out', 'adjust'
'quantity',
'reference_type',
'reference_id',
'staff_id',
'balance_after',
'note',
];
protected $casts = [
'quantity' => 'integer',
'balance_after' => 'integer',
];
public function product(): BelongsTo
{
return $this->belongsTo(Product::class);
}
public function staff(): BelongsTo
{
return $this->belongsTo(User::class, 'staff_id');
}
public function reference(): MorphTo
{
return $this->morphTo();
}
}
```
#### Filament 资源
**需要创建**: `app/Filament/Resources/InventoryTransactions/InventoryTransactionResource.php`
**来源**: v2 的 `InventoryTransactionResource.php`
**适配点**:
1. 修改权限检查
2. 保持只读(表单禁用)
3. 过滤器适配 Filament 4.x
#### 权限配置
**需要添加**:
```php
$inventoryPermissions = [
'view_any_inventory_transactions',
'view_inventory_transactions',
];
// 所有角色都可以查看库存流水
$allRoles->each(fn($role) => $role->givePermissionTo($inventoryPermissions));
```
---
### 5. 员工排班管理StaffSchedule
#### 数据库
**状态**: glass 项目没有此表
**已创建的迁移**:
-`2026_02_02_140500_create_staff_schedules_table.php`
#### 模型
**需要创建**: `app/Models/StaffSchedule.php`
**来源**: v2 的模型
**适配点**:
1. 添加 `BelongsToTenant` trait
2. 保持所有辅助方法
```php
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class StaffSchedule extends Model
{
use BelongsToTenant;
public const SHIFT_MORNING = 'morning';
public const SHIFT_AFTERNOON = 'afternoon';
public const SHIFT_FULL_DAY = 'full_day';
public const SHIFT_CUSTOM = 'custom';
// ... 其余代码保持不变 ...
}
```
#### Filament 资源
**需要创建**: `app/Filament/Resources/StaffSchedules/StaffScheduleResource.php`
**来源**: v2 的 `StaffScheduleResource.php`
**适配点**:
1. 修改权限检查
2. 移除 v2 特定的导航组(或保持"系统管理"
#### 权限配置
**需要添加**:
```php
$schedulePermissions = [
'view_any_staff_schedules',
'view_staff_schedules',
'create_staff_schedules',
'update_staff_schedules',
'delete_staff_schedules',
];
// 管理权限角色
$manager->givePermissionTo($schedulePermissions);
$shopOwner->givePermissionTo($schedulePermissions);
```
---
## 🔧 全局适配步骤
### 1. 权限系统扩展
`database/seeders/RoleAndPermissionSeeder.php` 中添加:
```php
// 产品分类和品牌权限
$categoryAndBrandPermissions = [
'view_any_categories',
'view_categories',
'create_categories',
'update_categories',
'delete_categories',
'view_any_brands',
'view_brands',
'create_brands',
'update_brands',
'delete_brands',
];
// 采购订单权限(已在上方定义)
// 库存流水权限(已在上方定义)
// 员工排班权限(已在上方定义)
// 分配给角色
$shopOwner->givePermissionTo([...$categoryAndBrandPermissions, ...$purchasePermissions, ...$inventoryPermissions, ...$schedulePermissions]);
$shopAdmin->givePermissionTo([...$categoryAndBrandPermissions, ...$purchasePermissions, ...$inventoryPermissions]);
$manager->givePermissionTo([...$categoryAndBrandPermissions, ...$purchasePermissions, ...$inventoryPermissions]);
$warehouseStaff->givePermissionTo(['view_any_categories', 'view_any_brands', ...$purchasePermissions, ...$inventoryPermissions]);
```
### 2. 中文本地化更新
`lang/zh_CN.json` 中添加:
```json
{
"resources": {
"categories": {
"label": "产品分类",
"plural_label": "产品分类",
"navigation_label": "产品分类"
},
"brands": {
"label": "品牌",
"plural_label": "品牌",
"navigation_label": "品牌管理"
},
"purchase_orders": {
"label": "采购单",
"plural_label": "采购管理",
"navigation_label": "采购订单"
},
"inventory_transactions": {
"label": "库存记录",
"plural_label": "库存交易记录",
"navigation_label": "库存"
},
"staff_schedules": {
"label": "排班",
"plural_label": "员工排班管理",
"navigation_label": "员工排班"
}
},
"actions": {
"receive": "收货入库",
"stock_in": "补入库",
"duplicate": "复制到明天"
}
}
```
### 3. 导航分组组织
在所有 Resource 中统一导航分组:
- 产品分类:`库存与供应链`
- 品牌管理:`库存与供应链`
- 采购订单:`库存与供应链`
- 库存流水:`库存与供应链`
- 员工排班:`系统管理` 或保持单独
---
## ✅ 检查清单
### 迁移前
- [ ] 备份 glass 项目数据库
- [ ] 确认 glass 项目的数据库连接配置
- [ ] 准备测试数据
### 数据库迁移
- [ ] 运行所有新迁移文件
- [ ] 验证表结构正确
- [ ] 检查外键约束
### 模型创建
- [ ] 创建所有模型文件
- [ ] 添加 BelongsToTenant trait
- [ ] 测试模型关系
### Filament 资源
- [ ] 创建所有 Resource 文件
- [ ] 适配权限检查
- [ ] 测试表单和表格
### Seeder
- [ ] 创建 CategorySeeder
- [ ] 创建 BrandSeeder
- [ ] 运行 Seeder 填充初始数据
### 测试
- [ ] 测试 CRUD 操作
- [ ] 测试权限隔离
- [ ] 测试跨租户数据隔离
- [ ] 测试采购订单收货流程
- [ ] 测试库存流水自动记录
---
## 🚀 执行顺序
### 第一步数据库结构30 分钟)
```bash
# 所有迁移文件已创建,运行迁移
#(需要先配置数据库连接)
php artisan migrate
```
### 第二步模型创建45 分钟)
1. 复制并适配所有模型
2. 添加 BelongsToTenant trait
3. 测试关系
### 第三步Filament 资源1 小时)
1. 复制所有 Resource 文件
2. 适配权限检查
3. 调整导航
### 第四步:权限和 Seeder30 分钟)
1. 更新 RoleAndPermissionSeeder
2. 创建 CategorySeeder 和 BrandSeeder
3. 运行 Seeder
### 第五步测试和调试30 分钟)
1. 测试所有功能
2. 修复问题
**总预计时间**: 3 小时
---
美羊羊 🐑