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 price, err := s.calculatePrice(ctx, item) 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, } // Save order with items if err := s.orderRepo.CreateWithItems(ctx, order, orderItems); err != nil { return nil, fmt.Errorf("failed to create order: %w", err) } return order, nil } func (s *OrderService) calculatePrice(ctx context.Context, item *CreateOrderItem) (float64, error) { // Get price list prices, err := s.priceRepo.GetAllActive(ctx) if err != nil { return 0, err } // Default price calculation (A4 B&W per page) defaultPrice := 0.10 colorMultiplier := 3.0 duplexDiscount := 0.8 // Calculate based on file type and settings // For simplicity, using default values // In production, look up from price list based on file type pageCount := 1 // Default, should get actual page count from file if item.Copies <= 0 { item.Copies = 1 } price := float64(pageCount) * defaultPrice * float64(item.Copies) if item.Color { price *= colorMultiplier } if item.Duplex { price *= duplexDiscount } 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]) }