material_manage_system/internal/service/application.go
2026-09-15 16:08:18 +08:00

221 lines
5.3 KiB
Go

package service
import (
"errors"
"github.com/wmms/wmms/internal/model"
"github.com/wmms/wmms/internal/repository"
)
var (
ErrApplicationNotFound = errors.New("application not found")
ErrCannotCancelApplication = errors.New("cannot cancel application")
ErrApplicationNotPending = errors.New("application is not pending")
)
type ApplicationService struct {
appRepo *repository.ApplicationRepository
}
func NewApplicationService() *ApplicationService {
return &ApplicationService{
appRepo: repository.NewApplicationRepository(),
}
}
func (s *ApplicationService) Create(userID int64, req *model.CreateApplicationRequest) (*model.MaterialApplication, error) {
// Build items from request
var items []model.ApplicationItem
for _, item := range req.Items {
// Check material exists and get stock
_, err := s.appRepo.GetMaterialCurrentStock(item.MaterialID)
if err != nil {
return nil, ErrMaterialNotFound
}
items = append(items, model.ApplicationItem{
MaterialID: item.MaterialID,
Quantity: item.Quantity,
})
}
app := &model.MaterialApplication{
UserID: userID,
Purpose: req.Purpose,
Status: model.ApplicationStatusPending,
Items: items,
}
if err := s.appRepo.Create(app); err != nil {
return nil, err
}
// Get full details
return s.appRepo.GetByID(app.ID)
}
func (s *ApplicationService) GetByID(id int64) (*model.MaterialApplication, error) {
app, err := s.appRepo.GetByID(id)
if err != nil {
return nil, ErrApplicationNotFound
}
return app, nil
}
func (s *ApplicationService) ListByUser(userID int64, status string, limit, offset int) ([]model.MaterialApplication, int, error) {
apps, total, err := s.appRepo.ListByUserID(userID, status, limit, offset)
if err != nil {
return nil, 0, err
}
if apps == nil {
apps = []model.MaterialApplication{}
}
return apps, total, nil
}
func (s *ApplicationService) ListAll(status string, limit, offset int) ([]model.MaterialApplication, int, error) {
apps, total, err := s.appRepo.ListAllApplications(status, limit, offset)
if err != nil {
return nil, 0, err
}
if apps == nil {
apps = []model.MaterialApplication{}
}
return apps, total, nil
}
func (s *ApplicationService) Approve(id int64, approverID int64, autoOutbound bool) error {
app, err := s.appRepo.GetByID(id)
if err != nil {
return ErrApplicationNotFound
}
if app.Status != model.ApplicationStatusPending {
return ErrApplicationNotPending
}
// Check stock for all items
for _, item := range app.Items {
stock, err := s.appRepo.GetMaterialCurrentStock(item.MaterialID)
if err != nil {
return ErrMaterialNotFound
}
if stock < item.Quantity {
return ErrInsufficientStock
}
}
// Auto outbound if requested
if autoOutbound {
// Start transaction
tx, err := repository.BeginTx()
if err != nil {
return err
}
defer func() {
if err != nil {
tx.Rollback()
}
}()
// Get approver name for handler
approverName, _ := s.appRepo.GetApproverName(approverID)
// Create outbound order within transaction
order := &model.OutboundOrder{
Handler: approverName,
Receiver: app.Username,
Status: "completed",
}
if err := s.appRepo.CreateOutboundOrderTx(tx, order); err != nil {
return err
}
// Process each item within transaction
for _, appItem := range app.Items {
// Re-check stock with row lock
_, err = s.appRepo.GetMaterialCurrentStockTx(tx, appItem.MaterialID)
if err != nil {
return ErrMaterialNotFound
}
// Deduct stock within transaction
if err := s.appRepo.DeductStockTx(tx, appItem.MaterialID, appItem.Quantity); err != nil {
return err
}
// Create outbound item within transaction
item := &model.OutboundItem{
OutboundOrderID: order.ID,
MaterialID: appItem.MaterialID,
Quantity: appItem.Quantity,
}
if err := s.appRepo.CreateOutboundItemTx(tx, item); err != nil {
return err
}
// Create stock log within transaction
stockLog := &model.StockLog{
MaterialID: appItem.MaterialID,
Type: "out",
Quantity: appItem.Quantity,
RelatedOrderType: "application",
RelatedOrderID: id,
}
if err := s.appRepo.CreateStockLogTx(tx, stockLog); err != nil {
return err
}
}
// Update status to approved within transaction
if err := s.appRepo.ApproveTx(tx, id, approverID); err != nil {
return err
}
// Commit transaction
if err = tx.Commit(); err != nil {
return err
}
} else {
// Non-auto-outbound: just approve
if err := s.appRepo.Approve(id, approverID); err != nil {
return err
}
}
return nil
}
func (s *ApplicationService) Reject(id int64, reason string) error {
app, err := s.appRepo.GetByID(id)
if err != nil {
return ErrApplicationNotFound
}
if app.Status != model.ApplicationStatusPending {
return ErrApplicationNotPending
}
return s.appRepo.Reject(id, reason)
}
func (s *ApplicationService) Cancel(id int64, userID int64) error {
app, err := s.appRepo.GetByID(id)
if err != nil {
return ErrApplicationNotFound
}
// User can only cancel their own applications
if app.UserID != userID {
return errors.New("cannot cancel others' applications")
}
if app.Status != model.ApplicationStatusPending {
return ErrCannotCancelApplication
}
return s.appRepo.Cancel(id)
}
func (s *ApplicationService) GetMaterialStock(materialID int64) (int, error) {
return s.appRepo.GetMaterialCurrentStock(materialID)
}