372 lines
14 KiB
Go
372 lines
14 KiB
Go
package model
|
||
|
||
import (
|
||
"fmt"
|
||
"time"
|
||
|
||
"github.com/shopspring/decimal"
|
||
)
|
||
|
||
// Material represents a material in the warehouse
|
||
type Material struct {
|
||
ID int64 `json:"id"`
|
||
OrganizationID int64 `json:"organization_id"`
|
||
MaterialCode string `json:"material_code"`
|
||
Name string `json:"name"`
|
||
MaterialImage *string `json:"material_image,omitempty"`
|
||
Spec string `json:"spec,omitempty"`
|
||
ManufacturerModel *string `json:"manufacturer_model,omitempty"`
|
||
CategoryID *int64 `json:"category_id,omitempty"`
|
||
BaseUnitID *int64 `json:"base_unit_id,omitempty"`
|
||
Unit string `json:"unit,omitempty"`
|
||
Status string `json:"status"`
|
||
Version int `json:"version"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
}
|
||
|
||
// Tag represents a tag associated with a material
|
||
type Tag struct {
|
||
ID int64 `json:"id"`
|
||
MaterialID int64 `json:"material_id"`
|
||
TagCode string `json:"tag_code"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
}
|
||
|
||
// ApplyOrder represents a material application/requisition order
|
||
type ApplyOrder struct {
|
||
ID int64 `json:"id"`
|
||
Applicant string `json:"applicant"`
|
||
ApplyDate time.Time `json:"apply_date"`
|
||
Status string `json:"status"`
|
||
ApprovalComment string `json:"approval_comment,omitempty"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
}
|
||
|
||
// ApplyItem represents an item in an application order
|
||
type ApplyItem struct {
|
||
ID int64 `json:"id"`
|
||
ApplyOrderID int64 `json:"apply_order_id"`
|
||
MaterialID int64 `json:"material_id"`
|
||
Quantity int `json:"quantity"`
|
||
Purpose string `json:"purpose,omitempty"`
|
||
}
|
||
|
||
// InboundOrder represents an inbound order
|
||
type InboundOrder struct {
|
||
ID int64 `json:"id"`
|
||
InboundNo string `json:"inbound_no"`
|
||
Operator string `json:"operator"`
|
||
InboundDate time.Time `json:"inbound_date"`
|
||
Handler string `json:"handler"`
|
||
Supplier string `json:"supplier"`
|
||
Remark string `json:"remark"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||
Status string `json:"status,omitempty"`
|
||
Items []InboundItem `json:"items,omitempty"`
|
||
}
|
||
|
||
// InboundItem represents an item in an inbound order
|
||
type InboundItem struct {
|
||
ID int64 `json:"id"`
|
||
InboundOrderID int64 `json:"inbound_order_id"`
|
||
MaterialID int64 `json:"material_id"`
|
||
Quantity int `json:"quantity"`
|
||
MaterialCode string `json:"material_code,omitempty"`
|
||
MaterialName string `json:"material_name,omitempty"`
|
||
}
|
||
|
||
// OutboundOrder represents an outbound order
|
||
type OutboundOrder struct {
|
||
ID int64 `json:"id"`
|
||
OutboundNo string `json:"outbound_no"`
|
||
Operator string `json:"operator"`
|
||
OutboundDate time.Time `json:"outbound_date"`
|
||
Handler string `json:"handler"`
|
||
Receiver string `json:"receiver"`
|
||
Remark string `json:"remark"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||
Status string `json:"status,omitempty"`
|
||
Items []OutboundItem `json:"items,omitempty"`
|
||
}
|
||
|
||
// OutboundItem represents an item in an outbound order
|
||
type OutboundItem struct {
|
||
ID int64 `json:"id"`
|
||
OutboundOrderID int64 `json:"outbound_order_id"`
|
||
MaterialID int64 `json:"material_id"`
|
||
Quantity int `json:"quantity"`
|
||
MaterialCode string `json:"material_code,omitempty"`
|
||
MaterialName string `json:"material_name,omitempty"`
|
||
}
|
||
|
||
// OrderNo returns the stable display number used by inbound and outbound
|
||
// documents. It derives from the database ID, so it remains unique and does
|
||
// not change when the document is edited.
|
||
func OrderNo(prefix string, id int64) string {
|
||
return fmt.Sprintf("%s-%08d", prefix, id)
|
||
}
|
||
|
||
// StockLog represents a stock change log
|
||
type StockLog struct {
|
||
ID int64 `json:"id"`
|
||
MaterialID int64 `json:"material_id"`
|
||
Type string `json:"type"`
|
||
Quantity int `json:"quantity"`
|
||
RelatedOrderType string `json:"related_order_type"`
|
||
RelatedOrderID int64 `json:"related_order_id"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
RunningBalance decimal.Decimal `json:"running_balance,omitempty"`
|
||
}
|
||
|
||
// Stock represents stock information with material details
|
||
type Stock struct {
|
||
MaterialID int64 `json:"material_id"`
|
||
MaterialCode string `json:"material_code"`
|
||
Name string `json:"name"`
|
||
Spec string `json:"spec,omitempty"`
|
||
Unit string `json:"unit,omitempty"`
|
||
MajorCategory string `json:"major_category"`
|
||
MinorCategory string `json:"minor_category"`
|
||
TotalOnHand decimal.Decimal `json:"total_on_hand"`
|
||
WarningStock decimal.Decimal `json:"warning_stock"`
|
||
WarningEnabled bool `json:"warning_enabled"`
|
||
// WarningConfigured 表示该物料到底配没配过预警规则。"从没配过"和"配过但被手动
|
||
// 关闭"都会被 WarningEnabled 报成 false,仅靠它无法区分;前端预填时需要知道是哪
|
||
// 一种,才能决定默认勾选还是默认不勾选。它只服务于这个用途,不参与任何判定。
|
||
WarningConfigured bool `json:"warning_configured"`
|
||
StockStatus string `json:"stock_status"`
|
||
}
|
||
|
||
// Stock.StockStatus 的取值。只要预警处于启用状态、并且现存量已经触达阈值就是 low,
|
||
// 其余一律为 normal。阈值为 0 是合法配置,含义是"库存降到 0 时预警",不会被排除。
|
||
const (
|
||
StockStatusLow = "low"
|
||
StockStatusNormal = "normal"
|
||
)
|
||
|
||
// InventoryBalanceDetail represents detailed inventory balance
|
||
type InventoryBalanceDetail struct {
|
||
ID int64 `json:"id"`
|
||
OrganizationID int64 `json:"organization_id"`
|
||
Organization string `json:"organization"`
|
||
WarehouseID int64 `json:"warehouse_id"`
|
||
Warehouse string `json:"warehouse"`
|
||
LocationID int64 `json:"location_id"`
|
||
Location string `json:"location"`
|
||
MaterialID int64 `json:"material_id"`
|
||
OnHand decimal.Decimal `json:"on_hand"`
|
||
Reserved decimal.Decimal `json:"reserved"`
|
||
Locked decimal.Decimal `json:"locked"`
|
||
InTransit decimal.Decimal `json:"in_transit"`
|
||
UpdatedAt string `json:"updated_at"`
|
||
}
|
||
|
||
// StockLogResponse represents a stock log with computed balance
|
||
type StockLogResponse struct {
|
||
ID int64 `json:"id"`
|
||
MaterialID int64 `json:"material_id"`
|
||
ChangeType string `json:"change_type"`
|
||
ChangeQuantity decimal.Decimal `json:"change_quantity"`
|
||
BeforeQuantity decimal.Decimal `json:"before_quantity"`
|
||
AfterQuantity decimal.Decimal `json:"after_quantity"`
|
||
ReferenceID int64 `json:"reference_id"`
|
||
ReferenceType string `json:"reference_type"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
}
|
||
|
||
// InboundRecord represents inbound records for a specific material
|
||
type InboundRecord struct {
|
||
ID int64 `json:"id"`
|
||
InboundID int64 `json:"inbound_id"`
|
||
Handler string `json:"handler"`
|
||
Supplier string `json:"supplier"`
|
||
Quantity int `json:"quantity"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
}
|
||
|
||
// OutboundRecord represents outbound records for a specific material
|
||
type OutboundRecord struct {
|
||
ID int64 `json:"id"`
|
||
OutboundID int64 `json:"outbound_id"`
|
||
Handler string `json:"handler"`
|
||
Receiver string `json:"receiver"`
|
||
Quantity int `json:"quantity"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
}
|
||
|
||
// ============= Request DTOs =============
|
||
|
||
// CreateMaterialRequest represents the request to create a material
|
||
type CreateMaterialRequest struct {
|
||
OrganizationID int64 `json:"organization_id"`
|
||
MaterialCode string `json:"material_code" binding:"required"`
|
||
Name string `json:"name" binding:"required"`
|
||
MaterialImage *string `json:"material_image"`
|
||
Spec string `json:"spec"`
|
||
ManufacturerModel *string `json:"manufacturer_model"`
|
||
CategoryID *int64 `json:"category_id" binding:"required,gt=0"`
|
||
BaseUnitID *int64 `json:"base_unit_id"`
|
||
Unit string `json:"unit"`
|
||
}
|
||
|
||
// UpdateMaterialRequest represents the request to update a material
|
||
type UpdateMaterialRequest struct {
|
||
MaterialCode string `json:"material_code"`
|
||
Name string `json:"name"`
|
||
MaterialImage *string `json:"material_image"`
|
||
Spec string `json:"spec"`
|
||
ManufacturerModel *string `json:"manufacturer_model"`
|
||
CategoryID *int64 `json:"category_id" binding:"required,gt=0"`
|
||
BaseUnitID *int64 `json:"base_unit_id"`
|
||
Unit string `json:"unit"`
|
||
Status string `json:"status"`
|
||
}
|
||
|
||
// CreateInboundRequest represents the request to create an inbound order
|
||
type CreateInboundRequest struct {
|
||
Handler string `json:"handler" binding:"required"`
|
||
Supplier string `json:"supplier" binding:"required"`
|
||
Remark string `json:"remark"`
|
||
Items []struct {
|
||
MaterialID int64 `json:"material_id" binding:"required"`
|
||
Quantity int `json:"quantity" binding:"required,gt=0"`
|
||
// WarningStock 可选,为 nil 表示本次不改动已存的预警阈值。
|
||
WarningStock *decimal.Decimal `json:"warning_stock"`
|
||
// WarningEnabled 可选,为 nil 表示本次不改动预警的启用状态。仅在 WarningStock
|
||
// 非 nil 时生效,用来表达"启用还是关闭预警"。
|
||
WarningEnabled *bool `json:"warning_enabled"`
|
||
} `json:"items" binding:"required,min=1"`
|
||
}
|
||
|
||
// CreateOutboundRequest represents the request to create an outbound order
|
||
type CreateOutboundRequest struct {
|
||
// Handler is retained for API compatibility but is ignored. The server
|
||
// always records the authenticated user's username as the operator.
|
||
Handler string `json:"handler"`
|
||
Receiver string `json:"receiver" binding:"required"`
|
||
Remark string `json:"remark"`
|
||
Items []struct {
|
||
MaterialID int64 `json:"material_id" binding:"required"`
|
||
Quantity int `json:"quantity" binding:"required,gt=0"`
|
||
} `json:"items" binding:"required,min=1"`
|
||
}
|
||
|
||
// UpdateInboundRequest represents the request to update an inbound order
|
||
type UpdateInboundRequest struct {
|
||
Handler string `json:"handler"`
|
||
Supplier string `json:"supplier"`
|
||
Remark string `json:"remark"`
|
||
Items []struct {
|
||
MaterialID int64 `json:"material_id" binding:"required"`
|
||
Quantity int `json:"quantity" binding:"required,gt=0"`
|
||
// WarningStock 可选,为 nil 表示本次不改动已存的预警阈值。
|
||
WarningStock *decimal.Decimal `json:"warning_stock"`
|
||
// WarningEnabled 可选,为 nil 表示本次不改动预警的启用状态。仅在 WarningStock
|
||
// 非 nil 时生效,用来表达"启用还是关闭预警"。
|
||
WarningEnabled *bool `json:"warning_enabled"`
|
||
} `json:"items" binding:"required,min=1"`
|
||
}
|
||
|
||
// UpdateOutboundRequest represents the request to update an outbound order
|
||
type UpdateOutboundRequest struct {
|
||
Handler string `json:"handler"`
|
||
Operator string `json:"-"`
|
||
Receiver string `json:"receiver"`
|
||
Remark string `json:"remark"`
|
||
Items []struct {
|
||
MaterialID int64 `json:"material_id" binding:"required"`
|
||
Quantity int `json:"quantity" binding:"required,gt=0"`
|
||
} `json:"items" binding:"required,min=1"`
|
||
}
|
||
|
||
// MaterialLog represents a material change log
|
||
type MaterialLog struct {
|
||
ID int64 `json:"id"`
|
||
OperationType string `json:"operation_type"`
|
||
MaterialID int64 `json:"material_id"`
|
||
OldValue string `json:"old_value,omitempty"`
|
||
NewValue string `json:"new_value,omitempty"`
|
||
Operator string `json:"operator,omitempty"`
|
||
Description string `json:"description,omitempty"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
}
|
||
|
||
// User represents a system user
|
||
type User struct {
|
||
ID int64 `json:"id"`
|
||
Username string `json:"username"`
|
||
Password string `json:"-"`
|
||
Role string `json:"role"`
|
||
Roles []Role `json:"roles"`
|
||
Description string `json:"description"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
}
|
||
|
||
// LoginRequest represents login request
|
||
type LoginRequest struct {
|
||
Username string `json:"username" binding:"required"`
|
||
Password string `json:"password" binding:"required"`
|
||
}
|
||
|
||
// LoginResponse represents login response
|
||
type LoginResponse struct {
|
||
Token string `json:"token"`
|
||
User User `json:"user"`
|
||
}
|
||
|
||
// ApplicationStatus constants
|
||
const (
|
||
ApplicationStatusPending = "pending"
|
||
ApplicationStatusApproved = "approved"
|
||
ApplicationStatusRejected = "rejected"
|
||
ApplicationStatusCancelled = "cancelled"
|
||
)
|
||
|
||
// MaterialApplication represents a material application record
|
||
type MaterialApplication struct {
|
||
ID int64 `json:"id"`
|
||
UserID int64 `json:"user_id"`
|
||
Username string `json:"username"`
|
||
Purpose string `json:"purpose,omitempty"`
|
||
Status string `json:"status"`
|
||
RejectionReason string `json:"rejection_reason,omitempty"`
|
||
ApprovedBy int64 `json:"approved_by,omitempty"`
|
||
ApproverName string `json:"approver_name,omitempty"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
Items []ApplicationItem `json:"items,omitempty"`
|
||
}
|
||
|
||
// CreateApplicationRequest represents request to create application
|
||
type CreateApplicationRequest struct {
|
||
Purpose string `json:"purpose"`
|
||
Items []struct {
|
||
MaterialID int64 `json:"material_id" binding:"required"`
|
||
Quantity int `json:"quantity" binding:"required,gt=0"`
|
||
} `json:"items" binding:"required,min=1"`
|
||
}
|
||
|
||
// ApplicationItem represents an item in a material application
|
||
type ApplicationItem struct {
|
||
ID int64 `json:"id"`
|
||
ApplicationID int64 `json:"application_id"`
|
||
MaterialID int64 `json:"material_id"`
|
||
MaterialCode string `json:"material_code"`
|
||
MaterialName string `json:"material_name"`
|
||
Quantity int `json:"quantity"`
|
||
}
|
||
|
||
// ApproveApplicationRequest represents request to approve application
|
||
type ApproveApplicationRequest struct {
|
||
AutoOutbound bool `json:"auto_outbound"`
|
||
}
|
||
|
||
// RejectApplicationRequest represents request to reject application
|
||
type RejectApplicationRequest struct {
|
||
Reason string `json:"reason" binding:"required"`
|
||
}
|