1(*@
2Represents a unique identifier for a customer.
3*@)
4type CustomerId = CustomerId of int
5
6(*@
7Represents a unique identifier for a product.
8*@)
9type ProductId = ProductId of int
10
11(*@
12Represents a product with its ID, name, and price.
13*@)
14type Product = {
15Id: ProductId;
16Name: string;
17Price: decimal;
18}
19
20(*@
21Represents an item within an order.
22Includes the product, quantity, and calculated subtotal.
23*@)
24type OrderItem = {
25Product: Product;
26Quantity: int;
27Subtotal: decimal;
28}
29
30(*@
31Represents a customer with their ID and name.
32*@)
33type Customer = {
34Id: CustomerId;
35Name: string;
36}
37
38(*@
39Represents an order.
40Includes customer, items, status, and total price.
41This is an immutable record.
42*@)
43type Order = {
44Id: int;
45Customer: Customer;
46Items: list<OrderItem>;
47Status: OrderStatus;
48TotalPrice: decimal;
49}
50
51(*@
52Defines the possible statuses of an order.
53*@)
54type OrderStatus =
55| Pending
56| Processing
57| Shipped
58| Cancelled
59
60(*@
61Creates a new, empty order for a given customer.
62*@)
63let createOrder (customerId: int) (customerName: string) : Order =
64{
65Id = System.Random().Next(1000, 9999);
66Customer = { Id = CustomerId customerId; Name = customerName };
67Items = [];
68Status = Pending;
69TotalPrice = 0.0m;
70}
71
72(*@
73Adds an item to an existing order.
74Returns a new Order record with the item added and total price updated.
75Handles cases where the order is not in a state to accept items.
76*@)
77let addItemToOrder (order: Order) (product: Product) (quantity: int) : Result<Order, string> =
78if order.Status <> Pending then
79Error "Cannot add items to an order that is not pending."
80elif quantity <= 0 then
81Error "Quantity must be positive."
82else
83let subtotal = product.Price * decimal quantity
84let newItem = {
85Product = product;
86Quantity = quantity;
87Subtotal = subtotal;
88}
89let newItems = order.Items @ [newItem]
90let newTotalPrice = order.TotalPrice + subtotal
91Ok {
92order with
93Items = newItems;
94TotalPrice = newTotalPrice;
95}
96
97(*@
98Calculates the total price of an order based on its items.
99This is a helper function, the main `Order` record keeps `TotalPrice` updated.
100*@)
101let calculateOrderTotal (order: Order) : decimal =
102order.Items |> List.sumBy (fun item -> item.Subtotal)
103
104(*@
105Processes an order, changing its status to Processing.
106Returns a new Order record.
107*@)
108let processOrder (order: Order) : Result<Order, string> =
109if order.Status <> Pending then
110Error "Order must be pending to be processed."
111else
112Ok {
113order with
114Status = Processing;
115}
116
117(*@
118Ships an order, changing its status to Shipped.
119Returns a new Order record.
120*@)
121let shipOrder (order: Order) : Result<Order, string> =
122match order.Status with
123| Pending | Processing ->
124Ok {
125order with
126Status = Shipped;
127}
128| Shipped | Cancelled ->
129Error "Order cannot be shipped if already shipped or cancelled."
130
131(*@
132Cancels an order, changing its status to Cancelled.
133Returns a new Order record.
134*@)
135let cancelOrder (order: Order) : Result<Order, string> =
136match order.Status with
137| Pending | Processing ->
138Ok {
139order with
140Status = Cancelled;
141}
142| Shipped | Cancelled ->
143Error "Order cannot be cancelled if already shipped or cancelled."
144
145(*@
146A helper to create a sample product.
147*@)
148let createSampleProduct (id: int) (name: string) (price: decimal) : Product =
149{
150Id = ProductId id;
151Name = name;
152Price = price;
153}
154
155(*@
156A helper to create a sample customer.
157*@)
158let createSampleCustomer (id: int) (name: string) : Customer =
159{
160Id = CustomerId id;
161Name = name;
162}
163
164(*@
165A more complex order creation scenario.
166*@)
167let createComplexOrder () : Order =
168let customer = createSampleCustomer 1 "Alice"
169let order = createOrder customer.Id customer.Name
170let product1 = createSampleProduct 101 "Laptop" 1200.0m
171let product2 = createSampleProduct 102 "Mouse" 25.0m
172
173let orderWithItem1 = addItemToOrder order product1 1
174let orderWithItems =
175match orderWithItem1 with
176| Ok o -> addItemToOrder o product2 2
177| Error _ -> Error "Failed to add first item"
178
179match orderWithItems with
180| Ok finalOrder -> finalOrder
181| Error _ -> failwith "Error creating complex order"
182
183(*@
184Demonstrates adding an item to a non-pending order.
185*@)
186let demonstrateAddItemToNonPending () =
187let customer = createSampleCustomer 2 "Bob"
188let initialOrder = createOrder customer.Id customer.Name
189let product = createSampleProduct 201 "Keyboard" 75.0m
190
191match processOrder initialOrder with
192| Ok processedOrder ->
193match addItemToOrder processedOrder product 1 with
194| Ok _ -> printfn "Unexpected success adding item to processed order."
195| Error msg -> printfn "Correctly failed to add item: %s" msg
196| Error _ -> printfn "Failed to process order initially."
197
198(*@
199Final order creation and modification.
200*@)
201let finalOrderScenario () =
202let customer = createSampleCustomer 3 "Charlie"
203let order = createOrder customer.Id customer.Name
204let product = createSampleProduct 301 "Monitor" 300.0m
205match addItemToOrder order product 1 with
206| Ok updatedOrder -> updatedOrder
207| Error _ -> failwith "Failed to add item"