175 lines
5.0 KiB
Go
175 lines
5.0 KiB
Go
package repository
|
|
|
|
import (
|
|
"git.mchus.pro/mchus/quoteforge/internal/localdb"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
// PartnumberBookRepository provides read-only access to local partnumber book snapshots.
|
|
type PartnumberBookRepository struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewPartnumberBookRepository(db *gorm.DB) *PartnumberBookRepository {
|
|
return &PartnumberBookRepository{db: db}
|
|
}
|
|
|
|
// GetActiveBook returns the most recently active local partnumber book.
|
|
func (r *PartnumberBookRepository) GetActiveBook() (*localdb.LocalPartnumberBook, error) {
|
|
var book localdb.LocalPartnumberBook
|
|
err := r.db.Where("is_active = 1").Order("created_at DESC, id DESC").First(&book).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &book, nil
|
|
}
|
|
|
|
// GetBookItems returns all items for the given local book ID.
|
|
func (r *PartnumberBookRepository) GetBookItems(bookID uint) ([]localdb.LocalPartnumberBookItem, error) {
|
|
book, err := r.getBook(bookID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items, _, err := r.listCatalogItems(book.PartnumbersJSON, "", 0, 0)
|
|
return items, err
|
|
}
|
|
|
|
// GetBookItemsPage returns items for the given local book ID with optional search and pagination.
|
|
func (r *PartnumberBookRepository) GetBookItemsPage(bookID uint, search string, page, perPage int) ([]localdb.LocalPartnumberBookItem, int64, error) {
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if perPage < 1 {
|
|
perPage = 100
|
|
}
|
|
|
|
book, err := r.getBook(bookID)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
return r.listCatalogItems(book.PartnumbersJSON, search, page, perPage)
|
|
}
|
|
|
|
// FindLotByPartnumber looks up a partnumber in the active book and returns the matching items.
|
|
func (r *PartnumberBookRepository) FindLotByPartnumber(bookID uint, partnumber string) ([]localdb.LocalPartnumberBookItem, error) {
|
|
book, err := r.getBook(bookID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
found := false
|
|
for _, pn := range book.PartnumbersJSON {
|
|
if pn == partnumber {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return nil, nil
|
|
}
|
|
var items []localdb.LocalPartnumberBookItem
|
|
err = r.db.Where("partnumber = ?", partnumber).Find(&items).Error
|
|
return items, err
|
|
}
|
|
|
|
// ListBooks returns all local partnumber books ordered newest first.
|
|
func (r *PartnumberBookRepository) ListBooks() ([]localdb.LocalPartnumberBook, error) {
|
|
var books []localdb.LocalPartnumberBook
|
|
err := r.db.Order("created_at DESC, id DESC").Find(&books).Error
|
|
return books, err
|
|
}
|
|
|
|
// SaveBook saves a new partnumber book snapshot.
|
|
func (r *PartnumberBookRepository) SaveBook(book *localdb.LocalPartnumberBook) error {
|
|
return r.db.Save(book).Error
|
|
}
|
|
|
|
// SaveBookItems upserts canonical PN catalog rows.
|
|
func (r *PartnumberBookRepository) SaveBookItems(items []localdb.LocalPartnumberBookItem) error {
|
|
if len(items) == 0 {
|
|
return nil
|
|
}
|
|
return r.db.Clauses(clause.OnConflict{
|
|
Columns: []clause.Column{{Name: "partnumber"}},
|
|
DoUpdates: clause.AssignmentColumns([]string{
|
|
"lots_json",
|
|
"description",
|
|
}),
|
|
}).CreateInBatches(items, 500).Error
|
|
}
|
|
|
|
// CountBookItems returns the number of items for a given local book ID.
|
|
func (r *PartnumberBookRepository) CountBookItems(bookID uint) int64 {
|
|
book, err := r.getBook(bookID)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return int64(len(book.PartnumbersJSON))
|
|
}
|
|
|
|
func (r *PartnumberBookRepository) CountDistinctLots(bookID uint) int64 {
|
|
items, err := r.GetBookItems(bookID)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
seen := make(map[string]struct{})
|
|
for _, item := range items {
|
|
for _, lot := range item.LotsJSON {
|
|
if lot.LotName == "" {
|
|
continue
|
|
}
|
|
seen[lot.LotName] = struct{}{}
|
|
}
|
|
}
|
|
return int64(len(seen))
|
|
}
|
|
|
|
func (r *PartnumberBookRepository) HasAllBookItems(bookID uint) bool {
|
|
book, err := r.getBook(bookID)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if len(book.PartnumbersJSON) == 0 {
|
|
return true
|
|
}
|
|
var count int64
|
|
if err := r.db.Model(&localdb.LocalPartnumberBookItem{}).
|
|
Where("partnumber IN ?", []string(book.PartnumbersJSON)).
|
|
Count(&count).Error; err != nil {
|
|
return false
|
|
}
|
|
return count == int64(len(book.PartnumbersJSON))
|
|
}
|
|
|
|
func (r *PartnumberBookRepository) getBook(bookID uint) (*localdb.LocalPartnumberBook, error) {
|
|
var book localdb.LocalPartnumberBook
|
|
if err := r.db.First(&book, bookID).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &book, nil
|
|
}
|
|
|
|
func (r *PartnumberBookRepository) listCatalogItems(partnumbers localdb.LocalStringList, search string, page, perPage int) ([]localdb.LocalPartnumberBookItem, int64, error) {
|
|
if len(partnumbers) == 0 {
|
|
return []localdb.LocalPartnumberBookItem{}, 0, nil
|
|
}
|
|
|
|
query := r.db.Model(&localdb.LocalPartnumberBookItem{}).Where("partnumber IN ?", []string(partnumbers))
|
|
if search != "" {
|
|
trimmedSearch := "%" + search + "%"
|
|
query = query.Where("partnumber LIKE ? OR lots_json LIKE ? OR description LIKE ?", trimmedSearch, trimmedSearch, trimmedSearch)
|
|
}
|
|
|
|
var total int64
|
|
if err := query.Count(&total).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
var items []localdb.LocalPartnumberBookItem
|
|
if page > 0 && perPage > 0 {
|
|
query = query.Offset((page - 1) * perPage).Limit(perPage)
|
|
}
|
|
err := query.Order("partnumber ASC, id ASC").Find(&items).Error
|
|
return items, total, err
|
|
}
|