From 068a6d9320f7464273fd139fd27a855265b2fb08 Mon Sep 17 00:00:00 2001 From: nochill Date: Mon, 6 Mar 2023 15:15:11 +0700 Subject: [PATCH] add purchase product transaction --- db/query/products.sql | 11 ++++-- db/sqlc/products.sql.go | 35 +++++++++++++++---- db/sqlc/products_test.go | 10 ++++-- db/sqlc/store.go | 39 +++++++++++++++++++-- db/sqlc/store_test.go | 74 ++++++++++++++++++++++++++++------------ go.mod | 1 + go.sum | 2 ++ util/random.go | 8 +++-- 8 files changed, 142 insertions(+), 38 deletions(-) diff --git a/db/query/products.sql b/db/query/products.sql index 44aa217..49e3117 100644 --- a/db/query/products.sql +++ b/db/query/products.sql @@ -10,11 +10,16 @@ INSERT INTO products ( ) RETURNING *; --- name: UpdateProductStock :one +-- name: GetStockForUpdateStock :one +SELECT * FROM products +WHERE id = $1 +LIMIT 1 +FOR NO KEY UPDATE; + +-- name: UpdateProductStock :exec UPDATE products SET stock = $1 -WHERE id = $2 -RETURNING stock; +WHERE id = $2; -- name: GetProduct :one SELECT * FROM products diff --git a/db/sqlc/products.sql.go b/db/sqlc/products.sql.go index e5d6f38..ae82bc7 100644 --- a/db/sqlc/products.sql.go +++ b/db/sqlc/products.sql.go @@ -87,6 +87,30 @@ func (q *Queries) GetProduct(ctx context.Context, id uuid.UUID) (Product, error) return i, err } +const getStockForUpdateStock = `-- name: GetStockForUpdateStock :one +SELECT id, merchant_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at FROM products +WHERE id = $1 +LIMIT 1 +FOR NO KEY UPDATE +` + +func (q *Queries) GetStockForUpdateStock(ctx context.Context, id uuid.UUID) (Product, error) { + row := q.db.QueryRowContext(ctx, getStockForUpdateStock, id) + var i Product + err := row.Scan( + &i.ID, + &i.MerchantID, + &i.IndexID, + &i.Name, + &i.SellingPrice, + &i.PurchasePrice, + &i.Stock, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + const listProducts = `-- name: ListProducts :many SELECT id, merchant_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at FROM products ORDER BY index_id @@ -172,11 +196,10 @@ func (q *Queries) UpdateProduct(ctx context.Context, arg UpdateProductParams) (P return i, err } -const updateProductStock = `-- name: UpdateProductStock :one +const updateProductStock = `-- name: UpdateProductStock :exec UPDATE products SET stock = $1 WHERE id = $2 -RETURNING stock ` type UpdateProductStockParams struct { @@ -184,9 +207,7 @@ type UpdateProductStockParams struct { ID uuid.UUID `json:"id"` } -func (q *Queries) UpdateProductStock(ctx context.Context, arg UpdateProductStockParams) (float64, error) { - row := q.db.QueryRowContext(ctx, updateProductStock, arg.Stock, arg.ID) - var stock float64 - err := row.Scan(&stock) - return stock, err +func (q *Queries) UpdateProductStock(ctx context.Context, arg UpdateProductStockParams) error { + _, err := q.db.ExecContext(ctx, updateProductStock, arg.Stock, arg.ID) + return err } diff --git a/db/sqlc/products_test.go b/db/sqlc/products_test.go index bb16a11..c2db9b8 100644 --- a/db/sqlc/products_test.go +++ b/db/sqlc/products_test.go @@ -13,12 +13,16 @@ import ( func createRandomProduct(t *testing.T) (Product, CreateProductParams) { + sellingPrice, _ := util.RandomFloat(999, 99999) + purchasePrice, _ := util.RandomFloat(999, 9999) + stock, _ := util.RandomFloat(10, 10000) + arg := CreateProductParams{ MerchantID: uuid.MustParse("1f81d072-0c98-4e7e-9ceb-233d2eadb674"), Name: util.RandomString(10), - SellingPrice: float64(123), - PurchasePrice: float64(123), - Stock: float64(120), + SellingPrice: sellingPrice, + PurchasePrice: purchasePrice, + Stock: stock, } product, err := testQueries.CreateProduct(context.Background(), arg) diff --git a/db/sqlc/store.go b/db/sqlc/store.go index c118f71..5bf100a 100644 --- a/db/sqlc/store.go +++ b/db/sqlc/store.go @@ -58,8 +58,8 @@ type PurchasoOrderTxParams struct { } type PurchaseOrderTxResult struct { - PurchaseOrder PurchaseOrder `json:"purchase_order"` - // PurchaseOrderDetail []PurchaseOrderDetail `json:"detail"` + PurchaseOrder PurchaseOrder `json:"purchase_order"` + PurchaseOrderDetail []PurchaseOrderDetail `json:"detail"` } func (store *Store) PurchaseOrderTx(ctx context.Context, arg PurchasoOrderTxParams) (PurchaseOrderTxResult, error) { @@ -67,6 +67,8 @@ func (store *Store) PurchaseOrderTx(ctx context.Context, arg PurchasoOrderTxPara err := store.execTx(ctx, func(q *Queries) error { var err error + + // create purchase order result.PurchaseOrder, err = q.CreatePurchaseOrder(ctx, CreatePurchaseOrderParams{ MerchantID: arg.MerchantID, SupplierID: arg.SupplierID, @@ -81,6 +83,39 @@ func (store *Store) PurchaseOrderTx(ctx context.Context, arg PurchasoOrderTxPara return err } + // create purchase order detail for each products in purchase order + for i := 0; i < len(arg.Products); i++ { + purchaseOrderDetail, err := q.CreatePurchaseOrderDetail(ctx, CreatePurchaseOrderDetailParams{ + PurchaseOrderID: result.PurchaseOrder.ID, + MerchantID: arg.MerchantID, + ProductID: arg.Products[i].ProductID, + Quantity: arg.Products[i].Quantity, + SubTotal: arg.Products[i].Sub_total, + ProductPrice: arg.Products[i].Price, + }) + + if err != nil { + return err + } + + result.PurchaseOrderDetail = append(result.PurchaseOrderDetail, purchaseOrderDetail) + + product, err := q.GetStockForUpdateStock(ctx, arg.Products[i].ProductID) + if err != nil { + return err + } + + err = q.UpdateProductStock(ctx, UpdateProductStockParams{ + ID: product.ID, + Stock: product.Stock + arg.Products[i].Quantity, + }) + + if err != nil { + return err + } + + } + return nil }) diff --git a/db/sqlc/store_test.go b/db/sqlc/store_test.go index 8aecab0..78a33d7 100644 --- a/db/sqlc/store_test.go +++ b/db/sqlc/store_test.go @@ -5,10 +5,13 @@ import ( "database/sql" "testing" + "git.nochill.in/nochill/nice_pos/util" "github.com/stretchr/testify/require" ) func TestPurchaseOrder(t *testing.T) { + var products []PurchaseOrderProduct + store := NewStore(testDB) supplier, _ := createRandomSupplier(t) product1, _ := createRandomProduct(t) @@ -17,29 +20,58 @@ func TestPurchaseOrder(t *testing.T) { errs := make(chan error) results := make(chan PurchaseOrderTxResult) - // for i := 0; i < testIteration; i++ { - go func() { - result, err := store.PurchaseOrderTx(context.Background(), PurchasoOrderTxParams{ - MerchantID: supplier.MerchantID, - SupplierID: supplier.ID, - Code: sql.NullString{Valid: true, String: ""}, - IsPaid: true, - Total: product1.PurchasePrice + product2.PurchasePrice, - PaidNominal: product1.PurchasePrice + product2.PurchasePrice, - Note: sql.NullString{Valid: true, String: ""}, - // Products: products, + purchaseProducts1Quantity, _ := util.RandomFloat(1, 99) + purchaseProducts1Price, _ := util.RandomFloat(999, 9999) + purchaseProducts1SubTotal := purchaseProducts1Price * purchaseProducts1Quantity - }) - errs <- err - results <- result - }() - // } + purchaseProducts2Quantity, _ := util.RandomFloat(1, 99) + purchaseProducts2Price, _ := util.RandomFloat(999, 9999) + purchaseProducts2SubTotal := purchaseProducts2Price * purchaseProducts2Quantity - // for i := 0; i < testIteration; i++ { - err := <-errs - require.NoError(t, err) + purchaseProducts_1 := PurchaseOrderProduct{ + ProductID: product1.ID, + Quantity: purchaseProducts1Quantity, + Sub_total: purchaseProducts1SubTotal, + Price: purchaseProducts1Price, + } - result := <-results - require.NotEmpty(t, result) + purchaseProducts_2 := PurchaseOrderProduct{ + ProductID: product2.ID, + Quantity: purchaseProducts2Quantity, + Sub_total: purchaseProducts2SubTotal, + Price: purchaseProducts2Price, + } + products = append(products, purchaseProducts_1, purchaseProducts_2) + + for i := 0; i < 5; i++ { + go func() { + result, err := store.PurchaseOrderTx(context.Background(), PurchasoOrderTxParams{ + MerchantID: supplier.MerchantID, + SupplierID: supplier.ID, + Code: sql.NullString{Valid: true, String: ""}, + IsPaid: true, + Total: product1.PurchasePrice + product2.PurchasePrice, + PaidNominal: product1.PurchasePrice + product2.PurchasePrice, + Note: sql.NullString{Valid: true, String: ""}, + Products: products, + }) + errs <- err + results <- result + }() + } + + for i := 0; i < 5; i++ { + err := <-errs + require.NoError(t, err) + + result := <-results + require.NotEmpty(t, result) + + purchaseOrder := result.PurchaseOrder + require.NotEmpty(t, purchaseOrder) + require.Equal(t, purchaseOrder.MerchantID, supplier.MerchantID) + require.Equal(t, purchaseOrder.SupplierID, supplier.ID) + require.NotZero(t, purchaseOrder.PaidNominal, product1.PurchasePrice+product2.PurchasePrice) + } } diff --git a/go.mod b/go.mod index 7a97e1b..230062e 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.20 require ( github.com/google/uuid v1.3.0 github.com/lib/pq v1.10.7 + github.com/shopspring/decimal v1.3.1 github.com/stretchr/testify v1.8.2 github.com/tabbed/pqtype v0.1.1 ) diff --git a/go.sum b/go.sum index 32cae5f..f389536 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/util/random.go b/util/random.go index 260348d..b23f899 100644 --- a/util/random.go +++ b/util/random.go @@ -4,6 +4,8 @@ import ( "math/rand" "strings" "time" + + "github.com/shopspring/decimal" ) const alphabet = "abcdefghijklmnopqrstuvwxyz" @@ -17,8 +19,10 @@ func RandomInt(min, max int64) int64 { return min + rand.Int63n(max-min+1) } -func RandomFloat(max, div float64) float64 { - return (rand.Float64() * max) * div +// RandomFloat with rounding for testing +func RandomFloat(min, max float64) (float64, bool) { + val := (min + rand.Float64()*(max-min)) * 100 + return decimal.NewFromFloat(val).Truncate(2).Float64() } // RandomString generates a random string of length n