99 lines
3.8 KiB
Go
99 lines
3.8 KiB
Go
package service
|
||
|
||
import (
|
||
"database/sql"
|
||
"testing"
|
||
|
||
"github.com/DATA-DOG/go-sqlmock"
|
||
"github.com/shopspring/decimal"
|
||
"github.com/wmms/wmms/internal/model"
|
||
)
|
||
|
||
func createInboundRequest() *model.CreateInboundRequest {
|
||
req := &model.CreateInboundRequest{Handler: "h", Supplier: "s", Remark: "r"}
|
||
req.Items = append(req.Items, struct {
|
||
MaterialID int64 `json:"material_id" binding:"required"`
|
||
Quantity int `json:"quantity" binding:"required,gt=0"`
|
||
WarningStock *decimal.Decimal `json:"warning_stock"`
|
||
WarningEnabled *bool `json:"warning_enabled"`
|
||
}{MaterialID: 7, Quantity: 2})
|
||
return req
|
||
}
|
||
|
||
// 入库单据的三个维度由服务端按当前公司解析后填写:请求体没有任何归属字段,
|
||
// 后端把 scope 的公司、仓库、库位写进单据头,落库 SQL 也带上这三列。
|
||
func TestInboundCreateFillsOrderDimensionsFromResolvedScope(t *testing.T) {
|
||
mock := outboundMock(t)
|
||
// Create 先校验明细物料,再解析库存维度。
|
||
expectActiveMaterial(mock, 7)
|
||
expectStockScope(mock, 1)
|
||
mock.ExpectBegin()
|
||
mock.ExpectExec("INSERT INTO inbound_orders").
|
||
WithArgs(int64(1), int64(10), int64(20), sqlmock.AnyArg(), "operator", "h", "s", "r").
|
||
WillReturnResult(sqlmock.NewResult(5, 1))
|
||
mock.ExpectExec("UPDATE inbound_orders").WillReturnResult(sqlmock.NewResult(0, 1))
|
||
mock.ExpectExec("INSERT INTO inbound_items").WithArgs(int64(5), int64(7), 2).WillReturnResult(sqlmock.NewResult(6, 1))
|
||
// 明细补物料编码/名称时再读一次物料。
|
||
mock.ExpectQuery("SELECT id, organization_id, material_code, name, material_image").WithArgs(int64(1), int64(7)).WillReturnRows(activeMaterialRow(7))
|
||
mock.ExpectExec("INSERT INTO inventory_balances").WithArgs(int64(1), int64(10), int64(20), int64(7), "2").WillReturnResult(sqlmock.NewResult(0, 1))
|
||
mock.ExpectExec("INSERT INTO stock_logs").WithArgs(int64(1), int64(10), int64(20), int64(7), "in", 2, "inbound", int64(5)).WillReturnResult(sqlmock.NewResult(1, 1))
|
||
mock.ExpectCommit()
|
||
|
||
order, err := NewInboundService().Create(1, createInboundRequest(), "operator")
|
||
if err != nil {
|
||
t.Fatalf("create inbound: %v", err)
|
||
}
|
||
if order.OrganizationID != 1 || order.WarehouseID != 10 || order.LocationID != 20 {
|
||
t.Fatalf("单据维度=%d/%d/%d, want 1/10/20", order.OrganizationID, order.WarehouseID, order.LocationID)
|
||
}
|
||
if order.InboundNo != "IN-00000005" {
|
||
t.Fatalf("单号=%s, want IN-00000005", order.InboundNo)
|
||
}
|
||
if err := mock.ExpectationsWereMet(); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
|
||
// 删除别家公司的单据:事务内锁定当前公司原单据时命中 ErrNoRows,整体不冲销任何库存,
|
||
// 也不会删掉别家公司的单据。
|
||
func TestInboundDeleteCrossOrganizationReturnsNoRows(t *testing.T) {
|
||
mock := outboundMock(t)
|
||
mock.ExpectBegin()
|
||
mock.ExpectQuery("SELECT id, COALESCE\\(inbound_no").WithArgs(int64(9), int64(1)).
|
||
WillReturnRows(sqlmock.NewRows([]string{"id"}))
|
||
mock.ExpectRollback()
|
||
|
||
err := NewInboundService().Delete(1, 9)
|
||
if err != sql.ErrNoRows {
|
||
t.Fatalf("got %v, want sql.ErrNoRows", err)
|
||
}
|
||
if err := mock.ExpectationsWereMet(); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
|
||
// resolveWarningEnabled 决定入库时写预警规则要不要启用。老客户端只会传 warning_stock、
|
||
// 从不传 warning_enabled,那种情况下必须保持原来的"带上阈值即启用"行为。
|
||
func TestResolveWarningEnabled(t *testing.T) {
|
||
enabled := true
|
||
disabled := false
|
||
|
||
cases := []struct {
|
||
name string
|
||
in *bool
|
||
want bool
|
||
}{
|
||
{"未传则默认启用", nil, true},
|
||
{"显式启用", &enabled, true},
|
||
{"显式关闭", &disabled, false},
|
||
}
|
||
|
||
for _, tc := range cases {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
if got := resolveWarningEnabled(tc.in); got != tc.want {
|
||
t.Fatalf("resolveWarningEnabled(%v) = %v, want %v", tc.in, got, tc.want)
|
||
}
|
||
})
|
||
}
|
||
}
|