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:
parent
06231cc490
commit
9aafbb94ec
8 changed files with 297 additions and 23 deletions
|
|
@ -1,25 +1,77 @@
|
|||
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 {
|
||||
ID int
|
||||
Name string
|
||||
Surname string
|
||||
Phone string
|
||||
Location string
|
||||
Date time.Time
|
||||
Accepted time.Time
|
||||
Status string
|
||||
Paid Price // increments of 0.01
|
||||
Cakes []Cake
|
||||
ID int
|
||||
Name string
|
||||
Surname string
|
||||
Phone string
|
||||
Location string
|
||||
Date time.Time
|
||||
Accepted time.Time
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
price := strconv.Itoa(int(in))
|
||||
i := len(price) - 2
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
10
store/migrations/2.sql
Normal 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
10
store/migrations/3.sql
Normal 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
5
store/migrations/4.sql
Normal 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
97
store/special_cake.go
Normal 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
78
store/tag.go
Normal 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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue