refactor: привести кодовую базу в соответствие с канонами bible
- 400 → 422 для всех ошибок валидации входных данных (handlers: export, quote, sync, vendor_spec, partnumber_books, pricelist) - SQL-запросы вынесены из handlers в localdb (partnumber_books, pricelist, support_bundle); ValidateMariaDBConnection перенесён в internal/db/validate.go - List-ответы унифицированы: ключ items, поля total_count/page/per_page/total_pages (component, pricelist, partnumber_books); шаблоны обновлены - Молчаливые ошибки заменены на slog.Warn/Error (support_bundle, vendor_spec, component, configuration, local_configuration, localdb) - N+1 запросы устранены: batch-запросы в export.go и vendor_workspace_import.go - fmt.Println → slog в cmd/ (qfs, migrate, migrate_ops_projects, migrate_project_updated_at) - Заголовки recovery/verify добавлены во все 28 SQL-миграций - Добавлены bible-local/runtime-flows.md и bible-local/decisions/ - Обновлён субмодуль bible до v0.2.0-13 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -497,7 +497,10 @@ func localReadOnlyCacheQuarantineTableName(tableName string, kind string) string
|
||||
// HasSettings returns true if connection settings exist
|
||||
func (l *LocalDB) HasSettings() bool {
|
||||
var count int64
|
||||
l.db.Model(&ConnectionSettings{}).Count(&count)
|
||||
if err := l.db.Model(&ConnectionSettings{}).Count(&count).Error; err != nil {
|
||||
slog.Error("localdb: HasSettings count failed", "err", err)
|
||||
return false
|
||||
}
|
||||
return count > 0
|
||||
}
|
||||
|
||||
@@ -1044,14 +1047,18 @@ func (l *LocalDB) DeactivateConfiguration(uuid string) error {
|
||||
// CountConfigurations returns the number of local configurations
|
||||
func (l *LocalDB) CountConfigurations() int64 {
|
||||
var count int64
|
||||
l.db.Model(&LocalConfiguration{}).Count(&count)
|
||||
if err := l.db.Model(&LocalConfiguration{}).Count(&count).Error; err != nil {
|
||||
slog.Error("localdb: CountConfigurations failed", "err", err)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// CountProjects returns the number of local projects
|
||||
func (l *LocalDB) CountProjects() int64 {
|
||||
var count int64
|
||||
l.db.Model(&LocalProject{}).Count(&count)
|
||||
if err := l.db.Model(&LocalProject{}).Count(&count).Error; err != nil {
|
||||
slog.Error("localdb: CountProjects failed", "err", err)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
@@ -1819,3 +1826,62 @@ func (l *LocalDB) SetSyncGuardState(status, reasonCode, reasonText string, requi
|
||||
}),
|
||||
}).Create(state).Error
|
||||
}
|
||||
|
||||
// GetLocalPartnumberBookByServerID returns a local partnumber book by its server-side ID.
|
||||
func (l *LocalDB) GetLocalPartnumberBookByServerID(serverID uint) (*LocalPartnumberBook, error) {
|
||||
var book LocalPartnumberBook
|
||||
if err := l.db.Where("server_id = ?", serverID).First(&book).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &book, nil
|
||||
}
|
||||
|
||||
// GetLocalPricelistItemsPage returns a paginated, searchable list of items for a pricelist.
|
||||
func (l *LocalDB) GetLocalPricelistItemsPage(pricelistID uint, search string, page, perPage int) ([]LocalPricelistItem, int64, error) {
|
||||
dbq := l.db.Model(&LocalPricelistItem{}).Where("pricelist_id = ?", pricelistID)
|
||||
if search != "" {
|
||||
dbq = dbq.Where("lot_name LIKE ?", "%"+search+"%")
|
||||
}
|
||||
var total int64
|
||||
if err := dbq.Count(&total).Error; err != nil {
|
||||
return nil, 0, fmt.Errorf("count pricelist items: %w", err)
|
||||
}
|
||||
offset := (page - 1) * perPage
|
||||
var items []LocalPricelistItem
|
||||
if err := dbq.Order("lot_name").Offset(offset).Limit(perPage).Find(&items).Error; err != nil {
|
||||
return nil, 0, fmt.Errorf("fetch pricelist items: %w", err)
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
// GetLocalComponentDescriptionsByLotNames returns a map of lot_name → lot_description for the given lots.
|
||||
func (l *LocalDB) GetLocalComponentDescriptionsByLotNames(lotNames []string) (map[string]string, error) {
|
||||
if len(lotNames) == 0 {
|
||||
return map[string]string{}, nil
|
||||
}
|
||||
type row struct {
|
||||
LotName string
|
||||
LotDescription string
|
||||
}
|
||||
var rows []row
|
||||
if err := l.db.Table("local_components").
|
||||
Select("lot_name, lot_description").
|
||||
Where("lot_name IN ?", lotNames).
|
||||
Scan(&rows).Error; err != nil {
|
||||
return nil, fmt.Errorf("fetch component descriptions: %w", err)
|
||||
}
|
||||
m := make(map[string]string, len(rows))
|
||||
for _, r := range rows {
|
||||
m[r.LotName] = r.LotDescription
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// GetSchemaMigrations returns all applied local schema migrations ordered by applied_at.
|
||||
func (l *LocalDB) GetSchemaMigrations() ([]LocalSchemaMigration, error) {
|
||||
var migrations []LocalSchemaMigration
|
||||
if err := l.db.Order("applied_at ASC").Find(&migrations).Error; err != nil {
|
||||
return nil, fmt.Errorf("fetch schema migrations: %w", err)
|
||||
}
|
||||
return migrations, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user