Skip to main content

everruns_core/capabilities/
fake_warehouse.rs

1//! Fake Warehouse Tools Capability - demo tools for warehouse management
2//!
3//! This capability provides mock warehouse management tools that store state
4//! in the session file system. Perfect for demos and testing.
5//!
6//! Tools provided:
7//! - `warehouse_get_inventory`: Get current inventory levels
8//! - `warehouse_update_inventory`: Update inventory quantities
9//! - `warehouse_create_shipment`: Create a new shipment
10//! - `warehouse_list_shipments`: List all shipments
11//! - `warehouse_update_shipment_status`: Update shipment status
12//! - `warehouse_create_order`: Create a new order
13//! - `warehouse_list_orders`: List all orders
14//! - `warehouse_create_invoice`: Generate an invoice
15//! - `warehouse_process_return`: Process a product return
16//! - `warehouse_inventory_report`: Generate inventory report
17
18use super::{Capability, CapabilityLocalization, CapabilityStatus};
19use crate::tool_types::ToolHints;
20use crate::tools::{Tool, ToolExecutionResult};
21use crate::traits::ToolContext;
22use async_trait::async_trait;
23use serde::{Deserialize, Serialize};
24use serde_json::{Value, json};
25
26pub const FAKE_WAREHOUSE_CAPABILITY_ID: &str = "fake_warehouse";
27
28/// Fake Warehouse Tools capability - mock warehouse management for demos
29pub struct FakeWarehouseCapability;
30
31impl Capability for FakeWarehouseCapability {
32    fn id(&self) -> &str {
33        FAKE_WAREHOUSE_CAPABILITY_ID
34    }
35
36    fn name(&self) -> &str {
37        "Fake Warehouse Tools"
38    }
39
40    fn description(&self) -> &str {
41        "Demo capability: warehouse management tools (inventory, shipments, orders, invoices, returns). State stored in session filesystem."
42    }
43
44    fn localizations(&self) -> Vec<CapabilityLocalization> {
45        vec![CapabilityLocalization::text(
46            "uk",
47            "Демо-інструменти складу",
48            "Демонстраційна можливість: інструменти керування складом (запаси, відвантаження, замовлення, рахунки, повернення). Стан зберігається у файловій системі сесії.",
49        )]
50    }
51
52    fn status(&self) -> CapabilityStatus {
53        CapabilityStatus::Available
54    }
55
56    fn icon(&self) -> Option<&str> {
57        Some("package")
58    }
59
60    fn category(&self) -> Option<&str> {
61        Some("Demo Tools")
62    }
63
64    fn system_prompt_addition(&self) -> Option<&str> {
65        Some(
66            "Warehouse data is stored in /warehouse/ (inventory.json, shipments.json, orders.json, invoices.json, returns.json).",
67        )
68    }
69
70    fn tools(&self) -> Vec<Box<dyn Tool>> {
71        vec![
72            Box::new(WarehouseGetInventoryTool),
73            Box::new(WarehouseUpdateInventoryTool),
74            Box::new(WarehouseCreateShipmentTool),
75            Box::new(WarehouseListShipmentsTool),
76            Box::new(WarehouseUpdateShipmentStatusTool),
77            Box::new(WarehouseCreateOrderTool),
78            Box::new(WarehouseListOrdersTool),
79            Box::new(WarehouseCreateInvoiceTool),
80            Box::new(WarehouseProcessReturnTool),
81            Box::new(WarehouseInventoryReportTool),
82        ]
83    }
84}
85
86// Helper structs for warehouse data
87#[derive(Debug, Clone, Serialize, Deserialize)]
88struct InventoryItem {
89    sku: String,
90    name: String,
91    quantity: i32,
92    location: String,
93    reorder_point: i32,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
97struct Shipment {
98    id: String,
99    status: String, // pending, in_transit, delivered
100    destination: String,
101    items: Vec<ShipmentItem>,
102    created_at: String,
103    updated_at: String,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
107struct ShipmentItem {
108    sku: String,
109    quantity: i32,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
113struct Order {
114    id: String,
115    customer_name: String,
116    items: Vec<ShipmentItem>,
117    status: String,
118    created_at: String,
119}
120
121// ============================================================================
122// Tool: warehouse_get_inventory
123// ============================================================================
124
125pub struct WarehouseGetInventoryTool;
126
127#[async_trait]
128impl Tool for WarehouseGetInventoryTool {
129    fn name(&self) -> &str {
130        "warehouse_get_inventory"
131    }
132
133    fn display_name(&self) -> Option<&str> {
134        Some("Get Inventory")
135    }
136
137    fn description(&self) -> &str {
138        "Get current inventory levels. Optionally filter by SKU or show only low stock items."
139    }
140
141    fn parameters_schema(&self) -> Value {
142        json!({
143            "type": "object",
144            "properties": {
145                "sku": {
146                    "type": "string",
147                    "description": "Optional: Get inventory for a specific SKU"
148                },
149                "low_stock_only": {
150                    "type": "boolean",
151                    "description": "Optional: Show only items below reorder point"
152                }
153            },
154            "additionalProperties": false
155        })
156    }
157
158    fn hints(&self) -> ToolHints {
159        ToolHints::default()
160            .with_readonly(true)
161            .with_idempotent(true)
162    }
163
164    async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
165        ToolExecutionResult::tool_error(
166            "warehouse_get_inventory requires context. Must be executed with session context.",
167        )
168    }
169
170    async fn execute_with_context(
171        &self,
172        arguments: Value,
173        context: &ToolContext,
174    ) -> ToolExecutionResult {
175        let file_store = match &context.file_store {
176            Some(store) => store,
177            None => return ToolExecutionResult::tool_error("File system not available"),
178        };
179
180        let sku_filter = arguments.get("sku").and_then(|v| v.as_str());
181        let low_stock_only = arguments
182            .get("low_stock_only")
183            .and_then(|v| v.as_bool())
184            .unwrap_or(false);
185
186        // Read inventory file
187        let inventory: Vec<InventoryItem> = match file_store
188            .read_file(context.session_id, "/warehouse/inventory.json")
189            .await
190        {
191            Ok(Some(file)) => serde_json::from_str(file.content.as_deref().unwrap_or(""))
192                .unwrap_or_else(|_| {
193                    // Initialize with sample data
194                    vec![
195                        InventoryItem {
196                            sku: "WH-001".to_string(),
197                            name: "Industrial Widget".to_string(),
198                            quantity: 150,
199                            location: "A1".to_string(),
200                            reorder_point: 50,
201                        },
202                        InventoryItem {
203                            sku: "WH-002".to_string(),
204                            name: "Premium Gadget".to_string(),
205                            quantity: 30,
206                            location: "B2".to_string(),
207                            reorder_point: 40,
208                        },
209                        InventoryItem {
210                            sku: "WH-003".to_string(),
211                            name: "Standard Component".to_string(),
212                            quantity: 200,
213                            location: "C3".to_string(),
214                            reorder_point: 75,
215                        },
216                    ]
217                }),
218            _ => {
219                // Create initial inventory
220                let initial = vec![
221                    InventoryItem {
222                        sku: "WH-001".to_string(),
223                        name: "Industrial Widget".to_string(),
224                        quantity: 150,
225                        location: "A1".to_string(),
226                        reorder_point: 50,
227                    },
228                    InventoryItem {
229                        sku: "WH-002".to_string(),
230                        name: "Premium Gadget".to_string(),
231                        quantity: 30,
232                        location: "B2".to_string(),
233                        reorder_point: 40,
234                    },
235                    InventoryItem {
236                        sku: "WH-003".to_string(),
237                        name: "Standard Component".to_string(),
238                        quantity: 200,
239                        location: "C3".to_string(),
240                        reorder_point: 75,
241                    },
242                ];
243
244                // Save initial data
245                let content = serde_json::to_string_pretty(&initial).unwrap();
246                let _ = file_store
247                    .write_file(
248                        context.session_id,
249                        "/warehouse/inventory.json",
250                        &content,
251                        "text",
252                    )
253                    .await;
254
255                initial
256            }
257        };
258
259        // Apply filters
260        let mut filtered: Vec<_> = inventory.into_iter().collect();
261
262        if let Some(sku) = sku_filter {
263            filtered.retain(|item| item.sku == sku);
264        }
265
266        if low_stock_only {
267            filtered.retain(|item| item.quantity < item.reorder_point);
268        }
269
270        ToolExecutionResult::success(json!({
271            "inventory": filtered,
272            "total_items": filtered.len(),
273            "low_stock_count": filtered.iter().filter(|item| item.quantity < item.reorder_point).count()
274        }))
275    }
276
277    fn requires_context(&self) -> bool {
278        true
279    }
280}
281
282// ============================================================================
283// Tool: warehouse_update_inventory
284// ============================================================================
285
286pub struct WarehouseUpdateInventoryTool;
287
288#[async_trait]
289impl Tool for WarehouseUpdateInventoryTool {
290    fn name(&self) -> &str {
291        "warehouse_update_inventory"
292    }
293
294    fn display_name(&self) -> Option<&str> {
295        Some("Update Inventory")
296    }
297
298    fn description(&self) -> &str {
299        "Update inventory quantity for a product. Use positive numbers to add stock, negative to remove."
300    }
301
302    fn parameters_schema(&self) -> Value {
303        json!({
304            "type": "object",
305            "properties": {
306                "sku": {
307                    "type": "string",
308                    "description": "Product SKU to update"
309                },
310                "quantity_change": {
311                    "type": "integer",
312                    "description": "Quantity to add (positive) or remove (negative)"
313                },
314                "reason": {
315                    "type": "string",
316                    "description": "Reason for update (e.g., 'restock', 'sale', 'damage')"
317                }
318            },
319            "required": ["sku", "quantity_change"],
320            "additionalProperties": false
321        })
322    }
323
324    fn hints(&self) -> ToolHints {
325        ToolHints::default().with_idempotent(true)
326    }
327
328    async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
329        ToolExecutionResult::tool_error("warehouse_update_inventory requires context")
330    }
331
332    async fn execute_with_context(
333        &self,
334        arguments: Value,
335        context: &ToolContext,
336    ) -> ToolExecutionResult {
337        let file_store = match &context.file_store {
338            Some(store) => store,
339            None => return ToolExecutionResult::tool_error("File system not available"),
340        };
341
342        let sku = match arguments.get("sku").and_then(|v| v.as_str()) {
343            Some(s) => s,
344            None => return ToolExecutionResult::tool_error("Missing required parameter: sku"),
345        };
346
347        let quantity_change = match arguments.get("quantity_change").and_then(|v| v.as_i64()) {
348            Some(q) => q as i32,
349            None => {
350                return ToolExecutionResult::tool_error(
351                    "Missing required parameter: quantity_change",
352                );
353            }
354        };
355
356        let reason = arguments
357            .get("reason")
358            .and_then(|v| v.as_str())
359            .unwrap_or("manual update");
360
361        // Read current inventory
362        let mut inventory: Vec<InventoryItem> = match file_store
363            .read_file(context.session_id, "/warehouse/inventory.json")
364            .await
365        {
366            Ok(Some(file)) => {
367                serde_json::from_str(file.content.as_deref().unwrap_or("")).unwrap_or_default()
368            }
369            _ => vec![],
370        };
371
372        // Find and update the item
373        let item = match inventory.iter_mut().find(|item| item.sku == sku) {
374            Some(i) => i,
375            None => return ToolExecutionResult::tool_error(format!("SKU not found: {}", sku)),
376        };
377
378        let old_quantity = item.quantity;
379        item.quantity += quantity_change;
380
381        if item.quantity < 0 {
382            item.quantity = old_quantity; // Rollback
383            return ToolExecutionResult::tool_error("Insufficient inventory");
384        }
385
386        let new_quantity = item.quantity;
387        let reorder_point = item.reorder_point;
388        // Save updated inventory
389        let content = serde_json::to_string_pretty(&inventory).unwrap();
390        match file_store
391            .write_file(
392                context.session_id,
393                "/warehouse/inventory.json",
394                &content,
395                "text",
396            )
397            .await
398        {
399            Ok(_) => ToolExecutionResult::success(json!({
400                "sku": sku,
401                "old_quantity": old_quantity,
402                "new_quantity": new_quantity,
403                "change": quantity_change,
404                "reason": reason,
405                "below_reorder_point": new_quantity < reorder_point
406            })),
407            Err(e) => ToolExecutionResult::internal_error(e),
408        }
409    }
410
411    fn requires_context(&self) -> bool {
412        true
413    }
414}
415
416// ============================================================================
417// Tool: warehouse_create_shipment
418// ============================================================================
419
420pub struct WarehouseCreateShipmentTool;
421
422#[async_trait]
423impl Tool for WarehouseCreateShipmentTool {
424    fn name(&self) -> &str {
425        "warehouse_create_shipment"
426    }
427
428    fn display_name(&self) -> Option<&str> {
429        Some("Create Shipment")
430    }
431
432    fn description(&self) -> &str {
433        "Create a new shipment. Automatically updates inventory levels."
434    }
435
436    fn parameters_schema(&self) -> Value {
437        json!({
438            "type": "object",
439            "properties": {
440                "destination": {
441                    "type": "string",
442                    "description": "Shipment destination address"
443                },
444                "items": {
445                    "type": "array",
446                    "description": "Items to ship",
447                    "items": {
448                        "type": "object",
449                        "properties": {
450                            "sku": {"type": "string"},
451                            "quantity": {"type": "integer", "minimum": 1}
452                        },
453                        "required": ["sku", "quantity"]
454                    }
455                }
456            },
457            "required": ["destination", "items"],
458            "additionalProperties": false
459        })
460    }
461
462    async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
463        ToolExecutionResult::tool_error("warehouse_create_shipment requires context")
464    }
465
466    async fn execute_with_context(
467        &self,
468        arguments: Value,
469        context: &ToolContext,
470    ) -> ToolExecutionResult {
471        let file_store = match &context.file_store {
472            Some(store) => store,
473            None => return ToolExecutionResult::tool_error("File system not available"),
474        };
475
476        let destination = match arguments.get("destination").and_then(|v| v.as_str()) {
477            Some(d) => d,
478            None => {
479                return ToolExecutionResult::tool_error("Missing required parameter: destination");
480            }
481        };
482
483        let items: Vec<ShipmentItem> = match arguments.get("items") {
484            Some(items_value) => match serde_json::from_value(items_value.clone()) {
485                Ok(items) => items,
486                Err(_) => return ToolExecutionResult::tool_error("Invalid items format"),
487            },
488            None => return ToolExecutionResult::tool_error("Missing required parameter: items"),
489        };
490
491        // Read current shipments
492        let mut shipments: Vec<Shipment> = match file_store
493            .read_file(context.session_id, "/warehouse/shipments.json")
494            .await
495        {
496            Ok(Some(file)) => {
497                serde_json::from_str(file.content.as_deref().unwrap_or("")).unwrap_or_default()
498            }
499            _ => vec![],
500        };
501
502        // Create new shipment
503        let shipment_id = format!("SHP-{:05}", shipments.len() + 1);
504        let now = chrono::Utc::now().to_rfc3339();
505
506        let shipment = Shipment {
507            id: shipment_id.clone(),
508            status: "pending".to_string(),
509            destination: destination.to_string(),
510            items: items.clone(),
511            created_at: now.clone(),
512            updated_at: now,
513        };
514
515        shipments.push(shipment.clone());
516
517        // Save shipments
518        let content = serde_json::to_string_pretty(&shipments).unwrap();
519        match file_store
520            .write_file(
521                context.session_id,
522                "/warehouse/shipments.json",
523                &content,
524                "text",
525            )
526            .await
527        {
528            Ok(_) => ToolExecutionResult::success(json!({
529                "shipment_id": shipment_id,
530                "status": "pending",
531                "destination": destination,
532                "items": items,
533                "message": "Shipment created successfully"
534            })),
535            Err(e) => ToolExecutionResult::internal_error(e),
536        }
537    }
538
539    fn requires_context(&self) -> bool {
540        true
541    }
542}
543
544// ============================================================================
545// Tool: warehouse_list_shipments
546// ============================================================================
547
548pub struct WarehouseListShipmentsTool;
549
550#[async_trait]
551impl Tool for WarehouseListShipmentsTool {
552    fn name(&self) -> &str {
553        "warehouse_list_shipments"
554    }
555
556    fn display_name(&self) -> Option<&str> {
557        Some("List Shipments")
558    }
559
560    fn description(&self) -> &str {
561        "List all shipments. Optionally filter by status."
562    }
563
564    fn parameters_schema(&self) -> Value {
565        json!({
566            "type": "object",
567            "properties": {
568                "status": {
569                    "type": "string",
570                    "enum": ["pending", "in_transit", "delivered"],
571                    "description": "Optional: Filter by shipment status"
572                }
573            },
574            "additionalProperties": false
575        })
576    }
577
578    fn hints(&self) -> ToolHints {
579        ToolHints::default()
580            .with_readonly(true)
581            .with_idempotent(true)
582    }
583
584    async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
585        ToolExecutionResult::tool_error("warehouse_list_shipments requires context")
586    }
587
588    async fn execute_with_context(
589        &self,
590        arguments: Value,
591        context: &ToolContext,
592    ) -> ToolExecutionResult {
593        let file_store = match &context.file_store {
594            Some(store) => store,
595            None => return ToolExecutionResult::tool_error("File system not available"),
596        };
597
598        let status_filter = arguments.get("status").and_then(|v| v.as_str());
599
600        // Read shipments
601        let shipments: Vec<Shipment> = match file_store
602            .read_file(context.session_id, "/warehouse/shipments.json")
603            .await
604        {
605            Ok(Some(file)) => {
606                serde_json::from_str(file.content.as_deref().unwrap_or("")).unwrap_or_default()
607            }
608            _ => vec![],
609        };
610
611        // Apply filter
612        let filtered: Vec<_> = if let Some(status) = status_filter {
613            shipments
614                .into_iter()
615                .filter(|s| s.status == status)
616                .collect()
617        } else {
618            shipments
619        };
620
621        ToolExecutionResult::success(json!({
622            "shipments": filtered,
623            "total_count": filtered.len()
624        }))
625    }
626
627    fn requires_context(&self) -> bool {
628        true
629    }
630}
631
632// ============================================================================
633// Tool: warehouse_update_shipment_status
634// ============================================================================
635
636pub struct WarehouseUpdateShipmentStatusTool;
637
638#[async_trait]
639impl Tool for WarehouseUpdateShipmentStatusTool {
640    fn name(&self) -> &str {
641        "warehouse_update_shipment_status"
642    }
643
644    fn display_name(&self) -> Option<&str> {
645        Some("Update Shipment Status")
646    }
647
648    fn description(&self) -> &str {
649        "Update the status of a shipment (pending -> in_transit -> delivered)."
650    }
651
652    fn parameters_schema(&self) -> Value {
653        json!({
654            "type": "object",
655            "properties": {
656                "shipment_id": {
657                    "type": "string",
658                    "description": "Shipment ID to update"
659                },
660                "status": {
661                    "type": "string",
662                    "enum": ["pending", "in_transit", "delivered"],
663                    "description": "New status"
664                }
665            },
666            "required": ["shipment_id", "status"],
667            "additionalProperties": false
668        })
669    }
670
671    fn hints(&self) -> ToolHints {
672        ToolHints::default().with_idempotent(true)
673    }
674
675    async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
676        ToolExecutionResult::tool_error("warehouse_update_shipment_status requires context")
677    }
678
679    async fn execute_with_context(
680        &self,
681        arguments: Value,
682        context: &ToolContext,
683    ) -> ToolExecutionResult {
684        let file_store = match &context.file_store {
685            Some(store) => store,
686            None => return ToolExecutionResult::tool_error("File system not available"),
687        };
688
689        let shipment_id = match arguments.get("shipment_id").and_then(|v| v.as_str()) {
690            Some(id) => id,
691            None => {
692                return ToolExecutionResult::tool_error("Missing required parameter: shipment_id");
693            }
694        };
695
696        let new_status = match arguments.get("status").and_then(|v| v.as_str()) {
697            Some(s) => s,
698            None => return ToolExecutionResult::tool_error("Missing required parameter: status"),
699        };
700
701        // Read shipments
702        let mut shipments: Vec<Shipment> = match file_store
703            .read_file(context.session_id, "/warehouse/shipments.json")
704            .await
705        {
706            Ok(Some(file)) => {
707                serde_json::from_str(file.content.as_deref().unwrap_or("")).unwrap_or_default()
708            }
709            _ => vec![],
710        };
711
712        // Find and update shipment
713        let shipment = match shipments.iter_mut().find(|s| s.id == shipment_id) {
714            Some(s) => s,
715            None => {
716                return ToolExecutionResult::tool_error(format!(
717                    "Shipment not found: {}",
718                    shipment_id
719                ));
720            }
721        };
722
723        let old_status = shipment.status.clone();
724        shipment.status = new_status.to_string();
725        let updated_at = shipment.updated_at.clone();
726        shipment.updated_at = chrono::Utc::now().to_rfc3339();
727
728        // Save updated shipments
729        let content = serde_json::to_string_pretty(&shipments).unwrap();
730        match file_store
731            .write_file(
732                context.session_id,
733                "/warehouse/shipments.json",
734                &content,
735                "text",
736            )
737            .await
738        {
739            Ok(_) => ToolExecutionResult::success(json!({
740                "shipment_id": shipment_id,
741                "old_status": old_status,
742                "new_status": new_status,
743                "updated_at": updated_at
744            })),
745            Err(e) => ToolExecutionResult::internal_error(e),
746        }
747    }
748
749    fn requires_context(&self) -> bool {
750        true
751    }
752}
753
754// ============================================================================
755// Remaining tools (simplified implementations)
756// ============================================================================
757
758pub struct WarehouseCreateOrderTool;
759
760#[async_trait]
761impl Tool for WarehouseCreateOrderTool {
762    fn name(&self) -> &str {
763        "warehouse_create_order"
764    }
765
766    fn display_name(&self) -> Option<&str> {
767        Some("Create Order")
768    }
769
770    fn description(&self) -> &str {
771        "Create a new customer order."
772    }
773
774    fn parameters_schema(&self) -> Value {
775        json!({
776            "type": "object",
777            "properties": {
778                "customer_name": {"type": "string"},
779                "items": {
780                    "type": "array",
781                    "items": {
782                        "type": "object",
783                        "properties": {
784                            "sku": {"type": "string"},
785                            "quantity": {"type": "integer"}
786                        }
787                    }
788                }
789            },
790            "required": ["customer_name", "items"],
791            "additionalProperties": false
792        })
793    }
794
795    async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
796        ToolExecutionResult::tool_error("Requires context")
797    }
798
799    async fn execute_with_context(
800        &self,
801        arguments: Value,
802        context: &ToolContext,
803    ) -> ToolExecutionResult {
804        let file_store = match &context.file_store {
805            Some(store) => store,
806            None => return ToolExecutionResult::tool_error("File system not available"),
807        };
808
809        let customer_name = arguments
810            .get("customer_name")
811            .and_then(|v| v.as_str())
812            .unwrap_or("Unknown");
813        let items: Vec<ShipmentItem> =
814            serde_json::from_value(arguments.get("items").cloned().unwrap_or(json!([])))
815                .unwrap_or_default();
816
817        let mut orders: Vec<Order> = match file_store
818            .read_file(context.session_id, "/warehouse/orders.json")
819            .await
820        {
821            Ok(Some(file)) => {
822                serde_json::from_str(file.content.as_deref().unwrap_or("")).unwrap_or_default()
823            }
824            _ => vec![],
825        };
826
827        let order_id = format!("ORD-{:05}", orders.len() + 1);
828        let order = Order {
829            id: order_id.clone(),
830            customer_name: customer_name.to_string(),
831            items: items.clone(),
832            status: "pending".to_string(),
833            created_at: chrono::Utc::now().to_rfc3339(),
834        };
835
836        orders.push(order);
837
838        let content = serde_json::to_string_pretty(&orders).unwrap();
839        let _ = file_store
840            .write_file(
841                context.session_id,
842                "/warehouse/orders.json",
843                &content,
844                "text",
845            )
846            .await;
847
848        ToolExecutionResult::success(
849            json!({"order_id": order_id, "status": "pending", "items": items}),
850        )
851    }
852
853    fn requires_context(&self) -> bool {
854        true
855    }
856}
857
858pub struct WarehouseListOrdersTool;
859
860#[async_trait]
861impl Tool for WarehouseListOrdersTool {
862    fn name(&self) -> &str {
863        "warehouse_list_orders"
864    }
865
866    fn display_name(&self) -> Option<&str> {
867        Some("List Orders")
868    }
869
870    fn description(&self) -> &str {
871        "List all customer orders."
872    }
873
874    fn parameters_schema(&self) -> Value {
875        json!({"type": "object", "properties": {}, "additionalProperties": false})
876    }
877
878    fn hints(&self) -> ToolHints {
879        ToolHints::default()
880            .with_readonly(true)
881            .with_idempotent(true)
882    }
883
884    async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
885        ToolExecutionResult::tool_error("Requires context")
886    }
887
888    async fn execute_with_context(
889        &self,
890        _arguments: Value,
891        context: &ToolContext,
892    ) -> ToolExecutionResult {
893        let file_store = match &context.file_store {
894            Some(store) => store,
895            None => return ToolExecutionResult::tool_error("File system not available"),
896        };
897
898        let orders: Vec<Order> = match file_store
899            .read_file(context.session_id, "/warehouse/orders.json")
900            .await
901        {
902            Ok(Some(file)) => {
903                serde_json::from_str(file.content.as_deref().unwrap_or("")).unwrap_or_default()
904            }
905            _ => vec![],
906        };
907
908        ToolExecutionResult::success(json!({"orders": orders, "total_count": orders.len()}))
909    }
910
911    fn requires_context(&self) -> bool {
912        true
913    }
914}
915
916pub struct WarehouseCreateInvoiceTool;
917
918#[async_trait]
919impl Tool for WarehouseCreateInvoiceTool {
920    fn name(&self) -> &str {
921        "warehouse_create_invoice"
922    }
923
924    fn display_name(&self) -> Option<&str> {
925        Some("Create Invoice")
926    }
927
928    fn description(&self) -> &str {
929        "Generate an invoice for an order."
930    }
931
932    fn parameters_schema(&self) -> Value {
933        json!({
934            "type": "object",
935            "properties": {
936                "order_id": {"type": "string"}
937            },
938            "required": ["order_id"],
939            "additionalProperties": false
940        })
941    }
942
943    async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
944        ToolExecutionResult::tool_error("Requires context")
945    }
946
947    async fn execute_with_context(
948        &self,
949        arguments: Value,
950        _context: &ToolContext,
951    ) -> ToolExecutionResult {
952        let order_id = arguments
953            .get("order_id")
954            .and_then(|v| v.as_str())
955            .unwrap_or("unknown");
956        let invoice_id = format!("INV-{:05}", chrono::Utc::now().timestamp() % 100000);
957
958        ToolExecutionResult::success(json!({
959            "invoice_id": invoice_id,
960            "order_id": order_id,
961            "amount": 1299.99,
962            "status": "generated"
963        }))
964    }
965
966    fn requires_context(&self) -> bool {
967        true
968    }
969}
970
971pub struct WarehouseProcessReturnTool;
972
973#[async_trait]
974impl Tool for WarehouseProcessReturnTool {
975    fn name(&self) -> &str {
976        "warehouse_process_return"
977    }
978
979    fn display_name(&self) -> Option<&str> {
980        Some("Process Return")
981    }
982
983    fn description(&self) -> &str {
984        "Process a product return and update inventory."
985    }
986
987    fn parameters_schema(&self) -> Value {
988        json!({
989            "type": "object",
990            "properties": {
991                "order_id": {"type": "string"},
992                "sku": {"type": "string"},
993                "quantity": {"type": "integer"},
994                "reason": {"type": "string"}
995            },
996            "required": ["order_id", "sku", "quantity"],
997            "additionalProperties": false
998        })
999    }
1000
1001    async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
1002        ToolExecutionResult::tool_error("Requires context")
1003    }
1004
1005    async fn execute_with_context(
1006        &self,
1007        arguments: Value,
1008        _context: &ToolContext,
1009    ) -> ToolExecutionResult {
1010        let return_id = format!("RET-{:05}", chrono::Utc::now().timestamp() % 100000);
1011        let sku = arguments
1012            .get("sku")
1013            .and_then(|v| v.as_str())
1014            .unwrap_or("unknown");
1015
1016        ToolExecutionResult::success(json!({
1017            "return_id": return_id,
1018            "sku": sku,
1019            "status": "processed",
1020            "inventory_updated": true
1021        }))
1022    }
1023
1024    fn requires_context(&self) -> bool {
1025        true
1026    }
1027}
1028
1029pub struct WarehouseInventoryReportTool;
1030
1031#[async_trait]
1032impl Tool for WarehouseInventoryReportTool {
1033    fn name(&self) -> &str {
1034        "warehouse_inventory_report"
1035    }
1036
1037    fn display_name(&self) -> Option<&str> {
1038        Some("Inventory Report")
1039    }
1040
1041    fn description(&self) -> &str {
1042        "Generate a comprehensive inventory report with metrics and alerts."
1043    }
1044
1045    fn parameters_schema(&self) -> Value {
1046        json!({"type": "object", "properties": {}, "additionalProperties": false})
1047    }
1048
1049    fn hints(&self) -> ToolHints {
1050        ToolHints::default()
1051            .with_readonly(true)
1052            .with_idempotent(true)
1053    }
1054
1055    async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
1056        ToolExecutionResult::tool_error("Requires context")
1057    }
1058
1059    async fn execute_with_context(
1060        &self,
1061        _arguments: Value,
1062        context: &ToolContext,
1063    ) -> ToolExecutionResult {
1064        let file_store = match &context.file_store {
1065            Some(store) => store,
1066            None => return ToolExecutionResult::tool_error("File system not available"),
1067        };
1068
1069        let inventory: Vec<InventoryItem> = match file_store
1070            .read_file(context.session_id, "/warehouse/inventory.json")
1071            .await
1072        {
1073            Ok(Some(file)) => {
1074                serde_json::from_str(file.content.as_deref().unwrap_or("")).unwrap_or_default()
1075            }
1076            _ => vec![],
1077        };
1078
1079        let total_items = inventory.len();
1080        let low_stock: Vec<_> = inventory
1081            .iter()
1082            .filter(|i| i.quantity < i.reorder_point)
1083            .collect();
1084        let total_value: i32 = inventory.iter().map(|i| i.quantity).sum();
1085
1086        ToolExecutionResult::success(json!({
1087            "report": {
1088                "total_items": total_items,
1089                "total_quantity": total_value,
1090                "low_stock_count": low_stock.len(),
1091                "low_stock_items": low_stock,
1092                "generated_at": chrono::Utc::now().to_rfc3339()
1093            }
1094        }))
1095    }
1096
1097    fn requires_context(&self) -> bool {
1098        true
1099    }
1100}