add purchase product transaction

This commit is contained in:
nochill 2023-03-06 15:15:11 +07:00
parent 287eb2f205
commit 068a6d9320
8 changed files with 142 additions and 38 deletions

View File

@ -10,11 +10,16 @@ INSERT INTO products (
) )
RETURNING *; RETURNING *;
-- name: UpdateProductStock :one -- name: GetStockForUpdateStock :one
SELECT * FROM products
WHERE id = $1
LIMIT 1
FOR NO KEY UPDATE;
-- name: UpdateProductStock :exec
UPDATE products UPDATE products
SET stock = $1 SET stock = $1
WHERE id = $2 WHERE id = $2;
RETURNING stock;
-- name: GetProduct :one -- name: GetProduct :one
SELECT * FROM products SELECT * FROM products

View File

@ -87,6 +87,30 @@ func (q *Queries) GetProduct(ctx context.Context, id uuid.UUID) (Product, error)
return i, err 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 const listProducts = `-- name: ListProducts :many
SELECT id, merchant_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at FROM products SELECT id, merchant_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at FROM products
ORDER BY index_id ORDER BY index_id
@ -172,11 +196,10 @@ func (q *Queries) UpdateProduct(ctx context.Context, arg UpdateProductParams) (P
return i, err return i, err
} }
const updateProductStock = `-- name: UpdateProductStock :one const updateProductStock = `-- name: UpdateProductStock :exec
UPDATE products UPDATE products
SET stock = $1 SET stock = $1
WHERE id = $2 WHERE id = $2
RETURNING stock
` `
type UpdateProductStockParams struct { type UpdateProductStockParams struct {
@ -184,9 +207,7 @@ type UpdateProductStockParams struct {
ID uuid.UUID `json:"id"` ID uuid.UUID `json:"id"`
} }
func (q *Queries) UpdateProductStock(ctx context.Context, arg UpdateProductStockParams) (float64, error) { func (q *Queries) UpdateProductStock(ctx context.Context, arg UpdateProductStockParams) error {
row := q.db.QueryRowContext(ctx, updateProductStock, arg.Stock, arg.ID) _, err := q.db.ExecContext(ctx, updateProductStock, arg.Stock, arg.ID)
var stock float64 return err
err := row.Scan(&stock)
return stock, err
} }

View File

@ -13,12 +13,16 @@ import (
func createRandomProduct(t *testing.T) (Product, CreateProductParams) { func createRandomProduct(t *testing.T) (Product, CreateProductParams) {
sellingPrice, _ := util.RandomFloat(999, 99999)
purchasePrice, _ := util.RandomFloat(999, 9999)
stock, _ := util.RandomFloat(10, 10000)
arg := CreateProductParams{ arg := CreateProductParams{
MerchantID: uuid.MustParse("1f81d072-0c98-4e7e-9ceb-233d2eadb674"), MerchantID: uuid.MustParse("1f81d072-0c98-4e7e-9ceb-233d2eadb674"),
Name: util.RandomString(10), Name: util.RandomString(10),
SellingPrice: float64(123), SellingPrice: sellingPrice,
PurchasePrice: float64(123), PurchasePrice: purchasePrice,
Stock: float64(120), Stock: stock,
} }
product, err := testQueries.CreateProduct(context.Background(), arg) product, err := testQueries.CreateProduct(context.Background(), arg)

View File

@ -58,8 +58,8 @@ type PurchasoOrderTxParams struct {
} }
type PurchaseOrderTxResult struct { type PurchaseOrderTxResult struct {
PurchaseOrder PurchaseOrder `json:"purchase_order"` PurchaseOrder PurchaseOrder `json:"purchase_order"`
// PurchaseOrderDetail []PurchaseOrderDetail `json:"detail"` PurchaseOrderDetail []PurchaseOrderDetail `json:"detail"`
} }
func (store *Store) PurchaseOrderTx(ctx context.Context, arg PurchasoOrderTxParams) (PurchaseOrderTxResult, error) { 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 { err := store.execTx(ctx, func(q *Queries) error {
var err error var err error
// create purchase order
result.PurchaseOrder, err = q.CreatePurchaseOrder(ctx, CreatePurchaseOrderParams{ result.PurchaseOrder, err = q.CreatePurchaseOrder(ctx, CreatePurchaseOrderParams{
MerchantID: arg.MerchantID, MerchantID: arg.MerchantID,
SupplierID: arg.SupplierID, SupplierID: arg.SupplierID,
@ -81,6 +83,39 @@ func (store *Store) PurchaseOrderTx(ctx context.Context, arg PurchasoOrderTxPara
return err 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 return nil
}) })

View File

@ -5,10 +5,13 @@ import (
"database/sql" "database/sql"
"testing" "testing"
"git.nochill.in/nochill/nice_pos/util"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestPurchaseOrder(t *testing.T) { func TestPurchaseOrder(t *testing.T) {
var products []PurchaseOrderProduct
store := NewStore(testDB) store := NewStore(testDB)
supplier, _ := createRandomSupplier(t) supplier, _ := createRandomSupplier(t)
product1, _ := createRandomProduct(t) product1, _ := createRandomProduct(t)
@ -17,29 +20,58 @@ func TestPurchaseOrder(t *testing.T) {
errs := make(chan error) errs := make(chan error)
results := make(chan PurchaseOrderTxResult) results := make(chan PurchaseOrderTxResult)
// for i := 0; i < testIteration; i++ { purchaseProducts1Quantity, _ := util.RandomFloat(1, 99)
go func() { purchaseProducts1Price, _ := util.RandomFloat(999, 9999)
result, err := store.PurchaseOrderTx(context.Background(), PurchasoOrderTxParams{ purchaseProducts1SubTotal := purchaseProducts1Price * purchaseProducts1Quantity
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,
}) purchaseProducts2Quantity, _ := util.RandomFloat(1, 99)
errs <- err purchaseProducts2Price, _ := util.RandomFloat(999, 9999)
results <- result purchaseProducts2SubTotal := purchaseProducts2Price * purchaseProducts2Quantity
}()
// }
// for i := 0; i < testIteration; i++ { purchaseProducts_1 := PurchaseOrderProduct{
err := <-errs ProductID: product1.ID,
require.NoError(t, err) Quantity: purchaseProducts1Quantity,
Sub_total: purchaseProducts1SubTotal,
Price: purchaseProducts1Price,
}
result := <-results purchaseProducts_2 := PurchaseOrderProduct{
require.NotEmpty(t, result) 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)
}
} }

1
go.mod
View File

@ -5,6 +5,7 @@ go 1.20
require ( require (
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/lib/pq v1.10.7 github.com/lib/pq v1.10.7
github.com/shopspring/decimal v1.3.1
github.com/stretchr/testify v1.8.2 github.com/stretchr/testify v1.8.2
github.com/tabbed/pqtype v0.1.1 github.com/tabbed/pqtype v0.1.1
) )

2
go.sum
View File

@ -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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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.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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=

View File

@ -4,6 +4,8 @@ import (
"math/rand" "math/rand"
"strings" "strings"
"time" "time"
"github.com/shopspring/decimal"
) )
const alphabet = "abcdefghijklmnopqrstuvwxyz" const alphabet = "abcdefghijklmnopqrstuvwxyz"
@ -17,8 +19,10 @@ func RandomInt(min, max int64) int64 {
return min + rand.Int63n(max-min+1) return min + rand.Int63n(max-min+1)
} }
func RandomFloat(max, div float64) float64 { // RandomFloat with rounding for testing
return (rand.Float64() * max) * div 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 // RandomString generates a random string of length n