File size: 3,433 Bytes
b26189e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import { Injectable, BadRequestException, NotFoundException } from '@nestjs/common';
import type { User } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';

export interface OrderItemInput {
  menuItemId: string;
  qty: number;
}
export interface CreateOrderInput {
  vendorId: string;
  items: OrderItemInput[];
  source?: 'web' | 'agent';
}

export interface ItemSnapshot {
  name: string;
  price: number;
  qty: number;
}

@Injectable()
export class OrdersService {
  constructor(private readonly prisma: PrismaService) {}

  /** Place a real order for the signed-in user. Used by the web app AND the agent. */
  async create(user: User, input: CreateOrderInput) {
    const vendor = await this.prisma.vendor.findUnique({ where: { id: input.vendorId }, include: { items: true } });
    if (!vendor) throw new NotFoundException('Vendor not found');

    const snapshot: ItemSnapshot[] = [];
    for (const line of input.items) {
      const item = vendor.items.find((i) => i.id === line.menuItemId);
      if (item && line.qty > 0) snapshot.push({ name: item.name, price: item.price, qty: Math.min(line.qty, 20) });
    }
    if (!snapshot.length) throw new BadRequestException('No valid items in order');

    const subtotal = snapshot.reduce((sum, i) => sum + i.price * i.qty, 0);
    const total = subtotal + vendor.deliveryFee;

    const order = await this.prisma.order.create({
      data: {
        userId: user.id,
        vendorId: vendor.id,
        items: snapshot as unknown as object,
        subtotal,
        deliveryFee: vendor.deliveryFee,
        total,
        status: 'preparing',
        source: input.source ?? 'web',
      },
      include: { vendor: true },
    });
    return shape(order);
  }

  async listMine(user: User) {
    if (!this.prisma.connected) return [];
    const orders = await this.prisma.order.findMany({
      where: { userId: user.id },
      orderBy: { createdAt: 'desc' },
      include: { vendor: true },
    });
    return orders.map(shape);
  }

  /** Cancel one of the user's own orders, unless it's already on its way or delivered. */
  async cancel(user: User, id: string) {
    const order = await this.prisma.order.findFirst({
      where: { id, userId: user.id },
      include: { vendor: true },
    });
    if (!order) throw new NotFoundException('Order not found');
    if (order.status === 'cancelled') return shape(order);
    if (order.status === 'delivered' || order.status === 'delivering') {
      throw new BadRequestException('This order is already on its way and can no longer be cancelled');
    }

    const updated = await this.prisma.order.update({
      where: { id: order.id },
      data: { status: 'cancelled' },
      include: { vendor: true },
    });
    return shape(updated);
  }
}

interface OrderWithVendor {
  id: string;
  vendorId: string;
  items: unknown;
  subtotal: number;
  deliveryFee: number;
  total: number;
  currency: string;
  status: string;
  source: string;
  createdAt: Date;
  vendor: { name: string; image: string | null };
}

function shape(o: OrderWithVendor) {
  return {
    id: o.id,
    vendorId: o.vendorId,
    vendorName: o.vendor.name,
    image: o.vendor.image,
    items: o.items as ItemSnapshot[],
    subtotal: o.subtotal,
    deliveryFee: o.deliveryFee,
    total: o.total,
    currency: o.currency,
    status: o.status,
    source: o.source,
    createdAt: o.createdAt,
  };
}