Merge pull request 'feat(order): add order domain structure' (#272) from feature/order into develop

Reviewed-on: ebhomengo/niki#272
Reviewed-by: hossein <h.nazari1990@gmail.com>
This commit is contained in:
hossein 2026-04-15 05:05:24 +00:00
commit d06e6fe6c4
19 changed files with 182 additions and 102 deletions

View File

@ -3,8 +3,8 @@ package main
import (
"flag"
"fmt"
purchaseMysql "git.gocasts.ir/ebhomengo/niki/domain/purchase/repository/mysql"
"git.gocasts.ir/ebhomengo/niki/purchaseapp/delivery/http"
purchaseMysql "git.gocasts.ir/ebhomengo/niki/purchaseapp/repository/mysql"
"git.gocasts.ir/ebhomengo/niki/purchaseapp/service/order"
"git.gocasts.ir/ebhomengo/niki/repository/migrator"
"git.gocasts.ir/ebhomengo/niki/repository/mysql"

View File

@ -0,0 +1,49 @@
package entity
import (
"git.gocasts.ir/ebhomengo/niki/types"
"time"
)
type Order struct {
ID types.ID
UserID types.ID
TotalAmount types.Price
TotalDiscount types.Price
ShippingID types.ID
PaymentMethod PaymentMethod
ProcessStatus ProcessStatus
PaymentStatus PaymentStatus
AddressID types.ID
CreatedAt time.Time
UpdatedAt time.Time
}
type PaymentMethod string
const (
Online PaymentMethod = "online"
Wallet = "wallet"
Cart = "cart"
)
type ProcessStatus string
const (
WaitingToPay ProcessStatus = "waiting-to-pay"
Processing = "processing"
Accepted = "accepted"
Preparing = "preparing"
Prepared = "prepared"
GivenToPost = "given-to-post"
Delivered = "delivered"
Cancelled = "cancelled"
SystemCancellation = "system-cancellation"
)
type PaymentStatus string
const (
Paid PaymentStatus = "paid"
UnPaid = "unpaid"
)

View File

@ -0,0 +1,16 @@
package entity
import (
"git.gocasts.ir/ebhomengo/niki/types"
"time"
)
type OrderItem struct {
ID types.ID
ProductID types.ID
Price types.Price
Quantity types.Count
PriceWithDiscount types.Price
OrderID types.ID
CreatedAt time.Time
}

View File

@ -0,0 +1,10 @@
package entity
import "git.gocasts.ir/ebhomengo/niki/types"
type Shipping struct {
ID types.ID
Name string
Price types.Price
IsActive bool
}

View File

@ -3,11 +3,11 @@
-- https://www.grouparoo.com/blog/varchar-191#why-varchar-and-not-text
CREATE TABLE `orders` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`user_id` INT NOT NULL,
`address` TEXT,
`user_id` INT,
`address_id` INT,
`shipping_id` INT NOT NULL,
`payment_method` ENUM('online', 'wallet', 'cart') DEFAULT 'online',
`payment_status` ENUM('unpaid', 'paid', 'cancelled') DEFAULT 'unpaid',
`payment_status` ENUM('unpaid', 'paid') DEFAULT 'unpaid',
`process_status` ENUM('waiting-to-pay', 'processing', 'accepted', 'preparing', 'prepared', 'given-to-post', 'delivered', 'cancelled') DEFAULT 'waiting-to-pay',
`total_amount` INT NOT NULL,
`total_discount` INT NULL,
@ -15,7 +15,7 @@ CREATE TABLE `orders` (
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-- FOREIGN KEY (`user_id`) REFERENCES `users`(`id`)
-- FOREIGN KEY (`shipping_id`) REFERENCES `shippings`(`id`)
FOREIGN KEY (`shipping_id`) REFERENCES `shippings`(`id`)
);

View File

@ -0,0 +1,16 @@
-- +migrate Up
-- please read this article to understand why we use VARCHAR(191)
-- https://www.grouparoo.com/blog/varchar-191#why-varchar-and-not-text
CREATE TABLE `orders` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR (191),
`price` INT NOT NULL ,
`is_active` INT NOT NULL DEFAULT 1,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- +migrate Down
DROP TABLE `orders`;

View File

@ -0,0 +1,11 @@
package mysql
import "git.gocasts.ir/ebhomengo/niki/repository/mysql"
type DB struct {
conn *mysql.DB
}
func New(db *mysql.DB) *DB {
return &DB{conn: db}
}

View File

@ -1,23 +1,14 @@
package mysql
import (
entity "git.gocasts.ir/ebhomengo/niki/domain/order/entity"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
"git.gocasts.ir/ebhomengo/niki/purchaseapp/entity"
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
"git.gocasts.ir/ebhomengo/niki/types"
)
type DB struct {
conn *mysql.DB
}
func New(db *mysql.DB) *DB {
return &DB{conn: db}
}
func (d *DB) CreateOrder(order entity.Order, orderItems []entity.OrderItem) (types.ID, error) {
const Op = "repository.mysql.order.createorder"
const Op = "domain.repository.mysql.order.create-order"
tx, err := d.conn.Conn().Begin()
if err != nil {
@ -26,10 +17,10 @@ func (d *DB) CreateOrder(order entity.Order, orderItems []entity.OrderItem) (typ
defer tx.Rollback()
query := "insert into orders(user_id, address, shipping_id," +
query := "insert into orders(user_id, address_id, shipping_id," +
" payment_method, payment_status, process_status," +
" total_amount, total_discount) values (?, ?, ?, ?, ?, ?, ?, ?);"
res, oErr := tx.Exec(query, order.UserID, order.Address, order.ShippingID,
res, oErr := tx.Exec(query, order.UserID, order.AddressID, order.ShippingID,
order.PaymentMethod, order.PaymentStatus, order.ProcessStatus,
order.TotalAmount, order.TotalDiscount)
@ -57,7 +48,7 @@ func (d *DB) CreateOrder(order entity.Order, orderItems []entity.OrderItem) (typ
}
func (d *DB) UpdateOrderProcessStatus(orderID types.ID, status string) (bool, error) {
const Op = "repository.mysql.order.update-order-process-status"
const Op = "domain.repository.mysql.order.update-order-process-status"
_, err := d.conn.Conn().Exec("update orders set process_status=? where id=?;", status, orderID)
if err != nil {
@ -67,3 +58,35 @@ func (d *DB) UpdateOrderProcessStatus(orderID types.ID, status string) (bool, er
return true, nil
}
func (d *DB) GetShipping() ([]entity.Shipping, error) {
const Op = "domain.repository.mysql.order.get-shipping"
rows, err := d.conn.Conn().Query("select * from shippings where is_active=1")
if err != nil {
return []entity.Shipping{}, richerror.New(Op).WithErr(err)
}
defer rows.Close()
var shippings []entity.Shipping
for rows.Next() {
var s entity.Shipping
err := rows.Scan(
&s.ID,
&s.Name,
&s.Price,
&s.IsActive,
)
if err != nil {
return nil, richerror.New(Op).WithErr(err)
}
shippings = append(shippings, s)
}
return shippings, nil
}

View File

@ -1,9 +1,9 @@
package order
package service
import (
entity "git.gocasts.ir/ebhomengo/niki/domain/order/entity"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
"git.gocasts.ir/ebhomengo/niki/purchaseapp/entity"
"git.gocasts.ir/ebhomengo/niki/types"
types "git.gocasts.ir/ebhomengo/niki/types"
)
type Service struct {
@ -13,26 +13,27 @@ type Service struct {
type Repo interface {
CreateOrder(order entity.Order, orderItems []entity.OrderItem) (types.ID, error)
UpdateOrderProcessStatus(orderID types.ID, status string) (bool, error)
GetShipping() ([]entity.Shipping, error)
}
func New(orderRepo Repo) Service {
return Service{repo: orderRepo}
}
func (s Service) CreateOrder(order entity.Order, orderItems []entity.OrderItem) (CreateOrderResponse, error) {
const Op = "purchaseapp.service.CreateOrder"
func (s *Service) CreateOrder(order entity.Order, orderItems []entity.OrderItem) (types.ID, error) {
const Op = "domain.order.service.order.CreateOrder"
orderID, err := s.repo.CreateOrder(order, orderItems)
if err != nil {
return CreateOrderResponse{}, richerror.New(Op).WithErr(err)
return 0, richerror.New(Op).WithErr(err)
}
return CreateOrderResponse{OrderID: orderID}, nil
return orderID, nil
}
func (s Service) UpdateOrderProcessStatus(orderID types.ID, status string) (bool, error) {
func (s *Service) UpdateOrderProcessStatus(orderID types.ID, status string) (bool, error) {
const Op = "purchaseapp.service.UpdateOrderProcessStatus"
const Op = "domain.order.service.order.UpdateOrderProcessStatus"
_, err := s.repo.UpdateOrderProcessStatus(orderID, status)
if err != nil {
return false, richerror.New(Op).WithErr(err)

View File

@ -0,0 +1,16 @@
package service
import (
"git.gocasts.ir/ebhomengo/niki/domain/order/entity"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
)
func (s *Service) GetShipping() ([]entity.Shipping, error) {
const Op = "domain.order.service.shipping.get-shipping"
shippings, err := s.repo.GetShipping()
if err != nil {
return []entity.Shipping{}, richerror.New(Op)
}
return shippings, nil
}

View File

@ -3,9 +3,9 @@ package purchaseapp
import (
"context"
"fmt"
purchaseMysql "git.gocasts.ir/ebhomengo/niki/domain/purchase/repository/mysql"
purchaseHTTP "git.gocasts.ir/ebhomengo/niki/purchaseapp/delivery/http"
purchaseHandler "git.gocasts.ir/ebhomengo/niki/purchaseapp/delivery/http/order"
purchaseMysql "git.gocasts.ir/ebhomengo/niki/purchaseapp/repository/mysql"
purchaseService "git.gocasts.ir/ebhomengo/niki/purchaseapp/service/order"
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
)

View File

@ -1,9 +1,9 @@
package order
import (
entity "git.gocasts.ir/ebhomengo/niki/domain/order/entity"
order "git.gocasts.ir/ebhomengo/niki/domain/order/service"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
"git.gocasts.ir/ebhomengo/niki/purchaseapp/entity"
"git.gocasts.ir/ebhomengo/niki/purchaseapp/service/order"
"github.com/labstack/echo/v4"
"net/http"
"time"
@ -18,7 +18,7 @@ func New(orderSvc order.Service) *Handler {
}
func (h *Handler) CreateOrderHandler(c echo.Context) error {
var req order.CreateOrderRequest
var req CreateOrderRequest
if err := c.Bind(&req); err != nil {
msg, code := getErrorDataFromRichError(err)
return echo.NewHTTPError(code, msg)
@ -34,7 +34,7 @@ func (h *Handler) CreateOrderHandler(c echo.Context) error {
PaymentMethod: req.PaymentMethod,
ProcessStatus: entity.WaitingToPay,
PaymentStatus: entity.UnPaid,
Address: req.Address,
AddressID: req.AddressID,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}

View File

@ -1,13 +1,13 @@
package order
import (
"git.gocasts.ir/ebhomengo/niki/purchaseapp/entity"
"git.gocasts.ir/ebhomengo/niki/domain/order/entity"
"git.gocasts.ir/ebhomengo/niki/types"
)
type CreateOrderRequest struct {
UserID types.ID `json:"user_id"`
Address string `json:"address"`
AddressID types.ID `json:"address_id"`
ShippingID types.ID `json:"shipping_id"`
PaymentMethod entity.PaymentMethod `json:"payment_method"`
TotalAmount types.Price `json:"total_amount"`

View File

@ -4,5 +4,4 @@ import "github.com/labstack/echo/v4"
func (h Handler) SetRoutes(e *echo.Echo) {
e.POST("/order/create", h.CreateOrderHandler)
}

View File

@ -1,19 +1,19 @@
package http
import (
"git.gocasts.ir/ebhomengo/niki/purchaseapp/delivery/http/order"
orderService "git.gocasts.ir/ebhomengo/niki/purchaseapp/service/order"
order "git.gocasts.ir/ebhomengo/niki/domain/order/service"
orderHandler "git.gocasts.ir/ebhomengo/niki/purchaseapp/delivery/http/order"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
type Server struct {
OrderHandler *order.Handler
OrderHandler *orderHandler.Handler
}
func New(orderSvc orderService.Service) *Server {
func New(orderSvc order.Service) *Server {
return &Server{
OrderHandler: order.New(orderSvc),
OrderHandler: orderHandler.New(orderSvc),
}
}

View File

@ -1,59 +0,0 @@
package entity
import (
"git.gocasts.ir/ebhomengo/niki/types"
"time"
)
type Order struct {
ID types.ID
UserID types.ID
TotalAmount types.Price
TotalDiscount types.Price
ShippingID types.ID
PaymentMethod PaymentMethod
ProcessStatus ProcessStatus
PaymentStatus PaymentStatus
Address string
CreatedAt time.Time
UpdatedAt time.Time
}
type OrderItem struct {
ID types.ID
ProductID types.ID
Price types.Price
Quantity types.Count
PriceWithDiscount types.Price
OrderID types.ID
CreatedAt time.Time
}
type PaymentMethod string
const (
Online PaymentMethod = "online"
Wallet = "wallet"
Cart = "cart"
)
type ProcessStatus string
const (
WaitingToPay ProcessStatus = "waiting-to-pay"
processing = "processing"
accepted = "accepted"
preparing = "preparing"
prepared = "prepared"
givenToPost = "given-to-post"
delivered = "delivered"
cancelled = "cancelled"
)
type PaymentStatus string
const (
Paid PaymentStatus = "paid"
UnPaid = "unpaid"
Cancelled = "cancelled"
)

View File

@ -1 +0,0 @@
package mysql

View File

@ -1 +0,0 @@
package order