commit 109fd2eb896c4d5d6c05d3de6850b830106b1237
parent bfc41dd7ac1e3a3afa390fe5fa9a5bcca00077bc
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date: Wed, 9 Oct 2024 10:46:56 -0700
Add furniture styles
Variety might have also been a good term for them, but it's already
taken. These are for things like round tables, etc.
Diffstat:
2 files changed, 113 insertions(+), 37 deletions(-)
diff --git a/cmd/api/migration/2024-10-09T04:46:11.sql b/cmd/api/migration/2024-10-09T04:46:11.sql
@@ -0,0 +1,44 @@
+BEGIN;
+
+CREATE TABLE spaceplanner.furniture_styles (
+ type varchar REFERENCES spaceplanner.furniture_types(name) NOT NULL,
+ style varchar NOT NULL,
+ CONSTRAINT unique_style_for_type UNIQUE(style, type)
+);
+
+INSERT INTO spaceplanner.furniture_styles (type, style) VALUES
+ ('table', 'round'),
+ ('table', 'semi-circle');
+
+ALTER TABLE spaceplanner.furniture
+ ADD COLUMN style varchar;
+
+ALTER TABLE spaceplanner.furniture_types
+ ADD COLUMN class varchar;
+
+INSERT INTO spaceplanner.furniture_types (name, class) VALUES
+ ('toilet', 'fixture');
+
+CREATE FUNCTION spaceplanner.is_style_for_type()
+ RETURNS trigger AS $is_style_for_type$
+ DECLARE
+ bad int;
+ BEGIN
+ SELECT count(style)
+ INTO bad
+ FROM spaceplanner.furniture_styles
+ WHERE type = NEW.type AND style = NEW.style
+ LIMIT 1;
+ IF (NEW.style <> null AND bad <> 1) THEN
+ RAISE EXCEPTION 'Style (%s) does not exist for that type (%s)', NEW.style, NEW.type;
+ END IF;
+ RETURN NEW;
+ END;
+ $is_style_for_type$ LANGUAGE plpgsql;
+
+CREATE CONSTRAINT TRIGGER check_furniture_style
+ AFTER INSERT OR UPDATE ON spaceplanner.furniture
+ FOR EACH ROW
+ EXECUTE FUNCTION spaceplanner.is_style_for_type();
+
+END;
diff --git a/internal/backend/floorplan_data.go b/internal/backend/floorplan_data.go
@@ -48,6 +48,7 @@ type Furniture struct {
id ObjectID
OldID *ObjectID `json:"old_id,omitempty"`
Type string `json:"type" binding:"required"`
+ Style *string `json:"style,omitempty"`
Name *string `json:"name"`
Width int `json:"width" binding:"required"`
Depth int `json:"depth" binding:"required"`
@@ -85,6 +86,8 @@ type DBObject interface {
type FurnitureType struct {
Varieties map[string]FurnitureVariety `json:"varieties,omit_empty"`
+ Class *string `json:"class,omit_empty"`
+ Styles *[]string `json:"styles,omit_empty"`
}
type FurnitureVariety struct {
@@ -108,58 +111,87 @@ func init() {
}
}
-func (e *Env) FurnitureTypes(tx *sql.Tx) (map[string]FurnitureType, error) {
+func (e *Env) FurnitureTypes(tx *sql.Tx) (map[string]*FurnitureType, error) {
types, err := e.CacheTxStmt(tx, "furn_types",
- `SELECT name from spaceplanner.furniture_types`)
+ `SELECT name, class FROM spaceplanner.furniture_types`)
if err != nil {
return nil, err
}
+
vars, err := e.CacheTxStmt(tx, "furn_vars",
- `SELECT type, name, width, depth from spaceplanner.furniture_varieties`)
+ `SELECT type, name, width, depth FROM spaceplanner.furniture_varieties`)
if err != nil {
return nil, err
}
- rows, err := vars.Query()
+ styles, err := e.CacheTxStmt(tx, "furn_styles",
+ `SELECT style FROM spaceplanner.furniture_styles WHERE type = $1`)
if err != nil {
return nil, err
}
- defer rows.Close()
- data := make(map[string]FurnitureType)
+ data := make(map[string]*FurnitureType)
+
+ rows, err := types.Query()
+ if err != nil {
+ return nil, err
+ }
+ // Is this evaluating the expression at execution or now?
+ // that would change whether this should be run.
+ defer rows.Close()
for rows.Next() {
- var tname, vname string
- var v FurnitureVariety
- if err := rows.Scan(&tname, &vname, &v.Width, &v.Depth); err != nil {
+ var key string
+ var class *string
+ if err := rows.Scan(&key, &class); err != nil {
return nil, err
}
-
- if t, exists := data[tname]; exists {
- t.Varieties[vname] = v
- } else {
- t := FurnitureType{}
- t.Varieties = make(map[string]FurnitureVariety)
- t.Varieties[vname] = v
- data[tname] = t
+ d := FurnitureType{}
+ if class != nil {
+ d.Class = class
}
+ data[key] = &d
}
- rows.Close()
- rows, err = types.Query()
+ rows, err = vars.Query()
if err != nil {
return nil, err
}
- // Is this evaluating the expression at execution or now?
- // that would change whether this should be run.
defer rows.Close()
-
for rows.Next() {
- var key string
- if err := rows.Scan(&key); err != nil {
+ var tn, vn string
+ var v FurnitureVariety
+ if err := rows.Scan(&tn, &vn, &v.Width, &v.Depth); err != nil {
return nil, err
}
- if _, exists := data[key]; !exists {
- data[key] = FurnitureType{}
+
+ d := data[tn]
+ if d.Varieties == nil {
+ d.Varieties = make(map[string]FurnitureVariety)
+ }
+ d.Varieties[vn] = v
+ }
+ rows.Close()
+
+ for k := range data {
+ a := make([]string, 0, 4)
+ kstyles, err := styles.Query(k)
+ if err != nil {
+ return nil, err
+ }
+
+ defer kstyles.Close()
+ for kstyles.Next() {
+ var s string
+ if err := kstyles.Scan(&s); err != nil {
+ return nil, err
+ }
+ a = append(a, s)
+ }
+ kstyles.Close()
+
+ if len(a) > 0 {
+ d := data[k]
+ d.Styles = &a
}
}
@@ -248,7 +280,7 @@ func (e *Env) getFloorplanPointMaps(tx *sql.Tx, floorplan ObjectID) (map[ObjectI
func (e *Env) getFloorplanFurnitureDefs(tx *sql.Tx, floorplan ObjectID) (map[ObjectID]Furniture, error) {
defsStmt, err := e.CacheTxStmt(tx, "furniture_defs",
- `SELECT id, type, name, width, depth
+ `SELECT id, type, style, name, width, depth
FROM spaceplanner.furniture
WHERE floorplan = $1`)
if err != nil {
@@ -649,33 +681,33 @@ func (e *Env) DeletePointMap(tx *sql.Tx, floorplan, id ObjectID) (PointMap, erro
func (f Furniture) Create(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) {
ins, err := e.CacheTxStmt(tx, "add_furn",
- `INSERT INTO spaceplanner.furniture (floorplan, type, name, width, depth)
- VALUES ($1, $2, $3, $4, $5)
- RETURNING id, type, name, width, depth`)
+ `INSERT INTO spaceplanner.furniture (floorplan, type, style, name, width, depth)
+ VALUES ($1, $2, $3, $4, $5, $6)
+ RETURNING id, type, style, name, width, depth`)
if err != nil {
return f, err
}
- return scanFurniture(ins.QueryRow(floorplan.Seq, f.Type, f.Name, f.Width, f.Depth))
+ return scanFurniture(ins.QueryRow(floorplan.Seq, f.Type, f.Style, f.Name, f.Width, f.Depth))
}
func (f Furniture) Update(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) {
update, err := e.CacheTxStmt(tx, "update_furn",
- `UPDATE spaceplanner.furniture SET (type, name, width, depth) =
- ($3, $4, $5, $6) WHERE floorplan = $1 AND id = $2
- RETURNING id, type, name, width, depth`)
+ `UPDATE spaceplanner.furniture SET (type, style, name, width, depth) =
+ ($3, $4, $5, $6, $7) WHERE floorplan = $1 AND id = $2
+ RETURNING id, type, style, name, width, depth`)
if err != nil {
return f, err
}
- return scanFurniture(update.QueryRow(floorplan.Seq, f.id.Seq, f.Type, f.Name, f.Width, f.Depth))
+ return scanFurniture(update.QueryRow(floorplan.Seq, f.id.Seq, f.Type, f.Style, f.Name, f.Width, f.Depth))
}
func (f Furniture) Delete(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) {
del, err := e.CacheTxStmt(tx, "dele_furn",
`DELETE FROM spaceplanner.furniture
WHERE floorplan = $1 AND id = $2
- RETURNING id, type, name, width, depth`)
+ RETURNING id, type, style, name, width, depth`)
if err != nil {
return f, err
}
@@ -882,7 +914,7 @@ func scanFurniture(row Scanner) (Furniture, error) {
var f Furniture
var id int64
- err := row.Scan(&id, &f.Type, &f.Name, &f.Width, &f.Depth)
+ err := row.Scan(&id, &f.Type, &f.Style, &f.Name, &f.Width, &f.Depth)
if err != nil {
return f, err
}