This commit is contained in:
nochill 2023-03-05 23:35:41 +07:00
commit 287eb2f205
28 changed files with 1453 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.env

4
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"editor.tabSize": 2,
"editor.minimap.enabled": false
}

15
Makefile Normal file
View File

@ -0,0 +1,15 @@
include .env
migrateup:
migrate -path db/migrations -database "${DB_TYPE}://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable" -verbose up
migratedown:
migrate -path db/migrations -database "${DB_TYPE}://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable" -verbose down
sqlc:
sqlc generate
test:
go test -v -cover ./...
.PHONY: migrateup migratedown sqlc

View File

@ -0,0 +1,9 @@
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS merchants;
DROP TABLE IF EXISTS suppliers;
DROP TABLE IF EXISTS customers;
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS purchase_order;
DROP TABLE IF EXISTS purchase_order_detail;
DROP TABLE IF EXISTS sale_order;
DROP TABLE IF EXISTS sale_order_detail;

View File

@ -0,0 +1,145 @@
CREATE TABLE users(
"id" uuid default gen_random_uuid() primary key not null,
"index_id" bigserial not null,
"email" varchar unique not null,
"password" varchar not null,
"fullname" varchar,
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
CREATE TABLE merchants (
"id" uuid default gen_random_uuid() primary key not null,
"index_id" bigserial not null,
"name" varchar not null,
"owner_id" uuid references "users"("id") not null,
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
create table suppliers (
"id" uuid default gen_random_uuid() primary key not null,
"index_id" bigserial not null,
"merchant_id" uuid references "merchants"("id") not null,
"name" varchar(100) not null,
"detail" jsonb,
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
CREATE TABLE customers (
"id" uuid default gen_random_uuid() primary key not null,
"index_id" bigserial not null,
"merchant_id" uuid references "merchants"("id") not null,
"name" varchar not null,
"detail" jsonb,
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
CREATE TABLE products (
"id" uuid default gen_random_uuid() primary key not null,
"merchant_id" uuid references "merchants"("id") not null,
"index_id" bigserial not null,
"name" varchar not null,
"selling_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,
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
CREATE TABLE purchase_order (
"id" uuid default gen_random_uuid() primary key not null,
"supplier_id" uuid references "suppliers"("id") not null,
"merchant_id" uuid references "merchants"("id") not null,
"index_id" bigserial not null,
"code" varchar(100),
"is_paid" boolean not null,
"total" double precision not null,
"paid_nominal" double precision not null,
"note" text,
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
CREATE TABLE purchase_order_detail (
"id" uuid default gen_random_uuid() primary key not null,
"index_id" bigserial not null,
"code" text,
"merchant_id" uuid references "merchants"("id") not null,
"purchase_order_id" uuid references "purchase_order"("id") not null,
"product_id" uuid references "products"("id") not null,
"quantity" double precision not null,
"sub_total" double precision not null,
"product_price" double precision not null,
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
CREATE TABLE sale_order (
"id" uuid default gen_random_uuid() primary key not null,
"index_id" bigserial not null,
"code" text,
"merchant_id" uuid references "merchants"("id") not null,
"customer_id" uuid references "customers"("id"),
"is_paid" boolean,
"total" double precision not null,
"paid_nominal" double precision not null,
"note" text,
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
CREATE TABLE sale_order_detail (
"id" uuid default gen_random_uuid() primary key not null,
"index_id" bigserial not null,
"sale_order_id" uuid references "sale_order"("id") not null,
"product_id" uuid references "products"("id") not null,
"product_name" varchar not null,
"quantity" double precision not null,
"sub_total" double precision not null,
"product_price" double precision not null,
"profit" double precision not null,
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
CREATE INDEX ON "users"("index_id");
CREATE INDEX ON "merchants"("index_id");
CREATE INDEX ON "suppliers"("index_id");
CREATE INDEX ON "customers"("index_id");
CREATE INDEX ON "products" ("name");
CREATE INDEX ON "products" ("selling_price");
CREATE INDEX ON "products" ("index_id");
CREATE INDEX ON "products" ("purchase_price");
CREATE INDEX ON "products" ("stock");
CREATE INDEX ON "purchase_order" ("merchant_id");
CREATE INDEX ON "purchase_order" ("supplier_id");
CREATE INDEX ON "purchase_order" ("index_id");
CREATE INDEX ON "purchase_order" ("created_at");
CREATE INDEX ON "purchase_order_detail" ("index_id");
CREATE INDEX ON "sale_order" ("index_id");
CREATE INDEX ON "sale_order_detail" ("index_id");

23
db/query/customers.sql Normal file
View File

@ -0,0 +1,23 @@
-- name: CreateCustomers :one
INSERT INTO customers (
merchant_id,
name,
detail
) VALUES (
$1, $2, $3
)
RETURNING *;
-- name: CustomersList :many
SELECT * FROM customers
WHERE merchant_id = $1
ORDER BY index_id
LIMIT $2
OFFSET $3;
-- name: UpdateCustomer :one
SELECT * FROM customers
WHERE id = $1;
-- name: DeleteCustomer :exec
DELETE FROM customers where id = $1;

8
db/query/merchant.sql Normal file
View File

@ -0,0 +1,8 @@
-- name: GetMerchantById :one
SELECT * FROM merchants
WHERE id = $1;
-- name: GetMerchantByUserId :one
SELECT * FROM merchants
WHERE owner_id = $1;

36
db/query/products.sql Normal file
View File

@ -0,0 +1,36 @@
-- name: CreateProduct :one
INSERT INTO products (
merchant_id,
name,
selling_price,
purchase_price,
stock
) VALUES (
$1, $2, $3, $4, $5
)
RETURNING *;
-- name: UpdateProductStock :one
UPDATE products
SET stock = $1
WHERE id = $2
RETURNING stock;
-- name: GetProduct :one
SELECT * FROM products
WHERE id = $1;
-- name: ListProducts :many
SELECT * FROM products
ORDER BY index_id
LIMIT $1
OFFSET $2;
-- name: UpdateProduct :one
UPDATE products
SET name = $2, selling_price = $3, purchase_price = $4, stock = $5, updated_at = $6
WHERE id = $1
RETURNING *;
-- name: DeleteProduct :exec
DELETE FROM products WHERE id = $1;

View File

@ -0,0 +1,13 @@
-- name: CreatePurchaseOrder :one
INSERT INTO purchase_order (
merchant_id,
supplier_id,
code,
is_paid,
total,
paid_nominal,
note
) VALUES (
$1, $2, $3, $4, $5, $6, $7
)
RETURNING *;

View File

@ -0,0 +1,12 @@
-- name: CreatePurchaseOrderDetail :one
INSERT INTO purchase_order_detail (
purchase_order_id,
merchant_id,
product_id,
quantity,
sub_total,
product_price
) VALUES (
$1, $2, $3, $4, $5, $6
)
RETURNING *;

23
db/query/suppliers.sql Normal file
View File

@ -0,0 +1,23 @@
-- name: CreateSuppliers :one
INSERT INTO suppliers (
merchant_id,
name,
detail
) VALUES (
$1, $2, $3
)
RETURNING *;
-- name: SuppliersList :many
SELECT * FROM suppliers
WHERE merchant_id = $1
ORDER BY index_id
LIMIT $2
OFFSET $3;
-- name: UpdateSupplier :one
SELECT * FROM suppliers
WHERE id = $1;
-- name: DeleteSupplier :exec
DELETE FROM suppliers where id = $1;

119
db/sqlc/customers.sql.go Normal file
View File

@ -0,0 +1,119 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
// source: customers.sql
package db
import (
"context"
"github.com/google/uuid"
"github.com/tabbed/pqtype"
)
const createCustomers = `-- name: CreateCustomers :one
INSERT INTO customers (
merchant_id,
name,
detail
) VALUES (
$1, $2, $3
)
RETURNING id, index_id, merchant_id, name, detail, created_at, updated_at
`
type CreateCustomersParams struct {
MerchantID uuid.UUID `json:"merchant_id"`
Name string `json:"name"`
Detail pqtype.NullRawMessage `json:"detail"`
}
func (q *Queries) CreateCustomers(ctx context.Context, arg CreateCustomersParams) (Customer, error) {
row := q.db.QueryRowContext(ctx, createCustomers, arg.MerchantID, arg.Name, arg.Detail)
var i Customer
err := row.Scan(
&i.ID,
&i.IndexID,
&i.MerchantID,
&i.Name,
&i.Detail,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const customersList = `-- name: CustomersList :many
SELECT id, index_id, merchant_id, name, detail, created_at, updated_at FROM customers
WHERE merchant_id = $1
ORDER BY index_id
LIMIT $2
OFFSET $3
`
type CustomersListParams struct {
MerchantID uuid.UUID `json:"merchant_id"`
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) CustomersList(ctx context.Context, arg CustomersListParams) ([]Customer, error) {
rows, err := q.db.QueryContext(ctx, customersList, arg.MerchantID, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Customer
for rows.Next() {
var i Customer
if err := rows.Scan(
&i.ID,
&i.IndexID,
&i.MerchantID,
&i.Name,
&i.Detail,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const deleteCustomer = `-- name: DeleteCustomer :exec
DELETE FROM customers where id = $1
`
func (q *Queries) DeleteCustomer(ctx context.Context, id uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteCustomer, id)
return err
}
const updateCustomer = `-- name: UpdateCustomer :one
SELECT id, index_id, merchant_id, name, detail, created_at, updated_at FROM customers
WHERE id = $1
`
func (q *Queries) UpdateCustomer(ctx context.Context, id uuid.UUID) (Customer, error) {
row := q.db.QueryRowContext(ctx, updateCustomer, id)
var i Customer
err := row.Scan(
&i.ID,
&i.IndexID,
&i.MerchantID,
&i.Name,
&i.Detail,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}

31
db/sqlc/db.go Normal file
View File

@ -0,0 +1,31 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
package db
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
}
}

30
db/sqlc/main_test.go Normal file
View File

@ -0,0 +1,30 @@
package db
import (
"database/sql"
"log"
"os"
"testing"
_ "github.com/lib/pq"
)
const (
dbDriver = "postgres"
dbSource = "postgresql://postgres:awksed123@localhost:5432/nice_pos?sslmode=disable"
)
var testQueries *Queries
var testDB *sql.DB
func TestMain(m *testing.M) {
var err error
testDB, err = sql.Open(dbDriver, dbSource)
if err != nil {
log.Fatal("cannot connect to db:", err)
}
testQueries = New(testDB)
os.Exit(m.Run())
}

50
db/sqlc/merchant.sql.go Normal file
View File

@ -0,0 +1,50 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
// source: merchant.sql
package db
import (
"context"
"github.com/google/uuid"
)
const getMerchantById = `-- name: GetMerchantById :one
SELECT id, index_id, name, owner_id, created_at, updated_at FROM merchants
WHERE id = $1
`
func (q *Queries) GetMerchantById(ctx context.Context, id uuid.UUID) (Merchant, error) {
row := q.db.QueryRowContext(ctx, getMerchantById, id)
var i Merchant
err := row.Scan(
&i.ID,
&i.IndexID,
&i.Name,
&i.OwnerID,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getMerchantByUserId = `-- name: GetMerchantByUserId :one
SELECT id, index_id, name, owner_id, created_at, updated_at FROM merchants
WHERE owner_id = $1
`
func (q *Queries) GetMerchantByUserId(ctx context.Context, ownerID uuid.UUID) (Merchant, error) {
row := q.db.QueryRowContext(ctx, getMerchantByUserId, ownerID)
var i Merchant
err := row.Scan(
&i.ID,
&i.IndexID,
&i.Name,
&i.OwnerID,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}

119
db/sqlc/models.go Normal file
View File

@ -0,0 +1,119 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
package db
import (
"database/sql"
"github.com/google/uuid"
"github.com/tabbed/pqtype"
)
type Customer struct {
ID uuid.UUID `json:"id"`
IndexID int64 `json:"index_id"`
MerchantID uuid.UUID `json:"merchant_id"`
Name string `json:"name"`
Detail pqtype.NullRawMessage `json:"detail"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
type Merchant struct {
ID uuid.UUID `json:"id"`
IndexID int64 `json:"index_id"`
Name string `json:"name"`
OwnerID uuid.UUID `json:"owner_id"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
type Product struct {
ID uuid.UUID `json:"id"`
MerchantID uuid.UUID `json:"merchant_id"`
IndexID int64 `json:"index_id"`
Name string `json:"name"`
SellingPrice float64 `json:"selling_price"`
PurchasePrice float64 `json:"purchase_price"`
Stock float64 `json:"stock"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
type PurchaseOrder struct {
ID uuid.UUID `json:"id"`
SupplierID uuid.UUID `json:"supplier_id"`
MerchantID uuid.UUID `json:"merchant_id"`
IndexID int64 `json:"index_id"`
Code sql.NullString `json:"code"`
IsPaid bool `json:"is_paid"`
Total float64 `json:"total"`
PaidNominal float64 `json:"paid_nominal"`
Note sql.NullString `json:"note"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
type PurchaseOrderDetail struct {
ID uuid.UUID `json:"id"`
IndexID int64 `json:"index_id"`
Code sql.NullString `json:"code"`
MerchantID uuid.UUID `json:"merchant_id"`
PurchaseOrderID uuid.UUID `json:"purchase_order_id"`
ProductID uuid.UUID `json:"product_id"`
Quantity float64 `json:"quantity"`
SubTotal float64 `json:"sub_total"`
ProductPrice float64 `json:"product_price"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
type SaleOrder struct {
ID uuid.UUID `json:"id"`
IndexID int64 `json:"index_id"`
Code sql.NullString `json:"code"`
MerchantID uuid.UUID `json:"merchant_id"`
CustomerID uuid.NullUUID `json:"customer_id"`
IsPaid sql.NullBool `json:"is_paid"`
Total float64 `json:"total"`
PaidNominal float64 `json:"paid_nominal"`
Note sql.NullString `json:"note"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
type SaleOrderDetail struct {
ID uuid.UUID `json:"id"`
IndexID int64 `json:"index_id"`
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"`
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"`
MerchantID uuid.UUID `json:"merchant_id"`
Name string `json:"name"`
Detail pqtype.NullRawMessage `json:"detail"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
type User struct {
ID uuid.UUID `json:"id"`
IndexID int64 `json:"index_id"`
Email string `json:"email"`
Password string `json:"password"`
Fullname sql.NullString `json:"fullname"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}

192
db/sqlc/products.sql.go Normal file
View File

@ -0,0 +1,192 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
// source: products.sql
package db
import (
"context"
"database/sql"
"github.com/google/uuid"
)
const createProduct = `-- name: CreateProduct :one
INSERT INTO products (
merchant_id,
name,
selling_price,
purchase_price,
stock
) VALUES (
$1, $2, $3, $4, $5
)
RETURNING id, merchant_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at
`
type CreateProductParams struct {
MerchantID uuid.UUID `json:"merchant_id"`
Name string `json:"name"`
SellingPrice float64 `json:"selling_price"`
PurchasePrice float64 `json:"purchase_price"`
Stock float64 `json:"stock"`
}
func (q *Queries) CreateProduct(ctx context.Context, arg CreateProductParams) (Product, error) {
row := q.db.QueryRowContext(ctx, createProduct,
arg.MerchantID,
arg.Name,
arg.SellingPrice,
arg.PurchasePrice,
arg.Stock,
)
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 deleteProduct = `-- name: DeleteProduct :exec
DELETE FROM products WHERE id = $1
`
func (q *Queries) DeleteProduct(ctx context.Context, id uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteProduct, id)
return err
}
const getProduct = `-- name: GetProduct :one
SELECT id, merchant_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at FROM products
WHERE id = $1
`
func (q *Queries) GetProduct(ctx context.Context, id uuid.UUID) (Product, error) {
row := q.db.QueryRowContext(ctx, getProduct, 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
LIMIT $1
OFFSET $2
`
type ListProductsParams struct {
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) ListProducts(ctx context.Context, arg ListProductsParams) ([]Product, error) {
rows, err := q.db.QueryContext(ctx, listProducts, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Product
for rows.Next() {
var i Product
if err := rows.Scan(
&i.ID,
&i.MerchantID,
&i.IndexID,
&i.Name,
&i.SellingPrice,
&i.PurchasePrice,
&i.Stock,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateProduct = `-- name: UpdateProduct :one
UPDATE products
SET name = $2, selling_price = $3, purchase_price = $4, stock = $5, updated_at = $6
WHERE id = $1
RETURNING id, merchant_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at
`
type UpdateProductParams struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
SellingPrice float64 `json:"selling_price"`
PurchasePrice float64 `json:"purchase_price"`
Stock float64 `json:"stock"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
func (q *Queries) UpdateProduct(ctx context.Context, arg UpdateProductParams) (Product, error) {
row := q.db.QueryRowContext(ctx, updateProduct,
arg.ID,
arg.Name,
arg.SellingPrice,
arg.PurchasePrice,
arg.Stock,
arg.UpdatedAt,
)
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 updateProductStock = `-- name: UpdateProductStock :one
UPDATE products
SET stock = $1
WHERE id = $2
RETURNING stock
`
type UpdateProductStockParams struct {
Stock float64 `json:"stock"`
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
}

124
db/sqlc/products_test.go Normal file
View File

@ -0,0 +1,124 @@
package db
import (
"context"
"database/sql"
"testing"
"time"
"git.nochill.in/nochill/nice_pos/util"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
func createRandomProduct(t *testing.T) (Product, CreateProductParams) {
arg := CreateProductParams{
MerchantID: uuid.MustParse("1f81d072-0c98-4e7e-9ceb-233d2eadb674"),
Name: util.RandomString(10),
SellingPrice: float64(123),
PurchasePrice: float64(123),
Stock: float64(120),
}
product, err := testQueries.CreateProduct(context.Background(), arg)
require.NoError(t, err)
return product, arg
}
func TestCreateProduct(t *testing.T) {
product, arg := createRandomProduct(t)
require.NotEmpty(t, product)
require.Equal(t, arg.Name, product.Name)
require.Equal(t, arg.MerchantID, product.MerchantID)
require.Equal(t, arg.SellingPrice, product.SellingPrice)
require.Equal(t, arg.PurchasePrice, product.PurchasePrice)
require.Equal(t, arg.Stock, product.Stock)
require.NotZero(t, product.ID)
require.NotZero(t, product.CreatedAt)
require.NotZero(t, product.UpdatedAt)
}
func TestGetProduct(t *testing.T) {
createProduct, _ := createRandomProduct(t)
getProduct, err := testQueries.GetProduct(context.Background(), createProduct.ID)
require.NoError(t, err)
require.NotEmpty(t, getProduct)
require.Equal(t, createProduct.ID, getProduct.ID)
require.Equal(t, createProduct.MerchantID, getProduct.MerchantID)
require.Equal(t, createProduct.Name, getProduct.Name)
require.Equal(t, createProduct.PurchasePrice, getProduct.PurchasePrice)
require.Equal(t, createProduct.SellingPrice, getProduct.SellingPrice)
require.Equal(t, createProduct.Stock, getProduct.Stock)
require.WithinDuration(t, createProduct.CreatedAt.Time, getProduct.CreatedAt.Time, time.Second)
require.WithinDuration(t, createProduct.UpdatedAt.Time, getProduct.UpdatedAt.Time, time.Second)
}
func TestUpdateProduct(t *testing.T) {
createProduct, _ := createRandomProduct(t)
getProduct, err := testQueries.GetProduct(context.Background(), createProduct.ID)
require.NoError(t, err)
arg := UpdateProductParams{
ID: getProduct.ID,
Name: util.RandomString(6),
SellingPrice: float64(200),
PurchasePrice: float64(200),
Stock: float64(200),
}
updatedProduct, err := testQueries.UpdateProduct(context.Background(), arg)
require.NotEmpty(t, updatedProduct)
require.NoError(t, err)
require.Equal(t, arg.ID, updatedProduct.ID)
require.Equal(t, arg.Name, updatedProduct.Name)
require.Equal(t, arg.PurchasePrice, updatedProduct.PurchasePrice)
require.Equal(t, arg.SellingPrice, updatedProduct.SellingPrice)
require.Equal(t, arg.Stock, updatedProduct.Stock)
require.NotSame(t, createProduct.Name, updatedProduct.Name)
require.NotSame(t, createProduct.SellingPrice, updatedProduct.SellingPrice)
require.NotSame(t, createProduct.PurchasePrice, updatedProduct.PurchasePrice)
require.NotSame(t, createProduct.Stock, updatedProduct.Stock)
}
func TestDeleteProduct(t *testing.T) {
product1, _ := createRandomProduct(t)
err := testQueries.DeleteProduct(context.Background(), product1.ID)
require.NoError(t, err)
product2, err := testQueries.GetProduct(context.Background(), product1.ID)
require.Error(t, err)
require.EqualError(t, err, sql.ErrNoRows.Error())
require.Empty(t, product2)
}
func TestGetProducts(t *testing.T) {
for i := 0; i < 6; i++ {
createRandomProduct(t)
}
arg := ListProductsParams{
Limit: 5,
Offset: 5,
}
products, err := testQueries.ListProducts(context.Background(), arg)
require.NoError(t, err)
require.Len(t, products, 5)
for _, product := range products {
require.NotEmpty(t, product)
}
}

View File

@ -0,0 +1,65 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
// source: purchase_order.sql
package db
import (
"context"
"database/sql"
"github.com/google/uuid"
)
const createPurchaseOrder = `-- name: CreatePurchaseOrder :one
INSERT INTO purchase_order (
merchant_id,
supplier_id,
code,
is_paid,
total,
paid_nominal,
note
) VALUES (
$1, $2, $3, $4, $5, $6, $7
)
RETURNING id, supplier_id, merchant_id, index_id, code, is_paid, total, paid_nominal, note, created_at, updated_at
`
type CreatePurchaseOrderParams struct {
MerchantID uuid.UUID `json:"merchant_id"`
SupplierID uuid.UUID `json:"supplier_id"`
Code sql.NullString `json:"code"`
IsPaid bool `json:"is_paid"`
Total float64 `json:"total"`
PaidNominal float64 `json:"paid_nominal"`
Note sql.NullString `json:"note"`
}
func (q *Queries) CreatePurchaseOrder(ctx context.Context, arg CreatePurchaseOrderParams) (PurchaseOrder, error) {
row := q.db.QueryRowContext(ctx, createPurchaseOrder,
arg.MerchantID,
arg.SupplierID,
arg.Code,
arg.IsPaid,
arg.Total,
arg.PaidNominal,
arg.Note,
)
var i PurchaseOrder
err := row.Scan(
&i.ID,
&i.SupplierID,
&i.MerchantID,
&i.IndexID,
&i.Code,
&i.IsPaid,
&i.Total,
&i.PaidNominal,
&i.Note,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}

View File

@ -0,0 +1,61 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
// source: purchase_order_detail.sql
package db
import (
"context"
"github.com/google/uuid"
)
const createPurchaseOrderDetail = `-- name: CreatePurchaseOrderDetail :one
INSERT INTO purchase_order_detail (
purchase_order_id,
merchant_id,
product_id,
quantity,
sub_total,
product_price
) VALUES (
$1, $2, $3, $4, $5, $6
)
RETURNING id, index_id, code, merchant_id, purchase_order_id, product_id, quantity, sub_total, product_price, created_at, updated_at
`
type CreatePurchaseOrderDetailParams struct {
PurchaseOrderID uuid.UUID `json:"purchase_order_id"`
MerchantID uuid.UUID `json:"merchant_id"`
ProductID uuid.UUID `json:"product_id"`
Quantity float64 `json:"quantity"`
SubTotal float64 `json:"sub_total"`
ProductPrice float64 `json:"product_price"`
}
func (q *Queries) CreatePurchaseOrderDetail(ctx context.Context, arg CreatePurchaseOrderDetailParams) (PurchaseOrderDetail, error) {
row := q.db.QueryRowContext(ctx, createPurchaseOrderDetail,
arg.PurchaseOrderID,
arg.MerchantID,
arg.ProductID,
arg.Quantity,
arg.SubTotal,
arg.ProductPrice,
)
var i PurchaseOrderDetail
err := row.Scan(
&i.ID,
&i.IndexID,
&i.Code,
&i.MerchantID,
&i.PurchaseOrderID,
&i.ProductID,
&i.Quantity,
&i.SubTotal,
&i.ProductPrice,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}

88
db/sqlc/store.go Normal file
View File

@ -0,0 +1,88 @@
package db
import (
"context"
"database/sql"
"fmt"
"github.com/google/uuid"
)
type Store struct {
*Queries
db *sql.DB
}
func NewStore(db *sql.DB) *Store {
return &Store{
db: db,
Queries: New(db),
}
}
func (store *Store) execTx(ctx context.Context, fn func(*Queries) error) error {
tx, err := store.db.BeginTx(ctx, nil)
if err != nil {
return err
}
q := New(tx)
err = fn(q)
if err != nil {
if rbErr := tx.Rollback(); rbErr != nil {
return fmt.Errorf("tx err: %v, rb err : %v", err, rbErr)
}
return err
}
return tx.Commit()
}
type PurchaseOrderProduct struct {
ProductID uuid.UUID `json:"product_id"`
Quantity float64 `json:"quantity"`
Sub_total float64 `json:"sub_total"`
Price float64 `json:"price"`
}
type PurchasoOrderTxParams struct {
MerchantID uuid.UUID `json:"merchant_id"`
SupplierID uuid.UUID `json:"supplier_id"`
Code sql.NullString `json:"code"`
IsPaid bool `json:"is_paid"`
Total float64 `json:"total"`
PaidNominal float64 `json:"paid_nominal"`
Note sql.NullString `json:"note"`
Products []PurchaseOrderProduct `json:"products"`
}
type PurchaseOrderTxResult struct {
PurchaseOrder PurchaseOrder `json:"purchase_order"`
// PurchaseOrderDetail []PurchaseOrderDetail `json:"detail"`
}
func (store *Store) PurchaseOrderTx(ctx context.Context, arg PurchasoOrderTxParams) (PurchaseOrderTxResult, error) {
var result PurchaseOrderTxResult
err := store.execTx(ctx, func(q *Queries) error {
var err error
result.PurchaseOrder, err = q.CreatePurchaseOrder(ctx, CreatePurchaseOrderParams{
MerchantID: arg.MerchantID,
SupplierID: arg.SupplierID,
Code: arg.Code,
IsPaid: arg.IsPaid,
Total: arg.Total,
PaidNominal: arg.PaidNominal,
Note: arg.Note,
})
if err != nil {
return err
}
return nil
})
return result, err
}

45
db/sqlc/store_test.go Normal file
View File

@ -0,0 +1,45 @@
package db
import (
"context"
"database/sql"
"testing"
"github.com/stretchr/testify/require"
)
func TestPurchaseOrder(t *testing.T) {
store := NewStore(testDB)
supplier, _ := createRandomSupplier(t)
product1, _ := createRandomProduct(t)
product2, _ := createRandomProduct(t)
errs := make(chan error)
results := make(chan PurchaseOrderTxResult)
// for i := 0; i < testIteration; i++ {
go func() {
result, err := store.PurchaseOrderTx(context.Background(), PurchasoOrderTxParams{
MerchantID: supplier.MerchantID,
SupplierID: supplier.ID,
Code: sql.NullString{Valid: true, String: ""},
IsPaid: true,
Total: product1.PurchasePrice + product2.PurchasePrice,
PaidNominal: product1.PurchasePrice + product2.PurchasePrice,
Note: sql.NullString{Valid: true, String: ""},
// Products: products,
})
errs <- err
results <- result
}()
// }
// for i := 0; i < testIteration; i++ {
err := <-errs
require.NoError(t, err)
result := <-results
require.NotEmpty(t, result)
}

119
db/sqlc/suppliers.sql.go Normal file
View File

@ -0,0 +1,119 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
// source: suppliers.sql
package db
import (
"context"
"github.com/google/uuid"
"github.com/tabbed/pqtype"
)
const createSuppliers = `-- name: CreateSuppliers :one
INSERT INTO suppliers (
merchant_id,
name,
detail
) VALUES (
$1, $2, $3
)
RETURNING id, index_id, merchant_id, name, detail, created_at, updated_at
`
type CreateSuppliersParams struct {
MerchantID uuid.UUID `json:"merchant_id"`
Name string `json:"name"`
Detail pqtype.NullRawMessage `json:"detail"`
}
func (q *Queries) CreateSuppliers(ctx context.Context, arg CreateSuppliersParams) (Supplier, error) {
row := q.db.QueryRowContext(ctx, createSuppliers, arg.MerchantID, arg.Name, arg.Detail)
var i Supplier
err := row.Scan(
&i.ID,
&i.IndexID,
&i.MerchantID,
&i.Name,
&i.Detail,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const deleteSupplier = `-- name: DeleteSupplier :exec
DELETE FROM suppliers where id = $1
`
func (q *Queries) DeleteSupplier(ctx context.Context, id uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteSupplier, id)
return err
}
const suppliersList = `-- name: SuppliersList :many
SELECT id, index_id, merchant_id, name, detail, created_at, updated_at FROM suppliers
WHERE merchant_id = $1
ORDER BY index_id
LIMIT $2
OFFSET $3
`
type SuppliersListParams struct {
MerchantID uuid.UUID `json:"merchant_id"`
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) SuppliersList(ctx context.Context, arg SuppliersListParams) ([]Supplier, error) {
rows, err := q.db.QueryContext(ctx, suppliersList, arg.MerchantID, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Supplier
for rows.Next() {
var i Supplier
if err := rows.Scan(
&i.ID,
&i.IndexID,
&i.MerchantID,
&i.Name,
&i.Detail,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateSupplier = `-- name: UpdateSupplier :one
SELECT id, index_id, merchant_id, name, detail, created_at, updated_at FROM suppliers
WHERE id = $1
`
func (q *Queries) UpdateSupplier(ctx context.Context, id uuid.UUID) (Supplier, error) {
row := q.db.QueryRowContext(ctx, updateSupplier, id)
var i Supplier
err := row.Scan(
&i.ID,
&i.IndexID,
&i.MerchantID,
&i.Name,
&i.Detail,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}

36
db/sqlc/suppliers_test.go Normal file
View File

@ -0,0 +1,36 @@
package db
import (
"context"
"testing"
"git.nochill.in/nochill/nice_pos/util"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/tabbed/pqtype"
)
func createRandomSupplier(t *testing.T) (Supplier, CreateSuppliersParams) {
arg := CreateSuppliersParams{
MerchantID: uuid.MustParse("1f81d072-0c98-4e7e-9ceb-233d2eadb674"),
Name: util.RandomString(10),
Detail: pqtype.NullRawMessage{},
}
supplier, err := testQueries.CreateSuppliers(context.Background(), arg)
require.NoError(t, err)
return supplier, arg
}
func TestCreateSupplier(t *testing.T) {
supplier, arg := createRandomSupplier(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)
}

16
go.mod Normal file
View File

@ -0,0 +1,16 @@
module git.nochill.in/nochill/nice_pos
go 1.20
require (
github.com/google/uuid v1.3.0
github.com/lib/pq v1.10.7
github.com/stretchr/testify v1.8.2
github.com/tabbed/pqtype v0.1.1
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

23
go.sum Normal file
View File

@ -0,0 +1,23 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/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=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tabbed/pqtype v0.1.1 h1:PhEcb9JZ8jr7SUjJDFjRPxny0M8fkXZrxn/a9yQfoZg=
github.com/tabbed/pqtype v0.1.1/go.mod h1:HLt2kLJPcUhODQkYn3mJkMHXVsuv3Z2n5NZEeKXL0Uk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

11
sqlc.yaml Normal file
View File

@ -0,0 +1,11 @@
version: "1"
packages:
- name: "db"
path: "./db/sqlc"
queries: "./db/query/"
schema: "./db/migrations/"
engine: "postgresql"
emit_json_tags: true
emit_prepared_queries: false
emit_interface: false
emit_exact_table_names: false

35
util/random.go Normal file
View File

@ -0,0 +1,35 @@
package util
import (
"math/rand"
"strings"
"time"
)
const alphabet = "abcdefghijklmnopqrstuvwxyz"
func init() {
rand.Seed(time.Now().UnixNano())
}
// RandomInt generates a random integer between min and max
func RandomInt(min, max int64) int64 {
return min + rand.Int63n(max-min+1)
}
func RandomFloat(max, div float64) float64 {
return (rand.Float64() * max) * div
}
// RandomString generates a random string of length n
func RandomString(n int) string {
var sb strings.Builder
k := len(alphabet)
for i := 0; i < n; i++ {
c := alphabet[rand.Intn(k)]
sb.WriteByte(c)
}
return sb.String()
}