package repository import ( "fmt" "gorm.io/gorm" "log" "math" "rrbec_server/internal/models" "time" ) type PaginationParams struct { Page int Limit int Sort string Filters map[string]interface{} } type PaginatedResponse struct { Data interface{} `json:"data"` Total int64 `json:"total"` Page int `json:"page"` Limit int `json:"limit"` TotalPages int `json:"total_pages"` } func (p *PaginationParams) Apply(db *gorm.DB, model interface{}) (*gorm.DB, int64, error) { if p.Page <= 0 { p.Page = 1 } if p.Limit <= 0 { p.Limit = 50 } if p.Limit > 200 { p.Limit = 200 } query := db.Model(model) for key, value := range p.Filters { if key == "status" { if s, ok := value.(string); ok && s != "" { query = query.Where("status = ?", s) } } else if key == "active" { if b, ok := value.(bool); ok { query = query.Where("active = ?", b) } } else if key == "category" { if c, ok := value.(uint); ok && c > 0 { query = query.Where("category = ?", c) } } else if key == "cuisine" { if b, ok := value.(bool); ok { query = query.Where("cuisine = ?", b) } } else if key == "date_from" { if t, ok := value.(time.Time); ok { query = query.Where("date_time >= ? OR dt_open >= ? OR created_at >= ?", t, t, t) } } else if key == "date_to" { if t, ok := value.(time.Time); ok { query = query.Where("date_time <= ? OR dt_open <= ? OR created_at <= ?", t, t, t) } } else if key == "comanda_id" { if c, ok := value.(uint); ok && c > 0 { query = query.Where("comanda_id = ?", c) } } else if key == "name" { if n, ok := value.(string); ok && n != "" { query = query.Where("name LIKE ?", "%"+n+"%") } } else if key == "canceled" { if v, ok := value.(bool); ok { if v { query = query.Where("canceled IS NOT NULL") } else { query = query.Where("canceled IS NULL") } } } else if key == "delivered" { if v, ok := value.(bool); ok { if v { query = query.Where("delivered IS NOT NULL") } else { query = query.Where("delivered IS NULL") } } } } var total int64 if err := query.Count(&total).Error; err != nil { return nil, 0, err } sort := "id ASC" if p.Sort != "" { sort = p.Sort } offset := (p.Page - 1) * p.Limit query = query.Order(sort).Offset(offset).Limit(p.Limit) return query, total, nil } func (p *PaginationParams) Paginate(db *gorm.DB, model interface{}, result interface{}) (*PaginatedResponse, error) { query, total, err := p.Apply(db, model) if err != nil { return nil, err } if err := query.Find(result).Error; err != nil { return nil, err } totalPages := int(math.Ceil(float64(total) / float64(p.Limit))) if totalPages == 0 { totalPages = 1 } return &PaginatedResponse{ Data: result, Total: total, Page: p.Page, Limit: p.Limit, TotalPages: totalPages, }, nil } type Repository struct { db *gorm.DB } func NewRepository(db *gorm.DB) *Repository { return &Repository{db: db} } // Mesa func (r *Repository) GetMesas(params *PaginationParams) (*PaginatedResponse, error) { var mesas []models.Mesa return params.Paginate(r.db, &models.Mesa{}, &mesas) } func (r *Repository) CreateMesa(mesa *models.Mesa) error { return r.db.Create(mesa).Error } func (r *Repository) SaveMesa(mesa *models.Mesa) error { return r.db.Save(mesa).Error } // Client func (r *Repository) GetClients(params *PaginationParams) (*PaginatedResponse, error) { var clients []models.Client return params.Paginate(r.db, &models.Client{}, &clients) } func (r *Repository) CreateClient(client *models.Client) error { return r.db.Create(client).Error } func (r *Repository) SaveClient(client *models.Client) error { return r.db.Save(client).Error } // Product func (r *Repository) GetProducts(params *PaginationParams) (*PaginatedResponse, error) { var products []models.Product return params.Paginate(r.db, &models.Product{}, &products) } func (r *Repository) GetProductsAll(products *[]models.Product) error { return r.db.Find(products).Error } func (r *Repository) SaveProduct(product *models.Product) error { return r.db.Save(product).Error } func (r *Repository) GetProductByID(id uint) (*models.Product, error) { var product models.Product err := r.db.First(&product, id).Error return &product, err } func (r *Repository) UpdateProductFields(id uint, updates map[string]interface{}) error { updates["sincronizado"] = false return r.db.Model(&models.Product{}).Where("id = ?", id).Updates(updates).Error } func (r *Repository) DeductStock(productID uint, quantity int) error { return r.db.Model(&models.Product{}).Where("id = ?", productID). Update("quantity", gorm.Expr("quantity - ?", quantity)).Error } func (r *Repository) RestoreStock(productID uint, quantity int) error { return r.db.Model(&models.Product{}).Where("id = ?", productID). Update("quantity", gorm.Expr("quantity + ?", quantity)).Error } func (r *Repository) GetProductComponents(productID uint) ([]models.ProductComponent, error) { var components []models.ProductComponent err := r.db.Where("composite_product = ?", productID).Find(&components).Error return components, err } func (r *Repository) CreateProduct(product *models.Product) error { return r.db.Create(product).Error } // Comanda func (r *Repository) GetComandas(params *PaginationParams) (*PaginatedResponse, error) { var comandas []models.Comanda if params == nil { params = &PaginationParams{Page: 1, Limit: 50} } query, total, err := params.Apply(r.db, &models.Comanda{}) if err != nil { return nil, err } if err := query.Preload("Items").Find(&comandas).Error; err != nil { return nil, err } totalPages := int(math.Ceil(float64(total) / float64(params.Limit))) if totalPages == 0 { totalPages = 1 } return &PaginatedResponse{ Data: comandas, Total: total, Page: params.Page, Limit: params.Limit, TotalPages: totalPages, }, nil } func (r *Repository) CreateComanda(comanda *models.Comanda) error { return r.db.Create(comanda).Error } func (r *Repository) SaveComanda(comanda *models.Comanda) error { return r.db.Save(comanda).Error } func (r *Repository) SaveCategory(cat *models.Category) error { return r.db.Save(cat).Error } func (r *Repository) CreateCategory(cat *models.Category) error { return r.db.Create(cat).Error } func (r *Repository) UpdateCategoryFields(id uint, updates map[string]interface{}) error { return r.db.Model(&models.Category{}).Where("id = ?", id).Updates(updates).Error } func (r *Repository) SaveTypePay(tp *models.TypePay) error { return r.db.Save(tp).Error } func (r *Repository) GetCategories(params *PaginationParams) (*PaginatedResponse, error) { var categories []models.Category return params.Paginate(r.db, &models.Category{}, &categories) } func (r *Repository) GetTypePayments(params *PaginationParams) (*PaginatedResponse, error) { var types []models.TypePay return params.Paginate(r.db, &models.TypePay{}, &types) } func (r *Repository) GetComandaByID(id uint) (*models.Comanda, error) { var comanda models.Comanda if err := r.db.First(&comanda, id).Error; err != nil { return nil, err } var items []models.ProductComanda r.db.Where("comanda_id = ?", comanda.ID).Find(&items) comanda.Items = items return &comanda, nil } // Items func (r *Repository) AddItemToComanda(item *models.ProductComanda) error { return r.db.Create(item).Error } func (r *Repository) SaveProductComanda(item *models.ProductComanda) error { return r.db.Save(item).Error } func (r *Repository) GetItemsByComanda(comandaID uint) ([]models.ProductComanda, error) { var items []models.ProductComanda err := r.db.Where("comanda_id = ?", comandaID).Find(&items).Error return items, err } func (r *Repository) DeleteItem(itemID uint) error { return r.db.Delete(&models.ProductComanda{}, itemID).Error } func (r *Repository) GetOrders(params *PaginationParams) (*PaginatedResponse, error) { var orders []models.Order return params.Paginate(r.db, &models.Order{}, &orders) } func (r *Repository) SaveOrder(order *models.Order) error { return r.db.Save(order).Error } func (r *Repository) CreateOrder(order *models.Order) error { return r.db.Create(order).Error } func (r *Repository) GetOrderByPC(pcID uint) (*models.Order, error) { var order models.Order err := r.db.Where("product_comanda_id = ?", pcID).First(&order).Error return &order, err } func (r *Repository) UpdateOrder(order *models.Order) error { return r.db.Save(order).Error } func (r *Repository) UpdateOrderFields(id uint, updates map[string]interface{}) error { mappedUpdates := make(map[string]interface{}) for k, v := range updates { switch k { case "productComanda": mappedUpdates["product_comanda_id"] = v case "id_product": mappedUpdates["product_id"] = v case "id_comanda": mappedUpdates["comanda_id"] = v default: mappedUpdates[k] = v } } mappedUpdates["sincronizado"] = false return r.db.Model(&models.Order{}).Where("id = ?", id).Updates(mappedUpdates).Error } func (r *Repository) GetPayments(params *PaginationParams) (*PaginatedResponse, error) { var payments []models.Payment return params.Paginate(r.db, &models.Payment{}, &payments) } func (r *Repository) CreatePayment(payment *models.Payment) error { return r.db.Create(payment).Error } func (r *Repository) SavePayment(payment *models.Payment) error { return r.db.Save(payment).Error } // Sync func (r *Repository) GetUnsynced() (map[string][]interface{}, error) { // This is a simplified helper to find anything not synced. // In a real app, you'd iterate per table. return nil, nil // Placeholder for sync worker logic } func (r *Repository) MarkAsSynced(model interface{}, id uint) error { return r.db.Model(model).Where("id = ?", id).Update("sincronizado", true).Error } func (r *Repository) SafeChangeID(tableName string, oldID, newID uint) error { if oldID == newID { return nil } var count int64 r.db.Table(tableName).Where("id = ?", newID).Count(&count) if count > 0 { squatterID := newID + 1000000 for { var sCount int64 r.db.Table(tableName).Where("id = ?", squatterID).Count(&sCount) if sCount == 0 { break } squatterID++ } log.Printf("COLLISION: ID %d in %s taken! Moving squatter to %d", newID, tableName, squatterID) r.db.Exec(fmt.Sprintf("UPDATE %s SET id = ? WHERE id = ?", tableName), squatterID, newID) if tableName == "comandas" { r.UpdateFK("product_comandas", "comanda_id", newID, squatterID) r.UpdateFK("orders", "id_comanda", newID, squatterID) r.UpdateFK("payments", "comanda_id", newID, squatterID) } else if tableName == "product_comandas" { r.UpdateFK("orders", "product_comanda_id", newID, squatterID) } } return r.UpdateID(tableName, oldID, newID) } func (r *Repository) UpdateID(tableName string, oldID, newID uint) error { return r.db.Exec(fmt.Sprintf("UPDATE %s SET id = ? WHERE id = ?", tableName), newID, oldID).Error } func (r *Repository) UpdateFK(tableName, fkColumn string, oldID, newID uint) error { return r.db.Exec(fmt.Sprintf("UPDATE %s SET %s = ? WHERE %s = ?", tableName, fkColumn, fkColumn), newID, oldID).Error } // User func (r *Repository) GetUserByUsername(username string) (*models.User, error) { var user models.User err := r.db.Where("username = ?", username).First(&user).Error return &user, err } func (r *Repository) SaveUser(user *models.User) error { return r.db.Save(user).Error } func (r *Repository) UpdateComandaStatus(id uint, status string) error { return r.db.Model(&models.Comanda{}).Where("id = ?", id).Updates(map[string]interface{}{"status": status, "sincronizado": false}).Error } func (r *Repository) ClearComandaItems(comandaID uint) error { return r.db.Where("comanda_id = ?", comandaID).Delete(&models.ProductComanda{}).Error } func (r *Repository) GetLastSyncID() int { var state models.SyncState r.db.FirstOrCreate(&state) return state.LastSyncID } func (r *Repository) SaveLastSyncID(id int) { r.db.Model(&models.SyncState{}).Where("id = ?", 1).Update("last_sync_id", id) } func (r *Repository) GetUnsyncedComandas() ([]models.Comanda, error) { var results []models.Comanda err := r.db.Where("sincronizado = ?", false).Find(&results).Error return results, err } func (r *Repository) GetUnsyncedItems() ([]models.ProductComanda, error) { var results []models.ProductComanda err := r.db.Where("sincronizado = ?", false).Find(&results).Error return results, err } func (r *Repository) GetUnsyncedOrders() ([]models.Order, error) { var results []models.Order err := r.db.Where("sincronizado = ?", false).Find(&results).Error return results, err } func (r *Repository) GetUnsyncedPayments() ([]models.Payment, error) { var results []models.Payment err := r.db.Where("sincronizado = ?", false).Find(&results).Error return results, err } func (r *Repository) GetUnsyncedProducts() ([]models.Product, error) { var results []models.Product err := r.db.Where("sincronizado = ?", false).Find(&results).Error return results, err } func (r *Repository) GetComandaToSync(id uint, dest *models.Comanda) error { return r.db.First(dest, id).Error } func (r *Repository) GetItemToSync(id uint, dest *models.ProductComanda) error { return r.db.First(dest, id).Error } func (r *Repository) GetPaymentToSync(id uint, dest *models.Payment) error { return r.db.First(dest, id).Error } func (r *Repository) GetProductToSync(id uint, dest *models.Product) error { return r.db.First(dest, id).Error } func (r *Repository) GetOrderToSync(id uint, dest *models.Order) error { return r.db.First(dest, id).Error } func (r *Repository) DeleteByID(modelName string, id uint) error { var model interface{} switch modelName { case "Product": model = &models.Product{} case "Comanda": model = &models.Comanda{} case "ProductComanda": model = &models.ProductComanda{} case "Order": model = &models.Order{} case "Client": model = &models.Client{} case "Categories": model = &models.Category{} case "Mesa": model = &models.Mesa{} case "Payments": model = &models.Payment{} default: return nil } return r.db.Delete(model, id).Error } func (r *Repository) UpdateComandaFields(id uint, updates map[string]interface{}) error { mappedUpdates := make(map[string]interface{}) for k, v := range updates { switch k { case "mesa": mappedUpdates["mesa_id"] = v case "user": mappedUpdates["user_id"] = v case "client": mappedUpdates["client_id"] = v case "type_pay": mappedUpdates["type_pay_id"] = v default: mappedUpdates[k] = v } } // Mark as unsynced so the background worker pushes the update mappedUpdates["sincronizado"] = false return r.db.Model(&models.Comanda{}).Where("id = ?", id).Updates(mappedUpdates).Error }