176 lines
4.9 KiB
Go
176 lines
4.9 KiB
Go
|
|
package service
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"database/sql"
|
||
|
|
"fmt"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/google/uuid"
|
||
|
|
"github.com/muyuqingfeng/iloom/shared/pkg/database"
|
||
|
|
"github.com/muyuqingfeng/iloom/washing-service/internal/model"
|
||
|
|
"github.com/muyuqingfeng/iloom/washing-service/internal/repository"
|
||
|
|
)
|
||
|
|
|
||
|
|
type CompleteRequest struct {
|
||
|
|
ActualShrinkageRate float64 `json:"actual_shrinkage_rate" binding:"required"`
|
||
|
|
ActualWashedMeters float64 `json:"actual_washed_meters" binding:"required,gt=0"`
|
||
|
|
Rolls int `json:"rolls" binding:"required,gt=0"`
|
||
|
|
WashingDate *time.Time `json:"washing_date"`
|
||
|
|
Notes string `json:"notes"`
|
||
|
|
WarehouseID string `json:"warehouse_id"`
|
||
|
|
WarehouseLocation string `json:"warehouse_location"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type CompletionService struct {
|
||
|
|
completionRepo *repository.CompletionRepo
|
||
|
|
planRepo *repository.PlanRepo
|
||
|
|
fpRepo *repository.FinishedProductRepo
|
||
|
|
db *sql.DB
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewCompletionService(
|
||
|
|
cr *repository.CompletionRepo,
|
||
|
|
pr *repository.PlanRepo,
|
||
|
|
fpr *repository.FinishedProductRepo,
|
||
|
|
db *sql.DB,
|
||
|
|
) *CompletionService {
|
||
|
|
return &CompletionService{
|
||
|
|
completionRepo: cr,
|
||
|
|
planRepo: pr,
|
||
|
|
fpRepo: fpr,
|
||
|
|
db: db,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *CompletionService) Complete(
|
||
|
|
ctx context.Context,
|
||
|
|
washingPlanID, companyID, userID string,
|
||
|
|
req CompleteRequest,
|
||
|
|
) (*model.WashingPlanCompletion, error) {
|
||
|
|
plan, err := s.planRepo.GetByID(ctx, washingPlanID)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("get plan: %w", err)
|
||
|
|
}
|
||
|
|
if plan == nil {
|
||
|
|
return nil, fmt.Errorf("washing plan not found")
|
||
|
|
}
|
||
|
|
if plan.WashingFactoryID == nil || *plan.WashingFactoryID != companyID {
|
||
|
|
return nil, fmt.Errorf("washing plan not found")
|
||
|
|
}
|
||
|
|
if plan.Status == "completed" {
|
||
|
|
return nil, fmt.Errorf("washing plan already completed")
|
||
|
|
}
|
||
|
|
|
||
|
|
now := time.Now()
|
||
|
|
washingDate := now
|
||
|
|
if req.WashingDate != nil {
|
||
|
|
washingDate = *req.WashingDate
|
||
|
|
}
|
||
|
|
|
||
|
|
totalCost := req.ActualWashedMeters * plan.WashingPrice
|
||
|
|
var unitCost float64
|
||
|
|
if req.ActualWashedMeters > 0 {
|
||
|
|
unitCost = totalCost / req.ActualWashedMeters
|
||
|
|
}
|
||
|
|
|
||
|
|
completionID := uuid.New().String()
|
||
|
|
fpID := uuid.New().String()
|
||
|
|
|
||
|
|
var notes *string
|
||
|
|
if req.Notes != "" {
|
||
|
|
notes = &req.Notes
|
||
|
|
}
|
||
|
|
var warehouseID *string
|
||
|
|
if req.WarehouseID != "" {
|
||
|
|
warehouseID = &req.WarehouseID
|
||
|
|
}
|
||
|
|
var warehouseLocation *string
|
||
|
|
if req.WarehouseLocation != "" {
|
||
|
|
warehouseLocation = &req.WarehouseLocation
|
||
|
|
}
|
||
|
|
|
||
|
|
completion := &model.WashingPlanCompletion{
|
||
|
|
ID: completionID,
|
||
|
|
WashingPlanID: washingPlanID,
|
||
|
|
CompanyID: companyID,
|
||
|
|
ActualShrinkageRate: req.ActualShrinkageRate,
|
||
|
|
ActualWashedMeters: req.ActualWashedMeters,
|
||
|
|
ActualWashingCost: &totalCost,
|
||
|
|
UnitCost: &unitCost,
|
||
|
|
Rolls: req.Rolls,
|
||
|
|
WashingDate: washingDate,
|
||
|
|
Notes: notes,
|
||
|
|
CompletedBy: &userID,
|
||
|
|
CompletedAt: now,
|
||
|
|
FinishedProductID: &fpID,
|
||
|
|
AutoImported: true,
|
||
|
|
WarehouseID: warehouseID,
|
||
|
|
WarehouseLocation: warehouseLocation,
|
||
|
|
CreatedAt: now,
|
||
|
|
}
|
||
|
|
|
||
|
|
err = database.WithTx(ctx, s.db, func(tx *sql.Tx) error {
|
||
|
|
if err := s.completionRepo.Create(ctx, tx, completion); err != nil {
|
||
|
|
return fmt.Errorf("create completion: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := s.planRepo.UpdateActuals(ctx, tx, washingPlanID,
|
||
|
|
req.ActualShrinkageRate, req.ActualWashedMeters, totalCost); err != nil {
|
||
|
|
return fmt.Errorf("update plan actuals: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
fp := &model.FinishedProduct{
|
||
|
|
ID: fpID,
|
||
|
|
CompanyID: companyID,
|
||
|
|
ProductName: plan.ProductName,
|
||
|
|
FabricCode: "",
|
||
|
|
Color: "",
|
||
|
|
ShrinkageRate: req.ActualShrinkageRate,
|
||
|
|
WashedMeters: req.ActualWashedMeters,
|
||
|
|
StockMeters: req.ActualWashedMeters,
|
||
|
|
StockRolls: req.Rolls,
|
||
|
|
Rolls: &req.Rolls,
|
||
|
|
WashingCost: &totalCost,
|
||
|
|
UnitCost: &unitCost,
|
||
|
|
Status: "in_stock",
|
||
|
|
Notes: notes,
|
||
|
|
ProductID: &plan.ProductID,
|
||
|
|
WashingPlanID: &washingPlanID,
|
||
|
|
WarehouseID: warehouseID,
|
||
|
|
WarehouseLocation: warehouseLocation,
|
||
|
|
CreatedBy: &userID,
|
||
|
|
CreatedAt: now,
|
||
|
|
UpdatedAt: now,
|
||
|
|
}
|
||
|
|
if err := s.fpRepo.Create(ctx, tx, fp); err != nil {
|
||
|
|
return fmt.Errorf("create finished product: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
invRecord := &model.FinishedProductInventoryRecord{
|
||
|
|
ID: uuid.New().String(),
|
||
|
|
FinishedProductID: fpID,
|
||
|
|
CompanyID: companyID,
|
||
|
|
RecordType: "inbound",
|
||
|
|
Rolls: req.Rolls,
|
||
|
|
Meters: req.ActualWashedMeters,
|
||
|
|
Notes: notes,
|
||
|
|
OperatorID: &userID,
|
||
|
|
WarehouseID: warehouseID,
|
||
|
|
WarehouseLocation: warehouseLocation,
|
||
|
|
WashingCompletionID: &completionID,
|
||
|
|
CreatedAt: now,
|
||
|
|
}
|
||
|
|
if err := s.fpRepo.CreateInventoryRecord(ctx, tx, invRecord); err != nil {
|
||
|
|
return fmt.Errorf("create inventory record: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
return completion, nil
|
||
|
|
}
|