heroagent/pkg/heroservices/billing/models/account.go
2025-04-23 04:18:28 +02:00

116 lines
3.2 KiB
Go

package models
import (
"encoding/binary"
"errors"
"math"
"strings"
)
// Account represents a financial account in the billing system
type Account struct {
ID uint32 // Unique identifier, auto-incremented
UserID uint32 // Link to the unique user ID
Name string // Name of the account
Currency string // Currency code (e.g., USD, EUR), always uppercase, 3 letters
Amount float64 // Current balance, always positive
}
// Serialize converts an Account to a binary representation
func (a *Account) Serialize() []byte {
// Calculate the size of the serialized data
// 4 bytes for ID + 4 bytes for UserID + 2 bytes for name length + len(name) bytes +
// 3 bytes for currency + 8 bytes for amount
size := 4 + 4 + 2 + len(a.Name) + 3 + 8
data := make([]byte, size)
// Write ID (4 bytes)
binary.LittleEndian.PutUint32(data[0:4], a.ID)
// Write UserID (4 bytes)
binary.LittleEndian.PutUint32(data[4:8], a.UserID)
// Write name length (2 bytes) and name
nameLen := uint16(len(a.Name))
binary.LittleEndian.PutUint16(data[8:10], nameLen)
copy(data[10:10+nameLen], a.Name)
// Write currency (3 bytes)
currencyOffset := 10 + nameLen
copy(data[currencyOffset:currencyOffset+3], a.Currency)
// Write amount (8 bytes)
amountOffset := currencyOffset + 3
binary.LittleEndian.PutUint64(data[amountOffset:amountOffset+8], math.Float64bits(a.Amount))
return data
}
// DeserializeAccount converts a binary representation back to an Account
func DeserializeAccount(data []byte) (*Account, error) {
if len(data) < 17 { // Minimum size: 4 (ID) + 4 (UserID) + 2 (name length) + 3 (currency) + 8 (amount)
return nil, errors.New("data too short to deserialize Account")
}
account := &Account{}
// Read ID
account.ID = binary.LittleEndian.Uint32(data[0:4])
// Read UserID
account.UserID = binary.LittleEndian.Uint32(data[4:8])
// Read name length and name
nameLen := binary.LittleEndian.Uint16(data[8:10])
if 10+nameLen > uint16(len(data)) {
return nil, errors.New("data too short to read name")
}
account.Name = string(data[10 : 10+nameLen])
// Read currency
currencyOffset := 10 + nameLen
if int(currencyOffset)+3 > len(data) {
return nil, errors.New("data too short to read currency")
}
account.Currency = string(data[currencyOffset : currencyOffset+3])
// Read amount
amountOffset := currencyOffset + 3
if int(amountOffset)+8 > len(data) {
return nil, errors.New("data too short to read amount")
}
account.Amount = math.Float64frombits(binary.LittleEndian.Uint64(data[amountOffset : amountOffset+8]))
return account, nil
}
// Validate checks if the account data is valid
func (a *Account) Validate() error {
if a.UserID == 0 {
return errors.New("user ID cannot be zero")
}
if a.Name == "" {
return errors.New("account name cannot be empty")
}
// Check currency format (3 uppercase letters)
if len(a.Currency) != 3 {
return errors.New("currency must be 3 characters")
}
a.Currency = strings.ToUpper(a.Currency)
for _, r := range a.Currency {
if r < 'A' || r > 'Z' {
return errors.New("currency must contain only uppercase letters")
}
}
// Ensure amount is non-negative
if a.Amount < 0 {
return errors.New("amount cannot be negative")
}
return nil
}