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

245 lines
6.9 KiB
Go

package service
import (
"context"
"database/sql"
"fmt"
"time"
"github.com/google/uuid"
"github.com/muyuqingfeng/iloom/purchaser-service/internal/model"
"github.com/muyuqingfeng/iloom/purchaser-service/internal/repository"
)
type ProductService struct {
repo *repository.ProductRepo
db *sql.DB
}
func NewProductService(repo *repository.ProductRepo, db *sql.DB) *ProductService {
return &ProductService{repo: repo, db: db}
}
type CreateProductRequest struct {
ProductName string `json:"product_name"`
FabricCode string `json:"fabric_code"`
Color string `json:"color"`
ColorCode string `json:"color_code"`
Weight float64 `json:"weight"`
YarnUsagePerMeter float64 `json:"yarn_usage_per_meter"`
ProductionPrice *float64 `json:"production_price"`
YarnTypes []string `json:"yarn_types"`
YarnRatios []float64 `json:"yarn_ratios"`
}
type UpdateProductRequest struct {
ProductName string `json:"product_name"`
FabricCode string `json:"fabric_code"`
Color string `json:"color"`
ColorCode string `json:"color_code"`
Weight float64 `json:"weight"`
YarnUsagePerMeter float64 `json:"yarn_usage_per_meter"`
ProductionPrice *float64 `json:"production_price"`
YarnTypes []string `json:"yarn_types"`
YarnRatios []float64 `json:"yarn_ratios"`
}
type InboundRequest struct {
BatchNo string `json:"batch_no"`
Rolls int `json:"rolls"`
Meters float64 `json:"meters"`
Notes string `json:"notes"`
WarehouseID string `json:"warehouse_id"`
}
type OutboundRequest struct {
BatchNo string `json:"batch_no"`
Rolls int `json:"rolls"`
Meters float64 `json:"meters"`
OutboundType string `json:"outbound_type"`
Notes string `json:"notes"`
RecipientCompanyID string `json:"recipient_company_id"`
RecipientCompanyName string `json:"recipient_company_name"`
SourceBatchID string `json:"source_batch_id"`
WashingPlanID string `json:"washing_plan_id"`
}
func (s *ProductService) List(ctx context.Context, companyID string) ([]model.Product, error) {
return s.repo.List(ctx, companyID)
}
func (s *ProductService) Create(ctx context.Context, companyID string, req CreateProductRequest) (*model.Product, error) {
now := time.Now()
p := &model.Product{
ID: uuid.New().String(),
CompanyID: companyID,
ProductName: req.ProductName,
FabricCode: req.FabricCode,
Color: req.Color,
ColorCode: req.ColorCode,
Weight: req.Weight,
YarnUsagePerMeter: req.YarnUsagePerMeter,
ProductionPrice: req.ProductionPrice,
TotalStock: 0,
RawFabricMeters: 0,
RawFabricRolls: 0,
YarnTypes: req.YarnTypes,
YarnRatios: req.YarnRatios,
CreatedAt: now,
UpdatedAt: now,
}
if err := s.repo.Create(ctx, p); err != nil {
return nil, err
}
return p, nil
}
func (s *ProductService) Update(ctx context.Context, id, companyID string, req UpdateProductRequest) error {
p := &model.Product{
ID: id,
ProductName: req.ProductName,
FabricCode: req.FabricCode,
Color: req.Color,
ColorCode: req.ColorCode,
Weight: req.Weight,
YarnUsagePerMeter: req.YarnUsagePerMeter,
ProductionPrice: req.ProductionPrice,
YarnTypes: req.YarnTypes,
YarnRatios: req.YarnRatios,
}
return s.repo.Update(ctx, p)
}
func (s *ProductService) Delete(ctx context.Context, id, companyID string) error {
return s.repo.Delete(ctx, id, companyID)
}
func (s *ProductService) Inbound(ctx context.Context, productID, companyID, userID string, req InboundRequest) error {
now := time.Now()
var notesPtr *string
if req.Notes != "" {
notesPtr = &req.Notes
}
var warehousePtr *string
if req.WarehouseID != "" {
warehousePtr = &req.WarehouseID
}
rec := &model.ProductInventoryRecord{
ID: uuid.New().String(),
ProductID: productID,
BatchNo: req.BatchNo,
Rolls: req.Rolls,
Meters: req.Meters,
Notes: notesPtr,
OperatorID: &userID,
CompanyID: &companyID,
WarehouseID: warehousePtr,
CreatedAt: now,
}
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("begin tx: %w", err)
}
defer tx.Rollback()
if err := s.repo.Inbound(ctx, tx, rec); err != nil {
return err
}
if err := s.repo.UpdateStock(ctx, tx, productID, req.Meters, req.Rolls); err != nil {
return err
}
return tx.Commit()
}
func (s *ProductService) Outbound(ctx context.Context, productID, companyID, userID string, req OutboundRequest) error {
now := time.Now()
var notesPtr *string
if req.Notes != "" {
notesPtr = &req.Notes
}
var recipientIDPtr, recipientNamePtr, sourceBatchPtr, washingPlanPtr *string
if req.RecipientCompanyID != "" {
recipientIDPtr = &req.RecipientCompanyID
}
if req.RecipientCompanyName != "" {
recipientNamePtr = &req.RecipientCompanyName
}
if req.SourceBatchID != "" {
sourceBatchPtr = &req.SourceBatchID
}
if req.WashingPlanID != "" {
washingPlanPtr = &req.WashingPlanID
}
outboundType := req.OutboundType
if outboundType == "" {
outboundType = "normal"
}
rec := &model.ProductOutboundRecord{
ID: uuid.New().String(),
ProductID: productID,
BatchNo: req.BatchNo,
Rolls: req.Rolls,
Meters: req.Meters,
OutboundType: outboundType,
Notes: notesPtr,
OperatorID: &userID,
CompanyID: &companyID,
RecipientCompanyID: recipientIDPtr,
RecipientCompanyName: recipientNamePtr,
SourceBatchID: sourceBatchPtr,
WashingPlanID: washingPlanPtr,
CreatedAt: now,
}
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("begin tx: %w", err)
}
defer tx.Rollback()
if err := s.repo.Outbound(ctx, tx, rec); err != nil {
return err
}
if err := s.repo.UpdateStock(ctx, tx, productID, -req.Meters, -req.Rolls); err != nil {
return err
}
return tx.Commit()
}
func (s *ProductService) Records(ctx context.Context, productID string) ([]model.ProductInventoryRecord, error) {
return s.repo.Records(ctx, productID)
}
func (s *ProductService) UpdatePrice(ctx context.Context, productID, companyID, userID string, newPrice float64, reason string) error {
oldPrice, err := s.repo.GetPrice(ctx, productID)
if err != nil {
return err
}
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("begin tx: %w", err)
}
defer tx.Rollback()
if err := s.repo.UpdatePrice(ctx, tx, productID, newPrice); err != nil {
return err
}
if err := s.repo.InsertPriceHistory(ctx, tx, productID, oldPrice, newPrice, reason, userID); err != nil {
return err
}
return tx.Commit()
}
func (s *ProductService) PriceHistory(ctx context.Context, productID string) ([]model.ProductPriceHistory, error) {
return s.repo.PriceHistory(ctx, productID)
}