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
 }