diff --git a/db/migrations/000005_create_stock_logs_table.down.sql b/db/migrations/000005_create_stock_logs_table.down.sql new file mode 100644 index 0000000..5ee59d8 --- /dev/null +++ b/db/migrations/000005_create_stock_logs_table.down.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS stock_logs; +DROP TYPE IF EXISTS stock_logs_type; \ No newline at end of file diff --git a/db/migrations/000005_create_stock_logs_table.up.sql b/db/migrations/000005_create_stock_logs_table.up.sql new file mode 100644 index 0000000..859eb32 --- /dev/null +++ b/db/migrations/000005_create_stock_logs_table.up.sql @@ -0,0 +1,24 @@ +CREATE TYPE stock_logs_type AS ENUM ( + 'in', + 'out' +); + +CREATE TABLE stock_logs ( + "id" uuid default gen_random_uuid() not null primary key, + "index_id" bigserial not null, + "merchant_id" uuid references "merchants"("id") not null, + "product_id" uuid references "products"("id") not null, + "created_by" uuid references "users"("id") not null, + "transaction_id" uuid, + "transaction_action_type" varchar not null , -- "penjualan / pembelian / pengaturan stok" + "transaction_description" varchar not null, + "type" stock_logs_type not null, + "selling_price" double precision default(0::double precision) NOT NULL, + "purchase_price" double precision default(0:: double precision) NOT NULL, + "quantity" double precision default(0::double precision) NOT NULL, + + "created_at" timestamp default(now()), + "updated_at" timestamp default(now()) +); + +CREATE INDEX ON "stock_logs"("index_id"); \ No newline at end of file diff --git a/db/mock/store.go b/db/mock/store.go index 9fd5d4f..c971e94 100644 --- a/db/mock/store.go +++ b/db/mock/store.go @@ -141,6 +141,21 @@ func (mr *MockStoreMockRecorder) CreateSession(arg0, arg1 interface{}) *gomock.C return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSession", reflect.TypeOf((*MockStore)(nil).CreateSession), arg0, arg1) } +// CreateStockLogs mocks base method. +func (m *MockStore) CreateStockLogs(arg0 context.Context, arg1 db.CreateStockLogsParams) (db.StockLog, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateStockLogs", arg0, arg1) + ret0, _ := ret[0].(db.StockLog) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateStockLogs indicates an expected call of CreateStockLogs. +func (mr *MockStoreMockRecorder) CreateStockLogs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateStockLogs", reflect.TypeOf((*MockStore)(nil).CreateStockLogs), arg0, arg1) +} + // CreateSuppliers mocks base method. func (m *MockStore) CreateSuppliers(arg0 context.Context, arg1 db.CreateSuppliersParams) (db.Supplier, error) { m.ctrl.T.Helper() @@ -378,10 +393,10 @@ func (mr *MockStoreMockRecorder) GetStockForUpdateStock(arg0, arg1 interface{}) } // GetUserByEmail mocks base method. -func (m *MockStore) GetUserByEmail(arg0 context.Context, arg1 string) (db.User, error) { +func (m *MockStore) GetUserByEmail(arg0 context.Context, arg1 string) (db.GetUserByEmailRow, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetUserByEmail", arg0, arg1) - ret0, _ := ret[0].(db.User) + ret0, _ := ret[0].(db.GetUserByEmailRow) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -393,10 +408,10 @@ func (mr *MockStoreMockRecorder) GetUserByEmail(arg0, arg1 interface{}) *gomock. } // GetUserById mocks base method. -func (m *MockStore) GetUserById(arg0 context.Context, arg1 uuid.UUID) (db.User, error) { +func (m *MockStore) GetUserById(arg0 context.Context, arg1 uuid.UUID) (db.GetUserByIdRow, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetUserById", arg0, arg1) - ret0, _ := ret[0].(db.User) + ret0, _ := ret[0].(db.GetUserByIdRow) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/db/query/stock_log.sql b/db/query/stock_log.sql new file mode 100644 index 0000000..b0dd02d --- /dev/null +++ b/db/query/stock_log.sql @@ -0,0 +1,16 @@ +-- name: CreateStockLogs :one +INSERT INTO stock_logs ( + product_id, + merchant_id, + created_by, + transaction_id, + transaction_action_type, + transaction_description, + type, + selling_price, + purchase_price, + quantity +) VALUES ( + $1, $2, $3, $4, $5 ,$6, $7, $8, $9, $10 +) +RETURNING *; \ No newline at end of file diff --git a/db/query/users.sql b/db/query/users.sql index 0ee2502..793a4d2 100644 --- a/db/query/users.sql +++ b/db/query/users.sql @@ -12,11 +12,13 @@ FROM users WHERE email = $1; -- name: GetUserById :one -SELECT * -FROM users -WHERE id = $1; +SELECT * +FROM users u +JOIN merchants m on u.id = m.owner_id +WHERE u.id = $1; -- name: GetUserByEmail :one SELECT * -FROM users -WHERE email = $1; \ No newline at end of file +FROM users u +JOIN merchants m on u.id = m.owner_id +WHERE email = $1; \ No newline at end of file diff --git a/db/sqlc/models.go b/db/sqlc/models.go index 5c6d551..d3005dc 100644 --- a/db/sqlc/models.go +++ b/db/sqlc/models.go @@ -6,13 +6,57 @@ package db import ( "database/sql" + "database/sql/driver" "encoding/json" + "fmt" "time" "github.com/google/uuid" "github.com/tabbed/pqtype" ) +type StockLogsType string + +const ( + StockLogsTypeIn StockLogsType = "in" + StockLogsTypeOut StockLogsType = "out" +) + +func (e *StockLogsType) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = StockLogsType(s) + case string: + *e = StockLogsType(s) + default: + return fmt.Errorf("unsupported scan type for StockLogsType: %T", src) + } + return nil +} + +type NullStockLogsType struct { + StockLogsType StockLogsType + Valid bool // Valid is true if StockLogsType is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullStockLogsType) Scan(value interface{}) error { + if value == nil { + ns.StockLogsType, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.StockLogsType.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullStockLogsType) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return string(ns.StockLogsType), nil +} + type Customer struct { ID uuid.UUID `json:"id"` IndexID int64 `json:"index_id"` @@ -108,6 +152,23 @@ type SaleOrderDetail struct { UpdatedAt sql.NullTime `json:"updated_at"` } +type StockLog struct { + ID uuid.UUID `json:"id"` + IndexID int64 `json:"index_id"` + MerchantID uuid.UUID `json:"merchant_id"` + ProductID uuid.UUID `json:"product_id"` + CreatedBy uuid.UUID `json:"created_by"` + TransactionID uuid.NullUUID `json:"transaction_id"` + TransactionActionType string `json:"transaction_action_type"` + TransactionDescription string `json:"transaction_description"` + Type StockLogsType `json:"type"` + SellingPrice float64 `json:"selling_price"` + PurchasePrice float64 `json:"purchase_price"` + Quantity float64 `json:"quantity"` + CreatedAt sql.NullTime `json:"created_at"` + UpdatedAt sql.NullTime `json:"updated_at"` +} + type Supplier struct { ID uuid.UUID `json:"id"` IndexID int64 `json:"index_id"` diff --git a/db/sqlc/querier.go b/db/sqlc/querier.go index f368ba9..2d5ac68 100644 --- a/db/sqlc/querier.go +++ b/db/sqlc/querier.go @@ -18,6 +18,7 @@ type Querier interface { CreatePurchaseOrder(ctx context.Context, arg CreatePurchaseOrderParams) (PurchaseOrder, error) CreatePurchaseOrderDetail(ctx context.Context, arg CreatePurchaseOrderDetailParams) (PurchaseOrderDetail, error) CreateSession(ctx context.Context, arg CreateSessionParams) (UserSession, error) + CreateStockLogs(ctx context.Context, arg CreateStockLogsParams) (StockLog, error) CreateSuppliers(ctx context.Context, arg CreateSuppliersParams) (Supplier, error) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) CustomersList(ctx context.Context, arg CustomersListParams) ([]Customer, error) @@ -33,8 +34,8 @@ type Querier interface { GetPurchaseOrderDetailsByPuchaseOrderId(ctx context.Context, purchaseOrderID uuid.UUID) (PurchaseOrderDetail, error) GetSession(ctx context.Context, id uuid.UUID) (UserSession, error) GetStockForUpdateStock(ctx context.Context, id uuid.UUID) (Product, error) - GetUserByEmail(ctx context.Context, email string) (User, error) - GetUserById(ctx context.Context, id uuid.UUID) (User, error) + GetUserByEmail(ctx context.Context, email string) (GetUserByEmailRow, error) + GetUserById(ctx context.Context, id uuid.UUID) (GetUserByIdRow, error) ListProductCategoriesByMerchantID(ctx context.Context, arg ListProductCategoriesByMerchantIDParams) ([]ProductCategory, error) ListProducts(ctx context.Context, arg ListProductsParams) ([]Product, error) ListPurchaseOrderByMerchantId(ctx context.Context, merchantID uuid.UUID) ([]PurchaseOrder, error) diff --git a/db/sqlc/stock_log.sql.go b/db/sqlc/stock_log.sql.go new file mode 100644 index 0000000..1959c58 --- /dev/null +++ b/db/sqlc/stock_log.sql.go @@ -0,0 +1,76 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.17.2 +// source: stock_log.sql + +package db + +import ( + "context" + + "github.com/google/uuid" +) + +const createStockLogs = `-- name: CreateStockLogs :one +INSERT INTO stock_logs ( + product_id, + merchant_id, + created_by, + transaction_id, + transaction_action_type, + transaction_description, + type, + selling_price, + purchase_price, + quantity +) VALUES ( + $1, $2, $3, $4, $5 ,$6, $7, $8, $9, $10 +) +RETURNING id, index_id, merchant_id, product_id, created_by, transaction_id, transaction_action_type, transaction_description, type, selling_price, purchase_price, quantity, created_at, updated_at +` + +type CreateStockLogsParams struct { + ProductID uuid.UUID `json:"product_id"` + MerchantID uuid.UUID `json:"merchant_id"` + CreatedBy uuid.UUID `json:"created_by"` + TransactionID uuid.NullUUID `json:"transaction_id"` + TransactionActionType string `json:"transaction_action_type"` + TransactionDescription string `json:"transaction_description"` + Type StockLogsType `json:"type"` + SellingPrice float64 `json:"selling_price"` + PurchasePrice float64 `json:"purchase_price"` + Quantity float64 `json:"quantity"` +} + +func (q *Queries) CreateStockLogs(ctx context.Context, arg CreateStockLogsParams) (StockLog, error) { + row := q.db.QueryRowContext(ctx, createStockLogs, + arg.ProductID, + arg.MerchantID, + arg.CreatedBy, + arg.TransactionID, + arg.TransactionActionType, + arg.TransactionDescription, + arg.Type, + arg.SellingPrice, + arg.PurchasePrice, + arg.Quantity, + ) + var i StockLog + err := row.Scan( + &i.ID, + &i.IndexID, + &i.MerchantID, + &i.ProductID, + &i.CreatedBy, + &i.TransactionID, + &i.TransactionActionType, + &i.TransactionDescription, + &i.Type, + &i.SellingPrice, + &i.PurchasePrice, + &i.Quantity, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} diff --git a/db/sqlc/stock_log_test.go b/db/sqlc/stock_log_test.go new file mode 100644 index 0000000..d5c53a3 --- /dev/null +++ b/db/sqlc/stock_log_test.go @@ -0,0 +1,37 @@ +package db + +import ( + "context" + "fmt" + "testing" + + "git.nochill.in/nochill/naice_pos/util" + "github.com/google/uuid" + "github.com/stretchr/testify/require" +) + +func createRandomStockLog(t *testing.T) { + product, _ := createRandomProduct(t) + user := createRandomUser(t) + + arg := CreateStockLogsParams{ + ProductID: product.ID, + MerchantID: product.MerchantID, + CreatedBy: user.ID, + TransactionID: uuid.NullUUID{Valid: true, UUID: uuid.New()}, + TransactionActionType: "Stock_log", + TransactionDescription: fmt.Sprintf("Penyesuaian stok %s", util.RandomTransactionCode("P", user.IndexID)), + SellingPrice: util.RandomFloat(10000, 99999), + PurchasePrice: util.RandomFloat(5000, 50000), + Quantity: util.RandomFloat(1, 100), + Type: util.STOCK_LOG_IN, + } + + stockLog, err := testQueries.CreateStockLogs(context.Background(), arg) + require.NoError(t, err) + require.NotEmpty(t, stockLog) +} + +func TestCreateStockLog(t *testing.T) { + createRandomStockLog(t) +} diff --git a/db/sqlc/store.go b/db/sqlc/store.go index a12e4a4..ba83003 100644 --- a/db/sqlc/store.go +++ b/db/sqlc/store.go @@ -5,6 +5,7 @@ import ( "database/sql" "fmt" + "git.nochill.in/nochill/naice_pos/util" "github.com/google/uuid" ) @@ -54,6 +55,7 @@ type PurchaseOrderProduct struct { type PurchasoOrderTxParams struct { MerchantID uuid.UUID `json:"merchant_id"` + CreatedBy uuid.UUID `json:"created_by"` // user_id SupplierID uuid.UUID `json:"supplier_id"` Code sql.NullString `json:"code"` IsPaid bool `json:"is_paid"` @@ -115,6 +117,22 @@ func (store *SQLStore) PurchaseOrderTx(ctx context.Context, arg PurchasoOrderTxP 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.PurchaseOrder.ID, Valid: true}, + TransactionActionType: "Purchase_order", + TransactionDescription: fmt.Sprintf("Penambahan produk %s dari pembelian dengan code %s", product.Name, arg.Code.String), + Type: util.STOCK_LOG_IN, + SellingPrice: product.SellingPrice, + PurchasePrice: product.PurchasePrice, + Quantity: arg.Products[i].Quantity, + }) if err != nil { return err diff --git a/db/sqlc/store_test.go b/db/sqlc/store_test.go index e7a3e36..6217bcb 100644 --- a/db/sqlc/store_test.go +++ b/db/sqlc/store_test.go @@ -13,6 +13,7 @@ func TestPurchaseOrder(t *testing.T) { var products []PurchaseOrderProduct store := NewStore(testDB) + user := createRandomUser(t) supplier, _ := createRandomSupplier(t) product1, _ := createRandomProduct(t) product2, _ := createRandomProduct(t) @@ -50,8 +51,9 @@ func TestPurchaseOrder(t *testing.T) { go func() { result, err := store.PurchaseOrderTx(context.Background(), PurchasoOrderTxParams{ MerchantID: supplier.MerchantID, + CreatedBy: user.ID, SupplierID: supplier.ID, - Code: sql.NullString{Valid: true, String: ""}, + Code: sql.NullString{Valid: true, String: util.RandomTransactionCode("P", util.RandomInt(1, 10))}, IsPaid: true, Total: product1.PurchasePrice + product2.PurchasePrice, PaidNominal: product1.PurchasePrice + product2.PurchasePrice, diff --git a/db/sqlc/users.sql.go b/db/sqlc/users.sql.go index 4600fb4..6eb3562 100644 --- a/db/sqlc/users.sql.go +++ b/db/sqlc/users.sql.go @@ -7,6 +7,7 @@ package db import ( "context" + "database/sql" "github.com/google/uuid" ) @@ -55,14 +56,31 @@ func (q *Queries) GetPasswordByEmail(ctx context.Context, email string) (string, } const getUserByEmail = `-- name: GetUserByEmail :one -SELECT id, index_id, email, password, fullname, created_at, updated_at -FROM users +SELECT u.id, u.index_id, email, password, fullname, u.created_at, u.updated_at, m.id, m.index_id, name, owner_id, m.created_at, m.updated_at +FROM users u +JOIN merchants m on u.id = m.owner_id WHERE email = $1 ` -func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error) { +type GetUserByEmailRow struct { + ID uuid.UUID `json:"id"` + IndexID int64 `json:"index_id"` + Email string `json:"email"` + Password string `json:"password"` + Fullname string `json:"fullname"` + CreatedAt sql.NullTime `json:"created_at"` + UpdatedAt sql.NullTime `json:"updated_at"` + ID_2 uuid.UUID `json:"id_2"` + IndexID_2 int64 `json:"index_id_2"` + Name string `json:"name"` + OwnerID uuid.UUID `json:"owner_id"` + CreatedAt_2 sql.NullTime `json:"created_at_2"` + UpdatedAt_2 sql.NullTime `json:"updated_at_2"` +} + +func (q *Queries) GetUserByEmail(ctx context.Context, email string) (GetUserByEmailRow, error) { row := q.db.QueryRowContext(ctx, getUserByEmail, email) - var i User + var i GetUserByEmailRow err := row.Scan( &i.ID, &i.IndexID, @@ -71,19 +89,42 @@ func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error &i.Fullname, &i.CreatedAt, &i.UpdatedAt, + &i.ID_2, + &i.IndexID_2, + &i.Name, + &i.OwnerID, + &i.CreatedAt_2, + &i.UpdatedAt_2, ) return i, err } const getUserById = `-- name: GetUserById :one -SELECT id, index_id, email, password, fullname, created_at, updated_at -FROM users -WHERE id = $1 +SELECT u.id, u.index_id, email, password, fullname, u.created_at, u.updated_at, m.id, m.index_id, name, owner_id, m.created_at, m.updated_at +FROM users u +JOIN merchants m on u.id = m.owner_id +WHERE u.id = $1 ` -func (q *Queries) GetUserById(ctx context.Context, id uuid.UUID) (User, error) { +type GetUserByIdRow struct { + ID uuid.UUID `json:"id"` + IndexID int64 `json:"index_id"` + Email string `json:"email"` + Password string `json:"password"` + Fullname string `json:"fullname"` + CreatedAt sql.NullTime `json:"created_at"` + UpdatedAt sql.NullTime `json:"updated_at"` + ID_2 uuid.UUID `json:"id_2"` + IndexID_2 int64 `json:"index_id_2"` + Name string `json:"name"` + OwnerID uuid.UUID `json:"owner_id"` + CreatedAt_2 sql.NullTime `json:"created_at_2"` + UpdatedAt_2 sql.NullTime `json:"updated_at_2"` +} + +func (q *Queries) GetUserById(ctx context.Context, id uuid.UUID) (GetUserByIdRow, error) { row := q.db.QueryRowContext(ctx, getUserById, id) - var i User + var i GetUserByIdRow err := row.Scan( &i.ID, &i.IndexID, @@ -92,6 +133,12 @@ func (q *Queries) GetUserById(ctx context.Context, id uuid.UUID) (User, error) { &i.Fullname, &i.CreatedAt, &i.UpdatedAt, + &i.ID_2, + &i.IndexID_2, + &i.Name, + &i.OwnerID, + &i.CreatedAt_2, + &i.UpdatedAt_2, ) return i, err } diff --git a/db/sqlc/users_test.go b/db/sqlc/users_test.go index 20d81b6..ec8675b 100644 --- a/db/sqlc/users_test.go +++ b/db/sqlc/users_test.go @@ -1,7 +1,30 @@ package db -import "testing" +import ( + "context" + "testing" + + "git.nochill.in/nochill/naice_pos/util" + "github.com/stretchr/testify/require" +) + +func createRandomUser(t *testing.T) User { + arg := CreateUserParams{ + Email: util.RandomEmail(), + Password: util.RandomString(5), + Fullname: util.RandomString(6), + } + + user, err := testQueries.CreateUser(context.Background(), arg) + + require.NoError(t, err) + require.Equal(t, arg.Email, user.Email) + require.Equal(t, arg.Password, user.Password) + require.Equal(t, arg.Fullname, user.Fullname) + + return user +} func TestGetUserByID(t *testing.T) { - + createRandomUser(t) } diff --git a/util/enums.go b/util/enums.go new file mode 100644 index 0000000..cb8c51f --- /dev/null +++ b/util/enums.go @@ -0,0 +1,6 @@ +package util + +const ( + STOCK_LOG_IN = "in" + STOCK_LOG_OUT = "out" +) diff --git a/util/random.go b/util/random.go index b0a36da..0aafc97 100644 --- a/util/random.go +++ b/util/random.go @@ -43,3 +43,8 @@ func RandomString(n int) string { func RandomEmail() string { return fmt.Sprintf("%s@mail.com", RandomString(5)) } + +func RandomTransactionCode(prefix string, merchant_index int64) string { + time_now := time.Now().Unix() + return fmt.Sprintf("%s%d%d", prefix, merchant_index, time_now) +}