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
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
|
|
}
|
|
|