Files
backend/cloudprint-backend/internal/service/order_service.go
2026-03-29 14:29:43 +08:00

240 lines
6.1 KiB
Go

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])
}