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
|
package models
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tag struct {
|
||||||
|
ID int
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
type Cake struct {
|
type Cake struct {
|
||||||
Name string
|
Name string
|
||||||
ID int
|
ID int
|
||||||
Price Price // increments of 0.01
|
Price Price // increments of 0.01
|
||||||
Amount int
|
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 {
|
type Order struct {
|
||||||
ID int
|
ID int
|
||||||
Name string
|
Name string
|
||||||
Surname string
|
Surname string
|
||||||
Phone string
|
Phone string
|
||||||
Location string
|
Location string
|
||||||
Date time.Time
|
Date time.Time
|
||||||
Accepted time.Time
|
Accepted time.Time
|
||||||
Status string
|
Status string
|
||||||
Paid Price // increments of 0.01
|
Paid Price // increments of 0.01
|
||||||
Cakes []Cake
|
Cakes []Cake
|
||||||
|
SpecialCakes []SpecialCake
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Order) Subtotal() Price {
|
func (o *Order) Subtotal() Price {
|
||||||
|
|
@ -27,6 +79,9 @@ func (o *Order) Subtotal() Price {
|
||||||
for _, e := range o.Cakes {
|
for _, e := range o.Cakes {
|
||||||
out += e.Price * Price(e.Amount)
|
out += e.Price * Price(e.Amount)
|
||||||
}
|
}
|
||||||
|
for _, s := range o.SpecialCakes {
|
||||||
|
out += s.Price
|
||||||
|
}
|
||||||
return out
|
return out
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -37,3 +92,14 @@ func (o *Order) Total() Price {
|
||||||
func (c *Cake) Total() Price {
|
func (c *Cake) Total() Price {
|
||||||
return Price(c.Amount) * c.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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
@ -12,18 +11,14 @@ type Price int
|
||||||
func (in Price) String() string {
|
func (in Price) String() string {
|
||||||
if in%100 == 0 {
|
if in%100 == 0 {
|
||||||
return strconv.Itoa(int(in) / 100)
|
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) {
|
func ParsePrice(in string) (Price, error) {
|
||||||
log.Println(in)
|
|
||||||
in = strings.ReplaceAll(in, ",", ".")
|
in = strings.ReplaceAll(in, ",", ".")
|
||||||
log.Println(in)
|
|
||||||
if !strings.Contains(in, ".") {
|
if !strings.Contains(in, ".") {
|
||||||
price, err := strconv.Atoi(in)
|
price, err := strconv.Atoi(in)
|
||||||
return Price(price) * 100, err
|
return Price(price) * 100, err
|
||||||
|
|
@ -39,6 +34,5 @@ func ParsePrice(in string) (Price, error) {
|
||||||
if len(after) < 1 {
|
if len(after) < 1 {
|
||||||
price *= 10
|
price *= 10
|
||||||
}
|
}
|
||||||
log.Println(price)
|
|
||||||
return Price(price), err
|
return Price(price), err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ func (s *Store) GetCake(id int) (models.Cake, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return models.Cake{}, errors.New("cake not found")
|
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) {
|
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 {
|
if err := rows.Scan(&cake.ID, &cake.Name, &cake.Price); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
tags, err := s.GetCakeTags(cake.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cake.Tags = tags
|
||||||
out = append(out, cake)
|
out = append(out, cake)
|
||||||
}
|
}
|
||||||
return out, rows.Err()
|
return out, rows.Err()
|
||||||
|
|
@ -47,7 +53,7 @@ func (s *Store) SaveCake(newCake models.Cake) (int, error) {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
log.Printf("UPDATE cake id=%d name=%q price=%d", newCake.ID, newCake.Name, newCake.Price)
|
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 (?, ?);"
|
q := "insert into cake(name, price) values (?, ?);"
|
||||||
|
|
@ -62,5 +68,13 @@ func (s *Store) SaveCake(newCake models.Cake) (int, error) {
|
||||||
}
|
}
|
||||||
newCake.ID = int(id)
|
newCake.ID = int(id)
|
||||||
log.Printf("INSERT cake id=%d name=%q price=%d", newCake.ID, newCake.Name, newCake.Price)
|
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