hss-pos / backend /internal /models /models.go
sashank1989
feat: hybrid cloud sync + fix table/KOT state after payment
46ae36f
Raw
History Blame Contribute Delete
44.2 kB
package models
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// ============================================================================
// BASE MODEL
// ============================================================================
type BaseModel struct {
ID string `json:"id" gorm:"type:text;primaryKey"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"deleted_at,omitempty" gorm:"index"`
}
func (b *BaseModel) BeforeCreate(tx *gorm.DB) error {
if b.ID == "" {
b.ID = uuid.New().String()
}
return nil
}
// ============================================================================
// ROLES & PERMISSIONS
// ============================================================================
type RoleType string
const (
RoleAdmin RoleType = "admin"
RoleManager RoleType = "manager"
RoleCashier RoleType = "cashier"
RoleCaptain RoleType = "captain"
RoleKitchen RoleType = "kitchen"
RoleAccountant RoleType = "accountant"
)
type Role struct {
BaseModel
Name RoleType `json:"name" gorm:"type:text;uniqueIndex;not null"`
DisplayName string `json:"display_name" gorm:"type:text;not null"`
Permissions []Permission `json:"permissions" gorm:"many2many:role_permissions;"`
Users []User `json:"users,omitempty" gorm:"foreignKey:RoleID"`
}
type Permission struct {
BaseModel
Module string `json:"module" gorm:"type:text;not null"`
Action string `json:"action" gorm:"type:text;not null"`
Roles []Role `json:"roles,omitempty" gorm:"many2many:role_permissions;"`
}
// ============================================================================
// USERS
// ============================================================================
type User struct {
BaseModel
Username string `json:"username" gorm:"type:text;uniqueIndex;not null"`
Password string `json:"-" gorm:"type:text;not null"`
FullName string `json:"full_name" gorm:"type:text;not null"`
Email string `json:"email" gorm:"type:text"`
Phone string `json:"phone" gorm:"type:text"`
RoleID string `json:"role_id" gorm:"type:text;not null"`
Role Role `json:"role" gorm:"foreignKey:RoleID"`
IsActive bool `json:"is_active" gorm:"default:true"`
Pin string `json:"-" gorm:"type:text"` // Quick PIN for POS login
LastLoginAt *time.Time `json:"last_login_at"`
}
// ============================================================================
// RESTAURANT CONFIG
// ============================================================================
type RestaurantConfig struct {
BaseModel
Name string `json:"name" gorm:"type:text;not null"`
Address string `json:"address" gorm:"type:text"`
Phone string `json:"phone" gorm:"type:text"`
Email string `json:"email" gorm:"type:text"`
GSTIN string `json:"gstin" gorm:"type:text"`
FSSAI string `json:"fssai" gorm:"type:text"`
LogoURL string `json:"logo_url" gorm:"type:text"`
Currency string `json:"currency" gorm:"type:text;default:'INR'"`
TaxRate float64 `json:"tax_rate" gorm:"type:real;default:5.0"`
CGSTRate float64 `json:"cgst_rate" gorm:"type:real;default:2.5"`
SGSTRate float64 `json:"sgst_rate" gorm:"type:real;default:2.5"`
ServiceCharge float64 `json:"service_charge" gorm:"type:real;default:0"`
PrinterIP string `json:"printer_ip" gorm:"type:text"`
PrinterPort int `json:"printer_port" gorm:"type:integer;default:9100"`
KOTPrinterIP string `json:"kot_printer_ip" gorm:"type:text"`
WhatsAppToken string `json:"-" gorm:"type:text"`
WhatsAppPhoneID string `json:"-" gorm:"type:text"`
WhatsAppBaseURL string `json:"whatsapp_base_url" gorm:"type:text"` // Override for testing (default: https://graph.facebook.com/v18.0)
SwiggyAPIKey string `json:"-" gorm:"type:text"`
ZomatoAPIKey string `json:"-" gorm:"type:text"`
// Pine Labs EDC Integration
EDCEnabled bool `json:"edc_enabled" gorm:"type:boolean;default:false"`
EDCProvider string `json:"edc_provider" gorm:"type:text;default:'pinelabs'"` // pinelabs, razorpay, etc.
EDCMerchantID string `json:"edc_merchant_id" gorm:"type:text"`
EDCSecurityToken string `json:"-" gorm:"type:text"`
EDCStoreID string `json:"edc_store_id" gorm:"type:text"`
EDCTerminalID string `json:"edc_terminal_id" gorm:"type:text"`
EDCBaseURL string `json:"edc_base_url" gorm:"type:text"` // Override for testing (default: Pine Labs production URL)
// KOT Print Format
KOTPaperWidth int `json:"kot_paper_width" gorm:"type:integer;default:80"`
KOTFontSize string `json:"kot_font_size" gorm:"type:text;default:'medium'"`
KOTShowHeader bool `json:"kot_show_header" gorm:"type:boolean;default:true"`
KOTHeaderText string `json:"kot_header_text" gorm:"type:text"`
KOTShowFooter bool `json:"kot_show_footer" gorm:"type:boolean;default:false"`
KOTFooterText string `json:"kot_footer_text" gorm:"type:text"`
KOTShowCaptain bool `json:"kot_show_captain" gorm:"type:boolean;default:true"`
KOTShowNotes bool `json:"kot_show_notes" gorm:"type:boolean;default:true"`
KOTShowTime bool `json:"kot_show_time" gorm:"type:boolean;default:true"`
// Bill Print Format
BillPaperWidth int `json:"bill_paper_width" gorm:"type:integer;default:80"`
BillFontSize string `json:"bill_font_size" gorm:"type:text;default:'medium'"`
BillShowAddress bool `json:"bill_show_address" gorm:"type:boolean;default:true"`
BillShowGSTIN bool `json:"bill_show_gstin" gorm:"type:boolean;default:true"`
BillShowFSSAI bool `json:"bill_show_fssai" gorm:"type:boolean;default:true"`
BillShowItemTax bool `json:"bill_show_item_tax" gorm:"type:boolean;default:false"`
BillShowCustomer bool `json:"bill_show_customer" gorm:"type:boolean;default:true"`
BillHeaderText string `json:"bill_header_text" gorm:"type:text"`
BillFooterText string `json:"bill_footer_text" gorm:"type:text;default:'Thank you! Visit again!'"`
BillShowQR bool `json:"bill_show_qr" gorm:"type:boolean;default:false"`
BillQRData string `json:"bill_qr_data" gorm:"type:text"`
}
// ============================================================================
// CUSTOMERS
// ============================================================================
type Customer struct {
BaseModel
Name string `json:"name" gorm:"type:text;not null"`
Phone string `json:"phone" gorm:"type:text;uniqueIndex"`
Email string `json:"email" gorm:"type:text"`
Address string `json:"address" gorm:"type:text"`
LoyaltyPoints int `json:"loyalty_points" gorm:"type:integer;default:0"`
TotalSpent float64 `json:"total_spent" gorm:"type:real;default:0"`
TotalOrders int `json:"total_orders" gorm:"type:integer;default:0"`
LastVisit *time.Time `json:"last_visit"`
Notes string `json:"notes" gorm:"type:text"`
Tags string `json:"tags" gorm:"type:text"` // comma-separated
}
// ============================================================================
// TABLES & SECTIONS
// ============================================================================
type TableStatus string
const (
TableAvailable TableStatus = "available"
TableOccupied TableStatus = "occupied"
TableReserved TableStatus = "reserved"
TableCleaning TableStatus = "cleaning"
)
type TableSection struct {
BaseModel
Name string `json:"name" gorm:"type:text;not null"`
Floor int `json:"floor" gorm:"type:integer;default:0"`
Tables []RestaurantTable `json:"tables" gorm:"foreignKey:SectionID"`
}
type RestaurantTable struct {
BaseModel
Number int `json:"number" gorm:"type:integer;not null"`
Name string `json:"name" gorm:"type:text"`
SectionID string `json:"section_id" gorm:"type:text;not null"`
Section TableSection `json:"section" gorm:"foreignKey:SectionID"`
Capacity int `json:"capacity" gorm:"type:integer;default:4"`
Status TableStatus `json:"status" gorm:"type:text;default:'available'"`
QRCode string `json:"qr_code" gorm:"type:text"`
PositionX int `json:"position_x" gorm:"type:integer;default:0"`
PositionY int `json:"position_y" gorm:"type:integer;default:0"`
}
type TableSession struct {
BaseModel
TableID string `json:"table_id" gorm:"type:text;not null"`
Table RestaurantTable `json:"table" gorm:"foreignKey:TableID"`
GuestCount int `json:"guest_count" gorm:"type:integer;default:1"`
CaptainID string `json:"captain_id" gorm:"type:text"`
Captain User `json:"captain" gorm:"foreignKey:CaptainID"`
CustomerID *string `json:"customer_id" gorm:"type:text"`
Customer *Customer `json:"customer,omitempty" gorm:"foreignKey:CustomerID"`
StartedAt time.Time `json:"started_at" gorm:"not null"`
EndedAt *time.Time `json:"ended_at"`
IsActive bool `json:"is_active" gorm:"default:true"`
Notes string `json:"notes" gorm:"type:text"`
}
// ============================================================================
// CATEGORIES & PRODUCTS
// ============================================================================
type Category struct {
BaseModel
Name string `json:"name" gorm:"type:text;not null"`
Description string `json:"description" gorm:"type:text"`
ParentID *string `json:"parent_id" gorm:"type:text"`
Parent *Category `json:"parent,omitempty" gorm:"foreignKey:ParentID"`
ImageURL string `json:"image_url" gorm:"type:text"`
SortOrder int `json:"sort_order" gorm:"type:integer;default:0"`
IsActive bool `json:"is_active" gorm:"default:true"`
Products []Product `json:"products,omitempty" gorm:"foreignKey:CategoryID"`
}
type ProductType string
const (
ProductVeg ProductType = "veg"
ProductNonVeg ProductType = "non_veg"
ProductEgg ProductType = "egg"
)
type KitchenStation string
const (
StationSouthIndian KitchenStation = "south_indian"
StationChinese KitchenStation = "chinese"
StationTandoor KitchenStation = "tandoor"
StationJuice KitchenStation = "juice"
StationBakery KitchenStation = "bakery"
StationMainKitchen KitchenStation = "main_kitchen"
)
type Product struct {
BaseModel
Name string `json:"name" gorm:"type:text;not null"`
ShortName string `json:"short_name" gorm:"type:text"`
SKU string `json:"sku" gorm:"type:text;uniqueIndex"`
CategoryID string `json:"category_id" gorm:"type:text;not null"`
Category Category `json:"category" gorm:"foreignKey:CategoryID"`
Price float64 `json:"price" gorm:"type:real;not null"`
TaxRate float64 `json:"tax_rate" gorm:"type:real;default:5.0"`
ProductType ProductType `json:"product_type" gorm:"type:text;default:'veg'"`
KitchenStation KitchenStation `json:"kitchen_station" gorm:"type:text;default:'main_kitchen'"`
Description string `json:"description" gorm:"type:text"`
ImageURL string `json:"image_url" gorm:"type:text"`
IsActive bool `json:"is_active" gorm:"default:true"`
IsAvailable bool `json:"is_available" gorm:"default:true"`
PrepTime int `json:"prep_time" gorm:"type:integer;default:15"` // minutes
SortOrder int `json:"sort_order" gorm:"type:integer;default:0"`
ModifierGroups []ModifierGroup `json:"modifier_groups,omitempty" gorm:"many2many:product_modifier_groups;"`
Recipes []Recipe `json:"recipes,omitempty" gorm:"foreignKey:ProductID"`
}
// ============================================================================
// MODIFIERS
// ============================================================================
type ModifierGroup struct {
BaseModel
Name string `json:"name" gorm:"type:text;not null"`
MinSelect int `json:"min_select" gorm:"type:integer;default:0"`
MaxSelect int `json:"max_select" gorm:"type:integer;default:1"`
IsRequired bool `json:"is_required" gorm:"default:false"`
Modifiers []Modifier `json:"modifiers" gorm:"foreignKey:GroupID"`
Products []Product `json:"products,omitempty" gorm:"many2many:product_modifier_groups;"`
}
type Modifier struct {
BaseModel
GroupID string `json:"group_id" gorm:"type:text;not null"`
Name string `json:"name" gorm:"type:text;not null"`
Price float64 `json:"price" gorm:"type:real;default:0"`
}
// ============================================================================
// ORDERS
// ============================================================================
type OrderSource string
const (
SourceDineIn OrderSource = "dine_in"
SourceTakeAway OrderSource = "take_away"
SourceDelivery OrderSource = "delivery"
SourceWebsite OrderSource = "website"
SourceWhatsApp OrderSource = "whatsapp"
SourceSwiggy OrderSource = "swiggy"
SourceZomato OrderSource = "zomato"
SourceQR OrderSource = "qr_table"
)
type OrderStatus string
const (
OrderPending OrderStatus = "pending"
OrderConfirmed OrderStatus = "confirmed"
OrderPreparing OrderStatus = "preparing"
OrderReady OrderStatus = "ready"
OrderServed OrderStatus = "served"
OrderCompleted OrderStatus = "completed"
OrderCancelled OrderStatus = "cancelled"
)
type PaymentStatus string
const (
PaymentPending PaymentStatus = "pending"
PaymentPartial PaymentStatus = "partial"
PaymentPaid PaymentStatus = "paid"
PaymentRefunded PaymentStatus = "refunded"
)
type Order struct {
BaseModel
OrderNumber string `json:"order_number" gorm:"type:text;uniqueIndex;not null"`
Source OrderSource `json:"source" gorm:"type:text;not null"`
Status OrderStatus `json:"status" gorm:"type:text;default:'pending'"`
PaymentStatus PaymentStatus `json:"payment_status" gorm:"type:text;default:'pending'"`
// Table info (for dine-in)
TableID *string `json:"table_id" gorm:"type:text"`
Table *RestaurantTable `json:"table,omitempty" gorm:"foreignKey:TableID"`
TableSessionID *string `json:"table_session_id" gorm:"type:text"`
GuestCount int `json:"guest_count" gorm:"type:integer;default:1"`
// Customer info
CustomerID *string `json:"customer_id" gorm:"type:text"`
Customer *Customer `json:"customer,omitempty" gorm:"foreignKey:CustomerID"`
CustomerName string `json:"customer_name" gorm:"type:text"`
CustomerPhone string `json:"customer_phone" gorm:"type:text"`
CustomerAddress string `json:"customer_address" gorm:"type:text"`
// Staff
CaptainID *string `json:"captain_id" gorm:"type:text"`
Captain *User `json:"captain,omitempty" gorm:"foreignKey:CaptainID"`
CashierID *string `json:"cashier_id" gorm:"type:text"`
Cashier *User `json:"cashier,omitempty" gorm:"foreignKey:CashierID"`
// Amounts
SubTotal float64 `json:"sub_total" gorm:"type:real;default:0"`
TaxAmount float64 `json:"tax_amount" gorm:"type:real;default:0"`
CGSTAmount float64 `json:"cgst_amount" gorm:"type:real;default:0"`
SGSTAmount float64 `json:"sgst_amount" gorm:"type:real;default:0"`
DiscountType string `json:"discount_type" gorm:"type:text"` // percentage, flat
DiscountValue float64 `json:"discount_value" gorm:"type:real;default:0"`
DiscountAmount float64 `json:"discount_amount" gorm:"type:real;default:0"`
ServiceCharge float64 `json:"service_charge" gorm:"type:real;default:0"`
RoundOff float64 `json:"round_off" gorm:"type:real;default:0"`
GrandTotal float64 `json:"grand_total" gorm:"type:real;default:0"`
PaidAmount float64 `json:"paid_amount" gorm:"type:real;default:0"`
ChangeAmount float64 `json:"change_amount" gorm:"type:real;default:0"`
// External order info
ExternalOrderID string `json:"external_order_id" gorm:"type:text"`
// Platform-specific (Swiggy/Zomato)
PlatformStatus string `json:"platform_status" gorm:"type:text"` // new, accepted, preparing, ready, picked_up, delivered, rejected
RiderName string `json:"rider_name" gorm:"type:text"`
RiderPhone string `json:"rider_phone" gorm:"type:text"`
EstimatedDeliveryTime string `json:"estimated_delivery_time" gorm:"type:text"`
PlatformCommission float64 `json:"platform_commission" gorm:"type:real;default:0"`
AcceptedAt *time.Time `json:"accepted_at"`
ReadyAt *time.Time `json:"ready_at"`
PickedUpAt *time.Time `json:"picked_up_at"`
DeliveredAt *time.Time `json:"delivered_at"`
RejectionReason string `json:"rejection_reason" gorm:"type:text"`
// Metadata
Notes string `json:"notes" gorm:"type:text"`
IsVoid bool `json:"is_void" gorm:"default:false"`
VoidReason string `json:"void_reason" gorm:"type:text"`
VoidedBy *string `json:"voided_by" gorm:"type:text"`
IsPrinted bool `json:"is_printed" gorm:"default:false"`
PrintCount int `json:"print_count" gorm:"type:integer;default:0"`
CompletedAt *time.Time `json:"completed_at"`
// Relations
Items []OrderItem `json:"items" gorm:"foreignKey:OrderID"`
KOTs []KOT `json:"kots" gorm:"foreignKey:OrderID"`
Payments []Payment `json:"payments" gorm:"foreignKey:OrderID"`
}
type OrderItem struct {
BaseModel
OrderID string `json:"order_id" gorm:"type:text;not null;index"`
ProductID string `json:"product_id" gorm:"type:text;not null"`
Product Product `json:"product" gorm:"foreignKey:ProductID"`
ProductName string `json:"product_name" gorm:"type:text;not null"`
Quantity int `json:"quantity" gorm:"type:integer;not null;default:1"`
UnitPrice float64 `json:"unit_price" gorm:"type:real;not null"`
TaxRate float64 `json:"tax_rate" gorm:"type:real;default:5.0"`
TaxAmount float64 `json:"tax_amount" gorm:"type:real;default:0"`
DiscountAmount float64 `json:"discount_amount" gorm:"type:real;default:0"`
TotalPrice float64 `json:"total_price" gorm:"type:real;not null"`
KitchenStation KitchenStation `json:"kitchen_station" gorm:"type:text"`
Notes string `json:"notes" gorm:"type:text"`
Status string `json:"status" gorm:"type:text;default:'pending'"` // pending, preparing, ready, served
IsCancelled bool `json:"is_cancelled" gorm:"default:false"`
CancelReason string `json:"cancel_reason" gorm:"type:text"`
CancelledBy string `json:"cancelled_by" gorm:"type:text"`
CancelledAt *time.Time `json:"cancelled_at"`
Modifiers []OrderItemModifier `json:"modifiers" gorm:"foreignKey:OrderItemID"`
}
type OrderItemModifier struct {
BaseModel
OrderItemID string `json:"order_item_id" gorm:"type:text;not null;index"`
ModifierID string `json:"modifier_id" gorm:"type:text;not null"`
Name string `json:"name" gorm:"type:text;not null"`
Price float64 `json:"price" gorm:"type:real;default:0"`
}
// ============================================================================
// KOT (Kitchen Order Tickets)
// ============================================================================
type KOTStatus string
const (
KOTPending KOTStatus = "pending"
KOTPreparing KOTStatus = "preparing"
KOTReady KOTStatus = "ready"
KOTServed KOTStatus = "served"
KOTCancelled KOTStatus = "cancelled"
)
type KOT struct {
BaseModel
KOTNumber string `json:"kot_number" gorm:"type:text;uniqueIndex;not null"`
OrderID string `json:"order_id" gorm:"type:text;not null;index"`
Order Order `json:"order" gorm:"foreignKey:OrderID"`
KitchenStation KitchenStation `json:"kitchen_station" gorm:"type:text;not null"`
Status KOTStatus `json:"status" gorm:"type:text;default:'pending'"`
TableNumber int `json:"table_number" gorm:"type:integer"`
CaptainName string `json:"captain_name" gorm:"type:text"`
IsPrinted bool `json:"is_printed" gorm:"default:false"`
IsModified bool `json:"is_modified" gorm:"default:false"`
CompletedAt *time.Time `json:"completed_at" gorm:"type:datetime"`
Notes string `json:"notes" gorm:"type:text"`
Items []KOTItem `json:"items" gorm:"foreignKey:KOTID"`
}
type KOTItem struct {
BaseModel
KOTID string `json:"kot_id" gorm:"type:text;not null;index"`
OrderItemID string `json:"order_item_id" gorm:"type:text;not null"`
ProductName string `json:"product_name" gorm:"type:text;not null"`
Quantity int `json:"quantity" gorm:"type:integer;not null"`
Notes string `json:"notes" gorm:"type:text"`
Modifiers string `json:"modifiers" gorm:"type:text"` // JSON string
IsCancelled bool `json:"is_cancelled" gorm:"default:false"`
CancelReason string `json:"cancel_reason" gorm:"type:text"`
Status string `json:"status" gorm:"type:text;default:'pending'"`
}
// ============================================================================
// PAYMENTS
// ============================================================================
type PaymentMethod string
const (
PayCash PaymentMethod = "cash"
PayCard PaymentMethod = "card"
PayUPI PaymentMethod = "upi"
PayWallet PaymentMethod = "wallet"
PayOnline PaymentMethod = "online"
)
type Payment struct {
BaseModel
OrderID string `json:"order_id" gorm:"type:text;not null;index"`
Method PaymentMethod `json:"method" gorm:"type:text;not null"`
Amount float64 `json:"amount" gorm:"type:real;not null"`
Reference string `json:"reference" gorm:"type:text"` // transaction ID
ReceivedBy string `json:"received_by" gorm:"type:text"`
Notes string `json:"notes" gorm:"type:text"`
}
// ============================================================================
// INVENTORY
// ============================================================================
type UnitType string
const (
UnitKg UnitType = "kg"
UnitGram UnitType = "g"
UnitLitre UnitType = "l"
UnitMl UnitType = "ml"
UnitPiece UnitType = "pc"
UnitBox UnitType = "box"
)
type InventoryItem struct {
BaseModel
Name string `json:"name" gorm:"type:text;not null"`
SKU string `json:"sku" gorm:"type:text;uniqueIndex"`
Unit UnitType `json:"unit" gorm:"type:text;not null"`
CurrentStock float64 `json:"current_stock" gorm:"type:real;default:0"`
MinStock float64 `json:"min_stock" gorm:"type:real;default:0"`
MaxStock float64 `json:"max_stock" gorm:"type:real;default:0"`
CostPerUnit float64 `json:"cost_per_unit" gorm:"type:real;default:0"`
CategoryName string `json:"category_name" gorm:"type:text"`
IsActive bool `json:"is_active" gorm:"default:true"`
LastRestocked *time.Time `json:"last_restocked"`
}
type StockMovementType string
const (
StockIn StockMovementType = "stock_in"
StockOut StockMovementType = "stock_out"
StockWastage StockMovementType = "wastage"
StockPurchase StockMovementType = "purchase"
StockConsumption StockMovementType = "consumption" // auto-deduct from recipe
StockAdjustment StockMovementType = "adjustment"
)
type StockMovement struct {
BaseModel
InventoryItemID string `json:"inventory_item_id" gorm:"type:text;not null;index"`
InventoryItem InventoryItem `json:"inventory_item" gorm:"foreignKey:InventoryItemID"`
Type StockMovementType `json:"type" gorm:"type:text;not null"`
Quantity float64 `json:"quantity" gorm:"type:real;not null"`
PreviousStock float64 `json:"previous_stock" gorm:"type:real"`
NewStock float64 `json:"new_stock" gorm:"type:real"`
CostPerUnit float64 `json:"cost_per_unit" gorm:"type:real;default:0"`
TotalCost float64 `json:"total_cost" gorm:"type:real;default:0"`
ReferenceType string `json:"reference_type" gorm:"type:text"` // order, purchase_order, manual
ReferenceID string `json:"reference_id" gorm:"type:text"`
Notes string `json:"notes" gorm:"type:text"`
CreatedByID string `json:"created_by_id" gorm:"type:text"`
CreatedBy User `json:"created_by" gorm:"foreignKey:CreatedByID"`
}
// ============================================================================
// RECIPES
// ============================================================================
type Recipe struct {
BaseModel
ProductID string `json:"product_id" gorm:"type:text;not null;index"`
Product Product `json:"product" gorm:"foreignKey:ProductID"`
Name string `json:"name" gorm:"type:text;not null"`
Yield int `json:"yield" gorm:"type:integer;default:1"` // serves N portions
Notes string `json:"notes" gorm:"type:text"`
Items []RecipeItem `json:"items" gorm:"foreignKey:RecipeID"`
}
type RecipeItem struct {
BaseModel
RecipeID string `json:"recipe_id" gorm:"type:text;not null;index"`
InventoryItemID string `json:"inventory_item_id" gorm:"type:text;not null"`
InventoryItem InventoryItem `json:"inventory_item" gorm:"foreignKey:InventoryItemID"`
Quantity float64 `json:"quantity" gorm:"type:real;not null"`
Unit UnitType `json:"unit" gorm:"type:text;not null"`
}
// ============================================================================
// VENDORS & PURCHASE ORDERS
// ============================================================================
type Vendor struct {
BaseModel
Name string `json:"name" gorm:"type:text;not null"`
ContactPerson string `json:"contact_person" gorm:"type:text"`
Phone string `json:"phone" gorm:"type:text"`
Email string `json:"email" gorm:"type:text"`
Address string `json:"address" gorm:"type:text"`
GSTIN string `json:"gstin" gorm:"type:text"`
BankDetails string `json:"bank_details" gorm:"type:text"`
IsActive bool `json:"is_active" gorm:"default:true"`
Notes string `json:"notes" gorm:"type:text"`
}
type POStatus string
const (
PODraft POStatus = "draft"
POSent POStatus = "sent"
POReceived POStatus = "received"
POPartial POStatus = "partial"
POCancelled POStatus = "cancelled"
)
type PurchaseOrder struct {
BaseModel
PONumber string `json:"po_number" gorm:"type:text;uniqueIndex;not null"`
VendorID string `json:"vendor_id" gorm:"type:text;not null"`
Vendor Vendor `json:"vendor" gorm:"foreignKey:VendorID"`
Status POStatus `json:"status" gorm:"type:text;default:'draft'"`
TotalAmount float64 `json:"total_amount" gorm:"type:real;default:0"`
TaxAmount float64 `json:"tax_amount" gorm:"type:real;default:0"`
GrandTotal float64 `json:"grand_total" gorm:"type:real;default:0"`
Notes string `json:"notes" gorm:"type:text"`
OrderedAt *time.Time `json:"ordered_at"`
ReceivedAt *time.Time `json:"received_at"`
CreatedByID string `json:"created_by_id" gorm:"type:text"`
CreatedBy User `json:"created_by" gorm:"foreignKey:CreatedByID"`
Items []PurchaseOrderItem `json:"items" gorm:"foreignKey:PurchaseOrderID"`
}
type PurchaseOrderItem struct {
BaseModel
PurchaseOrderID string `json:"purchase_order_id" gorm:"type:text;not null;index"`
InventoryItemID string `json:"inventory_item_id" gorm:"type:text;not null"`
InventoryItem InventoryItem `json:"inventory_item" gorm:"foreignKey:InventoryItemID"`
Quantity float64 `json:"quantity" gorm:"type:real;not null"`
Unit UnitType `json:"unit" gorm:"type:text;not null"`
UnitPrice float64 `json:"unit_price" gorm:"type:real;not null"`
TotalPrice float64 `json:"total_price" gorm:"type:real;not null"`
ReceivedQty float64 `json:"received_qty" gorm:"type:real;default:0"`
}
// ============================================================================
// EXPENSES
// ============================================================================
type ExpenseCategory string
const (
ExpElectricity ExpenseCategory = "electricity"
ExpSalary ExpenseCategory = "salary"
ExpAdvertisement ExpenseCategory = "advertisement"
ExpPettyCash ExpenseCategory = "petty_cash"
ExpMaintenance ExpenseCategory = "maintenance"
ExpRent ExpenseCategory = "rent"
ExpOther ExpenseCategory = "other"
)
type Expense struct {
BaseModel
Category ExpenseCategory `json:"category" gorm:"type:text;not null"`
Description string `json:"description" gorm:"type:text;not null"`
Amount float64 `json:"amount" gorm:"type:real;not null"`
Date time.Time `json:"date" gorm:"type:datetime;not null"`
VendorName string `json:"vendor_name" gorm:"type:text"`
Reference string `json:"reference" gorm:"type:text"`
Receipt string `json:"receipt" gorm:"type:text"` // file path
ApprovedBy *string `json:"approved_by" gorm:"type:text"`
CreatedByID string `json:"created_by_id" gorm:"type:text"`
CreatedBy User `json:"created_by" gorm:"foreignKey:CreatedByID"`
Notes string `json:"notes" gorm:"type:text"`
}
// ============================================================================
// CRM CAMPAIGNS
// ============================================================================
type CampaignType string
const (
CampaignSMS CampaignType = "sms"
CampaignWhatsApp CampaignType = "whatsapp"
CampaignEmail CampaignType = "email"
)
type CRMCampaign struct {
BaseModel
Name string `json:"name" gorm:"type:text;not null"`
Type CampaignType `json:"type" gorm:"type:text;not null"`
Message string `json:"message" gorm:"type:text;not null"`
TargetCount int `json:"target_count" gorm:"type:integer;default:0"`
SentCount int `json:"sent_count" gorm:"type:integer;default:0"`
Status string `json:"status" gorm:"type:text;default:'draft'"` // draft, sent, scheduled
ScheduledAt *time.Time `json:"scheduled_at"`
SentAt *time.Time `json:"sent_at"`
CreatedByID string `json:"created_by_id" gorm:"type:text"`
}
// ============================================================================
// PRINTERS & PRINT ROUTING
// ============================================================================
type PrinterType string
const (
PrinterTypeBill PrinterType = "bill"
PrinterTypeKOT PrinterType = "kot"
PrinterTypeBoth PrinterType = "both"
)
type Printer struct {
BaseModel
Name string `json:"name" gorm:"type:text;not null"`
IPAddress string `json:"ip_address" gorm:"type:text;not null"`
Port int `json:"port" gorm:"type:integer;default:9100"`
PaperWidth int `json:"paper_width" gorm:"type:integer;default:80"` // 58 or 80
Type PrinterType `json:"type" gorm:"type:text;default:'bill'"`
IsActive bool `json:"is_active" gorm:"default:true"`
IsDefault bool `json:"is_default" gorm:"default:false"`
Routes []PrintRoute `json:"routes,omitempty" gorm:"foreignKey:PrinterID"`
}
type PrintRouteType string
const (
RouteByOrderType PrintRouteType = "order_type"
RouteByTableRange PrintRouteType = "table_range"
)
type PrintRoute struct {
BaseModel
PrinterID string `json:"printer_id" gorm:"type:text;not null;index"`
Printer Printer `json:"printer,omitempty" gorm:"foreignKey:PrinterID"`
RouteType PrintRouteType `json:"route_type" gorm:"type:text;not null"`
OrderSource string `json:"order_source" gorm:"type:text"` // for order_type routing
TableFrom int `json:"table_from" gorm:"type:integer;default:0"` // for table_range routing
TableTo int `json:"table_to" gorm:"type:integer;default:0"` // for table_range routing
}
// PrintJob stores print history
type PrintJob struct {
BaseModel
PrinterID string `json:"printer_id" gorm:"type:text;not null;index"`
Printer Printer `json:"printer,omitempty" gorm:"foreignKey:PrinterID"`
OrderID string `json:"order_id" gorm:"type:text;index"`
JobType string `json:"job_type" gorm:"type:text;not null"` // bill, kot
Status string `json:"status" gorm:"type:text;default:'pending'"` // pending, sent, failed
Error string `json:"error" gorm:"type:text"`
PrintedByID string `json:"printed_by_id" gorm:"type:text"`
PrintedBy User `json:"printed_by,omitempty" gorm:"foreignKey:PrintedByID"`
}
// ============================================================================
// AUDIT LOG
// ============================================================================
type AuditLog struct {
BaseModel
UserID string `json:"user_id" gorm:"type:text;not null;index"`
Username string `json:"username" gorm:"type:text"`
Action string `json:"action" gorm:"type:text;not null"` // create, update, delete, void, reprint
Module string `json:"module" gorm:"type:text;not null"` // order, kot, product, etc.
EntityID string `json:"entity_id" gorm:"type:text"`
OldValue string `json:"old_value" gorm:"type:text"` // JSON
NewValue string `json:"new_value" gorm:"type:text"` // JSON
IPAddress string `json:"ip_address" gorm:"type:text"`
Description string `json:"description" gorm:"type:text"`
}
// ============================================================================
// NOTIFICATION LOG
// ============================================================================
type NotificationLog struct {
BaseModel
Type string `json:"type" gorm:"type:text;not null"` // sms, whatsapp, email, push
Recipient string `json:"recipient" gorm:"type:text;not null"`
Subject string `json:"subject" gorm:"type:text"`
Message string `json:"message" gorm:"type:text;not null"`
Status string `json:"status" gorm:"type:text;default:'pending'"` // pending, sent, failed
Error string `json:"error" gorm:"type:text"`
SentAt *time.Time `json:"sent_at"`
}
// ============================================================================
// COUNTERS (for sequential numbering)
// ============================================================================
type Counter struct {
ID string `json:"id" gorm:"type:text;primaryKey"` // e.g., "order", "kot", "po"
Prefix string `json:"prefix" gorm:"type:text"`
Current int64 `json:"current" gorm:"type:integer;default:0"`
Date string `json:"date" gorm:"type:text"` // for daily reset
}
// ============================================================================
// PLATFORM CONFIG (Swiggy / Zomato / etc.)
// ============================================================================
type PlatformConfig struct {
BaseModel
Platform string `json:"platform" gorm:"type:text;uniqueIndex;not null"` // swiggy, zomato
DisplayName string `json:"display_name" gorm:"type:text"`
IsActive bool `json:"is_active" gorm:"type:boolean;default:false"`
AutoAccept bool `json:"auto_accept" gorm:"type:boolean;default:false"`
// API credentials
APIKey string `json:"api_key" gorm:"type:text"`
APISecret string `json:"-" gorm:"type:text"`
MerchantID string `json:"merchant_id" gorm:"type:text"`
RestaurantID string `json:"restaurant_id" gorm:"type:text"`
WebhookSecret string `json:"-" gorm:"type:text"`
// Callback URLs (for pushing status back to platform)
StatusCallbackURL string `json:"status_callback_url" gorm:"type:text"`
BaseURL string `json:"base_url" gorm:"type:text"`
// Commission
CommissionRate float64 `json:"commission_rate" gorm:"type:real;default:0"` // percentage
// Prep time defaults
DefaultPrepTime int `json:"default_prep_time" gorm:"type:integer;default:20"` // minutes
}
// ============================================================================
// WEBHOOK LOG
// ============================================================================
type WebhookLog struct {
BaseModel
Platform string `json:"platform" gorm:"type:text;not null;index"`
EventType string `json:"event_type" gorm:"type:text"`
Payload string `json:"payload" gorm:"type:text"`
Status string `json:"status" gorm:"type:text;default:'received'"` // received, processed, failed
OrderID string `json:"order_id" gorm:"type:text"`
ErrorMsg string `json:"error_message" gorm:"type:text"`
IPAddress string `json:"ip_address" gorm:"type:text"`
}
// ============================================================================
// HYBRID SYNC
// ============================================================================
// SyncOutbox stores pending events to be pushed to cloud.
// Local writes here after every significant operation; sync worker reads and pushes.
type SyncOutbox struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
EntityType string `json:"entity_type" gorm:"type:text;not null;index"` // order, payment, product, category, inventory, expense, customer
EntityID string `json:"entity_id" gorm:"type:text;not null"`
Action string `json:"action" gorm:"type:text;not null"` // create, update, delete
Payload string `json:"payload" gorm:"type:text"` // JSON snapshot of entity
LocationID string `json:"location_id" gorm:"type:text;not null;index"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
SyncedAt *time.Time `json:"synced_at" gorm:"index"` // NULL = not synced yet
RetryCount int `json:"retry_count" gorm:"default:0"`
Error string `json:"error" gorm:"type:text"`
}
// SyncLocation represents a registered restaurant location (stored on cloud).
type SyncLocation struct {
BaseModel
LocationID string `json:"location_id" gorm:"type:text;uniqueIndex;not null"`
Name string `json:"name" gorm:"type:text;not null"`
Address string `json:"address" gorm:"type:text"`
Phone string `json:"phone" gorm:"type:text"`
SyncToken string `json:"-" gorm:"type:text;not null"` // Hashed token for auth
LastSyncAt *time.Time `json:"last_sync_at"`
IsActive bool `json:"is_active" gorm:"default:true"`
TotalOrders int `json:"total_orders" gorm:"default:0"`
TotalRevenue float64 `json:"total_revenue" gorm:"default:0"`
}
// SyncedOrder stores order data received from local instances (cloud-side).
type SyncedOrder struct {
ID string `json:"id" gorm:"type:text;primaryKey"`
LocationID string `json:"location_id" gorm:"type:text;not null;index"`
OrderNumber string `json:"order_number" gorm:"type:text"`
Source string `json:"source" gorm:"type:text"`
Status string `json:"status" gorm:"type:text"`
PaymentStatus string `json:"payment_status" gorm:"type:text"`
SubTotal float64 `json:"sub_total"`
TaxAmount float64 `json:"tax_amount"`
DiscountAmount float64 `json:"discount_amount"`
GrandTotal float64 `json:"grand_total"`
PaymentMethod string `json:"payment_method" gorm:"type:text"`
ItemCount int `json:"item_count"`
CustomerName string `json:"customer_name" gorm:"type:text"`
CustomerPhone string `json:"customer_phone" gorm:"type:text"`
TableName string `json:"table_name" gorm:"type:text"`
StaffName string `json:"staff_name" gorm:"type:text"`
ItemsSummary string `json:"items_summary" gorm:"type:text"` // JSON array of {name, qty, total}
OrderDate time.Time `json:"order_date" gorm:"index"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// SyncedDailySummary stores aggregated daily data per location (cloud-side).
type SyncedDailySummary struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
LocationID string `json:"location_id" gorm:"type:text;not null;index:idx_daily_loc_date"`
Date string `json:"date" gorm:"type:text;not null;index:idx_daily_loc_date"` // YYYY-MM-DD
TotalOrders int `json:"total_orders"`
TotalRevenue float64 `json:"total_revenue"`
TotalTax float64 `json:"total_tax"`
TotalDiscount float64 `json:"total_discount"`
CashRevenue float64 `json:"cash_revenue"`
CardRevenue float64 `json:"card_revenue"`
UPIRevenue float64 `json:"upi_revenue"`
OtherRevenue float64 `json:"other_revenue"`
AvgOrderValue float64 `json:"avg_order_value"`
TopItems string `json:"top_items" gorm:"type:text"` // JSON
OrdersByHour string `json:"orders_by_hour" gorm:"type:text"` // JSON
CancelledOrders int `json:"cancelled_orders"`
CancelledValue float64 `json:"cancelled_value"`
ExpenseTotal float64 `json:"expense_total"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
// SyncStatus holds the last sync state (stored locally for UI display).
type SyncStatus struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
LastSyncAt *time.Time `json:"last_sync_at"`
LastSyncStatus string `json:"last_sync_status" gorm:"type:text;default:'never'"` // never, success, failed, syncing
LastSyncError string `json:"last_sync_error" gorm:"type:text"`
PendingCount int `json:"pending_count" gorm:"default:0"`
CloudReachable bool `json:"cloud_reachable" gorm:"default:false"`
LastMenuPullAt *time.Time `json:"last_menu_pull_at"`
}
// PendingExternalOrder stores incoming webhook orders on the cloud, waiting for local to pull.
// Cloud receives webhooks from Swiggy/Zomato/WhatsApp → stores here → local polls every 15s.
type PendingExternalOrder struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
LocationID string `json:"location_id" gorm:"type:text;not null;index"`
Platform string `json:"platform" gorm:"type:text;not null"` // swiggy, zomato, whatsapp
ExternalID string `json:"external_id" gorm:"type:text"` // Platform's order ID
Payload string `json:"payload" gorm:"type:text;not null"` // Full webhook payload (JSON)
Status string `json:"status" gorm:"type:text;default:'pending';index"` // pending, pulled, processed, failed
PulledAt *time.Time `json:"pulled_at"`
ProcessedAt *time.Time `json:"processed_at"`
ErrorMsg string `json:"error_msg" gorm:"type:text"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
}
// ============================================================================
// ALL MODELS for migration
// ============================================================================
func AllModels() []interface{} {
return []interface{}{
&Role{},
&Permission{},
&User{},
&RestaurantConfig{},
&Customer{},
&TableSection{},
&RestaurantTable{},
&TableSession{},
&Category{},
&Product{},
&ModifierGroup{},
&Modifier{},
&Order{},
&OrderItem{},
&OrderItemModifier{},
&KOT{},
&KOTItem{},
&Payment{},
&InventoryItem{},
&StockMovement{},
&Recipe{},
&RecipeItem{},
&Vendor{},
&PurchaseOrder{},
&PurchaseOrderItem{},
&Expense{},
&CRMCampaign{},
&AuditLog{},
&NotificationLog{},
&Counter{},
&Printer{},
&PrintRoute{},
&PrintJob{},
&PlatformConfig{},
&WebhookLog{},
// Hybrid sync models
&SyncOutbox{},
&SyncLocation{},
&SyncedOrder{},
&SyncedDailySummary{},
&SyncStatus{},
&PendingExternalOrder{},
}
}