2026-07-04 02:34:20 +00:00

300 lines
8.7 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 YarnRatio struct {
YarnName string `json:"yarn_name"`
YarnType string `json:"yarn_type"`
Ratio float64 `json:"ratio"`
}
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"`
WarpWeight *float64 `json:"warp_weight"`
WeftWeight *float64 `json:"weft_weight"`
TotalWeight *float64 `json:"total_weight"`
YarnUsagePerMeter float64 `json:"yarn_usage_per_meter"`
ProductionPrice *float64 `json:"production_price"`
ImageURL *string `json:"image_url"`
ProductType string `json:"product_type"`
YarnTypes []string `json:"yarn_types"`
YarnRatios []YarnRatio `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"`
WarpWeight *float64 `json:"warp_weight"`
WeftWeight *float64 `json:"weft_weight"`
TotalWeight *float64 `json:"total_weight"`
YarnUsagePerMeter float64 `json:"yarn_usage_per_meter"`
ProductionPrice *float64 `json:"production_price"`
ImageURL *string `json:"image_url"`
ProductType string `json:"product_type"`
YarnTypes []string `json:"yarn_types"`
YarnRatios []YarnRatio `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()
yarnRatios := make(model.JSONYarnRatios, len(req.YarnRatios))
for i, yr := range req.YarnRatios {
yarnRatios[i] = model.ProductYarnRatio{
YarnName: yr.YarnName,
YarnType: yr.YarnType,
Ratio: yr.Ratio,
}
}
productType := req.ProductType
if productType == "" {
productType = "raw_fabric"
}
p := &model.Product{
ID: uuid.New().String(),
CompanyID: companyID,
ProductName: req.ProductName,
FabricCode: req.FabricCode,
Color: req.Color,
ColorCode: req.ColorCode,
Weight: req.Weight,
WarpWeight: req.WarpWeight,
WeftWeight: req.WeftWeight,
TotalWeight: req.TotalWeight,
YarnUsagePerMeter: req.YarnUsagePerMeter,
ProductionPrice: req.ProductionPrice,
ImageURL: req.ImageURL,
ProductType: productType,
TotalStock: 0,
RawFabricMeters: 0,
RawFabricRolls: 0,
YarnTypes: model.JSONStringSlice(req.YarnTypes),
YarnRatios: 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 {
yarnRatios := make(model.JSONYarnRatios, len(req.YarnRatios))
for i, yr := range req.YarnRatios {
yarnRatios[i] = model.ProductYarnRatio{
YarnName: yr.YarnName,
YarnType: yr.YarnType,
Ratio: yr.Ratio,
}
}
productType := req.ProductType
if productType == "" {
productType = "raw_fabric"
}
p := &model.Product{
ID: id,
ProductName: req.ProductName,
FabricCode: req.FabricCode,
Color: req.Color,
ColorCode: req.ColorCode,
Weight: req.Weight,
WarpWeight: req.WarpWeight,
WeftWeight: req.WeftWeight,
TotalWeight: req.TotalWeight,
YarnUsagePerMeter: req.YarnUsagePerMeter,
ProductionPrice: req.ProductionPrice,
ImageURL: req.ImageURL,
ProductType: productType,
YarnTypes: model.JSONStringSlice(req.YarnTypes),
YarnRatios: 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)
}