package service import ( "context" "errors" "fmt" "time" "github.com/cloudprint/cloudprint-backend/internal/model" "github.com/cloudprint/cloudprint-backend/internal/repository" "github.com/google/uuid" ) type OrderService struct { orderRepo *repository.OrderRepository orderItemRepo *repository.OrderItemRepository fileRepo *repository.FileRepository priceRepo *repository.PriceRepository userRepo *repository.UserRepository printerRepo *repository.PrinterRepository printJobRepo *repository.PrintJobRepository } func NewOrderService( orderRepo *repository.OrderRepository, orderItemRepo *repository.OrderItemRepository, fileRepo *repository.FileRepository, priceRepo *repository.PriceRepository, userRepo *repository.UserRepository, printerRepo *repository.PrinterRepository, printJobRepo *repository.PrintJobRepository, ) *OrderService { return &OrderService{ orderRepo: orderRepo, orderItemRepo: orderItemRepo, fileRepo: fileRepo, priceRepo: priceRepo, userRepo: userRepo, printerRepo: printerRepo, printJobRepo: printJobRepo, } } type CreateOrderItem struct { FileID int32 PrinterID int32 Copies int Color bool Duplex bool PageRange string } func (s *OrderService) CreateOrder(ctx context.Context, userID int32, items []*CreateOrderItem, remark string) (*model.Order, error) { if len(items) == 0 { return nil, errors.New("order must have at least one item") } // Generate order number orderNo := generateOrderNo() // Calculate total amount var totalAmount float64 orderItems := make([]model.OrderItem, 0, len(items)) for _, item := range items { // Verify file exists file, err := s.fileRepo.GetByID(ctx, item.FileID) if err != nil || file == nil { return nil, fmt.Errorf("file not found: %d", item.FileID) } // Calculate price based on file type and settings price, err := s.calculatePrice(ctx, item, file.FileType) if err != nil { return nil, err } orderItem := model.OrderItem{ FileID: item.FileID, PrinterID: intPtr(item.PrinterID), Copies: item.Copies, Color: item.Color, Duplex: item.Duplex, PageRange: item.PageRange, Price: price, } orderItems = append(orderItems, orderItem) totalAmount += price } // Create order order := &model.Order{ OrderNo: orderNo, UserID: userID, Status: model.OrderStatusPending, TotalAmount: totalAmount, Remark: remark, Items: orderItems, } // Convert to pointer slice for repository orderItemsPtr := make([]*model.OrderItem, len(orderItems)) for i := range orderItems { orderItemsPtr[i] = &orderItems[i] } // Save order with items if err := s.orderRepo.CreateWithItems(ctx, order, orderItemsPtr); err != nil { return nil, fmt.Errorf("failed to create order: %w", err) } return order, nil } func (s *OrderService) calculatePrice(ctx context.Context, item *CreateOrderItem, fileType string) (float64, error) { // Get price list from database prices, err := s.priceRepo.GetAllActive(ctx) if err != nil { return 0, fmt.Errorf("failed to get price list: %w", err) } if len(prices) == 0 { return 0, fmt.Errorf("no active price items found") } // Find matching price based on file type and color setting var basePrice float64 for _, p := range prices { // Match by type (A4/A3/照片纸) - default to A4 if no match if p.Type == "A4" || (p.Type == "" && basePrice == 0) { if !item.Color || (item.Color && p.ColorEnabled) { basePrice = p.Price break } } } // Fallback to first available price if no match found if basePrice == 0 { basePrice = prices[0].Price } // Default values if not set if item.Copies <= 0 { item.Copies = 1 } // Calculate total price price := basePrice * float64(item.Copies) // Apply color multiplier if color printing if item.Color { price *= 3.0 // Color costs 3x } // Apply duplex discount if item.Duplex { price *= 0.8 // Duplex gets 20% off } return price, nil } func generateOrderNo() string { now := time.Now() return fmt.Sprintf("CP%d%s", now.UnixNano()/1000000, uuid.New().String()[:8]) } func intPtr(i int32) *int32 { return &i } func (s *OrderService) GetOrder(ctx context.Context, orderNo string) (*model.Order, error) { return s.orderRepo.GetByOrderNo(ctx, orderNo) } func (s *OrderService) GetOrderList(ctx context.Context, userID int32, status int8, page, pageSize int) ([]*model.Order, int64, error) { return s.orderRepo.GetListByUserID(ctx, userID, status, page, pageSize) } func (s *OrderService) UpdateOrderStatus(ctx context.Context, orderNo string, status int8) error { return s.orderRepo.UpdateStatus(ctx, orderNo, status) } func (s *OrderService) CancelOrder(ctx context.Context, orderNo string) error { order, err := s.orderRepo.GetByOrderNo(ctx, orderNo) if err != nil || order == nil { return errors.New("order not found") } if order.Status != model.OrderStatusPending { return errors.New("only pending orders can be cancelled") } return s.orderRepo.UpdateStatus(ctx, orderNo, model.OrderStatusCancelled) } // PayOrder handles payment for an order func (s *OrderService) PayOrder(ctx context.Context, orderNo string, payType string) error { order, err := s.orderRepo.GetByOrderNo(ctx, orderNo) if err != nil || order == nil { return errors.New("order not found") } if order.Status != model.OrderStatusPending { return errors.New("order is not pending") } // Update order status to paid if err := s.orderRepo.UpdateStatus(ctx, orderNo, model.OrderStatusPaid); err != nil { return err } // Create print jobs for each order item for _, item := range order.Items { if item.PrinterID != nil { jobNo := generateJobNo() job := &model.PrintJob{ JobNo: jobNo, PrinterID: *item.PrinterID, OrderItemID: item.ID, Status: model.PrintJobStatusPending, Progress: 0, } if err := s.printJobRepo.Create(ctx, job); err != nil { // Log error but don't fail continue } } } // Update order status to printing return s.orderRepo.UpdateStatus(ctx, orderNo, model.OrderStatusPrinting) } func generateJobNo() string { now := time.Now() return fmt.Sprintf("PJ%d%s", now.UnixNano()/1000000, uuid.New().String()[:8]) }