add sale order
This commit is contained in:
parent
2de83644b8
commit
5604f34e97
@ -21,27 +21,27 @@ func authMiddleware(tokenMaker token.Maker) gin.HandlerFunc {
|
|||||||
authorizationHeader := ctx.GetHeader(authorizationHeaderKey)
|
authorizationHeader := ctx.GetHeader(authorizationHeaderKey)
|
||||||
if len(authorizationHeader) == 0 {
|
if len(authorizationHeader) == 0 {
|
||||||
err := errors.New("authorization header is not provided")
|
err := errors.New("authorization header is not provided")
|
||||||
ctx.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err))
|
ctx.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := strings.Fields(authorizationHeader)
|
fields := strings.Fields(authorizationHeader)
|
||||||
if len(fields) < 2 {
|
if len(fields) < 2 {
|
||||||
err := errors.New("Invalid authorization header format")
|
err := errors.New("Invalid authorization header format")
|
||||||
ctx.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err))
|
ctx.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
authorizationType := strings.ToLower(fields[0])
|
authorizationType := strings.ToLower(fields[0])
|
||||||
if authorizationType != authorizationTypeBearer {
|
if authorizationType != authorizationTypeBearer {
|
||||||
err := fmt.Errorf("Authorization only accept bearer type")
|
err := fmt.Errorf("Authorization only accept bearer type")
|
||||||
ctx.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err))
|
ctx.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err, ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
accessToken := fields[1]
|
accessToken := fields[1]
|
||||||
payload, err := tokenMaker.VerifyToken(accessToken)
|
payload, err := tokenMaker.VerifyToken(accessToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err))
|
ctx.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ func (server *Server) createProduct(ctx *gin.Context) {
|
|||||||
var req createProductRequest
|
var req createProductRequest
|
||||||
|
|
||||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
ctx.JSON(http.StatusBadRequest, errorResponse(err))
|
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ func (server *Server) createProduct(ctx *gin.Context) {
|
|||||||
|
|
||||||
product, err := server.store.CreateProduct(ctx, arg)
|
product, err := server.store.CreateProduct(ctx, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,24 +54,24 @@ type getProductRequest struct {
|
|||||||
func (server *Server) getProduct(ctx *gin.Context) {
|
func (server *Server) getProduct(ctx *gin.Context) {
|
||||||
var req getProductRequest
|
var req getProductRequest
|
||||||
if err := ctx.ShouldBindUri(&req); err != nil {
|
if err := ctx.ShouldBindUri(&req); err != nil {
|
||||||
ctx.JSON(http.StatusBadRequest, errorResponse(err))
|
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
product, err := server.store.GetProduct(ctx, uuid.MustParse(req.ID))
|
product, err := server.store.GetProduct(ctx, uuid.MustParse(req.ID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
ctx.JSON(http.StatusNotFound, errorResponse(err))
|
ctx.JSON(http.StatusNotFound, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
authPayload := ctx.MustGet(authorizationPayloadKey).(*token.Payload)
|
authPayload := ctx.MustGet(authorizationPayloadKey).(*token.Payload)
|
||||||
if product.MerchantID != authPayload.MerchantID {
|
if product.MerchantID != authPayload.MerchantID {
|
||||||
err := errors.New("Product doesn't belong to the user")
|
err := errors.New("Product doesn't belong to the user")
|
||||||
ctx.JSON(http.StatusUnauthorized, errorResponse(err))
|
ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ type listProductsRequest struct {
|
|||||||
func (server *Server) listProducts(ctx *gin.Context) {
|
func (server *Server) listProducts(ctx *gin.Context) {
|
||||||
var req listProductsRequest
|
var req listProductsRequest
|
||||||
if err := ctx.ShouldBindQuery(&req); err != nil {
|
if err := ctx.ShouldBindQuery(&req); err != nil {
|
||||||
ctx.JSON(http.StatusBadRequest, errorResponse(err))
|
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ func (server *Server) listProducts(ctx *gin.Context) {
|
|||||||
|
|
||||||
products, err := server.store.ListProducts(ctx, arg)
|
products, err := server.store.ListProducts(ctx, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ type updateProductRequest struct {
|
|||||||
func (server *Server) updateProduct(ctx *gin.Context) {
|
func (server *Server) updateProduct(ctx *gin.Context) {
|
||||||
var req updateProductRequest
|
var req updateProductRequest
|
||||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
ctx.JSON(http.StatusBadRequest, errorResponse(err))
|
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,10 +131,10 @@ func (server *Server) updateProduct(ctx *gin.Context) {
|
|||||||
product, err := server.store.UpdateProduct(ctx, arg)
|
product, err := server.store.UpdateProduct(ctx, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
ctx.JSON(http.StatusNotFound, errorResponse(err))
|
ctx.JSON(http.StatusNotFound, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ func (server *Server) createProductCategory(ctx *gin.Context) {
|
|||||||
var req createProductCategoryRequest
|
var req createProductCategoryRequest
|
||||||
|
|
||||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
ctx.JSON(http.StatusBadRequest, errorResponse(err))
|
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ func (server *Server) createProductCategory(ctx *gin.Context) {
|
|||||||
|
|
||||||
ProductCategory, err := server.store.CreateProductCategory(ctx, arg)
|
ProductCategory, err := server.store.CreateProductCategory(ctx, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ type listProductCategoriesRequest struct {
|
|||||||
func (server *Server) listProductCategories(ctx *gin.Context) {
|
func (server *Server) listProductCategories(ctx *gin.Context) {
|
||||||
var req listProductCategoriesRequest
|
var req listProductCategoriesRequest
|
||||||
if err := ctx.ShouldBindQuery(&req); err != nil {
|
if err := ctx.ShouldBindQuery(&req); err != nil {
|
||||||
ctx.JSON(http.StatusBadRequest, errorResponse(err))
|
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ func (server *Server) listProductCategories(ctx *gin.Context) {
|
|||||||
|
|
||||||
ProductCategories, err := server.store.ListProductCategoriesByMerchantID(ctx, arg)
|
ProductCategories, err := server.store.ListProductCategoriesByMerchantID(ctx, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ type updateProductCategoryRequest struct {
|
|||||||
func (server *Server) updateProductCategory(ctx *gin.Context) {
|
func (server *Server) updateProductCategory(ctx *gin.Context) {
|
||||||
var req updateProductCategoryRequest
|
var req updateProductCategoryRequest
|
||||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
ctx.JSON(http.StatusBadRequest, errorResponse(err))
|
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,10 +86,10 @@ func (server *Server) updateProductCategory(ctx *gin.Context) {
|
|||||||
ProductCategory, err := server.store.UpdateProductCategory(ctx, arg)
|
ProductCategory, err := server.store.UpdateProductCategory(ctx, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
ctx.JSON(http.StatusNotFound, errorResponse(err))
|
ctx.JSON(http.StatusNotFound, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,11 +35,11 @@ func TestCreateProductCategory(t *testing.T) {
|
|||||||
"merchant_id": productCategory.MerchantID,
|
"merchant_id": productCategory.MerchantID,
|
||||||
},
|
},
|
||||||
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
|
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
|
||||||
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, util.RandomEmail(), "a848090f-0409-4386-9caa-929ae6874dbb", time.Minute)
|
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, util.RandomEmail(), "54b8a2d9-16be-4239-8828-5daa317028dc", time.Minute)
|
||||||
},
|
},
|
||||||
buildStubs: func(store *mockdb.MockStore) {
|
buildStubs: func(store *mockdb.MockStore) {
|
||||||
arg := db.CreateProductCategoryParams{
|
arg := db.CreateProductCategoryParams{
|
||||||
MerchantID: uuid.MustParse("a848090f-0409-4386-9caa-929ae6874dbb"),
|
MerchantID: uuid.MustParse("54b8a2d9-16be-4239-8828-5daa317028dc"),
|
||||||
Name: productCategory.Name,
|
Name: productCategory.Name,
|
||||||
}
|
}
|
||||||
store.EXPECT().
|
store.EXPECT().
|
||||||
@ -83,7 +83,7 @@ func TestCreateProductCategory(t *testing.T) {
|
|||||||
func createRandomProductCategory() db.ProductCategory {
|
func createRandomProductCategory() db.ProductCategory {
|
||||||
return db.ProductCategory{
|
return db.ProductCategory{
|
||||||
ID: uuid.New(),
|
ID: uuid.New(),
|
||||||
MerchantID: uuid.MustParse("a848090f-0409-4386-9caa-929ae6874dbb"),
|
MerchantID: uuid.MustParse("54b8a2d9-16be-4239-8828-5daa317028dc"),
|
||||||
Name: util.RandomString(5),
|
Name: util.RandomString(5),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var MERCHANTID = "a848090f-0409-4386-9caa-929ae6874dbb"
|
var MERCHANTID = "54b8a2d9-16be-4239-8828-5daa317028dc"
|
||||||
|
|
||||||
func TestGetProductApi(t *testing.T) {
|
func TestGetProductApi(t *testing.T) {
|
||||||
product := randomProduct(MERCHANTID)
|
product := randomProduct(MERCHANTID)
|
||||||
|
@ -34,7 +34,7 @@ func (server Server) createPurchase(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
ctx.JSON(http.StatusBadRequest, errorResponse(err))
|
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ func (server Server) createPurchase(ctx *gin.Context) {
|
|||||||
|
|
||||||
result, err := server.store.PurchaseOrderTx(ctx, arg)
|
result, err := server.store.PurchaseOrderTx(ctx, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
78
api/sale_order.go
Normal file
78
api/sale_order.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
db "git.nochill.in/nochill/naice_pos/db/sqlc"
|
||||||
|
"git.nochill.in/nochill/naice_pos/util"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
type createSaleOrderRequest struct {
|
||||||
|
MerchantID uuid.UUID `json:"merchant_id" binding:"required"`
|
||||||
|
MerchantIDX int64 `json:"merchant_index" binding:"required"`
|
||||||
|
CustomerID string `json:"customer_id"`
|
||||||
|
CreatedBy uuid.UUID `json:"created_by" binding:"required"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
IsPaid bool `json:"is_paid" binding:"required"`
|
||||||
|
Total float64 `json:"total" binding:"required"`
|
||||||
|
PaidNominal float64 `json:"paid_nominal" binding:"required"`
|
||||||
|
Change float64 `json:"change"`
|
||||||
|
Note string `json:"note"`
|
||||||
|
IsKeep bool `json:"is_keep"`
|
||||||
|
Products []db.SaleOrderProduct `json:"products" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server Server) createSaleOrder(ctx *gin.Context) {
|
||||||
|
var req createSaleOrderRequest
|
||||||
|
|
||||||
|
code := util.RandomTransactionCode("S", req.MerchantIDX)
|
||||||
|
customerID := uuid.NullUUID{Valid: false, UUID: uuid.Nil}
|
||||||
|
|
||||||
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
|
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.CustomerID) > 0 {
|
||||||
|
customerID = uuid.NullUUID{Valid: true, UUID: uuid.MustParse(req.CustomerID)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Code) > 0 {
|
||||||
|
code = req.Code
|
||||||
|
}
|
||||||
|
|
||||||
|
arg := db.SaleOrderTxParams{
|
||||||
|
MerchantID: req.MerchantID,
|
||||||
|
CreatedBy: req.CreatedBy,
|
||||||
|
CustomerID: customerID,
|
||||||
|
Code: code,
|
||||||
|
IsPaid: req.IsPaid,
|
||||||
|
Total: req.Total,
|
||||||
|
PaidNominal: req.PaidNominal,
|
||||||
|
Note: sql.NullString{Valid: len(req.Note) > 0, String: req.Note},
|
||||||
|
IsKeep: req.IsKeep,
|
||||||
|
Change: req.Change,
|
||||||
|
Products: req.Products,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := server.store.SaleOrderTx(ctx, arg)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
if pqErr, ok := err.(*pq.Error); ok {
|
||||||
|
switch pqErr.Code.Name() {
|
||||||
|
case "check_violation":
|
||||||
|
ctx.JSON(http.StatusConflict, errorResponse(err, "Stok tidak bisa kurang dari 0"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, result)
|
||||||
|
}
|
@ -42,6 +42,7 @@ func (server *Server) getRoutes() {
|
|||||||
|
|
||||||
apiRoutes.POST("/products", server.createProduct)
|
apiRoutes.POST("/products", server.createProduct)
|
||||||
apiRoutes.PATCH("/products", server.updateProduct)
|
apiRoutes.PATCH("/products", server.updateProduct)
|
||||||
|
apiRoutes.GET("/products", server.listProducts)
|
||||||
apiRoutes.GET("/product/:id", server.getProduct)
|
apiRoutes.GET("/product/:id", server.getProduct)
|
||||||
|
|
||||||
apiRoutes.POST("/product/category", server.createProductCategory)
|
apiRoutes.POST("/product/category", server.createProductCategory)
|
||||||
@ -53,6 +54,8 @@ func (server *Server) getRoutes() {
|
|||||||
|
|
||||||
apiRoutes.POST("/purchase-products", server.createPurchase)
|
apiRoutes.POST("/purchase-products", server.createPurchase)
|
||||||
|
|
||||||
|
apiRoutes.POST("/sale-order", server.createSaleOrder)
|
||||||
|
|
||||||
server.router = router
|
server.router = router
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ func (server *Server) createSupplier(ctx *gin.Context) {
|
|||||||
var supDetail pqtype.NullRawMessage
|
var supDetail pqtype.NullRawMessage
|
||||||
|
|
||||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
ctx.JSON(http.StatusBadRequest, errorResponse(err))
|
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ func (server *Server) createSupplier(ctx *gin.Context) {
|
|||||||
|
|
||||||
supplier, err := server.store.CreateSuppliers(ctx, arg)
|
supplier, err := server.store.CreateSuppliers(ctx, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
24
api/token.go
24
api/token.go
@ -21,63 +21,63 @@ type renewAccessResponse struct {
|
|||||||
func (server *Server) renewAccessToken(ctx *gin.Context) {
|
func (server *Server) renewAccessToken(ctx *gin.Context) {
|
||||||
var req renewAccessRequest
|
var req renewAccessRequest
|
||||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
ctx.JSON(http.StatusBadRequest, errorResponse(err))
|
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshPayload, err := server.tokenMaker.VerifyToken(req.RefreshToken)
|
refreshPayload, err := server.tokenMaker.VerifyToken(req.RefreshToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusUnauthorized, errorResponse(err))
|
ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := server.store.GetSession(ctx, refreshPayload.ID)
|
session, err := server.store.GetSession(ctx, refreshPayload.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
ctx.JSON(http.StatusNotFound, errorResponse(err))
|
ctx.JSON(http.StatusNotFound, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if session.IsBlocked {
|
if session.IsBlocked {
|
||||||
err := fmt.Errorf("blocked session")
|
err := fmt.Errorf("blocked session")
|
||||||
ctx.JSON(http.StatusUnauthorized, errorResponse(err))
|
ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if session.Email != refreshPayload.Email {
|
if session.Email != refreshPayload.Email {
|
||||||
err := fmt.Errorf("incorrect session user")
|
err := fmt.Errorf("incorrect session user")
|
||||||
ctx.JSON(http.StatusUnauthorized, errorResponse(err))
|
ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if session.RefreshToken != req.RefreshToken {
|
if session.RefreshToken != req.RefreshToken {
|
||||||
err := fmt.Errorf("mismatched session token")
|
err := fmt.Errorf("mismatched session token")
|
||||||
ctx.JSON(http.StatusUnauthorized, errorResponse(err))
|
ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Now().After(refreshPayload.ExpiredAt) {
|
if time.Now().After(refreshPayload.ExpiredAt) {
|
||||||
err := fmt.Errorf("Exprired session")
|
err := fmt.Errorf("Exprired session")
|
||||||
ctx.JSON(http.StatusUnauthorized, errorResponse(err))
|
ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := server.store.GetUserByEmail(ctx, refreshPayload.Email)
|
user, err := server.store.GetUserByEmail(ctx, refreshPayload.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
ctx.JSON(http.StatusNotFound, errorResponse(err))
|
ctx.JSON(http.StatusNotFound, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
merchant, err := server.store.GetMerchantByUserId(ctx, user.ID)
|
merchant, err := server.store.GetMerchantByUserId(ctx, user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ func (server *Server) renewAccessToken(ctx *gin.Context) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
20
api/user.go
20
api/user.go
@ -50,13 +50,13 @@ func newUserMerchantResponse(user db.GetUserByEmailRow) userMerchantResponse {
|
|||||||
func (server *Server) createUserMerchant(ctx *gin.Context) {
|
func (server *Server) createUserMerchant(ctx *gin.Context) {
|
||||||
var req createUserMerchantRequest
|
var req createUserMerchantRequest
|
||||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
ctx.JSON(http.StatusBadRequest, errorResponse(err))
|
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hashedPassword, err := util.HashPassword(req.Password)
|
hashedPassword, err := util.HashPassword(req.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,11 +72,11 @@ func (server *Server) createUserMerchant(ctx *gin.Context) {
|
|||||||
if pqErr, ok := err.(*pq.Error); ok {
|
if pqErr, ok := err.(*pq.Error); ok {
|
||||||
switch pqErr.Code.Name() {
|
switch pqErr.Code.Name() {
|
||||||
case "foreign_key_violation", "unique_violation":
|
case "foreign_key_violation", "unique_violation":
|
||||||
ctx.JSON(http.StatusConflict, errorResponse(err))
|
ctx.JSON(http.StatusConflict, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,23 +117,23 @@ type userLoginResponse struct {
|
|||||||
func (server *Server) loginUser(ctx *gin.Context) {
|
func (server *Server) loginUser(ctx *gin.Context) {
|
||||||
var req userLoginRequest
|
var req userLoginRequest
|
||||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
ctx.JSON(http.StatusBadRequest, errorResponse(err))
|
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := server.store.GetUserByEmail(ctx, req.Email)
|
user, err := server.store.GetUserByEmail(ctx, req.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
ctx.JSON(http.StatusNotFound, errorResponse(err))
|
ctx.JSON(http.StatusNotFound, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = util.CheckPassword(req.Password, user.Password)
|
err = util.CheckPassword(req.Password, user.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusUnauthorized, errorResponse(err))
|
ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ func (server *Server) loginUser(ctx *gin.Context) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,7 +165,7 @@ func (server *Server) loginUser(ctx *gin.Context) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
|
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ CREATE TABLE products (
|
|||||||
"name" varchar not null,
|
"name" varchar not null,
|
||||||
"selling_price" double precision default(0::double precision) NOT NULL,
|
"selling_price" double precision default(0::double precision) NOT NULL,
|
||||||
"purchase_price" double precision default(0:: double precision) NOT NULL,
|
"purchase_price" double precision default(0:: double precision) NOT NULL,
|
||||||
"stock" double precision default(0::double precision) NOT NULL,
|
"stock" double precision default(0::double precision) NOT NULL CHECK("stock" >= 0),
|
||||||
"created_at" timestamp default(now()),
|
"created_at" timestamp default(now()),
|
||||||
"updated_at" timestamp default(now())
|
"updated_at" timestamp default(now())
|
||||||
);
|
);
|
||||||
@ -94,6 +94,7 @@ CREATE TABLE sale_order (
|
|||||||
"created_at" timestamp default(now()),
|
"created_at" timestamp default(now()),
|
||||||
"updated_at" timestamp default(now())
|
"updated_at" timestamp default(now())
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE sale_order_detail (
|
CREATE TABLE sale_order_detail (
|
||||||
"id" uuid default gen_random_uuid() primary key not null,
|
"id" uuid default gen_random_uuid() primary key not null,
|
||||||
"index_id" bigserial not null,
|
"index_id" bigserial not null,
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE sale_order DROP COLUMN IF EXISTS is_keep;
|
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE sale_order ADD COLUMN is_keep boolean not null default false;
|
@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE sale_order DROP COLUMN change;
|
||||||
|
ALTER TABLE sale_order ALTER COLUMN is_paid DROP NOT null;
|
||||||
|
ALTER TABLE sale_order ALTER COLUMN code DROP NOT null;
|
@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE sale_order ADD COLUMN change double precision default(0::double precision) NOT NULL;
|
||||||
|
ALTER TABLE sale_order ALTER COLUMN is_paid SET not null;
|
||||||
|
ALTER TABLE sale_order ALTER COLUMN code SET not null;
|
@ -127,10 +127,10 @@ func (mr *MockStoreMockRecorder) CreatePurchaseOrderDetail(arg0, arg1 interface{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateSaleOrder mocks base method.
|
// CreateSaleOrder mocks base method.
|
||||||
func (m *MockStore) CreateSaleOrder(arg0 context.Context, arg1 db.CreateSaleOrderParams) (db.PurchaseOrder, error) {
|
func (m *MockStore) CreateSaleOrder(arg0 context.Context, arg1 db.CreateSaleOrderParams) (db.SaleOrder, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "CreateSaleOrder", arg0, arg1)
|
ret := m.ctrl.Call(m, "CreateSaleOrder", arg0, arg1)
|
||||||
ret0, _ := ret[0].(db.PurchaseOrder)
|
ret0, _ := ret[0].(db.SaleOrder)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
@ -141,6 +141,21 @@ func (mr *MockStoreMockRecorder) CreateSaleOrder(arg0, arg1 interface{}) *gomock
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSaleOrder", reflect.TypeOf((*MockStore)(nil).CreateSaleOrder), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSaleOrder", reflect.TypeOf((*MockStore)(nil).CreateSaleOrder), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateSaleOrderDetail mocks base method.
|
||||||
|
func (m *MockStore) CreateSaleOrderDetail(arg0 context.Context, arg1 db.CreateSaleOrderDetailParams) (db.SaleOrderDetail, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CreateSaleOrderDetail", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(db.SaleOrderDetail)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSaleOrderDetail indicates an expected call of CreateSaleOrderDetail.
|
||||||
|
func (mr *MockStoreMockRecorder) CreateSaleOrderDetail(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSaleOrderDetail", reflect.TypeOf((*MockStore)(nil).CreateSaleOrderDetail), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
// CreateSession mocks base method.
|
// CreateSession mocks base method.
|
||||||
func (m *MockStore) CreateSession(arg0 context.Context, arg1 db.CreateSessionParams) (db.UserSession, error) {
|
func (m *MockStore) CreateSession(arg0 context.Context, arg1 db.CreateSessionParams) (db.UserSession, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -497,6 +512,21 @@ func (mr *MockStoreMockRecorder) PurchaseOrderTx(arg0, arg1 interface{}) *gomock
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PurchaseOrderTx", reflect.TypeOf((*MockStore)(nil).PurchaseOrderTx), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PurchaseOrderTx", reflect.TypeOf((*MockStore)(nil).PurchaseOrderTx), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaleOrderTx mocks base method.
|
||||||
|
func (m *MockStore) SaleOrderTx(arg0 context.Context, arg1 db.SaleOrderTxParams) (db.SaleOrderTxResult, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SaleOrderTx", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(db.SaleOrderTxResult)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaleOrderTx indicates an expected call of SaleOrderTx.
|
||||||
|
func (mr *MockStoreMockRecorder) SaleOrderTx(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaleOrderTx", reflect.TypeOf((*MockStore)(nil).SaleOrderTx), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
// SuppliersList mocks base method.
|
// SuppliersList mocks base method.
|
||||||
func (m *MockStore) SuppliersList(arg0 context.Context, arg1 db.SuppliersListParams) ([]db.Supplier, error) {
|
func (m *MockStore) SuppliersList(arg0 context.Context, arg1 db.SuppliersListParams) ([]db.Supplier, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
16
db/query/sale_order.sql
Normal file
16
db/query/sale_order.sql
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
-- name: CreateSaleOrder :one
|
||||||
|
INSERT INTO sale_order (
|
||||||
|
merchant_id,
|
||||||
|
customer_id,
|
||||||
|
code,
|
||||||
|
created_by,
|
||||||
|
is_paid,
|
||||||
|
total,
|
||||||
|
paid_nominal,
|
||||||
|
note,
|
||||||
|
change,
|
||||||
|
is_keep
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
|
||||||
|
)
|
||||||
|
RETURNING *;
|
13
db/query/sale_order_detail.sql
Normal file
13
db/query/sale_order_detail.sql
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
-- name: CreateSaleOrderDetail :one
|
||||||
|
INSERT INTO sale_order_detail (
|
||||||
|
sale_order_id,
|
||||||
|
product_id,
|
||||||
|
product_name,
|
||||||
|
quantity,
|
||||||
|
sub_total,
|
||||||
|
product_price,
|
||||||
|
profit
|
||||||
|
) VALUES(
|
||||||
|
$1, $2, $3, $4, $5, $6, $7
|
||||||
|
)
|
||||||
|
RETURNING *;
|
34
db/sqlc/customer_test.go
Normal file
34
db/sqlc/customer_test.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.nochill.in/nochill/naice_pos/util"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createRandomCustomer(t *testing.T) (Customer, CreateCustomersParams) {
|
||||||
|
arg := CreateCustomersParams{
|
||||||
|
MerchantID: uuid.MustParse("54b8a2d9-16be-4239-8828-5daa317028dc"),
|
||||||
|
Name: util.RandomString(10),
|
||||||
|
}
|
||||||
|
|
||||||
|
customer, err := testQueries.CreateCustomers(context.Background(), arg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return customer, arg
|
||||||
|
|
||||||
|
}
|
||||||
|
func TestCreateCustomer(t *testing.T) {
|
||||||
|
supplier, arg := createRandomCustomer(t)
|
||||||
|
|
||||||
|
require.Equal(t, arg.Name, supplier.Name)
|
||||||
|
require.Equal(t, arg.MerchantID, supplier.MerchantID)
|
||||||
|
|
||||||
|
require.NotZero(t, supplier.ID)
|
||||||
|
require.NotZero(t, supplier.CreatedAt)
|
||||||
|
require.NotZero(t, supplier.UpdatedAt)
|
||||||
|
|
||||||
|
}
|
@ -128,16 +128,18 @@ type PurchaseOrderDetail struct {
|
|||||||
type SaleOrder struct {
|
type SaleOrder struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
IndexID int64 `json:"index_id"`
|
IndexID int64 `json:"index_id"`
|
||||||
Code sql.NullString `json:"code"`
|
Code string `json:"code"`
|
||||||
CreatedBy uuid.UUID `json:"created_by"`
|
CreatedBy uuid.UUID `json:"created_by"`
|
||||||
MerchantID uuid.UUID `json:"merchant_id"`
|
MerchantID uuid.UUID `json:"merchant_id"`
|
||||||
CustomerID uuid.NullUUID `json:"customer_id"`
|
CustomerID uuid.NullUUID `json:"customer_id"`
|
||||||
IsPaid sql.NullBool `json:"is_paid"`
|
IsPaid bool `json:"is_paid"`
|
||||||
Total float64 `json:"total"`
|
Total float64 `json:"total"`
|
||||||
PaidNominal float64 `json:"paid_nominal"`
|
PaidNominal float64 `json:"paid_nominal"`
|
||||||
Note sql.NullString `json:"note"`
|
Note sql.NullString `json:"note"`
|
||||||
CreatedAt sql.NullTime `json:"created_at"`
|
CreatedAt sql.NullTime `json:"created_at"`
|
||||||
UpdatedAt sql.NullTime `json:"updated_at"`
|
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||||
|
IsKeep bool `json:"is_keep"`
|
||||||
|
Change float64 `json:"change"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SaleOrderDetail struct {
|
type SaleOrderDetail struct {
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var merchantID = uuid.MustParse("04a1b0a7-69b4-41da-a053-2f6b95c93195")
|
var merchantID = uuid.MustParse("54b8a2d9-16be-4239-8828-5daa317028dc")
|
||||||
|
|
||||||
func createRandomProductCategory(t *testing.T) ProductCategory {
|
func createRandomProductCategory(t *testing.T) ProductCategory {
|
||||||
arg := CreateProductCategoryParams{
|
arg := CreateProductCategoryParams{
|
||||||
|
@ -19,7 +19,7 @@ func createRandomProduct(t *testing.T) (Product, CreateProductParams) {
|
|||||||
productCategory := createRandomProductCategory(t)
|
productCategory := createRandomProductCategory(t)
|
||||||
|
|
||||||
arg := CreateProductParams{
|
arg := CreateProductParams{
|
||||||
MerchantID: uuid.MustParse("04a1b0a7-69b4-41da-a053-2f6b95c93195"),
|
MerchantID: uuid.MustParse("54b8a2d9-16be-4239-8828-5daa317028dc"),
|
||||||
Name: util.RandomString(10),
|
Name: util.RandomString(10),
|
||||||
SellingPrice: sellingPrice,
|
SellingPrice: sellingPrice,
|
||||||
PurchasePrice: purchasePrice,
|
PurchasePrice: purchasePrice,
|
||||||
|
@ -17,7 +17,8 @@ type Querier interface {
|
|||||||
CreateProductCategory(ctx context.Context, arg CreateProductCategoryParams) (ProductCategory, error)
|
CreateProductCategory(ctx context.Context, arg CreateProductCategoryParams) (ProductCategory, error)
|
||||||
CreatePurchaseOrder(ctx context.Context, arg CreatePurchaseOrderParams) (PurchaseOrder, error)
|
CreatePurchaseOrder(ctx context.Context, arg CreatePurchaseOrderParams) (PurchaseOrder, error)
|
||||||
CreatePurchaseOrderDetail(ctx context.Context, arg CreatePurchaseOrderDetailParams) (PurchaseOrderDetail, error)
|
CreatePurchaseOrderDetail(ctx context.Context, arg CreatePurchaseOrderDetailParams) (PurchaseOrderDetail, error)
|
||||||
CreateSaleOrder(ctx context.Context, arg CreateSaleOrderParams) (PurchaseOrder, error)
|
CreateSaleOrder(ctx context.Context, arg CreateSaleOrderParams) (SaleOrder, error)
|
||||||
|
CreateSaleOrderDetail(ctx context.Context, arg CreateSaleOrderDetailParams) (SaleOrderDetail, error)
|
||||||
CreateSession(ctx context.Context, arg CreateSessionParams) (UserSession, error)
|
CreateSession(ctx context.Context, arg CreateSessionParams) (UserSession, error)
|
||||||
CreateStockLogs(ctx context.Context, arg CreateStockLogsParams) (StockLog, error)
|
CreateStockLogs(ctx context.Context, arg CreateStockLogsParams) (StockLog, error)
|
||||||
CreateSuppliers(ctx context.Context, arg CreateSuppliersParams) (Supplier, error)
|
CreateSuppliers(ctx context.Context, arg CreateSuppliersParams) (Supplier, error)
|
||||||
|
77
db/sqlc/sale_order.sql.go
Normal file
77
db/sqlc/sale_order.sql.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.17.2
|
||||||
|
// source: sale_order.sql
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createSaleOrder = `-- name: CreateSaleOrder :one
|
||||||
|
INSERT INTO sale_order (
|
||||||
|
merchant_id,
|
||||||
|
customer_id,
|
||||||
|
code,
|
||||||
|
created_by,
|
||||||
|
is_paid,
|
||||||
|
total,
|
||||||
|
paid_nominal,
|
||||||
|
note,
|
||||||
|
change,
|
||||||
|
is_keep
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
|
||||||
|
)
|
||||||
|
RETURNING id, index_id, code, created_by, merchant_id, customer_id, is_paid, total, paid_nominal, note, created_at, updated_at, is_keep, change
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateSaleOrderParams struct {
|
||||||
|
MerchantID uuid.UUID `json:"merchant_id"`
|
||||||
|
CustomerID uuid.NullUUID `json:"customer_id"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
CreatedBy uuid.UUID `json:"created_by"`
|
||||||
|
IsPaid bool `json:"is_paid"`
|
||||||
|
Total float64 `json:"total"`
|
||||||
|
PaidNominal float64 `json:"paid_nominal"`
|
||||||
|
Note sql.NullString `json:"note"`
|
||||||
|
Change float64 `json:"change"`
|
||||||
|
IsKeep bool `json:"is_keep"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateSaleOrder(ctx context.Context, arg CreateSaleOrderParams) (SaleOrder, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, createSaleOrder,
|
||||||
|
arg.MerchantID,
|
||||||
|
arg.CustomerID,
|
||||||
|
arg.Code,
|
||||||
|
arg.CreatedBy,
|
||||||
|
arg.IsPaid,
|
||||||
|
arg.Total,
|
||||||
|
arg.PaidNominal,
|
||||||
|
arg.Note,
|
||||||
|
arg.Change,
|
||||||
|
arg.IsKeep,
|
||||||
|
)
|
||||||
|
var i SaleOrder
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.IndexID,
|
||||||
|
&i.Code,
|
||||||
|
&i.CreatedBy,
|
||||||
|
&i.MerchantID,
|
||||||
|
&i.CustomerID,
|
||||||
|
&i.IsPaid,
|
||||||
|
&i.Total,
|
||||||
|
&i.PaidNominal,
|
||||||
|
&i.Note,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.IsKeep,
|
||||||
|
&i.Change,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
64
db/sqlc/sale_order_detail.sql.go
Normal file
64
db/sqlc/sale_order_detail.sql.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.17.2
|
||||||
|
// source: sale_order_detail.sql
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createSaleOrderDetail = `-- name: CreateSaleOrderDetail :one
|
||||||
|
INSERT INTO sale_order_detail (
|
||||||
|
sale_order_id,
|
||||||
|
product_id,
|
||||||
|
product_name,
|
||||||
|
quantity,
|
||||||
|
sub_total,
|
||||||
|
product_price,
|
||||||
|
profit
|
||||||
|
) VALUES(
|
||||||
|
$1, $2, $3, $4, $5, $6, $7
|
||||||
|
)
|
||||||
|
RETURNING id, index_id, sale_order_id, product_id, product_name, quantity, sub_total, product_price, profit, created_at, updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateSaleOrderDetailParams struct {
|
||||||
|
SaleOrderID uuid.UUID `json:"sale_order_id"`
|
||||||
|
ProductID uuid.UUID `json:"product_id"`
|
||||||
|
ProductName string `json:"product_name"`
|
||||||
|
Quantity float64 `json:"quantity"`
|
||||||
|
SubTotal float64 `json:"sub_total"`
|
||||||
|
ProductPrice float64 `json:"product_price"`
|
||||||
|
Profit float64 `json:"profit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateSaleOrderDetail(ctx context.Context, arg CreateSaleOrderDetailParams) (SaleOrderDetail, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, createSaleOrderDetail,
|
||||||
|
arg.SaleOrderID,
|
||||||
|
arg.ProductID,
|
||||||
|
arg.ProductName,
|
||||||
|
arg.Quantity,
|
||||||
|
arg.SubTotal,
|
||||||
|
arg.ProductPrice,
|
||||||
|
arg.Profit,
|
||||||
|
)
|
||||||
|
var i SaleOrderDetail
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.IndexID,
|
||||||
|
&i.SaleOrderID,
|
||||||
|
&i.ProductID,
|
||||||
|
&i.ProductName,
|
||||||
|
&i.Quantity,
|
||||||
|
&i.SubTotal,
|
||||||
|
&i.ProductPrice,
|
||||||
|
&i.Profit,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
119
db/sqlc/store.go
119
db/sqlc/store.go
@ -13,6 +13,7 @@ type Store interface {
|
|||||||
Querier
|
Querier
|
||||||
PurchaseOrderTx(ctx context.Context, arg PurchasoOrderTxParams) (PurchaseOrderTxResult, error)
|
PurchaseOrderTx(ctx context.Context, arg PurchasoOrderTxParams) (PurchaseOrderTxResult, error)
|
||||||
CreateUserMerchantTx(ctx context.Context, arg UserMerchantTxParams) (UserMerchantTxResult, error)
|
CreateUserMerchantTx(ctx context.Context, arg UserMerchantTxParams) (UserMerchantTxResult, error)
|
||||||
|
SaleOrderTx(ctx context.Context, arg SaleOrderTxParams) (SaleOrderTxResult, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SQLStore struct {
|
type SQLStore struct {
|
||||||
@ -46,6 +47,8 @@ func (store *SQLStore) execTx(ctx context.Context, fn func(*Queries) error) erro
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------- PURCHASE ORDER TRANSACTION --------- //
|
||||||
|
|
||||||
type PurchaseOrderProduct struct {
|
type PurchaseOrderProduct struct {
|
||||||
ProductID uuid.UUID `json:"product_id"`
|
ProductID uuid.UUID `json:"product_id"`
|
||||||
Quantity float64 `json:"quantity"`
|
Quantity float64 `json:"quantity"`
|
||||||
@ -127,11 +130,11 @@ func (store *SQLStore) PurchaseOrderTx(ctx context.Context, arg PurchasoOrderTxP
|
|||||||
MerchantID: arg.MerchantID,
|
MerchantID: arg.MerchantID,
|
||||||
CreatedBy: arg.CreatedBy,
|
CreatedBy: arg.CreatedBy,
|
||||||
TransactionID: uuid.NullUUID{UUID: result.PurchaseOrder.ID, Valid: true},
|
TransactionID: uuid.NullUUID{UUID: result.PurchaseOrder.ID, Valid: true},
|
||||||
TransactionActionType: "Purchase_order",
|
TransactionActionType: "purchase_order",
|
||||||
TransactionDescription: fmt.Sprintf("Penambahan produk %s dari pembelian dengan code %s", product.Name, arg.Code.String),
|
TransactionDescription: fmt.Sprintf("Penambahan stok produk %s dari pembelian dengan code %s", product.Name, arg.Code.String),
|
||||||
Type: util.STOCK_LOG_IN,
|
Type: util.STOCK_LOG_IN,
|
||||||
SellingPrice: product.SellingPrice,
|
SellingPrice: product.SellingPrice,
|
||||||
PurchasePrice: product.PurchasePrice,
|
PurchasePrice: arg.Products[i].Price,
|
||||||
Quantity: arg.Products[i].Quantity,
|
Quantity: arg.Products[i].Quantity,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -147,6 +150,116 @@ func (store *SQLStore) PurchaseOrderTx(ctx context.Context, arg PurchasoOrderTxP
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------ SALE ORDER TRANSACTION ----------- //
|
||||||
|
|
||||||
|
type SaleOrderProduct struct {
|
||||||
|
ProductID uuid.UUID `json:"product_id"`
|
||||||
|
ProductName string `json:"product_name"`
|
||||||
|
Quantity float64 `json:"quantity"`
|
||||||
|
Sub_total float64 `json:"sub_total"`
|
||||||
|
Price float64 `json:"price"`
|
||||||
|
Profit float64 `json:"profit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SaleOrderTxParams struct {
|
||||||
|
MerchantID uuid.UUID `json:"merchant_id"`
|
||||||
|
CreatedBy uuid.UUID `json:"created_by"` // user_id
|
||||||
|
CustomerID uuid.NullUUID `json:"customer_id"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
IsPaid bool `json:"is_paid"`
|
||||||
|
Total float64 `json:"total"`
|
||||||
|
PaidNominal float64 `json:"paid_nominal"`
|
||||||
|
Note sql.NullString `json:"note"`
|
||||||
|
IsKeep bool `json:"is_keep"`
|
||||||
|
Change float64 `json:"change"`
|
||||||
|
Products []SaleOrderProduct `json:"products"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SaleOrderTxResult struct {
|
||||||
|
SaleOrder SaleOrder `json:"sale_order"`
|
||||||
|
SaleOrderDetail []SaleOrderDetail `json:"detail"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *SQLStore) SaleOrderTx(ctx context.Context, arg SaleOrderTxParams) (SaleOrderTxResult, error) {
|
||||||
|
var result SaleOrderTxResult
|
||||||
|
|
||||||
|
err := store.execTx(ctx, func(q *Queries) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
result.SaleOrder, err = q.CreateSaleOrder(ctx, CreateSaleOrderParams{
|
||||||
|
MerchantID: arg.MerchantID,
|
||||||
|
CustomerID: uuid.NullUUID{Valid: arg.CustomerID.Valid, UUID: arg.CustomerID.UUID},
|
||||||
|
Code: arg.Code,
|
||||||
|
CreatedBy: arg.CreatedBy,
|
||||||
|
IsPaid: arg.IsPaid,
|
||||||
|
Total: arg.Total,
|
||||||
|
PaidNominal: arg.PaidNominal,
|
||||||
|
Note: sql.NullString{Valid: len(arg.Note.String) > 0, String: arg.Note.String},
|
||||||
|
IsKeep: arg.IsKeep,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(arg.Products); i++ {
|
||||||
|
saleOrderDetail, err := q.CreateSaleOrderDetail(ctx, CreateSaleOrderDetailParams{
|
||||||
|
SaleOrderID: result.SaleOrder.ID,
|
||||||
|
ProductID: arg.Products[i].ProductID,
|
||||||
|
ProductName: arg.Products[i].ProductName,
|
||||||
|
Quantity: arg.Products[i].Quantity,
|
||||||
|
SubTotal: arg.Products[i].Sub_total,
|
||||||
|
ProductPrice: arg.Products[i].Price,
|
||||||
|
Profit: arg.Products[i].Profit,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.SaleOrderDetail = append(result.SaleOrderDetail, saleOrderDetail)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = q.CreateStockLogs(ctx, CreateStockLogsParams{
|
||||||
|
ProductID: arg.Products[i].ProductID,
|
||||||
|
MerchantID: arg.MerchantID,
|
||||||
|
CreatedBy: arg.CreatedBy,
|
||||||
|
TransactionID: uuid.NullUUID{UUID: result.SaleOrder.ID, Valid: true},
|
||||||
|
TransactionActionType: "sale_order",
|
||||||
|
TransactionDescription: fmt.Sprintf("Pengurangan stok produk %s dari penjualan dengan code %s", product.Name, arg.Code),
|
||||||
|
Type: util.STOCK_LOG_OUT,
|
||||||
|
SellingPrice: arg.Products[i].Price,
|
||||||
|
PurchasePrice: product.PurchasePrice,
|
||||||
|
Quantity: arg.Products[i].Quantity,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- CREATE USER MERCHANT TRANSACTION ------------ //
|
||||||
|
|
||||||
type UserMerchantTxParams struct {
|
type UserMerchantTxParams struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Fullname string `json:"fullname"`
|
Fullname string `json:"fullname"`
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.nochill.in/nochill/naice_pos/util"
|
"git.nochill.in/nochill/naice_pos/util"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -13,7 +14,7 @@ func TestPurchaseOrder(t *testing.T) {
|
|||||||
var products []PurchaseOrderProduct
|
var products []PurchaseOrderProduct
|
||||||
|
|
||||||
store := NewStore(testDB)
|
store := NewStore(testDB)
|
||||||
user := createRandomUser(t)
|
user := createRandomUser(t) // TODO: Change to use UserMerchantTx
|
||||||
supplier, _ := createRandomSupplier(t)
|
supplier, _ := createRandomSupplier(t)
|
||||||
product1, _ := createRandomProduct(t)
|
product1, _ := createRandomProduct(t)
|
||||||
product2, _ := createRandomProduct(t)
|
product2, _ := createRandomProduct(t)
|
||||||
@ -50,8 +51,8 @@ func TestPurchaseOrder(t *testing.T) {
|
|||||||
for i := 0; i < tries; i++ {
|
for i := 0; i < tries; 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, // GET FROM UserMerchantTx
|
||||||
CreatedBy: user.ID,
|
CreatedBy: user.ID, // TODO: GET FROM UserMerchantTx
|
||||||
SupplierID: supplier.ID,
|
SupplierID: supplier.ID,
|
||||||
Code: sql.NullString{Valid: true, String: util.RandomTransactionCode("P", util.RandomInt(1, 10))},
|
Code: sql.NullString{Valid: true, String: util.RandomTransactionCode("P", util.RandomInt(1, 10))},
|
||||||
IsPaid: true,
|
IsPaid: true,
|
||||||
@ -80,6 +81,82 @@ func TestPurchaseOrder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateSaleOrder(t *testing.T) {
|
||||||
|
var products []SaleOrderProduct
|
||||||
|
|
||||||
|
store := NewStore(testDB)
|
||||||
|
user := createRandomUser(t)
|
||||||
|
customer, _ := createRandomCustomer(t)
|
||||||
|
product1, _ := createRandomProduct(t)
|
||||||
|
product2, _ := createRandomProduct(t)
|
||||||
|
|
||||||
|
errs := make(chan error)
|
||||||
|
results := make(chan SaleOrderTxResult)
|
||||||
|
|
||||||
|
saleProducts1Quantity := util.RandomFloat(1, 99)
|
||||||
|
saleProducts1Price := util.RandomFloat(999, 9999)
|
||||||
|
saleProducts1SubTotal := saleProducts1Price * saleProducts1Quantity
|
||||||
|
saleProducts1Profit := saleProducts1SubTotal - saleProducts1Quantity*product1.SellingPrice
|
||||||
|
|
||||||
|
saleProducts2Quantity := util.RandomFloat(1, 99)
|
||||||
|
saleProducts2Price := util.RandomFloat(999, 9999)
|
||||||
|
saleProducts2SubTotal := saleProducts2Price * saleProducts2Quantity
|
||||||
|
saleProducts2Profit := saleProducts2SubTotal - saleProducts2Quantity*product2.SellingPrice
|
||||||
|
|
||||||
|
saleProducts_1 := SaleOrderProduct{
|
||||||
|
ProductID: product1.ID,
|
||||||
|
Quantity: saleProducts1Quantity,
|
||||||
|
Sub_total: saleProducts1SubTotal,
|
||||||
|
Price: saleProducts1Price,
|
||||||
|
ProductName: product1.Name,
|
||||||
|
Profit: saleProducts1Profit,
|
||||||
|
}
|
||||||
|
|
||||||
|
saleProducts_2 := SaleOrderProduct{
|
||||||
|
ProductID: product2.ID,
|
||||||
|
Quantity: saleProducts2Quantity,
|
||||||
|
Sub_total: saleProducts2SubTotal,
|
||||||
|
Price: saleProducts2Price,
|
||||||
|
ProductName: product2.Name,
|
||||||
|
Profit: saleProducts2Profit,
|
||||||
|
}
|
||||||
|
|
||||||
|
products = append(products, saleProducts_1, saleProducts_2)
|
||||||
|
|
||||||
|
tries := 3
|
||||||
|
|
||||||
|
for i := 0; i < tries; i++ {
|
||||||
|
go func() {
|
||||||
|
result, err := store.SaleOrderTx(context.Background(), SaleOrderTxParams{
|
||||||
|
MerchantID: customer.MerchantID,
|
||||||
|
CreatedBy: user.ID,
|
||||||
|
CustomerID: uuid.NullUUID{Valid: true, UUID: customer.ID},
|
||||||
|
Code: util.RandomTransactionCode("S", util.RandomInt(1, 10)),
|
||||||
|
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 < tries; i++ {
|
||||||
|
err := <-errs
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
result := <-results
|
||||||
|
require.NotEmpty(t, result)
|
||||||
|
|
||||||
|
saleOrder := result.SaleOrder
|
||||||
|
require.NotEmpty(t, saleOrder)
|
||||||
|
require.Equal(t, saleOrder.MerchantID, customer.MerchantID) // TODO: Change to use UserMerchantTx result
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestCreateUserMerchant(t *testing.T) {
|
func TestCreateUserMerchant(t *testing.T) {
|
||||||
store := NewStore(testDB)
|
store := NewStore(testDB)
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
func createRandomSupplier(t *testing.T) (Supplier, CreateSuppliersParams) {
|
func createRandomSupplier(t *testing.T) (Supplier, CreateSuppliersParams) {
|
||||||
arg := CreateSuppliersParams{
|
arg := CreateSuppliersParams{
|
||||||
MerchantID: uuid.MustParse("04a1b0a7-69b4-41da-a053-2f6b95c93195"),
|
MerchantID: uuid.MustParse("54b8a2d9-16be-4239-8828-5daa317028dc"),
|
||||||
Name: util.RandomString(10),
|
Name: util.RandomString(10),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
8
util/validate.go
Normal file
8
util/validate.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import "github.com/google/uuid"
|
||||||
|
|
||||||
|
func IsValidUUID(u string) bool {
|
||||||
|
_, err := uuid.Parse(u)
|
||||||
|
return err == nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user