package order import ( "context" "encoding/json" "errors" "fmt" "strconv" "time" "juwan-backend/app/order/api/internal/svc" "juwan-backend/app/order/api/internal/types" "juwan-backend/app/order/rpc/orderservice" "juwan-backend/common/utils/contextj" ) func int64Ptr(v int64) *int64 { return &v } func stringPtr(v string) *string { return &v } func formatUnix(ts int64) string { if ts <= 0 { return "" } return time.Unix(ts, 0).UTC().Format(time.RFC3339) } func toAPIOrder(in *orderservice.Orders) types.Order { order := types.Order{} if in == nil { return order } totalPrice, _ := strconv.ParseFloat(in.GetTotalPrice(), 64) playerID := strconv.FormatInt(in.GetPlayerId(), 10) service := types.PlayerService{} _ = json.Unmarshal([]byte(in.GetServiceSnapshot()), &service) order = types.Order{ Id: in.GetId(), ConsumerId: in.GetConsumerId(), PlayerId: playerID, ShopId: in.GetShopId(), Service: service, Status: in.GetStatus(), TotalPrice: totalPrice, Note: in.GetNote(), CreatedAt: formatUnix(in.GetCreatedAt()), AcceptedAt: formatUnix(in.GetAcceptedAt()), CompletedAt: formatUnix(in.GetCompletedAt()), } return order } // validTransitions defines the allowed order state transitions per the API spec. var validTransitions = map[string][]string{ "pending_payment": {"pending_accept"}, "pending_accept": {"in_progress", "cancelled"}, "in_progress": {"pending_close", "disputed"}, "pending_close": {"pending_review", "disputed"}, "pending_review": {"completed"}, "disputed": {"pending_review"}, } func transitionOrderStatus(ctx context.Context, svcCtx *svc.ServiceContext, orderID int64, toStatus string, setAcceptedAt bool, setClosedAt bool, setCompletedAt bool, setCancelledAt bool) error { current, err := svcCtx.OrderRpc.GetOrdersById(ctx, &orderservice.GetOrdersByIdReq{Id: orderID}) if err != nil { return err } if current.GetOrders() == nil { return errors.New("order not found") } fromStatus := current.Orders.GetStatus() allowed, ok := validTransitions[fromStatus] if !ok { return fmt.Errorf("order status %q does not allow any transition", fromStatus) } found := false for _, s := range allowed { if s == toStatus { found = true break } } if !found { return fmt.Errorf("transition from %q to %q is not allowed", fromStatus, toStatus) } now := time.Now().Unix() updateReq := &orderservice.UpdateOrdersReq{ Id: orderID, Status: stringPtr(toStatus), UpdatedAt: int64Ptr(now), } if setAcceptedAt { updateReq.AcceptedAt = int64Ptr(now) } if setClosedAt { updateReq.ClosedAt = int64Ptr(now) } if setCompletedAt { updateReq.CompletedAt = int64Ptr(now) } if setCancelledAt { updateReq.CancelledAt = int64Ptr(now) } if _, err = svcCtx.OrderRpc.UpdateOrders(ctx, updateReq); err != nil { return err } actorID, _ := contextj.UserIDFrom(ctx) actorRole := "system" if actorID > 0 { actorRole = "user" } _, err = svcCtx.OrderRpc.AddOrderStateLogs(ctx, &orderservice.AddOrderStateLogsReq{ OrderId: orderID, FromStatus: &fromStatus, ToStatus: toStatus, Action: "status_transition", ActorId: actorID, ActorRole: actorRole, CreatedAt: &now, }) if err != nil { return err } return nil }