add purchase product transaction
This commit is contained in:
parent
287eb2f205
commit
068a6d9320
@ -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
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -59,7 +59,7 @@ 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
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -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,7 +20,31 @@ 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)
|
||||||
|
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() {
|
go func() {
|
||||||
result, err := store.PurchaseOrderTx(context.Background(), PurchasoOrderTxParams{
|
result, err := store.PurchaseOrderTx(context.Background(), PurchasoOrderTxParams{
|
||||||
MerchantID: supplier.MerchantID,
|
MerchantID: supplier.MerchantID,
|
||||||
@ -27,19 +54,24 @@ func TestPurchaseOrder(t *testing.T) {
|
|||||||
Total: product1.PurchasePrice + product2.PurchasePrice,
|
Total: product1.PurchasePrice + product2.PurchasePrice,
|
||||||
PaidNominal: product1.PurchasePrice + product2.PurchasePrice,
|
PaidNominal: product1.PurchasePrice + product2.PurchasePrice,
|
||||||
Note: sql.NullString{Valid: true, String: ""},
|
Note: sql.NullString{Valid: true, String: ""},
|
||||||
// Products: products,
|
Products: products,
|
||||||
|
|
||||||
})
|
})
|
||||||
errs <- err
|
errs <- err
|
||||||
results <- result
|
results <- result
|
||||||
}()
|
}()
|
||||||
// }
|
}
|
||||||
|
|
||||||
// for i := 0; i < testIteration; i++ {
|
for i := 0; i < 5; i++ {
|
||||||
err := <-errs
|
err := <-errs
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
result := <-results
|
result := <-results
|
||||||
require.NotEmpty(t, result)
|
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
1
go.mod
@ -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
2
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/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=
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user