diff --git a/api/api.go b/api/api.go index 2c852c5c82d578ca8b57f4689f798f89f05c0303..d0c23184dcc49065edcfbd0d2c44caf1fb839aac 100644 --- a/api/api.go +++ b/api/api.go @@ -51,6 +51,10 @@ func Init(dbPath string, signKey string, mail *Mail, r *mux.Router) error { r.HandleFunc("/supplier", a.auth(a.ListSuppliers)).Methods("GET") r.HandleFunc("/supplier", a.authAdmin(a.AddSupplier)).Methods("POST") + r.HandleFunc("/inventary", a.authAdmin(a.ListInventary)).Methods("GET") + r.HandleFunc("/inventary/{id:[0-9]+}", a.authAdmin(a.GetInventary)).Methods("GET") + r.HandleFunc("/inventary", a.authAdminNum(a.AddInventary)).Methods("POST") + r.HandleFunc("/transaction", a.authAdmin(a.ListTransactions)).Methods("GET") r.HandleFunc("/transaction/{id:[0-9]+}", a.authNumRole(a.GetTransaction)).Methods("GET") r.HandleFunc("/transaction/mine", a.authNum(a.getTransactionsByMember)).Methods("GET") diff --git a/api/db/db.go b/api/db/db.go index 694ca20bcbcfc6fe4096afd3e833425d43bcc099..d7752f57a4a126f6e0ca1ac19baa25351d49aeef 100644 --- a/api/db/db.go +++ b/api/db/db.go @@ -16,6 +16,7 @@ func Init(dbPath string) (*DB, error) { } db.AutoMigrate(&Member{}, &Product{}, &Purchase{}, &Topup{}, &Transaction{}, - &OrderPurchase{}, &Order{}, &PasswordReset{}, &Supplier{}) + &OrderPurchase{}, &Order{}, &PasswordReset{}, &Supplier{}, + &Inventary{}, &InventaryProduct{}) return &DB{db}, err } diff --git a/api/db/inventary.go b/api/db/inventary.go index 9489f4fb27fae3c4dd86ebc70822d3d61364fd34..9841dab5ecf5a4ca2d644ef9a932ceb4b78749e9 100644 --- a/api/db/inventary.go +++ b/api/db/inventary.go @@ -1,15 +1,81 @@ package db +import ( + "time" + + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + type Supplier struct { gorm.Model - Name string `json:"name"` + Name string `json:"name" gorm:"unique;index"` } -func (d *DB) AddSupplier(supplier Supplier) error { - return d.db.Create(&supplier).Error +type Inventary struct { + gorm.Model + MemberNum int `json:"-" gorm:"column:member"` + Member *Member `json:"member,omitempty" gorm:"foreignKey:MemberNum;references:Num"` + Date time.Time `json:"date"` + SupplierID *uint `json:"-"` + Supplier *Supplier `json:"supplier"` + Comment string `json:"comment"` + Products []InventaryProduct `json:"products"` +} + +type InventaryProduct struct { + gorm.Model + InventaryID uint `json:"-"` + ProductCode int `json:"code" gorm:"column:product"` + Product *Product `json:"product" gorm:"foreignKey:ProductCode;references:Code"` + StockUpdate *int `json:"stock"` + Price *int `json:"price"` +} + +func (d *DB) AddSupplier(supplier *Supplier) error { + return d.db.Create(supplier).Error } func (d *DB) ListSuppliers() (suppliers []Supplier, err error) { err = d.db.Find(&suppliers).Error return } + +func (d *DB) AddInventary(num int, inventary *Inventary) error { + inventary.Date = time.Now() + inventary.MemberNum = num + return d.db.Transaction(func(tx *gorm.DB) error { + for _, product := range inventary.Products { + query := tx.Model(&Product{}). + Where("code = ?", product.ProductCode) + if product.StockUpdate != nil { + query = query.Update("stock", gorm.Expr("stock + ?", *product.StockUpdate)) + } + if product.Price != nil { + query = query.Update("price", *product.Price) + } + err := query.Error + if err != nil { + return err + } + } + + return tx.Create(inventary).Error + }) +} + +func (d *DB) GetInventary(id int) (inventary Inventary, err error) { + err = d.db.Preload("Products.Product"). + Preload(clause.Associations). + First(&inventary, id). + Error + return +} + +func (d *DB) ListInventary() (inventaries []Inventary, err error) { + err = d.db.Preload("Products.Product"). + Preload(clause.Associations). + Find(&inventaries). + Error + return +} diff --git a/api/inventary.go b/api/inventary.go index 48e826ab00c4c3a1ed49691ef0f989e3663f9701..1c067012cca18fbdc562d94b4f60cfff49d99f04 100644 --- a/api/inventary.go +++ b/api/inventary.go @@ -4,8 +4,10 @@ import ( "encoding/json" "log" "net/http" + "strconv" "0xacab.org/meskio/cicer/api/db" + "github.com/gorilla/mux" ) func (a *api) AddSupplier(w http.ResponseWriter, req *http.Request) { @@ -16,7 +18,7 @@ func (a *api) AddSupplier(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusInternalServerError) return } - err = a.db.AddSupplier(supplier) + err = a.db.AddSupplier(&supplier) if err != nil { log.Printf("Can't create supplier: %v\n%v", err, supplier) w.WriteHeader(http.StatusInternalServerError) @@ -49,3 +51,64 @@ func (a *api) ListSuppliers(w http.ResponseWriter, req *http.Request) { return } } + +func (a *api) AddInventary(num int, w http.ResponseWriter, req *http.Request) { + var inventary db.Inventary + err := json.NewDecoder(req.Body).Decode(&inventary) + if err != nil { + log.Printf("Can't decode inventary: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + err = a.db.AddInventary(num, &inventary) + if err != nil { + log.Printf("Can't create inventary: %v\n%v", err, inventary) + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + err = json.NewEncoder(w).Encode(inventary) + if err != nil { + log.Printf("Can't encode added inventary: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } +} + +func (a *api) GetInventary(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + id, _ := strconv.Atoi(vars["id"]) + inventary, err := a.db.GetInventary(id) + if err != nil { + log.Printf("Can't get inventary: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + err = json.NewEncoder(w).Encode(inventary) + if err != nil { + log.Printf("Can't encode inventary: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } +} + +func (a *api) ListInventary(w http.ResponseWriter, req *http.Request) { + inventary, err := a.db.ListInventary() + if err != nil { + log.Printf("Can't list inventary: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + err = json.NewEncoder(w).Encode(inventary) + if err != nil { + log.Printf("Can't encode inventary: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } +} diff --git a/api/inventary_test.go b/api/inventary_test.go index 0d7ecc88c8fc9fdf8b1d344c69620e561788f46b..e34e7136289c58ca811e35d7387f3db69e9860d3 100644 --- a/api/inventary_test.go +++ b/api/inventary_test.go @@ -1,6 +1,7 @@ package api import ( + "fmt" "net/http" "testing" @@ -30,9 +31,139 @@ func TestSupplierAddList(t *testing.T) { } } -func (tapi *testAPI) addTestSuppliers() { - resp := tapi.doAdmin("POST", "/supplier", testSupplier, nil) +func TestInventaryUpdateStock(t *testing.T) { + tapi := newTestAPI(t) + defer tapi.close() + supplierID := tapi.addTestSuppliers() + tapi.addTestProducts() + + update := -5 + inventary := db.Inventary{ + SupplierID: &supplierID, + Comment: "Got aceite", + Products: []db.InventaryProduct{ + { + ProductCode: testProduct.Code, + StockUpdate: &update, + }, + }, + } + resp := tapi.doAdmin("POST", "/inventary", inventary, nil) + if resp.StatusCode != http.StatusCreated { + t.Fatal("Can't create inventary:", resp.Status) + } + + var newInventary []db.Inventary + resp = tapi.doAdmin("GET", "/inventary", nil, &newInventary) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get inventary:", resp.Status) + } + if len(newInventary) != 1 { + t.Fatal("Wrong inventary length:", newInventary) + } + if newInventary[0].Comment != inventary.Comment { + t.Error("Wrong comment:", newInventary[0].Comment) + } + if len(newInventary[0].Products) != 1 { + t.Error("Wrong products:", newInventary[0].Products) + } + + var entry db.Inventary + resp = tapi.doAdmin("GET", fmt.Sprintf("/inventary/%d", newInventary[0].ID), nil, &entry) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get inventary:", resp.Status) + } + if entry.Comment != inventary.Comment { + t.Error("Wrong comment:", entry.Comment) + } + if len(entry.Products) != 1 { + t.Error("Wrong products:", entry.Products) + } + + var product db.Product + resp = tapi.doAdmin("GET", fmt.Sprintf("/product/%d", testProduct.Code), nil, &product) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get product:", resp.Status) + } + if product.Stock != testProduct.Stock+*inventary.Products[0].StockUpdate { + t.Error("Wrong stock:", product.Stock) + } +} + +func TestInventaryUpdatePrice(t *testing.T) { + tapi := newTestAPI(t) + defer tapi.close() + supplierID := tapi.addTestSuppliers() + tapi.addTestProducts() + + update := 1000 + inventary := db.Inventary{ + SupplierID: &supplierID, + Comment: "Got aceite", + Products: []db.InventaryProduct{ + { + ProductCode: testProduct.Code, + Price: &update, + }, + }, + } + resp := tapi.doAdmin("POST", "/inventary", inventary, nil) + if resp.StatusCode != http.StatusCreated { + t.Fatal("Can't create inventary:", resp.Status) + } + + var product db.Product + resp = tapi.doAdmin("GET", fmt.Sprintf("/product/%d", testProduct.Code), nil, &product) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get product:", resp.Status) + } + if product.Price != *inventary.Products[0].Price { + t.Error("Wrong price:", product.Price) + } +} + +func TestInventaryUpdateBoth(t *testing.T) { + tapi := newTestAPI(t) + defer tapi.close() + supplierID := tapi.addTestSuppliers() + tapi.addTestProducts() + + updateStock := 5 + price := 800 + inventary := db.Inventary{ + SupplierID: &supplierID, + Comment: "Got aceite", + Products: []db.InventaryProduct{ + { + ProductCode: testProduct.Code, + StockUpdate: &updateStock, + Price: &price, + }, + }, + } + resp := tapi.doAdmin("POST", "/inventary", inventary, nil) + if resp.StatusCode != http.StatusCreated { + t.Fatal("Can't create inventary:", resp.Status) + } + + var product db.Product + resp = tapi.doAdmin("GET", fmt.Sprintf("/product/%d", testProduct.Code), nil, &product) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get product:", resp.Status) + } + if product.Stock != testProduct.Stock+*inventary.Products[0].StockUpdate { + t.Error("Wrong stock:", product.Stock) + } + if product.Price != *inventary.Products[0].Price { + t.Error("Wrong price:", product.Price) + } +} + +func (tapi *testAPI) addTestSuppliers() uint { + var supplier db.Supplier + resp := tapi.doAdmin("POST", "/supplier", testSupplier, &supplier) if resp.StatusCode != http.StatusCreated { tapi.t.Fatal("Can't create supplier:", resp.Status) } + return supplier.ID }