tags and special cakes: models, migrations, store layer

Add Tag, SpecialCake, Cake.Tags, Order.SpecialCakes types.
Add migrations for tag, cake_tag, special_cake, special_cake_tag tables.
Add store methods: GetTags, GetCakeTags, SaveCakeTags, CreateTag,
GetSpecialCakes, GetSpecialCakeTags, SaveSpecialCakes, saveSpecialCakeTags.
Load tags in GetCake/GetCakes, cascade tag saves in SaveCake.
Remove debug log calls from ParsePrice.
This commit is contained in:
bronkuu 2026-06-15 18:49:21 +02:00
parent 06231cc490
commit 9aafbb94ec
8 changed files with 297 additions and 23 deletions

View file

@ -1,12 +1,63 @@
package models
import "time"
import (
"fmt"
"time"
)
type Tag struct {
ID int
Name string
}
type Cake struct {
Name string
ID int
Price Price // increments of 0.01
Amount int
Tags []Tag
}
type SpecialCake struct {
ID int
OrderID int
Name string
Price Price
Size string
Shape string
Flavour string
Notes string
Tags []Tag
}
func (s SpecialCake) Detail() string {
parts := make([]string, 0, 4)
if s.Size != "" {
parts = append(parts, s.Size)
}
if s.Flavour != "" {
parts = append(parts, s.Flavour)
}
if s.Shape == "square" {
parts = append(parts, "kwadrat")
} else if s.Shape == "round" {
parts = append(parts, "okrągły")
}
if s.Notes != "" {
parts = append(parts, "notatka: "+s.Notes)
}
if len(parts) == 0 {
return ""
}
return fmt.Sprintf("(%s)", joinParts(parts))
}
func joinParts(parts []string) string {
out := parts[0]
for i := 1; i < len(parts); i++ {
out += ", " + parts[i]
}
return out
}
type Order struct {
@ -20,6 +71,7 @@ type Order struct {
Status string
Paid Price // increments of 0.01
Cakes []Cake
SpecialCakes []SpecialCake
}
func (o *Order) Subtotal() Price {
@ -27,6 +79,9 @@ func (o *Order) Subtotal() Price {
for _, e := range o.Cakes {
out += e.Price * Price(e.Amount)
}
for _, s := range o.SpecialCakes {
out += s.Price
}
return out
}
@ -37,3 +92,14 @@ func (o *Order) Total() Price {
func (c *Cake) Total() Price {
return Price(c.Amount) * c.Price
}
type OrderCounts struct {
Active int
DueToday int
}
type CakeCount struct {
ID int
Name string
Amount int
}

View file

@ -2,7 +2,6 @@ package models
import (
"fmt"
"log"
"strconv"
"strings"
)
@ -12,18 +11,14 @@ type Price int
func (in Price) String() string {
if in%100 == 0 {
return strconv.Itoa(int(in) / 100)
} else {
}
price := strconv.Itoa(int(in))
i := len(price) - 2
price = price[:i] + "." + price[i:]
return price
}
return price[:i] + "." + price[i:]
}
func ParsePrice(in string) (Price, error) {
log.Println(in)
in = strings.ReplaceAll(in, ",", ".")
log.Println(in)
if !strings.Contains(in, ".") {
price, err := strconv.Atoi(in)
return Price(price) * 100, err
@ -39,6 +34,5 @@ func ParsePrice(in string) (Price, error) {
if len(after) < 1 {
price *= 10
}
log.Println(price)
return Price(price), err
}

View file

@ -18,7 +18,8 @@ func (s *Store) GetCake(id int) (models.Cake, error) {
if err != nil {
return models.Cake{}, errors.New("cake not found")
}
return out, nil
out.Tags, err = s.GetCakeTags(id)
return out, err
}
func (s *Store) GetCakes() ([]models.Cake, error) {
@ -34,6 +35,11 @@ func (s *Store) GetCakes() ([]models.Cake, error) {
if err := rows.Scan(&cake.ID, &cake.Name, &cake.Price); err != nil {
return nil, err
}
tags, err := s.GetCakeTags(cake.ID)
if err != nil {
return nil, err
}
cake.Tags = tags
out = append(out, cake)
}
return out, rows.Err()
@ -47,7 +53,7 @@ func (s *Store) SaveCake(newCake models.Cake) (int, error) {
return 0, err
}
log.Printf("UPDATE cake id=%d name=%q price=%d", newCake.ID, newCake.Name, newCake.Price)
return newCake.ID, nil
return newCake.ID, s.SaveCakeTags(newCake.ID, tagIDs(newCake.Tags))
}
q := "insert into cake(name, price) values (?, ?);"
@ -62,5 +68,13 @@ func (s *Store) SaveCake(newCake models.Cake) (int, error) {
}
newCake.ID = int(id)
log.Printf("INSERT cake id=%d name=%q price=%d", newCake.ID, newCake.Name, newCake.Price)
return newCake.ID, nil
return newCake.ID, s.SaveCakeTags(newCake.ID, tagIDs(newCake.Tags))
}
func tagIDs(tags []models.Tag) []int {
ids := make([]int, len(tags))
for i, t := range tags {
ids[i] = t.ID
}
return ids
}

10
store/migrations/2.sql Normal file
View file

@ -0,0 +1,10 @@
create table tag (
id integer primary key autoincrement,
name text not null unique
);
create table cake_tag (
cake integer not null references cake(id) on delete cascade,
tag integer not null references tag(id) on delete cascade,
primary key (cake, tag)
);

10
store/migrations/3.sql Normal file
View file

@ -0,0 +1,10 @@
create table special_cake (
id integer primary key autoincrement,
customer_order integer not null references customer_order(id) on delete cascade,
name text not null,
price integer not null,
size text not null default '',
shape text not null default '',
flavour text not null default '',
notes text not null default ''
);

5
store/migrations/4.sql Normal file
View file

@ -0,0 +1,5 @@
create table special_cake_tag (
special_cake integer not null references special_cake(id) on delete cascade,
tag integer not null references tag(id) on delete cascade,
primary key (special_cake, tag)
);

97
store/special_cake.go Normal file
View file

@ -0,0 +1,97 @@
package store
import (
"database/sql"
"log"
"git.bronku.xyz/bronku/cake-order-tracker/models"
)
func (s *Store) GetSpecialCakeTags(specialCakeID int) ([]models.Tag, error) {
rows, err := s.db.Query(`
select t.id, t.name from tag t
join special_cake_tag sct on sct.tag = t.id
where sct.special_cake = ?
order by t.name
`, specialCakeID)
if err != nil {
return nil, err
}
defer rows.Close()
var out []models.Tag
for rows.Next() {
var t models.Tag
if err := rows.Scan(&t.ID, &t.Name); err != nil {
return nil, err
}
out = append(out, t)
}
return out, rows.Err()
}
func (s *Store) GetSpecialCakes(orderID int) ([]models.SpecialCake, error) {
rows, err := s.db.Query("select id, customer_order, name, price, size, shape, flavour, notes from special_cake where customer_order = ?;", orderID)
if err != nil {
return nil, err
}
defer rows.Close()
var out []models.SpecialCake
for rows.Next() {
var sc models.SpecialCake
var price int
if err := rows.Scan(&sc.ID, &sc.OrderID, &sc.Name, &price, &sc.Size, &sc.Shape, &sc.Flavour, &sc.Notes); err != nil {
return nil, err
}
sc.Price = models.Price(price)
tags, err := s.GetSpecialCakeTags(sc.ID)
if err != nil {
return nil, err
}
sc.Tags = tags
out = append(out, sc)
}
return out, rows.Err()
}
func (s *Store) saveSpecialCakeTags(tx *sql.Tx, specialCakeID int, tagIDs []int) error {
if _, err := tx.Exec("delete from special_cake_tag where special_cake = ?;", specialCakeID); err != nil {
return err
}
for _, tid := range tagIDs {
if _, err := tx.Exec("insert or ignore into special_cake_tag(special_cake, tag) values (?, ?);", specialCakeID, tid); err != nil {
return err
}
}
return nil
}
func (s *Store) SaveSpecialCakes(tx *sql.Tx, orderID int, cakes []models.SpecialCake) error {
if _, err := tx.Exec("delete from special_cake where customer_order = ?;", orderID); err != nil {
return err
}
if len(cakes) == 0 {
return nil
}
q := "insert into special_cake(customer_order, name, price, size, shape, flavour, notes) values (?, ?, ?, ?, ?, ?, ?);"
for _, sc := range cakes {
log.Printf("INSERT special_cake order=%d name=%q price=%d", orderID, sc.Name, sc.Price)
res, err := tx.Exec(q, orderID, sc.Name, int(sc.Price), sc.Size, sc.Shape, sc.Flavour, sc.Notes)
if err != nil {
return err
}
id, err := res.LastInsertId()
if err != nil {
return err
}
tagIDs := make([]int, len(sc.Tags))
for i, t := range sc.Tags {
tagIDs[i] = t.ID
}
if err := s.saveSpecialCakeTags(tx, int(id), tagIDs); err != nil {
return err
}
}
return nil
}

78
store/tag.go Normal file
View file

@ -0,0 +1,78 @@
package store
import (
"git.bronku.xyz/bronku/cake-order-tracker/models"
)
func (s *Store) GetTags() ([]models.Tag, error) {
rows, err := s.db.Query("select id, name from tag order by name;")
if err != nil {
return nil, err
}
defer rows.Close()
var out []models.Tag
for rows.Next() {
var t models.Tag
if err := rows.Scan(&t.ID, &t.Name); err != nil {
return nil, err
}
out = append(out, t)
}
return out, rows.Err()
}
func (s *Store) GetCakeTags(cakeID int) ([]models.Tag, error) {
rows, err := s.db.Query(`
select t.id, t.name from tag t
join cake_tag ct on ct.tag = t.id
where ct.cake = ?
order by t.name
`, cakeID)
if err != nil {
return nil, err
}
defer rows.Close()
var out []models.Tag
for rows.Next() {
var t models.Tag
if err := rows.Scan(&t.ID, &t.Name); err != nil {
return nil, err
}
out = append(out, t)
}
return out, rows.Err()
}
func (s *Store) SaveCakeTags(cakeID int, tagIDs []int) error {
_, err := s.db.Exec("delete from cake_tag where cake = ?;", cakeID)
if err != nil {
return err
}
for _, tid := range tagIDs {
_, err := s.db.Exec("insert or ignore into cake_tag(cake, tag) values (?, ?);", cakeID, tid)
if err != nil {
return err
}
}
return nil
}
func (s *Store) CreateTag(name string) (int, error) {
result, err := s.db.Exec("insert into tag(name) values (?) on conflict(name) do nothing;", name)
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
if err != nil {
return 0, err
}
if id == 0 {
row := s.db.QueryRow("select id from tag where name = ?;", name)
if err := row.Scan(&id); err != nil {
return 0, err
}
}
return int(id), nil
}