init
This commit is contained in:
commit
287eb2f205
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.env
|
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"editor.minimap.enabled": false
|
||||||
|
}
|
15
Makefile
Normal file
15
Makefile
Normal 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
|
9
db/migrations/000001_init_schema.down.sql
Normal file
9
db/migrations/000001_init_schema.down.sql
Normal 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;
|
145
db/migrations/000001_init_schema.up.sql
Normal file
145
db/migrations/000001_init_schema.up.sql
Normal 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
23
db/query/customers.sql
Normal 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
8
db/query/merchant.sql
Normal 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
36
db/query/products.sql
Normal 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;
|
13
db/query/purchase_order.sql
Normal file
13
db/query/purchase_order.sql
Normal 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 *;
|
12
db/query/purchase_order_detail.sql
Normal file
12
db/query/purchase_order_detail.sql
Normal 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
23
db/query/suppliers.sql
Normal 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
119
db/sqlc/customers.sql.go
Normal 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
31
db/sqlc/db.go
Normal 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
30
db/sqlc/main_test.go
Normal 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
50
db/sqlc/merchant.sql.go
Normal 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
119
db/sqlc/models.go
Normal 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
192
db/sqlc/products.sql.go
Normal 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
124
db/sqlc/products_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
65
db/sqlc/purchase_order.sql.go
Normal file
65
db/sqlc/purchase_order.sql.go
Normal 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
|
||||||
|
}
|
61
db/sqlc/purchase_order_detail.sql.go
Normal file
61
db/sqlc/purchase_order_detail.sql.go
Normal 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
88
db/sqlc/store.go
Normal 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
45
db/sqlc/store_test.go
Normal 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
119
db/sqlc/suppliers.sql.go
Normal 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
36
db/sqlc/suppliers_test.go
Normal 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
16
go.mod
Normal 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
23
go.sum
Normal 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
11
sqlc.yaml
Normal 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
35
util/random.go
Normal 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()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user