You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

142 lines
5.8 KiB

package console
import (
"fmt"
"yunyan/comm"
"yunyan/lego/sys/postgres"
)
// ============================ 主键改号(BID / PID) ============================
//
// 方案商的 BID 与产品的 PID 都是「有含义的十六进制编码」,且都是所在表的主键。
// 建档时填错过去只能删了重建(删产品会连带丢掉版本记录),所以放开了改号。
//
// ⚠️ 改主键是不可逆操作,且这两个值会烧进固件 / 决定 license 分表名,所以一律
// 「有任何引用就不许改」,而不是级联更新:
// - 方案商 BID:被 product.providerid 引用;
// - 产品 PID:被 production_batch.productid、license_<pid十六进制> 分表、
// 以及各应用业务库的 userdevice.productid / product_stat.productid 引用。
// 后两者在别的库里,console 够不着也保证不了一致性——所以只要本库已经有批次或授权码,
// 就说明货已经生产出去了,此时改号必然造成线上设备对不上,直接拒绝。
//
// 允许改的窗口就是「刚建档、还没投产」。这也是实际会填错的时候。
// checkChipVendorIdChange 校验芯片厂商能否从 oldId 改成 newId(即改 CID)。返回空串表示可以改。
//
// 与 BID/PID 不同:芯片厂商的下游只有方案商的 vendor 列,且完全在本库内,所以这里【级联更新】
// 而不是拒绝——CID 是最可能填错、也最需要事后订正的一个(新芯片方案定型前常常只有暂定值)。
func checkChipVendorIdChange(sys *appConn, oldId, newId uint32) string {
if newId == 0 {
return "CID 必填"
}
if newId == oldId {
return ""
}
if exist, _ := dvChipVendor(sys, newId); exist != nil && exist.Id != 0 {
return fmt.Sprintf("CID 0x%X 已被芯片厂商「%s」占用", newId, exist.Name)
}
return ""
}
// renameChipVendorId 改芯片厂商的 CID,并级联把方案商的 vendor 引用一起搬过去。
// 两步必须都做:只改厂商表会让名下方案商指向不存在的厂商,列表里显示成「厂商#N」。
func renameChipVendorId(oldId, newId uint32) error {
if oldId == newId {
return nil
}
if res := postgres.Exec("UPDATE "+comm.TableChipVendor+" SET id=? WHERE id=?", newId, oldId); res.Error != nil {
return res.Error
}
if res := postgres.Exec("UPDATE "+comm.TableSolutionProvider+" SET vendor=? WHERE vendor=?", newId, oldId); res.Error != nil {
return res.Error
}
return nil
}
// checkProviderIdChange 校验方案商能否从 oldId 改成 newId。返回空串表示可以改。
func checkProviderIdChange(sys *appConn, oldId, newId uint32) string {
if newId == 0 {
return "BID 必填"
}
if newId == oldId {
return ""
}
// 新号不能已被占用
if exist, _ := dvSolutionProvider(sys, newId); exist != nil && exist.Id != 0 {
return fmt.Sprintf("BID 0x%X 已被方案商「%s」占用", newId, exist.Name)
}
// 已有产品挂在旧号下就不许改:product.providerid 是裸整数、没有外键,
// 改了那些产品会指向一个不存在的方案商,而 CID 正是从这里推导的。
n, err := dvCountProductsByProvider(sys, oldId)
if err != nil {
return "校验产品引用失败: " + err.Error()
}
if n > 0 {
return fmt.Sprintf("该方案商下已有 %d 个产品,不能改 BID。请先把这些产品改挂到别的方案商下", n)
}
return ""
}
// checkProductIdChange 校验产品能否从 oldId 改成 newId。返回空串表示可以改。
func checkProductIdChange(sys *appConn, oldId, newId uint32) string {
if newId == 0 {
return "PID 必填"
}
if newId == oldId {
return ""
}
if exist, _ := dvProduct(sys, newId); exist != nil && exist.Id != 0 {
return fmt.Sprintf("PID 0x%X 已被产品「%s」占用", newId, exist.Devicename)
}
// 已投产就不许改:批次台账与授权码分表都按 PID 组织,且设备码已经发出去了。
var batches int64
if err := sys.AdminDB().Table(comm.TableProductionBatch).Where("productid=?", oldId).Count(&batches).Error; err != nil {
return "校验生产批次失败: " + err.Error()
}
if batches > 0 {
return fmt.Sprintf("该产品已有 %d 个生产批次,不能改 PID(授权码分表 license_%x 与已发出的设备都按它组织)", batches, oldId)
}
if n, err := countLicenseRows(oldId); err != nil {
return "校验授权码失败: " + err.Error()
} else if n > 0 {
return fmt.Sprintf("该产品已生产 %d 个授权码,不能改 PID", n)
}
return ""
}
// countLicenseRows 统计某产品名下已生产的设备行数(改 PID 前的前置检查)。
// 2026-09-04 合表后设备都在 device_mac,按 productid 过滤即可。
func countLicenseRows(pid uint32) (int64, error) {
var n int64
if err := postgres.Table(comm.TableDeviceMac).Where("productid=?", pid).Count(&n).Error; err != nil {
return 0, err
}
return n, nil
}
// renameProviderId 执行方案商改号(先校验后调用)。用裸 UPDATE 改主键:
// gorm 的 Save 是「按主键定位再更新」,改不动主键本身。
func renameProviderId(oldId, newId uint32) error {
if oldId == newId {
return nil
}
res := postgres.Exec("UPDATE "+comm.TableSolutionProvider+" SET id=? WHERE id=?", newId, oldId)
return res.Error
}
// renameProductId 执行产品改号(先校验后调用)。产品版本表按 productid 归属,
// 跟着一起搬——版本是产品的附属数据,不搬会变成谁也看不见的孤儿行。
// 授权码/批次在校验里已确认为空,无需处理。
func renameProductId(oldId, newId uint32) error {
if oldId == newId {
return nil
}
if res := postgres.Exec("UPDATE "+comm.TableProduct+" SET id=? WHERE id=?", newId, oldId); res.Error != nil {
return res.Error
}
if res := postgres.Exec("UPDATE "+comm.TableProductVersion+" SET productid=? WHERE productid=?", newId, oldId); res.Error != nil {
return res.Error
}
return nil
}