Chever John 9c39c8cbd7
init: iloom WMS monorepo with K8s deployment manifests
Go workspace (go.work) with 5 microservices + shared library:
- gateway (8080), auth-service (8081), purchaser-service (8082)
- textile-service (8083), washing-service (8084)
- shared: proto definitions, common packages

Infrastructure: docker-compose for local dev, K8s manifests for
K3s cluster deployment (mysql/redis/etcd + traefik ingress).

Frontend (iloom-flatten) added as git submodule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-14 11:33:31 +08:00

118 lines
3.1 KiB
Go

package service
import (
"context"
"errors"
"fmt"
"time"
"github.com/muyuqingfeng/iloom/textile-service/internal/model"
"github.com/muyuqingfeng/iloom/textile-service/internal/repository"
)
type ProcessService struct {
stepRepo *repository.StepRepo
inventoryRepo *repository.InventoryRepo
}
func NewProcessService(stepRepo *repository.StepRepo, inventoryRepo *repository.InventoryRepo) *ProcessService {
return &ProcessService{
stepRepo: stepRepo,
inventoryRepo: inventoryRepo,
}
}
func (s *ProcessService) CompleteStep(ctx context.Context, planID, stepID, companyID, userID, notes string) error {
step, err := s.stepRepo.GetByID(ctx, stepID)
if err != nil {
return fmt.Errorf("step not found: %w", err)
}
if step.PlanID != planID {
return errors.New("step does not belong to this plan")
}
if step.Status != "pending" && step.Status != "active" {
return fmt.Errorf("step status is %s, cannot complete", step.Status)
}
// Validate prerequisite steps are completed
allSteps, err := s.stepRepo.GetByPlan(ctx, planID)
if err != nil {
return fmt.Errorf("failed to get plan steps: %w", err)
}
if err := s.validatePrerequisites(step.StepType, allSteps); err != nil {
return err
}
var notePtr *string
if notes != "" {
notePtr = &notes
}
if err := s.stepRepo.UpdateStatus(ctx, stepID, "completed", userID, notePtr); err != nil {
return fmt.Errorf("failed to update step: %w", err)
}
// Check if this is the last step (fabric_warehouse)
if step.StepType == "fabric_warehouse" {
// All steps completed - plan production is done at textile side
// Purchaser service can be notified via event/HTTP if needed
_ = companyID // available for future cross-service notification
}
return nil
}
func (s *ProcessService) RejectStep(ctx context.Context, planID, stepID, companyID, userID, reason string) error {
step, err := s.stepRepo.GetByID(ctx, stepID)
if err != nil {
return fmt.Errorf("step not found: %w", err)
}
if step.PlanID != planID {
return errors.New("step does not belong to this plan")
}
if step.Status != "pending" && step.Status != "active" {
return fmt.Errorf("step status is %s, cannot reject", step.Status)
}
return s.stepRepo.UpdateStatus(ctx, stepID, "rejected", userID, &reason)
}
func (s *ProcessService) validatePrerequisites(currentType string, allSteps []model.ProcessStep) error {
currentIdx := -1
for i, st := range model.StepOrder {
if st == currentType {
currentIdx = i
break
}
}
if currentIdx < 0 {
return fmt.Errorf("unknown step type: %s", currentType)
}
// Build a map of step type -> status
statusMap := make(map[string]string, len(allSteps))
for _, step := range allSteps {
statusMap[step.StepType] = step.Status
}
// All preceding steps must be completed
for i := 0; i < currentIdx; i++ {
prevType := model.StepOrder[i]
if statusMap[prevType] != "completed" {
return fmt.Errorf("prerequisite step %s is not completed (status: %s)", prevType, statusMap[prevType])
}
}
return nil
}
// StepTimestamp returns the completion timestamp for a step, if available.
func StepTimestamp(step *model.ProcessStep) *time.Time {
return step.Timestamp
}