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 *;
-- 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

View File

@ -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
}

View File

@ -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)

View File

@ -59,7 +59,7 @@ type PurchasoOrderTxParams struct {
type PurchaseOrderTxResult struct {
PurchaseOrder PurchaseOrder `json:"purchase_order"`
// PurchaseOrderDetail []PurchaseOrderDetail `json:"detail"`
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
})

View File

@ -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,7 +20,31 @@ func TestPurchaseOrder(t *testing.T) {
errs := make(chan error)
results := make(chan PurchaseOrderTxResult)
// for i := 0; i < testIteration; i++ {
purchaseProducts1Quantity, _ := util.RandomFloat(1, 99)
purchaseProducts1Price, _ := util.RandomFloat(999, 9999)
purchaseProducts1SubTotal := purchaseProducts1Price * purchaseProducts1Quantity
purchaseProducts2Quantity, _ := util.RandomFloat(1, 99)
purchaseProducts2Price, _ := util.RandomFloat(999, 9999)
purchaseProducts2SubTotal := purchaseProducts2Price * purchaseProducts2Quantity
purchaseProducts_1 := PurchaseOrderProduct{
ProductID: product1.ID,
Quantity: purchaseProducts1Quantity,
Sub_total: purchaseProducts1SubTotal,
Price: purchaseProducts1Price,
}
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,
@ -27,19 +54,24 @@ func TestPurchaseOrder(t *testing.T) {
Total: product1.PurchasePrice + product2.PurchasePrice,
PaidNominal: product1.PurchasePrice + product2.PurchasePrice,
Note: sql.NullString{Valid: true, String: ""},
// Products: products,
Products: products,
})
errs <- err
results <- result
}()
// }
}
// for i := 0; i < testIteration; i++ {
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 (
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
)

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/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=

View File

@ -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