mirror of
				https://github.com/inventree/inventree-app.git
				synced 2025-10-31 13:25:40 +00:00 
			
		
		
		
	Sales order support (#438)
* Add new models for SalesOrder - Create generic Order and OrderLine models with common functionality * Refactor - Move some widgets around - Cleanup directory structure * Add link to home screen and nav drawer * Add SalesOrder list widget * Linting fixes * Fix string * Refactor PurchaseOrderDetailWidget * Tweaks to existing code * linting * Fixes for drawer widget * Add "detail" page for SalesOrder * Add more tiles to SalesOrder detail * Allow editing of salesorder * add list filters for sales orders * Display list of line items * Customer updates - Display customer icon on home screen - Fetch sales orders for customer detail page * Cleanup company detail view * Create new sales order from list * Stricter typing for formFields method * Create new PurchaseOrder and SalesOrder from company deatil * Status code updates - Add function for name comparison - Remove hard-coded values * Update view permission checks for home widget * Add ability to manually add SalesOrderLineItem * Add nice progress bar widgets * Display detail view for sales order line item * edit SalesOrderLineItem * Fix unused import * Hide "shipped items" tab - Will be added in a future update
This commit is contained in:
		
							
								
								
									
										17
									
								
								lib/api.dart
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								lib/api.dart
									
									
									
									
									
								
							| @@ -620,6 +620,8 @@ class InvenTreeAPI { | ||||
|     _globalSettings.clear(); | ||||
|     _userSettings.clear(); | ||||
|  | ||||
|     roles.clear(); | ||||
|     _plugins.clear(); | ||||
|     serverInfo.clear(); | ||||
|     _connectionStatusChanged(); | ||||
|   } | ||||
| @@ -672,6 +674,8 @@ class InvenTreeAPI { | ||||
|  | ||||
|     _connectionStatusChanged(); | ||||
|  | ||||
|     fetchStatusCodeData(); | ||||
|  | ||||
|     return _connected; | ||||
|   } | ||||
|  | ||||
| @@ -735,6 +739,10 @@ class InvenTreeAPI { | ||||
|    */ | ||||
|   bool checkPermission(String role, String permission) { | ||||
|  | ||||
|     if (!_connected) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     // If we do not have enough information, assume permission is allowed | ||||
|     if (roles.isEmpty) { | ||||
|       debug("checkPermission - no roles defined!"); | ||||
| @@ -1624,11 +1632,20 @@ class InvenTreeAPI { | ||||
|   InvenTreeStatusCode get StockHistoryStatus => _get_status_class("stock/track/status/"); | ||||
|   InvenTreeStatusCode get StockStatus => _get_status_class("stock/status/"); | ||||
|   InvenTreeStatusCode get PurchaseOrderStatus => _get_status_class("order/po/status/"); | ||||
|   InvenTreeStatusCode get SalesOrderStatus => _get_status_class("order/so/status/"); | ||||
|  | ||||
|   void clearStatusCodeData() { | ||||
|     StockHistoryStatus.data.clear(); | ||||
|     StockStatus.data.clear(); | ||||
|     PurchaseOrderStatus.data.clear(); | ||||
|     SalesOrderStatus.data.clear(); | ||||
|   } | ||||
|  | ||||
|   Future<void> fetchStatusCodeData({bool forceReload = true}) async { | ||||
|     StockHistoryStatus.load(forceReload: forceReload); | ||||
|     StockStatus.load(forceReload: forceReload); | ||||
|     PurchaseOrderStatus.load(forceReload: forceReload); | ||||
|     SalesOrderStatus.load(forceReload: forceReload); | ||||
|   } | ||||
|  | ||||
|   int notification_counter = 0; | ||||
|   | ||||
| @@ -23,13 +23,13 @@ import "package:inventree/inventree/purchase_order.dart"; | ||||
| import "package:inventree/inventree/stock.dart"; | ||||
|  | ||||
| import "package:inventree/widget/dialogs.dart"; | ||||
| import "package:inventree/widget/location_display.dart"; | ||||
| import "package:inventree/widget/part_detail.dart"; | ||||
| import "package:inventree/widget/purchase_order_detail.dart"; | ||||
| import "package:inventree/widget/stock/location_display.dart"; | ||||
| import "package:inventree/widget/part/part_detail.dart"; | ||||
| import "package:inventree/widget/order/purchase_order_detail.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| import "package:inventree/widget/snacks.dart"; | ||||
| import "package:inventree/widget/stock_detail.dart"; | ||||
| import "package:inventree/widget/supplier_part_detail.dart"; | ||||
| import "package:inventree/widget/stock/stock_detail.dart"; | ||||
| import "package:inventree/widget/company/supplier_part_detail.dart"; | ||||
|  | ||||
|  | ||||
| /* | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
|  | ||||
| import 'dart:async'; | ||||
| import "dart:async'; | ||||
|  | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import "package:flutter/foundation.dart'; | ||||
| import "package:flutter/material.dart'; | ||||
| // ignore_for_file: non_constant_identifier_names | ||||
| // ignore_for_file: camel_case_types | ||||
| // ignore_for_file: prefer_single_quotes | ||||
|   | ||||
| @@ -22,7 +22,7 @@ class InvenTreeCompany extends InvenTreeModel { | ||||
|   List<String> get rolesRequired => ["purchase_order", "sales_order", "return_order"]; | ||||
|  | ||||
|   @override | ||||
|   Map<String, dynamic> formFields() { | ||||
|   Map<String, Map<String, dynamic>> formFields() { | ||||
|     return { | ||||
|       "name": {}, | ||||
|       "description": {}, | ||||
| @@ -121,8 +121,8 @@ class InvenTreeSupplierPart extends InvenTreeModel { | ||||
|   List<String> get rolesRequired => ["part", "purchase_order"]; | ||||
|  | ||||
|   @override | ||||
|   Map<String, dynamic> formFields() { | ||||
|     Map<String, dynamic> fields = { | ||||
|   Map<String, Map<String, dynamic>> formFields() { | ||||
|     Map<String, Map<String, dynamic>> fields = { | ||||
|       "supplier": {}, | ||||
|       "SKU": {}, | ||||
|       "link": {}, | ||||
|   | ||||
| @@ -230,7 +230,7 @@ class InvenTreeModel { | ||||
|  | ||||
|   // Fields for editing / creating this model | ||||
|   // Override per-model | ||||
|   Map<String, dynamic> formFields() { | ||||
|   Map<String, Map<String, dynamic>> formFields() { | ||||
|  | ||||
|     return {}; | ||||
|   } | ||||
|   | ||||
							
								
								
									
										107
									
								
								lib/inventree/orders.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								lib/inventree/orders.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| /* | ||||
|  * Base model for various "orders" which share common properties | ||||
|  */ | ||||
|  | ||||
|  | ||||
| import "package:inventree/inventree/model.dart"; | ||||
| import "package:inventree/inventree/part.dart"; | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Generic class representing an "order" | ||||
|  */ | ||||
| class InvenTreeOrder extends InvenTreeModel { | ||||
|  | ||||
|   InvenTreeOrder() : super(); | ||||
|  | ||||
|   InvenTreeOrder.fromJson(Map<String, dynamic> json) : super.fromJson(json); | ||||
|  | ||||
|   String get issueDate => getString("issue_date"); | ||||
|  | ||||
|   String get completeDate => getString("complete_date"); | ||||
|  | ||||
|   String get creationDate => getString("creation_date"); | ||||
|  | ||||
|   String get targetDate => getString("target_date"); | ||||
|  | ||||
|   int get lineItemCount => getInt("line_items", backup: 0); | ||||
|  | ||||
|   bool get overdue => getBool("overdue"); | ||||
|  | ||||
|   String get reference => getString("reference"); | ||||
|  | ||||
|   int get responsibleId => getInt("responsible"); | ||||
|  | ||||
|   // Project code information | ||||
|   int get projectCodeId => getInt("project_code"); | ||||
|  | ||||
|   String get projectCode => getString("code", subKey: "project_code_detail"); | ||||
|  | ||||
|   String get projectCodeDescription => getString("description", subKey: "project_code_detail"); | ||||
|  | ||||
|   bool get hasProjectCode => projectCode.isNotEmpty; | ||||
|  | ||||
|   int get status => getInt("status"); | ||||
|  | ||||
|   String get statusText => getString("status_text"); | ||||
|  | ||||
|   double? get totalPrice { | ||||
|     String price = getString("total_price"); | ||||
|  | ||||
|     if (price.isEmpty) { | ||||
|       return null; | ||||
|     } else { | ||||
|       return double.tryParse(price); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Return the currency for this order | ||||
|   // Note that the nomenclature in the API changed at some point | ||||
|   String get totalPriceCurrency { | ||||
|     if (jsondata.containsKey("order_currency")) { | ||||
|       return getString("order_currency"); | ||||
|     } else if (jsondata.containsKey("total_price_currency")) { | ||||
|       return getString("total_price_currency"); | ||||
|     } else { | ||||
|       return ""; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Generic class representing an "order line" | ||||
|  */ | ||||
| class InvenTreeOrderLine extends InvenTreeModel { | ||||
|  | ||||
|   InvenTreeOrderLine() : super(); | ||||
|  | ||||
|   InvenTreeOrderLine.fromJson(Map<String, dynamic> json) : super.fromJson(json); | ||||
|  | ||||
|   bool get overdue => getBool("overdue"); | ||||
|  | ||||
|   double get quantity => getDouble("quantity"); | ||||
|  | ||||
|   String get reference => getString("reference"); | ||||
|  | ||||
|   int get orderId => getInt("order"); | ||||
|  | ||||
|   InvenTreePart? get part { | ||||
|     dynamic part_detail = jsondata["part_detail"]; | ||||
|  | ||||
|     if (part_detail == null) { | ||||
|       return null; | ||||
|     } else { | ||||
|       return InvenTreePart.fromJson(part_detail as Map<String, dynamic>); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   int get partId => getInt("pk", subKey: "part_detail"); | ||||
|  | ||||
|   String get partName => getString("name", subKey: "part_detail"); | ||||
|  | ||||
|   String get partImage => getString("thumbnail", subKey: "part_detail"); | ||||
|  | ||||
|   // TODO: Perhaps parse this as an actual date? | ||||
|   String get targetDate => getString("target_date"); | ||||
| } | ||||
| @@ -27,9 +27,9 @@ class InvenTreePartCategory extends InvenTreeModel { | ||||
|   List<String> get rolesRequired => ["part_category"]; | ||||
|  | ||||
|   @override | ||||
|   Map<String, dynamic> formFields() { | ||||
|   Map<String, Map<String, dynamic>> formFields() { | ||||
|  | ||||
|     Map<String, dynamic> fields = { | ||||
|     Map<String, Map<String, dynamic>> fields = { | ||||
|       "name": {}, | ||||
|       "description": {}, | ||||
|       "parent": {}, | ||||
| @@ -140,9 +140,9 @@ class InvenTreePartParameter extends InvenTreeModel { | ||||
|   InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePartParameter.fromJson(json); | ||||
|  | ||||
|   @override | ||||
|   Map<String, dynamic> formFields() { | ||||
|   Map<String, Map<String, dynamic>> formFields() { | ||||
|  | ||||
|     Map<String, dynamic> fields = { | ||||
|     Map<String, Map<String, dynamic>> fields = { | ||||
|       "header": { | ||||
|         "type": "string", | ||||
|         "read_only": true, | ||||
| @@ -200,7 +200,7 @@ class InvenTreePart extends InvenTreeModel { | ||||
|   List<String> get rolesRequired => ["part"]; | ||||
|  | ||||
|   @override | ||||
|   Map<String, dynamic> formFields() { | ||||
|   Map<String, Map<String, dynamic>> formFields() { | ||||
|     return { | ||||
|       "name": {}, | ||||
|       "description": {}, | ||||
|   | ||||
| @@ -17,7 +17,7 @@ class InvenTreeProjectCode extends InvenTreeModel { | ||||
|   String get URL => "project-code/"; | ||||
|  | ||||
|   @override | ||||
|   Map<String, dynamic> formFields() { | ||||
|   Map<String, Map<String, dynamic>> formFields() { | ||||
|     return { | ||||
|       "code": {}, | ||||
|       "description": {}, | ||||
|   | ||||
| @@ -1,22 +1,22 @@ | ||||
| import "package:inventree/api.dart"; | ||||
| import "package:inventree/helpers.dart"; | ||||
| import "package:inventree/inventree/company.dart"; | ||||
| import "package:inventree/inventree/part.dart"; | ||||
| import "package:inventree/inventree/model.dart"; | ||||
| import "package:inventree/inventree/orders.dart"; | ||||
|  | ||||
| const int PO_STATUS_PENDING = 10; | ||||
| const int PO_STATUS_PLACED = 20; | ||||
| const int PO_STATUS_COMPLETE = 30; | ||||
| const int PO_STATUS_CANCELLED = 40; | ||||
| const int PO_STATUS_LOST = 50; | ||||
| const int PO_STATUS_RETURNED = 60; | ||||
|  | ||||
| class InvenTreePurchaseOrder extends InvenTreeModel { | ||||
| /* | ||||
|  * Class representing an individual PurchaseOrder instance | ||||
|  */ | ||||
| class InvenTreePurchaseOrder extends InvenTreeOrder { | ||||
|  | ||||
|   InvenTreePurchaseOrder() : super(); | ||||
|  | ||||
|   InvenTreePurchaseOrder.fromJson(Map<String, dynamic> json) : super.fromJson(json); | ||||
|  | ||||
|   @override | ||||
|   InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePurchaseOrder.fromJson(json); | ||||
|  | ||||
|   @override | ||||
|   String get URL => "order/po/"; | ||||
|  | ||||
| @@ -26,8 +26,8 @@ class InvenTreePurchaseOrder extends InvenTreeModel { | ||||
|   String get receive_url => "${url}receive/"; | ||||
|  | ||||
|   @override | ||||
|   Map<String, dynamic> formFields() { | ||||
|     var fields = { | ||||
|   Map<String, Map<String, dynamic>> formFields() { | ||||
|     Map<String, Map<String, dynamic>> fields = { | ||||
|       "reference": {}, | ||||
|       "supplier": { | ||||
|         "filters": { | ||||
| @@ -69,33 +69,8 @@ class InvenTreePurchaseOrder extends InvenTreeModel { | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   String get issueDate => getString("issue_date"); | ||||
|  | ||||
|   String get completeDate => getString("complete_date"); | ||||
|  | ||||
|   String get creationDate => getString("creation_date"); | ||||
|  | ||||
|   String get targetDate => getString("target_date"); | ||||
|  | ||||
|   int get lineItemCount => getInt("line_items", backup: 0); | ||||
|    | ||||
|   bool get overdue => getBool("overdue"); | ||||
|  | ||||
|   String get reference => getString("reference"); | ||||
|  | ||||
|   int get responsibleId => getInt("responsible"); | ||||
|  | ||||
|   int get supplierId => getInt("supplier"); | ||||
|  | ||||
|   // Project code information | ||||
|   int get projectCodeId => getInt("project_code"); | ||||
|  | ||||
|   String get projectCode => getString("code", subKey: "project_code_detail"); | ||||
|  | ||||
|   String get projectCodeDescription => getString("description", subKey: "project_code_detail"); | ||||
|  | ||||
|   bool get hasProjectCode => projectCode.isNotEmpty; | ||||
|  | ||||
|   InvenTreeCompany? get supplier { | ||||
|  | ||||
|     dynamic supplier_detail = jsondata["supplier_detail"]; | ||||
| @@ -109,39 +84,13 @@ class InvenTreePurchaseOrder extends InvenTreeModel { | ||||
|  | ||||
|   String get supplierReference => getString("supplier_reference"); | ||||
|  | ||||
|   int get status => getInt("status"); | ||||
|   bool get isOpen => api.PurchaseOrderStatus.isNameIn(status, ["PENDING", "PLACED"]); | ||||
|  | ||||
|   String get statusText => getString("status_text"); | ||||
|   bool get isPending => api.PurchaseOrderStatus.isNameIn(status, ["PENDING"]); | ||||
|  | ||||
|   bool get isOpen => status == PO_STATUS_PENDING || status == PO_STATUS_PLACED; | ||||
|   bool get isPlaced => api.PurchaseOrderStatus.isNameIn(status, ["PLACED"]); | ||||
|  | ||||
|   bool get isPending => status == PO_STATUS_PENDING; | ||||
|  | ||||
|   bool get isPlaced => status == PO_STATUS_PLACED; | ||||
|  | ||||
|   bool get isFailed => status == PO_STATUS_CANCELLED || status == PO_STATUS_LOST || status == PO_STATUS_RETURNED; | ||||
|  | ||||
|   double? get totalPrice { | ||||
|     String price = getString("total_price"); | ||||
|  | ||||
|     if (price.isEmpty) { | ||||
|       return null; | ||||
|     } else { | ||||
|       return double.tryParse(price); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Return the currency for this order | ||||
|   // Note that the nomenclature in the API changed at some point | ||||
|   String get totalPriceCurrency { | ||||
|     if (jsondata.containsKey("order_currency")) { | ||||
|       return getString("order_currency"); | ||||
|     } else if (jsondata.containsKey("total_price_currency")) { | ||||
|       return getString("total_price_currency"); | ||||
|     } else { | ||||
|       return ""; | ||||
|     } | ||||
|   } | ||||
|   bool get isFailed => api.PurchaseOrderStatus.isNameIn(status, ["CANCELLED", "LOST", "RETURNED"]); | ||||
|  | ||||
|   Future<List<InvenTreePOLineItem>> getLineItems() async { | ||||
|  | ||||
| @@ -162,9 +111,6 @@ class InvenTreePurchaseOrder extends InvenTreeModel { | ||||
|     return items; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePurchaseOrder.fromJson(json); | ||||
|  | ||||
|   /// Mark this order as "placed" / "issued" | ||||
|   Future<void> issueOrder() async { | ||||
|     // Order can only be placed when the order is 'pending' | ||||
| @@ -185,12 +131,15 @@ class InvenTreePurchaseOrder extends InvenTreeModel { | ||||
|   } | ||||
| } | ||||
|  | ||||
| class InvenTreePOLineItem extends InvenTreeModel { | ||||
| class InvenTreePOLineItem extends InvenTreeOrderLine { | ||||
|  | ||||
|   InvenTreePOLineItem() : super(); | ||||
|  | ||||
|   InvenTreePOLineItem.fromJson(Map<String, dynamic> json) : super.fromJson(json); | ||||
|  | ||||
|   @override | ||||
|   InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePOLineItem.fromJson(json); | ||||
|  | ||||
|   @override | ||||
|   String get URL => "order/po-line/"; | ||||
|  | ||||
| @@ -198,7 +147,7 @@ class InvenTreePOLineItem extends InvenTreeModel { | ||||
|   List<String> get rolesRequired => ["purchase_order"]; | ||||
|  | ||||
|   @override | ||||
|   Map<String, dynamic> formFields() { | ||||
|   Map<String, Map<String, dynamic>> formFields() { | ||||
|     return { | ||||
|       "part": { | ||||
|         // We cannot edit the supplier part field here | ||||
| @@ -232,38 +181,24 @@ class InvenTreePOLineItem extends InvenTreeModel { | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   double get received => getDouble("received"); | ||||
|  | ||||
|   bool get isComplete => received >= quantity; | ||||
|  | ||||
|   double get quantity => getDouble("quantity"); | ||||
|   double get progressRatio { | ||||
|     if (quantity <= 0 || received <= 0) { | ||||
|       return 0; | ||||
|     } | ||||
|  | ||||
|   double get received => getDouble("received"); | ||||
|     return received / quantity; | ||||
|   } | ||||
|  | ||||
|   String get progressString => simpleNumberString(received) + " / " + simpleNumberString(quantity); | ||||
|  | ||||
|   double get outstanding => quantity - received; | ||||
|  | ||||
|   String get reference => getString("reference"); | ||||
|  | ||||
|   int get orderId => getInt("order"); | ||||
|  | ||||
|   int get supplierPartId => getInt("part"); | ||||
|  | ||||
|   InvenTreePart? get part { | ||||
|     dynamic part_detail = jsondata["part_detail"]; | ||||
|  | ||||
|     if (part_detail == null) { | ||||
|       return null; | ||||
|     } else { | ||||
|       return InvenTreePart.fromJson(part_detail as Map<String, dynamic>); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   int get partId => getInt("pk", subKey: "part_detail"); | ||||
|    | ||||
|   String get partName => getString("name", subKey: "part_detail"); | ||||
|  | ||||
|   String get partImage => getString("thumbnail", subKey: "part_detail"); | ||||
|  | ||||
|   InvenTreeSupplierPart? get supplierPart { | ||||
|  | ||||
|     dynamic detail = jsondata["supplier_part_detail"]; | ||||
| @@ -281,19 +216,13 @@ class InvenTreePOLineItem extends InvenTreeModel { | ||||
|    | ||||
|   String get purchasePriceCurrency => getString("purchase_price_currency"); | ||||
|  | ||||
|   String get purchasePriceString => getString("purchase_price_string"); | ||||
|  | ||||
|   int get destination => getInt("destination"); | ||||
|  | ||||
|   Map<String, dynamic> get destinationDetail => getMap("destination_detail"); | ||||
|    | ||||
|   @override | ||||
|   InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePOLineItem.fromJson(json); | ||||
|  | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Class representing an attachment file against a StockItem object | ||||
|  * Class representing an attachment file against a PurchaseOrder object | ||||
|  */ | ||||
| class InvenTreePurchaseOrderAttachment extends InvenTreeAttachment { | ||||
|  | ||||
|   | ||||
							
								
								
									
										190
									
								
								lib/inventree/sales_order.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								lib/inventree/sales_order.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,190 @@ | ||||
|  | ||||
|  | ||||
| import "package:inventree/helpers.dart"; | ||||
| import "package:inventree/inventree/company.dart"; | ||||
| import "package:inventree/inventree/model.dart"; | ||||
| import "package:inventree/inventree/orders.dart"; | ||||
|  | ||||
| import "package:inventree/api.dart"; | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Class representing an individual SalesOrder | ||||
|  */ | ||||
| class InvenTreeSalesOrder extends InvenTreeOrder { | ||||
|  | ||||
|   InvenTreeSalesOrder() : super(); | ||||
|  | ||||
|   InvenTreeSalesOrder.fromJson(Map<String, dynamic> json) : super.fromJson(json); | ||||
|  | ||||
|   @override | ||||
|   InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeSalesOrder.fromJson(json); | ||||
|  | ||||
|   @override | ||||
|   String get URL => "order/so/"; | ||||
|  | ||||
|   @override | ||||
|   List<String> get rolesRequired => ["sales_order"]; | ||||
|  | ||||
|   @override | ||||
|   Map<String, Map<String, dynamic>> formFields() { | ||||
|     Map<String, Map<String, dynamic>> fields = { | ||||
|       "reference": {}, | ||||
|       "customer": { | ||||
|         "filters": { | ||||
|           "is_customer": true, | ||||
|         } | ||||
|       }, | ||||
|       "customer_reference": {}, | ||||
|       "description": {}, | ||||
|       "project_code": {}, | ||||
|       "target_date": {}, | ||||
|       "link": {}, | ||||
|       "responsible": {}, | ||||
|       "contact": { | ||||
|         "filters": { | ||||
|           "company": customerId, | ||||
|         } | ||||
|       } | ||||
|     }; | ||||
|  | ||||
|     if (!InvenTreeAPI().supportsProjectCodes) { | ||||
|       fields.remove("project_code"); | ||||
|     } | ||||
|  | ||||
|     if (!InvenTreeAPI().supportsContactModel) { | ||||
|       fields.remove("contact"); | ||||
|     } | ||||
|  | ||||
|     return fields; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Map<String, String> defaultGetFilters() { | ||||
|     return { | ||||
|       "customer_detail": "true", | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Map<String, String> defaultListFilters() { | ||||
|     return { | ||||
|       "customer_detail": "true", | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   int get customerId => getInt("customer"); | ||||
|  | ||||
|   InvenTreeCompany? get customer { | ||||
|     dynamic customer_detail = jsondata["customer_detail"]; | ||||
|  | ||||
|     if (customer_detail == null) { | ||||
|       return null; | ||||
|     } else { | ||||
|       return InvenTreeCompany.fromJson(customer_detail as Map<String, dynamic>); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   String get customerReference => getString("customer_reference"); | ||||
|  | ||||
|   bool get isOpen => api.SalesOrderStatus.isNameIn(status, ["PENDING", "IN_PROGRESS"]); | ||||
|  | ||||
|   bool get isComplete => api.SalesOrderStatus.isNameIn(status, ["SHIPPED"]); | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Class representing an individual line item in a SalesOrder | ||||
|  */ | ||||
| class InvenTreeSOLineItem extends InvenTreeOrderLine { | ||||
|  | ||||
|   InvenTreeSOLineItem() : super(); | ||||
|  | ||||
|   InvenTreeSOLineItem.fromJson(Map<String, dynamic> json) : super.fromJson(json); | ||||
|  | ||||
|   @override | ||||
|   InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeSOLineItem.fromJson(json); | ||||
|  | ||||
|   @override | ||||
|   String get URL => "order/so-line/"; | ||||
|  | ||||
|   @override | ||||
|   List<String> get rolesRequired => ["sales_order"]; | ||||
|  | ||||
|   @override | ||||
|   Map<String, Map<String, dynamic>> formFields() { | ||||
|     return { | ||||
|       "order": { | ||||
|         "hidden": true, | ||||
|       }, | ||||
|       "part": {}, | ||||
|       "quantity": {}, | ||||
|       "reference": {}, | ||||
|       "notes": {}, | ||||
|       "link": {}, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Map<String, String> defaultGetFilters() { | ||||
|     return { | ||||
|       "part_detail": "true", | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Map<String, String> defaultListFilters() { | ||||
|     return { | ||||
|       "part_detail": "true", | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   double get allocated => getDouble("allocated"); | ||||
|  | ||||
|   bool get isAllocated => allocated >= quantity; | ||||
|  | ||||
|   double get shipped => getDouble("shipped"); | ||||
|  | ||||
|   double get outstanding => quantity - shipped; | ||||
|  | ||||
|   double get progressRatio { | ||||
|     if (quantity <= 0 || shipped <= 0) { | ||||
|       return 0; | ||||
|     } | ||||
|  | ||||
|     return shipped / quantity; | ||||
|   } | ||||
|  | ||||
|   String get progressString => simpleNumberString(shipped) + " / " + simpleNumberString(quantity); | ||||
|  | ||||
|   bool get isComplete => shipped >= quantity; | ||||
|  | ||||
|   double get available => getDouble("available_stock") + getDouble("available_variant_stock"); | ||||
|  | ||||
|   double get salePrice => getDouble("sale_price"); | ||||
|  | ||||
|   String get salePriceCurrency => getString("sale_price_currency"); | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Class representing an attachment file against a SalesOrder object | ||||
|  */ | ||||
| class InvenTreeSalesOrderAttachment extends InvenTreeAttachment { | ||||
|  | ||||
|   InvenTreeSalesOrderAttachment() : super(); | ||||
|  | ||||
|   InvenTreeSalesOrderAttachment.fromJson(Map<String, dynamic> json) : super.fromJson(json); | ||||
|  | ||||
|   @override | ||||
|   InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeSalesOrderAttachment.fromJson(json); | ||||
|  | ||||
|   @override | ||||
|   String get REFERENCE_FIELD => "order"; | ||||
|  | ||||
|   @override | ||||
|   String get URL => "order/po/attachment/"; | ||||
|  | ||||
| } | ||||
| @@ -105,6 +105,24 @@ class InvenTreeStatusCode { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Return the 'name' (untranslated) associated with a given status code | ||||
|   String name(int status) { | ||||
|     Map<String, dynamic> _entry = entry(status); | ||||
|  | ||||
|     String _name = (_entry["name"] ?? "") as String; | ||||
|  | ||||
|     if (_name.isEmpty) { | ||||
|       debug("No match for status code ${status} at '${URL}'"); | ||||
|     } | ||||
|  | ||||
|     return _name; | ||||
|   } | ||||
|  | ||||
|   // Test if the name associated with the given code is in the provided list | ||||
|   bool isNameIn(int code, List<String> names) { | ||||
|     return names.contains(name(code)); | ||||
|   } | ||||
|  | ||||
|   // Return the 'color' associated with a given status code | ||||
|   Color color(int status) { | ||||
|     Map<String, dynamic> _entry = entry(status); | ||||
|   | ||||
| @@ -27,7 +27,7 @@ class InvenTreeStockItemTestResult extends InvenTreeModel { | ||||
|   List<String> get rolesRequired => ["stock"]; | ||||
|  | ||||
|   @override | ||||
|   Map<String, dynamic> formFields() { | ||||
|   Map<String, Map<String, dynamic>> formFields() { | ||||
|     return { | ||||
|       "stock_item": {"hidden": true}, | ||||
|       "test": {}, | ||||
| @@ -158,8 +158,8 @@ class InvenTreeStockItem extends InvenTreeModel { | ||||
|   String get WEB_URL => "stock/item/"; | ||||
|  | ||||
|   @override | ||||
|   Map<String, dynamic> formFields() { | ||||
|     return { | ||||
|   Map<String, Map<String, dynamic>> formFields() { | ||||
|     Map<String, Map<String, dynamic>> fields = { | ||||
|       "part": {}, | ||||
|       "location": {}, | ||||
|       "quantity": {}, | ||||
| @@ -175,6 +175,8 @@ class InvenTreeStockItem extends InvenTreeModel { | ||||
|       "packaging": {}, | ||||
|       "link": {}, | ||||
|     }; | ||||
|  | ||||
|     return fields; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
| @@ -609,8 +611,8 @@ class InvenTreeStockLocation extends InvenTreeModel { | ||||
|   String get pathstring => getString("pathstring"); | ||||
|  | ||||
|   @override | ||||
|   Map<String, dynamic> formFields() { | ||||
|     Map<String, dynamic> fields = { | ||||
|   Map<String, Map<String, dynamic>> formFields() { | ||||
|     Map<String, Map<String, dynamic>> fields = { | ||||
|       "name": {}, | ||||
|       "description": {}, | ||||
|       "parent": {}, | ||||
|   | ||||
| @@ -235,9 +235,15 @@ | ||||
|   "credits": "Credits", | ||||
|   "@credits": {}, | ||||
|  | ||||
|   "customer": "Customer", | ||||
|   "@customer": {}, | ||||
|  | ||||
|   "customers": "Customers", | ||||
|   "@customers": {}, | ||||
|  | ||||
|   "customerReference": "Customer Reference", | ||||
|   "@customerReference": {}, | ||||
|  | ||||
|   "damaged": "Damaged", | ||||
|   "@damaged": {}, | ||||
|  | ||||
| @@ -440,15 +446,21 @@ | ||||
|   "homeShowPo":  "Show Purchase Orders", | ||||
|   "@homeShowPo": {}, | ||||
|  | ||||
|   "homeShowPoDescription": "Show purchase order button on home screen", | ||||
|   "@homeShowPoDescription": {}, | ||||
|  | ||||
|   "homeShowSo": "Show Sales Orders", | ||||
|   "@homeShowSo": {}, | ||||
|  | ||||
|   "homeShowSoDescription": "Show sales order button on home screen", | ||||
|   "@homeShowSoDescription": {}, | ||||
|  | ||||
|   "homeShowSubscribed": "Subscribed Parts", | ||||
|   "@homeShowSubscribed": {}, | ||||
|  | ||||
|   "homeShowSubscribedDescription": "Show subscribed parts on home screen", | ||||
|   "@homeShowSubscsribedDescription": {}, | ||||
|  | ||||
|   "homeShowPoDescription": "Show purchase order button on home screen", | ||||
|   "@homeShowPoDescription": {}, | ||||
|  | ||||
|   "homeShowSuppliers": "Show Suppliers", | ||||
|   "@homeShowSuppliers": {}, | ||||
|  | ||||
| @@ -576,6 +588,9 @@ | ||||
|   "level": "Level", | ||||
|   "@level": {}, | ||||
|  | ||||
|   "lineItemAdd": "Add Line Item", | ||||
|   "@lineItemAdd": {}, | ||||
|  | ||||
|   "lineItem": "Line Item", | ||||
|   "@lineItem": {}, | ||||
|  | ||||
| @@ -687,9 +702,15 @@ | ||||
|   "outstanding": "Outstanding", | ||||
|   "@outstanding": {}, | ||||
|  | ||||
|   "outstandingOrderDetail": "Show outstanding items", | ||||
|   "outstandingOrderDetail": "Show outstanding orders", | ||||
|   "@outstandingOrderDetail": {}, | ||||
|  | ||||
|   "overdue": "Overdue", | ||||
|   "@overdue": {}, | ||||
|  | ||||
|   "overdueDetail": "Show overdue orders", | ||||
|   "@overdueDetail": {}, | ||||
|  | ||||
|   "packaging": "Packaging", | ||||
|   "@packaging": {}, | ||||
|  | ||||
| @@ -997,9 +1018,21 @@ | ||||
|   "returned": "Returned", | ||||
|   "@returned": {}, | ||||
|  | ||||
|   "salesOrder": "Sales Order", | ||||
|   "@salesOrder": {}, | ||||
|  | ||||
|   "salesOrders": "Sales Orders", | ||||
|   "@salesOrders": {}, | ||||
|  | ||||
|   "salesOrderCreate": "New Sales Order", | ||||
|   "@saleOrderCreate": {}, | ||||
|  | ||||
|   "salesOrderEdit": "Edit Sales Order", | ||||
|   "@salesOrderEdit": {}, | ||||
|  | ||||
|   "salesOrderUpdated": "Sales order updated", | ||||
|   "@salesOrderUpdated": {}, | ||||
|  | ||||
|   "save": "Save", | ||||
|   "@save": { | ||||
|     "description": "Save" | ||||
| @@ -1125,6 +1158,9 @@ | ||||
|   "serverNotSelected": "Server not selected", | ||||
|   "@serverNotSelected": {}, | ||||
|  | ||||
|   "shipped": "Shipped", | ||||
|   "@shipped": {}, | ||||
|  | ||||
|   "sku": "SKU", | ||||
|   "@sku": {}, | ||||
|  | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import "package:path/path.dart"; | ||||
| // Settings key values | ||||
| const String INV_HOME_SHOW_SUBSCRIBED = "homeShowSubscribed"; | ||||
| const String INV_HOME_SHOW_PO = "homeShowPo"; | ||||
| const String INV_HOME_SHOW_SO = "homeShowSo"; | ||||
| const String INV_HOME_SHOW_MANUFACTURERS = "homeShowManufacturers"; | ||||
| const String INV_HOME_SHOW_CUSTOMERS = "homeShowCustomers"; | ||||
| const String INV_HOME_SHOW_SUPPLIERS = "homeShowSuppliers"; | ||||
|   | ||||
| @@ -21,6 +21,7 @@ class _HomeScreenSettingsState extends State<HomeScreenSettingsWidget> { | ||||
|   // Home screen settings | ||||
|   bool homeShowSubscribed = true; | ||||
|   bool homeShowPo = true; | ||||
|   bool homeShowSo = true; | ||||
|   bool homeShowSuppliers = true; | ||||
|   bool homeShowManufacturers = true; | ||||
|   bool homeShowCustomers = true; | ||||
| @@ -38,6 +39,7 @@ class _HomeScreenSettingsState extends State<HomeScreenSettingsWidget> { | ||||
|  | ||||
|     homeShowSubscribed = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_SUBSCRIBED, true) as bool; | ||||
|     homeShowPo = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_PO, true) as bool; | ||||
|     homeShowSo = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_SO, true) as bool; | ||||
|     homeShowManufacturers = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_MANUFACTURERS, true) as bool; | ||||
|     homeShowCustomers = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_CUSTOMERS, true) as bool; | ||||
|     homeShowSuppliers = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_SUPPLIERS, true) as bool; | ||||
| @@ -85,6 +87,20 @@ class _HomeScreenSettingsState extends State<HomeScreenSettingsWidget> { | ||||
|                       }, | ||||
|                     ), | ||||
|                   ), | ||||
|                   ListTile( | ||||
|                     title: Text(L10().homeShowSo), | ||||
|                     subtitle: Text(L10().homeShowSoDescription), | ||||
|                     leading: FaIcon(FontAwesomeIcons.truck), | ||||
|                     trailing: Switch( | ||||
|                       value: homeShowSo, | ||||
|                       onChanged: (bool value) { | ||||
|                         InvenTreeSettingsManager().setValue(INV_HOME_SHOW_SO, value); | ||||
|                         setState(() { | ||||
|                           homeShowSo = value; | ||||
|                         }); | ||||
|                       }, | ||||
|                     ), | ||||
|                   ), | ||||
|                   ListTile( | ||||
|                     title: Text(L10().homeShowSuppliers), | ||||
|                     subtitle: Text(L10().homeShowSuppliersDescription), | ||||
| @@ -116,6 +132,7 @@ class _HomeScreenSettingsState extends State<HomeScreenSettingsWidget> { | ||||
|                       }, | ||||
|                     ), | ||||
|                   ), | ||||
|                   */ | ||||
|                   ListTile( | ||||
|                     title: Text(L10().homeShowCustomers), | ||||
|                     subtitle: Text(L10().homeShowCustomersDescription), | ||||
| @@ -130,7 +147,6 @@ class _HomeScreenSettingsState extends State<HomeScreenSettingsWidget> { | ||||
|                       }, | ||||
|                     ), | ||||
|                   ), | ||||
|                    */ | ||||
|                 ] | ||||
|             ) | ||||
|         ) | ||||
|   | ||||
| @@ -9,12 +9,16 @@ import "package:inventree/helpers.dart"; | ||||
| 
 | ||||
| import "package:inventree/inventree/company.dart"; | ||||
| import "package:inventree/inventree/purchase_order.dart"; | ||||
| import "package:inventree/inventree/sales_order.dart"; | ||||
| 
 | ||||
| import "package:inventree/widget/attachment_widget.dart"; | ||||
| import "package:inventree/widget/purchase_order_list.dart"; | ||||
| import "package:inventree/widget/order/purchase_order_list.dart"; | ||||
| import "package:inventree/widget/order/sales_order_list.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| import "package:inventree/widget/snacks.dart"; | ||||
| import "package:inventree/widget/supplier_part_list.dart"; | ||||
| import "package:inventree/widget/company/supplier_part_list.dart"; | ||||
| import "package:inventree/widget/order/sales_order_detail.dart"; | ||||
| import "package:inventree/widget/order/purchase_order_detail.dart"; | ||||
| 
 | ||||
| 
 | ||||
| /* | ||||
| @@ -36,10 +40,11 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> { | ||||
| 
 | ||||
|   _CompanyDetailState(); | ||||
| 
 | ||||
|   List<InvenTreePurchaseOrder> outstandingOrders = []; | ||||
|    | ||||
|   int supplierPartCount = 0; | ||||
| 
 | ||||
|   int outstandingPurchaseOrders = 0; | ||||
|   int outstandingSalesOrders = 0; | ||||
| 
 | ||||
|   int attachmentCount = 0; | ||||
| 
 | ||||
|   @override | ||||
| @@ -68,11 +73,87 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> { | ||||
|   List<SpeedDialChild> actionButtons(BuildContext context) { | ||||
|     List<SpeedDialChild> actions = []; | ||||
| 
 | ||||
|     // TODO - Actions for this company | ||||
|     if (widget.company.isCustomer && InvenTreeSalesOrder().canCreate) { | ||||
|       actions.add(SpeedDialChild( | ||||
|         child: FaIcon(FontAwesomeIcons.truck), | ||||
|         label: L10().salesOrderCreate, | ||||
|         onTap: () async { | ||||
|           _createSalesOrder(context); | ||||
|         } | ||||
|       )); | ||||
|     } | ||||
| 
 | ||||
|     if (widget.company.isSupplier && InvenTreePurchaseOrder().canCreate) { | ||||
|       actions.add(SpeedDialChild( | ||||
|         child: FaIcon(FontAwesomeIcons.cartShopping), | ||||
|         label: L10().purchaseOrderCreate, | ||||
|         onTap: () async { | ||||
|           _createPurchaseOrder(context); | ||||
|         } | ||||
|       )); | ||||
|     } | ||||
| 
 | ||||
|     return actions; | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _createSalesOrder(BuildContext context) async { | ||||
|     var fields = InvenTreeSalesOrder().formFields(); | ||||
| 
 | ||||
|     // Cannot set contact until company is locked in | ||||
|     fields.remove("contact"); | ||||
| 
 | ||||
|     fields["customer"]?["value"] = widget.company.pk; | ||||
| 
 | ||||
|     InvenTreeSalesOrder().createForm( | ||||
|         context, | ||||
|         L10().salesOrderCreate, | ||||
|         fields: fields, | ||||
|         onSuccess: (result) async { | ||||
|           Map<String, dynamic> data = result as Map<String, dynamic>; | ||||
| 
 | ||||
|           if (data.containsKey("pk")) { | ||||
|             var order = InvenTreeSalesOrder.fromJson(data); | ||||
| 
 | ||||
|             Navigator.push( | ||||
|                 context, | ||||
|                 MaterialPageRoute( | ||||
|                     builder: (context) => SalesOrderDetailWidget(order) | ||||
|                 ) | ||||
|             ); | ||||
|           } | ||||
|         } | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _createPurchaseOrder(BuildContext context) async { | ||||
|     var fields = InvenTreePurchaseOrder().formFields(); | ||||
| 
 | ||||
|     // Cannot set contact until company is locked in | ||||
|     fields.remove("contact"); | ||||
| 
 | ||||
|     fields["supplier"]?["value"] = widget.company.pk; | ||||
| 
 | ||||
|     InvenTreePurchaseOrder().createForm( | ||||
|         context, | ||||
|         L10().purchaseOrderCreate, | ||||
|         fields: fields, | ||||
|         onSuccess: (result) async { | ||||
|           Map<String, dynamic> data = result as Map<String, dynamic>; | ||||
| 
 | ||||
|           if (data.containsKey("pk")) { | ||||
|             var order = InvenTreePurchaseOrder.fromJson(data); | ||||
| 
 | ||||
|             Navigator.push( | ||||
|                 context, | ||||
|                 MaterialPageRoute( | ||||
|                     builder: (context) => PurchaseOrderDetailWidget(order) | ||||
|                 ) | ||||
|             ); | ||||
|           } | ||||
|         } | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> request(BuildContext context) async { | ||||
|     final bool result = await widget.company.reload(); | ||||
| @@ -83,11 +164,18 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (widget.company.isSupplier) { | ||||
|       outstandingOrders = | ||||
|       await widget.company.getPurchaseOrders(outstanding: true); | ||||
|     } | ||||
|     outstandingPurchaseOrders = widget.company.isSupplier ? | ||||
|         await InvenTreePurchaseOrder().count(filters: { | ||||
|           "supplier": widget.company.pk.toString(), | ||||
|           "outstanding": "true" | ||||
|         }) : 0; | ||||
| 
 | ||||
|     outstandingSalesOrders = widget.company.isCustomer ? | ||||
|         await InvenTreeSalesOrder().count(filters: { | ||||
|           "customer": widget.company.pk.toString(), | ||||
|           "outstanding": "true" | ||||
|         }) : 0; | ||||
|    | ||||
|     InvenTreeSupplierPart().count( | ||||
|         filters: { | ||||
|           "supplier": widget.company.pk.toString() | ||||
| @@ -224,7 +312,7 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> { | ||||
|         ListTile( | ||||
|           title: Text(L10().purchaseOrders), | ||||
|           leading: FaIcon(FontAwesomeIcons.cartShopping, color: COLOR_ACTION), | ||||
|           trailing: Text("${outstandingOrders.length}"), | ||||
|           trailing: Text("${outstandingPurchaseOrders}"), | ||||
|           onTap: () { | ||||
|             Navigator.push( | ||||
|               context, | ||||
| @@ -257,7 +345,25 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> { | ||||
|     } | ||||
| 
 | ||||
|     if (widget.company.isCustomer) { | ||||
|       // TODO - Add list of sales orders | ||||
|       tiles.add( | ||||
|         ListTile( | ||||
|           title: Text(L10().salesOrders), | ||||
|           leading: FaIcon(FontAwesomeIcons.truck, color: COLOR_ACTION), | ||||
|           trailing: Text("${outstandingSalesOrders}"), | ||||
|           onTap: () { | ||||
|             Navigator.push( | ||||
|               context, | ||||
|               MaterialPageRoute( | ||||
|                 builder: (context) => SalesOrderListWidget( | ||||
|                   filters: { | ||||
|                     "customer": widget.company.pk.toString() | ||||
|                   } | ||||
|                 ) | ||||
|               ) | ||||
|             ); | ||||
|           } | ||||
|         ) | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (widget.company.notes.isNotEmpty) { | ||||
| @@ -8,7 +8,7 @@ import "package:inventree/inventree/model.dart"; | ||||
| 
 | ||||
| import "package:inventree/widget/paginator.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| import "package:inventree/widget/company_detail.dart"; | ||||
| import "package:inventree/widget/company/company_detail.dart"; | ||||
| 
 | ||||
| 
 | ||||
| /* | ||||
| @@ -10,8 +10,8 @@ import "package:inventree/l10.dart"; | ||||
| import "package:inventree/inventree/part.dart"; | ||||
| import "package:inventree/inventree/company.dart"; | ||||
| 
 | ||||
| import "package:inventree/widget/company_detail.dart"; | ||||
| import "package:inventree/widget/part_detail.dart"; | ||||
| import "package:inventree/widget/company/company_detail.dart"; | ||||
| import "package:inventree/widget/part/part_detail.dart"; | ||||
| import "package:inventree/widget/progress.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| import "package:inventree/widget/snacks.dart"; | ||||
| @@ -8,7 +8,7 @@ import "package:inventree/inventree/model.dart"; | ||||
| 
 | ||||
| import "package:inventree/widget/paginator.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| import "package:inventree/widget/supplier_part_detail.dart"; | ||||
| import "package:inventree/widget/company/supplier_part_detail.dart"; | ||||
| 
 | ||||
| 
 | ||||
| /* | ||||
| @@ -3,15 +3,17 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart"; | ||||
|  | ||||
| import "package:inventree/api.dart"; | ||||
| import "package:inventree/app_colors.dart"; | ||||
| import "package:inventree/inventree/company.dart"; | ||||
| import "package:inventree/inventree/part.dart"; | ||||
| import "package:inventree/inventree/purchase_order.dart"; | ||||
| import "package:inventree/inventree/sales_order.dart"; | ||||
| import "package:inventree/inventree/stock.dart"; | ||||
| import "package:inventree/l10.dart"; | ||||
| import "package:inventree/settings/settings.dart"; | ||||
| import "package:inventree/widget/category_display.dart"; | ||||
| import "package:inventree/widget/order/sales_order_list.dart"; | ||||
| import "package:inventree/widget/part/category_display.dart"; | ||||
| import "package:inventree/widget/notifications.dart"; | ||||
| import "package:inventree/widget/purchase_order_list.dart"; | ||||
| import "package:inventree/widget/location_display.dart"; | ||||
| import "package:inventree/widget/order/purchase_order_list.dart"; | ||||
| import "package:inventree/widget/stock/location_display.dart"; | ||||
|  | ||||
|  | ||||
| /* | ||||
| @@ -28,6 +30,10 @@ class InvenTreeDrawer extends StatelessWidget { | ||||
|     Navigator.of(context).pop(); | ||||
|   } | ||||
|  | ||||
|   bool _checkConnection() { | ||||
|     return InvenTreeAPI().checkConnection(); | ||||
|   } | ||||
|  | ||||
|   /* | ||||
|    * Return to the 'home' screen. | ||||
|    * This will empty the navigation stack. | ||||
| @@ -43,37 +49,63 @@ class InvenTreeDrawer extends StatelessWidget { | ||||
|   // Load "parts" page | ||||
|   void _parts() { | ||||
|     _closeDrawer(); | ||||
|     Navigator.push( | ||||
|       context, | ||||
|       MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null)) | ||||
|     ); | ||||
|  | ||||
|     if (_checkConnection()) { | ||||
|       Navigator.push( | ||||
|           context, | ||||
|           MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null)) | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Load "stock" page | ||||
|   void _stock() { | ||||
|     _closeDrawer(); | ||||
|     Navigator.push( | ||||
|       context, | ||||
|       MaterialPageRoute(builder: (context) => LocationDisplayWidget(null)) | ||||
|     ); | ||||
|  | ||||
|     if (_checkConnection()) { | ||||
|       Navigator.push( | ||||
|           context, | ||||
|           MaterialPageRoute(builder: (context) => LocationDisplayWidget(null)) | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Load "sales orders" page | ||||
|   void _salesOrders() { | ||||
|     _closeDrawer(); | ||||
|  | ||||
|     if (_checkConnection()) { | ||||
|       Navigator.push( | ||||
|           context, | ||||
|           MaterialPageRoute( | ||||
|               builder: (context) => SalesOrderListWidget(filters: {}) | ||||
|           ) | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   // Load "purchase orders" page | ||||
|   void _purchaseOrders() { | ||||
|     _closeDrawer(); | ||||
|  | ||||
|     Navigator.push( | ||||
|         context, | ||||
|         MaterialPageRoute( | ||||
|             builder: (context) => PurchaseOrderListWidget(filters: {}) | ||||
|         ) | ||||
|     ); | ||||
|     if (_checkConnection()) { | ||||
|       Navigator.push( | ||||
|           context, | ||||
|           MaterialPageRoute( | ||||
|               builder: (context) => PurchaseOrderListWidget(filters: {}) | ||||
|           ) | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Load notifications screen | ||||
|   void _notifications() { | ||||
|     _closeDrawer(); | ||||
|     Navigator.push(context, MaterialPageRoute(builder: (context) => NotificationWidget())); | ||||
|  | ||||
|     if (_checkConnection()) { | ||||
|       Navigator.push(context, | ||||
|           MaterialPageRoute(builder: (context) => NotificationWidget())); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Load settings widget | ||||
| @@ -98,7 +130,7 @@ class InvenTreeDrawer extends StatelessWidget { | ||||
|  | ||||
|     tiles.add(Divider()); | ||||
|  | ||||
|     if (InvenTreeCompany().canView) { | ||||
|     if (InvenTreePart().canView) { | ||||
|       tiles.add( | ||||
|         ListTile( | ||||
|           title: Text(L10().parts), | ||||
| @@ -128,6 +160,16 @@ class InvenTreeDrawer extends StatelessWidget { | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     if (InvenTreeSalesOrder().canView) { | ||||
|       tiles.add( | ||||
|         ListTile( | ||||
|           title: Text(L10().salesOrders), | ||||
|           leading: FaIcon(FontAwesomeIcons.truck, color: COLOR_ACTION), | ||||
|           onTap: _salesOrders, | ||||
|         ) | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     if (tiles.length > 2) { | ||||
|       tiles.add(Divider()); | ||||
|     } | ||||
|   | ||||
| @@ -6,20 +6,25 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart"; | ||||
|  | ||||
| import "package:inventree/api.dart"; | ||||
| import "package:inventree/app_colors.dart"; | ||||
| import "package:inventree/inventree/part.dart"; | ||||
| import "package:inventree/inventree/purchase_order.dart"; | ||||
| import "package:inventree/inventree/sales_order.dart"; | ||||
| import "package:inventree/inventree/stock.dart"; | ||||
| import "package:inventree/preferences.dart"; | ||||
| import "package:inventree/l10.dart"; | ||||
| import "package:inventree/settings/select_server.dart"; | ||||
| import "package:inventree/user_profile.dart"; | ||||
|  | ||||
| import "package:inventree/widget/category_display.dart"; | ||||
| import "package:inventree/widget/part/category_display.dart"; | ||||
| import "package:inventree/widget/drawer.dart"; | ||||
| import "package:inventree/widget/location_display.dart"; | ||||
| import "package:inventree/widget/part_list.dart"; | ||||
| import "package:inventree/widget/purchase_order_list.dart"; | ||||
| import "package:inventree/widget/stock/location_display.dart"; | ||||
| import "package:inventree/widget/part/part_list.dart"; | ||||
| import "package:inventree/widget/order/purchase_order_list.dart"; | ||||
| import "package:inventree/widget/order/sales_order_list.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| import "package:inventree/widget/snacks.dart"; | ||||
| import "package:inventree/widget/spinner.dart"; | ||||
| import "package:inventree/widget/company_list.dart"; | ||||
| import "package:inventree/widget/company/company_list.dart"; | ||||
|  | ||||
|  | ||||
| class InvenTreeHomePage extends StatefulWidget { | ||||
| @@ -53,6 +58,7 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr | ||||
|   final homeKey = GlobalKey<ScaffoldState>(); | ||||
|  | ||||
|   bool homeShowPo = false; | ||||
|   bool homeShowSo = false; | ||||
|   bool homeShowSubscribed = false; | ||||
|   bool homeShowManufacturers = false; | ||||
|   bool homeShowCustomers = false; | ||||
| @@ -97,6 +103,17 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   void _showSalesOrders(BuildContext context) { | ||||
|     if (!InvenTreeAPI().checkConnection()) return; | ||||
|  | ||||
|     Navigator.push( | ||||
|         context, | ||||
|         MaterialPageRoute( | ||||
|             builder: (context) => SalesOrderListWidget(filters: {}) | ||||
|         ) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   void _showSuppliers(BuildContext context) { | ||||
|     if (!InvenTreeAPI().checkConnection()) return; | ||||
|  | ||||
| @@ -110,12 +127,12 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr | ||||
|     Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().manufacturers, {"is_manufacturer": "true"}))); | ||||
|   } | ||||
|  | ||||
|   */ | ||||
|   void _showCustomers(BuildContext context) { | ||||
|     if (!InvenTreeAPI().checkConnection()) return; | ||||
|  | ||||
|     Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().customers, {"is_customer": "true"}))); | ||||
|   } | ||||
|    */ | ||||
|  | ||||
|   void _selectProfile() { | ||||
|     Navigator.push( | ||||
| @@ -130,6 +147,7 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr | ||||
|  | ||||
|     homeShowSubscribed = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_SUBSCRIBED, true) as bool; | ||||
|     homeShowPo = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_PO, true) as bool; | ||||
|     homeShowSo = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_SO, true) as bool; | ||||
|     homeShowManufacturers = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_MANUFACTURERS, true) as bool; | ||||
|     homeShowCustomers = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_CUSTOMERS, true) as bool; | ||||
|     homeShowSuppliers = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_SUPPLIERS, true) as bool; | ||||
| @@ -207,17 +225,19 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr | ||||
|     ]; | ||||
|  | ||||
|     // Parts | ||||
|     tiles.add(_listTile( | ||||
|       context, | ||||
|       L10().parts, | ||||
|       FontAwesomeIcons.shapes, | ||||
|       callback: () { | ||||
|         _showParts(context); | ||||
|       }, | ||||
|     )); | ||||
|     if (InvenTreePart().canView) { | ||||
|       tiles.add(_listTile( | ||||
|         context, | ||||
|         L10().parts, | ||||
|         FontAwesomeIcons.shapes, | ||||
|         callback: () { | ||||
|           _showParts(context); | ||||
|         }, | ||||
|       )); | ||||
|     } | ||||
|  | ||||
|     // Starred parts | ||||
|     if (homeShowSubscribed) { | ||||
|     if (homeShowSubscribed && InvenTreePart().canView) { | ||||
|       tiles.add(_listTile( | ||||
|         context, | ||||
|         L10().partsStarred, | ||||
| @@ -229,17 +249,19 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr | ||||
|     } | ||||
|  | ||||
|     // Stock button | ||||
|     tiles.add(_listTile( | ||||
|         context, | ||||
|         L10().stock, | ||||
|         FontAwesomeIcons.boxesStacked, | ||||
|         callback: () { | ||||
|           _showStock(context); | ||||
|         } | ||||
|     )); | ||||
|     if (InvenTreeStockItem().canView) { | ||||
|       tiles.add(_listTile( | ||||
|           context, | ||||
|           L10().stock, | ||||
|           FontAwesomeIcons.boxesStacked, | ||||
|           callback: () { | ||||
|             _showStock(context); | ||||
|           } | ||||
|       )); | ||||
|     } | ||||
|  | ||||
|     // Purchase orders | ||||
|     if (homeShowPo) { | ||||
|     if (homeShowPo && InvenTreePurchaseOrder().canView) { | ||||
|       tiles.add(_listTile( | ||||
|           context, | ||||
|           L10().purchaseOrders, | ||||
| @@ -250,8 +272,19 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr | ||||
|       )); | ||||
|     } | ||||
|  | ||||
|     if (homeShowSo && InvenTreeSalesOrder().canView) { | ||||
|       tiles.add(_listTile( | ||||
|         context, | ||||
|         L10().salesOrders, | ||||
|         FontAwesomeIcons.truck, | ||||
|         callback: () { | ||||
|           _showSalesOrders(context); | ||||
|         } | ||||
|       )); | ||||
|     } | ||||
|  | ||||
|     // Suppliers | ||||
|     if (homeShowSuppliers) { | ||||
|     if (homeShowSuppliers && InvenTreePurchaseOrder().canView) { | ||||
|       tiles.add(_listTile( | ||||
|           context, | ||||
|           L10().suppliers, | ||||
| @@ -277,7 +310,7 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr | ||||
|           } | ||||
|       )); | ||||
|     } | ||||
|  | ||||
|     */ | ||||
|     // Customers | ||||
|     if (homeShowCustomers) { | ||||
|       tiles.add(_listTile( | ||||
| @@ -289,7 +322,6 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr | ||||
|           } | ||||
|       )); | ||||
|     } | ||||
|      */ | ||||
|  | ||||
|     return tiles; | ||||
|   } | ||||
|   | ||||
| @@ -7,17 +7,17 @@ import "package:inventree/app_colors.dart"; | ||||
| import "package:inventree/helpers.dart"; | ||||
| import "package:inventree/l10.dart"; | ||||
| import "package:inventree/widget/progress.dart"; | ||||
| import "package:inventree/widget/part_detail.dart"; | ||||
| import "package:inventree/widget/part/part_detail.dart"; | ||||
| 
 | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| import "package:inventree/inventree/company.dart"; | ||||
| import "package:inventree/inventree/part.dart"; | ||||
| import "package:inventree/inventree/purchase_order.dart"; | ||||
| import "package:inventree/widget/snacks.dart"; | ||||
| import "package:inventree/widget/supplier_part_detail.dart"; | ||||
| import "package:inventree/widget/company/supplier_part_detail.dart"; | ||||
| 
 | ||||
| /* | ||||
|  * Widget for displaying detail view of a purchase order line item | ||||
|  * Widget for displaying detail view of a single PurchaseOrderLineItem | ||||
| */ | ||||
| class POLineDetailWidget extends StatefulWidget { | ||||
| 
 | ||||
| @@ -171,7 +171,6 @@ class _POLineDetailWidgetState extends RefreshableState<POLineDetailWidget> { | ||||
|         trailing: api.getThumbnail(widget.item.partImage), | ||||
|         onTap: () async { | ||||
|           showLoadingOverlay(context); | ||||
|           print("part id: ${widget.item.partId}"); | ||||
|           var part = await InvenTreePart().get(widget.item.partId); | ||||
|           hideLoadingOverlay(); | ||||
| 
 | ||||
| @@ -200,16 +199,32 @@ class _POLineDetailWidgetState extends RefreshableState<POLineDetailWidget> { | ||||
|       ) | ||||
|     ); | ||||
| 
 | ||||
|     // Recevied | ||||
|     // Received quantity | ||||
|     tiles.add( | ||||
|       ListTile( | ||||
|         title: Text(L10().received), | ||||
|         subtitle: Text(widget.item.received.toString()), | ||||
|         trailing: Text(widget.item.progressString, style: TextStyle(color: widget.item.isComplete ? COLOR_SUCCESS: COLOR_WARNING)), | ||||
|         subtitle: ProgressBar(widget.item.progressRatio), | ||||
|         trailing: Text( | ||||
|             widget.item.progressString, | ||||
|             style: TextStyle( | ||||
|                 color: widget.item.isComplete ? COLOR_SUCCESS: COLOR_WARNING | ||||
|             ) | ||||
|         ), | ||||
|         leading: FaIcon(FontAwesomeIcons.boxOpen), | ||||
|       ) | ||||
|     ); | ||||
| 
 | ||||
|     // Reference | ||||
|     if (widget.item.reference.isNotEmpty) { | ||||
|       tiles.add( | ||||
|           ListTile( | ||||
|             title: Text(L10().reference), | ||||
|             subtitle: Text(widget.item.reference), | ||||
|             leading: FaIcon(FontAwesomeIcons.hashtag), | ||||
|           ) | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     // Pricing information | ||||
|     tiles.add( | ||||
|       ListTile( | ||||
| @@ -9,7 +9,7 @@ import "package:inventree/inventree/model.dart"; | ||||
| import "package:inventree/inventree/purchase_order.dart"; | ||||
| 
 | ||||
| import "package:inventree/widget/paginator.dart"; | ||||
| import "package:inventree/widget/po_line_detail.dart"; | ||||
| import "package:inventree/widget/order/po_line_detail.dart"; | ||||
| import "package:inventree/widget/progress.dart"; | ||||
| 
 | ||||
| /* | ||||
| @@ -2,9 +2,8 @@ import "package:flutter/material.dart"; | ||||
| import "package:flutter_speed_dial/flutter_speed_dial.dart"; | ||||
| import "package:font_awesome_flutter/font_awesome_flutter.dart"; | ||||
| import "package:inventree/widget/dialogs.dart"; | ||||
| import "package:inventree/widget/po_line_list.dart"; | ||||
| import "package:inventree/widget/order/po_line_list.dart"; | ||||
| 
 | ||||
| import "package:inventree/api.dart"; | ||||
| import "package:inventree/app_colors.dart"; | ||||
| import "package:inventree/barcode/barcode.dart"; | ||||
| import "package:inventree/helpers.dart"; | ||||
| @@ -13,13 +12,17 @@ import "package:inventree/l10.dart"; | ||||
| import "package:inventree/inventree/company.dart"; | ||||
| import "package:inventree/inventree/purchase_order.dart"; | ||||
| import "package:inventree/widget/attachment_widget.dart"; | ||||
| import "package:inventree/widget/company_detail.dart"; | ||||
| import "package:inventree/widget/company/company_detail.dart"; | ||||
| import "package:inventree/widget/notes_widget.dart"; | ||||
| import "package:inventree/widget/progress.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| import "package:inventree/widget/snacks.dart"; | ||||
| import "package:inventree/widget/stock_list.dart"; | ||||
| import "package:inventree/widget/stock/stock_list.dart"; | ||||
| 
 | ||||
| 
 | ||||
| /* | ||||
|  * Widget for viewing a single PurchaseOrder instance | ||||
|  */ | ||||
| class PurchaseOrderDetailWidget extends StatefulWidget { | ||||
| 
 | ||||
|   const PurchaseOrderDetailWidget(this.order, {Key? key}): super(key: key); | ||||
| @@ -27,16 +30,14 @@ class PurchaseOrderDetailWidget extends StatefulWidget { | ||||
|   final InvenTreePurchaseOrder order; | ||||
| 
 | ||||
|   @override | ||||
|   _PurchaseOrderDetailState createState() => _PurchaseOrderDetailState(order); | ||||
|   _PurchaseOrderDetailState createState() => _PurchaseOrderDetailState(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidget> { | ||||
| 
 | ||||
|   _PurchaseOrderDetailState(this.order); | ||||
| 
 | ||||
|   final InvenTreePurchaseOrder order; | ||||
| 
 | ||||
|   _PurchaseOrderDetailState(); | ||||
|    | ||||
|   List<InvenTreePOLineItem> lines = []; | ||||
| 
 | ||||
|   int completedLines = 0; | ||||
| @@ -52,7 +53,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
|   List<Widget> appBarActions(BuildContext context) { | ||||
|     List<Widget> actions = []; | ||||
| 
 | ||||
|     if (order.canEdit) { | ||||
|     if (widget.order.canEdit) { | ||||
|       actions.add( | ||||
|         IconButton( | ||||
|           icon: Icon(Icons.edit_square), | ||||
| @@ -71,8 +72,8 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
|   List<SpeedDialChild> actionButtons(BuildContext context) { | ||||
|     List<SpeedDialChild> actions = []; | ||||
| 
 | ||||
|     if (order.canCreate) { | ||||
|       if (order.isPending) { | ||||
|     if (widget.order.canCreate) { | ||||
|       if (widget.order.isPending) { | ||||
|         actions.add( | ||||
|           SpeedDialChild( | ||||
|             child: FaIcon(FontAwesomeIcons.paperPlane, color: Colors.blue), | ||||
| @@ -84,7 +85,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
|         ); | ||||
|       } | ||||
| 
 | ||||
|       if (order.isOpen) { | ||||
|       if (widget.order.isOpen) { | ||||
|         actions.add( | ||||
|           SpeedDialChild( | ||||
|             child: FaIcon(FontAwesomeIcons.circleXmark, color: Colors.red), | ||||
| @@ -109,7 +110,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
|       color: Colors.blue, | ||||
|       acceptText: L10().issue, | ||||
|       onAccept: () async { | ||||
|         await order.issueOrder().then((dynamic) { | ||||
|         await widget.order.issueOrder().then((dynamic) { | ||||
|           refresh(context); | ||||
|         }); | ||||
|       } | ||||
| @@ -125,7 +126,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
|       color: Colors.red, | ||||
|       acceptText: L10().cancel, | ||||
|       onAccept: () async { | ||||
|         await order.cancelOrder().then((dynamic) { | ||||
|         await widget.order.cancelOrder().then((dynamic) { | ||||
|           print("callback"); | ||||
|           refresh(context); | ||||
|         }); | ||||
| @@ -145,7 +146,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
|           onTap:() async { | ||||
|             scanBarcode( | ||||
|               context, | ||||
|               handler: POReceiveBarcodeHandler(purchaseOrder: order), | ||||
|               handler: POReceiveBarcodeHandler(purchaseOrder: widget.order), | ||||
|             ); | ||||
|           }, | ||||
|         ) | ||||
| @@ -158,11 +159,11 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> request(BuildContext context) async { | ||||
|     await order.reload(); | ||||
|     await widget.order.reload(); | ||||
| 
 | ||||
|     await api.PurchaseOrderStatus.load(); | ||||
| 
 | ||||
|     lines = await order.getLineItems(); | ||||
|     lines = await widget.order.getLineItems(); | ||||
| 
 | ||||
|     supportProjectCodes = api.supportsProjectCodes && await api.getGlobalBooleanSetting("PROJECT_CODES_ENABLED"); | ||||
| 
 | ||||
| @@ -174,16 +175,20 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     attachmentCount = await InvenTreePurchaseOrderAttachment().count( | ||||
|       filters: { | ||||
|         "order": order.pk.toString() | ||||
|     InvenTreePurchaseOrderAttachment().count(filters: { | ||||
|       "order": widget.order.pk.toString() | ||||
|     }).then((int value) { | ||||
|       if (mounted) { | ||||
|         setState(() { | ||||
|           attachmentCount = value; | ||||
|         }); | ||||
|       } | ||||
|     ); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // Edit the currently displayed PurchaseOrder | ||||
|   Future <void> editOrder(BuildContext context) async { | ||||
|     var fields = order.formFields(); | ||||
|     var fields = widget.order.formFields(); | ||||
| 
 | ||||
|     // Cannot edit supplier field from here | ||||
|     fields.remove("supplier"); | ||||
| @@ -198,7 +203,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
|       fields.remove("project_code"); | ||||
|     } | ||||
| 
 | ||||
|     order.editForm( | ||||
|     widget.order.editForm( | ||||
|       context, | ||||
|       L10().purchaseOrderEdit, | ||||
|       fields: fields, | ||||
| @@ -211,17 +216,17 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
| 
 | ||||
|   Widget headerTile(BuildContext context) { | ||||
| 
 | ||||
|     InvenTreeCompany? supplier = order.supplier; | ||||
|     InvenTreeCompany? supplier = widget.order.supplier; | ||||
| 
 | ||||
|     return Card( | ||||
|         child: ListTile( | ||||
|           title: Text(order.reference), | ||||
|           subtitle: Text(order.description), | ||||
|           leading: supplier == null ? null : InvenTreeAPI().getThumbnail(supplier.thumbnail), | ||||
|           title: Text(widget.order.reference), | ||||
|           subtitle: Text(widget.order.description), | ||||
|           leading: supplier == null ? null : api.getThumbnail(supplier.thumbnail), | ||||
|           trailing: Text( | ||||
|             api.PurchaseOrderStatus.label(order.status), | ||||
|             api.PurchaseOrderStatus.label(widget.order.status), | ||||
|             style: TextStyle( | ||||
|               color: api.PurchaseOrderStatus.color(order.status) | ||||
|               color: api.PurchaseOrderStatus.color(widget.order.status) | ||||
|             ), | ||||
|           ) | ||||
|         ) | ||||
| @@ -233,14 +238,14 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
| 
 | ||||
|     List<Widget> tiles = []; | ||||
| 
 | ||||
|     InvenTreeCompany? supplier = order.supplier; | ||||
|     InvenTreeCompany? supplier = widget.order.supplier; | ||||
| 
 | ||||
|     tiles.add(headerTile(context)); | ||||
| 
 | ||||
|     if (supportProjectCodes && order.hasProjectCode) { | ||||
|     if (supportProjectCodes && widget.order.hasProjectCode) { | ||||
|       tiles.add(ListTile( | ||||
|         title: Text(L10().projectCode), | ||||
|         subtitle: Text("${order.projectCode} - ${order.projectCodeDescription}"), | ||||
|         subtitle: Text("${widget.order.projectCode} - ${widget.order.projectCodeDescription}"), | ||||
|         leading: FaIcon(FontAwesomeIcons.list), | ||||
|       )); | ||||
|     } | ||||
| @@ -261,20 +266,24 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
|       )); | ||||
|     } | ||||
| 
 | ||||
|     if (order.supplierReference.isNotEmpty) { | ||||
|     if (widget.order.supplierReference.isNotEmpty) { | ||||
|       tiles.add(ListTile( | ||||
|         title: Text(L10().supplierReference), | ||||
|         subtitle: Text(order.supplierReference), | ||||
|         subtitle: Text(widget.order.supplierReference), | ||||
|         leading: FaIcon(FontAwesomeIcons.hashtag), | ||||
|       )); | ||||
|     } | ||||
| 
 | ||||
|     Color lineColor = completedLines < order.lineItemCount ? COLOR_WARNING : COLOR_SUCCESS; | ||||
|     Color lineColor = completedLines < widget.order.lineItemCount ? COLOR_WARNING : COLOR_SUCCESS; | ||||
| 
 | ||||
|     tiles.add(ListTile( | ||||
|       title: Text(L10().lineItems), | ||||
|       subtitle: ProgressBar( | ||||
|         completedLines.toDouble(), | ||||
|         maximum: widget.order.lineItemCount.toDouble(), | ||||
|       ), | ||||
|       leading: FaIcon(FontAwesomeIcons.clipboardCheck), | ||||
|       trailing: Text("${completedLines} /  ${order.lineItemCount}", style: TextStyle(color: lineColor)), | ||||
|       trailing: Text("${completedLines} /  ${widget.order.lineItemCount}", style: TextStyle(color: lineColor)), | ||||
|     )); | ||||
| 
 | ||||
|     tiles.add(ListTile( | ||||
| @@ -285,18 +294,18 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
|       ), | ||||
|     )); | ||||
| 
 | ||||
|     if (order.issueDate.isNotEmpty) { | ||||
|     if (widget.order.issueDate.isNotEmpty) { | ||||
|       tiles.add(ListTile( | ||||
|         title: Text(L10().issueDate), | ||||
|         subtitle: Text(order.issueDate), | ||||
|         subtitle: Text(widget.order.issueDate), | ||||
|         leading: FaIcon(FontAwesomeIcons.calendarDays), | ||||
|       )); | ||||
|     } | ||||
| 
 | ||||
|     if (order.targetDate.isNotEmpty) { | ||||
|     if (widget.order.targetDate.isNotEmpty) { | ||||
|       tiles.add(ListTile( | ||||
|         title: Text(L10().targetDate), | ||||
|         subtitle: Text(order.targetDate), | ||||
|         subtitle: Text(widget.order.targetDate), | ||||
|         leading: FaIcon(FontAwesomeIcons.calendarDays), | ||||
|       )); | ||||
|     } | ||||
| @@ -310,7 +319,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
|             Navigator.push( | ||||
|               context, | ||||
|               MaterialPageRoute( | ||||
|                 builder: (context) => NotesWidget(order) | ||||
|                 builder: (context) => NotesWidget(widget.order) | ||||
|               ) | ||||
|             ); | ||||
|           }, | ||||
| @@ -329,8 +338,8 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
|               MaterialPageRoute( | ||||
|                 builder: (context) => AttachmentWidget( | ||||
|                     InvenTreePurchaseOrderAttachment(), | ||||
|                     order.pk, | ||||
|                     order.canEdit | ||||
|                     widget.order.pk, | ||||
|                     widget.order.canEdit | ||||
|                 ) | ||||
|               ) | ||||
|             ); | ||||
| @@ -355,9 +364,9 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg | ||||
|   List<Widget> getTabs(BuildContext context) { | ||||
|     return [ | ||||
|       ListView(children: orderTiles(context)), | ||||
|       PaginatedPOLineList({"order": order.pk.toString()}), | ||||
|       PaginatedPOLineList({"order": widget.order.pk.toString()}), | ||||
|       // ListView(children: lineTiles(context)), | ||||
|       PaginatedStockItemList({"purchase_order": order.pk.toString()}), | ||||
|       PaginatedStockItemList({"purchase_order": widget.order.pk.toString()}), | ||||
|     ]; | ||||
|   } | ||||
| 
 | ||||
| @@ -5,7 +5,7 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart"; | ||||
| import "package:inventree/inventree/company.dart"; | ||||
| import "package:inventree/inventree/model.dart"; | ||||
| import "package:inventree/widget/paginator.dart"; | ||||
| import "package:inventree/widget/purchase_order_detail.dart"; | ||||
| import "package:inventree/widget/order/purchase_order_detail.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| import "package:inventree/l10.dart"; | ||||
| import "package:inventree/api.dart"; | ||||
| @@ -22,15 +22,13 @@ class PurchaseOrderListWidget extends StatefulWidget { | ||||
|   final Map<String, String> filters; | ||||
| 
 | ||||
|   @override | ||||
|   _PurchaseOrderListWidgetState createState() => _PurchaseOrderListWidgetState(filters); | ||||
|   _PurchaseOrderListWidgetState createState() => _PurchaseOrderListWidgetState(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| class _PurchaseOrderListWidgetState extends RefreshableState<PurchaseOrderListWidget> { | ||||
| 
 | ||||
|   _PurchaseOrderListWidgetState(this.filters); | ||||
| 
 | ||||
|   final Map<String, String> filters; | ||||
|   _PurchaseOrderListWidgetState(); | ||||
| 
 | ||||
|   @override | ||||
|   String getAppBarTitle() => L10().purchaseOrders; | ||||
| @@ -45,7 +43,7 @@ class _PurchaseOrderListWidgetState extends RefreshableState<PurchaseOrderListWi | ||||
|           child: FaIcon(FontAwesomeIcons.circlePlus), | ||||
|           label: L10().purchaseOrderCreate, | ||||
|           onTap: () { | ||||
|             createPurchaseOrder(context); | ||||
|             _createPurchaseOrder(context); | ||||
|           } | ||||
|         ) | ||||
|       ); | ||||
| @@ -54,9 +52,11 @@ class _PurchaseOrderListWidgetState extends RefreshableState<PurchaseOrderListWi | ||||
|     return actions; | ||||
|   } | ||||
| 
 | ||||
|   Future<void> createPurchaseOrder(BuildContext context) async { | ||||
|   // Launch form to create a new PurchaseOrder | ||||
|   Future<void> _createPurchaseOrder(BuildContext context) async { | ||||
|     var fields = InvenTreePurchaseOrder().formFields(); | ||||
| 
 | ||||
|     // Cannot set contact until company is locked in | ||||
|     fields.remove("contact"); | ||||
| 
 | ||||
|     InvenTreePurchaseOrder().createForm( | ||||
| @@ -104,7 +104,7 @@ class _PurchaseOrderListWidgetState extends RefreshableState<PurchaseOrderListWi | ||||
| 
 | ||||
|   @override | ||||
|   Widget getBody(BuildContext context) { | ||||
|     return PaginatedPurchaseOrderList(filters); | ||||
|     return PaginatedPurchaseOrderList(widget.filters); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @@ -143,6 +143,11 @@ class _PaginatedPurchaseOrderListState extends PaginatedSearchState<PaginatedPur | ||||
|       "label": L10().outstanding, | ||||
|       "help_text": L10().outstandingOrderDetail, | ||||
|       "tristate": true, | ||||
|     }, | ||||
|     "overdue": { | ||||
|       "label": L10().overdue, | ||||
|       "help_text": L10().overdueDetail, | ||||
|       "tristate": true, | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
| @@ -153,7 +158,6 @@ class _PaginatedPurchaseOrderListState extends PaginatedSearchState<PaginatedPur | ||||
|     final page = await InvenTreePurchaseOrder().listPaginated(limit, offset, filters: params); | ||||
| 
 | ||||
|     return page; | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
							
								
								
									
										303
									
								
								lib/widget/order/sales_order_detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										303
									
								
								lib/widget/order/sales_order_detail.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,303 @@ | ||||
|  | ||||
| import "package:flutter/material.dart"; | ||||
| import "package:flutter_speed_dial/flutter_speed_dial.dart"; | ||||
| import "package:font_awesome_flutter/font_awesome_flutter.dart"; | ||||
| import "package:inventree/inventree/company.dart"; | ||||
| import "package:inventree/inventree/sales_order.dart"; | ||||
| import "package:inventree/widget/order/so_line_list.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
|  | ||||
| import "package:inventree/l10.dart"; | ||||
|  | ||||
| import "package:inventree/app_colors.dart"; | ||||
| import "package:inventree/widget/attachment_widget.dart"; | ||||
| import "package:inventree/widget/notes_widget.dart"; | ||||
| import "package:inventree/widget/snacks.dart"; | ||||
| import "package:inventree/widget/company/company_detail.dart"; | ||||
| import "package:inventree/widget/progress.dart"; | ||||
|  | ||||
| /* | ||||
|  * Widget for viewing a single SalesOrder instance | ||||
|  */ | ||||
| class SalesOrderDetailWidget extends StatefulWidget { | ||||
|  | ||||
|   const SalesOrderDetailWidget(this.order, {Key? key}) : super(key: key); | ||||
|  | ||||
|   final InvenTreeSalesOrder order; | ||||
|  | ||||
|   @override | ||||
|   _SalesOrderDetailState createState() => _SalesOrderDetailState(); | ||||
| } | ||||
|  | ||||
|  | ||||
| class _SalesOrderDetailState extends RefreshableState<SalesOrderDetailWidget> { | ||||
|  | ||||
|   _SalesOrderDetailState(); | ||||
|  | ||||
|   List<InvenTreeSOLineItem> lines = []; | ||||
|  | ||||
|   bool supportsProjectCodes = false; | ||||
|   int completedLines = 0; | ||||
|   int attachmentCount = 0; | ||||
|  | ||||
|   @override | ||||
|   String getAppBarTitle() => L10().salesOrder; | ||||
|  | ||||
|   @override | ||||
|   List<Widget> appBarActions(BuildContext context) { | ||||
|     List<Widget> actions = []; | ||||
|  | ||||
|     if (widget.order.canEdit) { | ||||
|       actions.add( | ||||
|         IconButton( | ||||
|           icon: Icon(Icons.edit_square), | ||||
|           onPressed: () { | ||||
|             editOrder(context); | ||||
|           }, | ||||
|         ) | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return actions; | ||||
|   } | ||||
|  | ||||
|   // Add a new line item to this sales order | ||||
|   Future<void> _addLineItem(BuildContext context) async { | ||||
|     var fields = InvenTreeSOLineItem().formFields(); | ||||
|  | ||||
|     fields["order"]?["value"] = widget.order.pk; | ||||
|     fields["order"]?["hidden"] = true; | ||||
|  | ||||
|     InvenTreeSOLineItem().createForm( | ||||
|         context, | ||||
|         L10().lineItemAdd, | ||||
|         fields: fields, | ||||
|         onSuccess: (result) async { | ||||
|           refresh(context); | ||||
|         } | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   List<SpeedDialChild> actionButtons(BuildContext context) { | ||||
|     List<SpeedDialChild> actions = []; | ||||
|  | ||||
|     // Add line item | ||||
|     if (widget.order.isOpen && InvenTreeSOLineItem().canCreate) { | ||||
|       actions.add( | ||||
|         SpeedDialChild( | ||||
|           child: FaIcon(FontAwesomeIcons.circlePlus), | ||||
|           label: L10().lineItemAdd, | ||||
|           onTap: () async { | ||||
|             _addLineItem(context); | ||||
|           } | ||||
|         ) | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return actions; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   List<SpeedDialChild> barcodeButtons(BuildContext context) { | ||||
|     List<SpeedDialChild> actions = []; | ||||
|  | ||||
|     // TODO | ||||
|  | ||||
|     return actions; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<void> request(BuildContext context) async { | ||||
|     await widget.order.reload(); | ||||
|     await api.SalesOrderStatus.load(); | ||||
|  | ||||
|     supportsProjectCodes = api.supportsProjectCodes && await api.getGlobalBooleanSetting("PROJECT_CODES_ENABLED"); | ||||
|  | ||||
|     completedLines = 0; | ||||
|  | ||||
|     for (var line in lines) { | ||||
|       if (line.isComplete) { | ||||
|         completedLines += 1; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     InvenTreeSalesOrderAttachment().count(filters: { | ||||
|       "order": widget.order.pk.toString() | ||||
|     }).then((int value) { | ||||
|       if (mounted) { | ||||
|         setState(() { | ||||
|           attachmentCount = value; | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   // Edit the current SalesOrder instance | ||||
|   Future<void> editOrder(BuildContext context) async { | ||||
|     var fields = widget.order.formFields(); | ||||
|  | ||||
|     fields.remove("customer"); | ||||
|  | ||||
|     // Contact model not supported by server | ||||
|     if (!api.supportsContactModel) { | ||||
|       fields.remove("contact"); | ||||
|     } | ||||
|  | ||||
|     // ProjectCode model not supported by server | ||||
|     if (!supportsProjectCodes) { | ||||
|       fields.remove("project_code"); | ||||
|     } | ||||
|  | ||||
|     widget.order.editForm( | ||||
|       context, | ||||
|       L10().salesOrderEdit, | ||||
|       fields: fields, | ||||
|       onSuccess: (data) async { | ||||
|         refresh(context); | ||||
|         showSnackIcon(L10().salesOrderUpdated, success: true); | ||||
|       } | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   // Construct header tile | ||||
|   Widget headerTile(BuildContext context) { | ||||
|     InvenTreeCompany? customer = widget.order.customer; | ||||
|  | ||||
|     return Card( | ||||
|       child: ListTile( | ||||
|         title: Text(widget.order.reference), | ||||
|         subtitle: Text(widget.order.description), | ||||
|         leading: customer == null ? null : api.getThumbnail(customer.thumbnail), | ||||
|         trailing: Text( | ||||
|           api.SalesOrderStatus.label(widget.order.status), | ||||
|           style: TextStyle( | ||||
|               color: api.SalesOrderStatus.color(widget.order.status) | ||||
|           ), | ||||
|         ), | ||||
|       ) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   List<Widget> orderTiles(BuildContext context) { | ||||
|  | ||||
|     List<Widget> tiles = [ | ||||
|       headerTile(context) | ||||
|     ]; | ||||
|  | ||||
|     InvenTreeCompany? customer = widget.order.customer; | ||||
|  | ||||
|     if (supportsProjectCodes && widget.order.hasProjectCode) { | ||||
|       tiles.add(ListTile( | ||||
|         title: Text(L10().projectCode), | ||||
|         subtitle: Text("${widget.order.projectCode} - ${widget.order.projectCodeDescription}"), | ||||
|         leading: FaIcon(FontAwesomeIcons.list), | ||||
|       )); | ||||
|     } | ||||
|  | ||||
|     if (customer != null) { | ||||
|       tiles.add(ListTile( | ||||
|         title: Text(L10().customer), | ||||
|         subtitle: Text(customer.name), | ||||
|         leading: FaIcon(FontAwesomeIcons.userTie, color: COLOR_ACTION), | ||||
|         onTap: () { | ||||
|           Navigator.push( | ||||
|               context, | ||||
|               MaterialPageRoute( | ||||
|                   builder: (context) => CompanyDetailWidget(customer) | ||||
|               ) | ||||
|           ); | ||||
|         } | ||||
|       )); | ||||
|     } | ||||
|  | ||||
|     if (widget.order.customerReference.isNotEmpty) { | ||||
|       tiles.add(ListTile( | ||||
|         title: Text(L10().customerReference), | ||||
|         subtitle: Text(widget.order.customerReference), | ||||
|         leading: FaIcon(FontAwesomeIcons.hashtag), | ||||
|       )); | ||||
|     } | ||||
|  | ||||
|     Color lineColor = completedLines < widget.order.lineItemCount ? COLOR_WARNING : COLOR_SUCCESS; | ||||
|  | ||||
|     tiles.add(ListTile( | ||||
|       title: Text(L10().lineItems), | ||||
|       subtitle: ProgressBar( | ||||
|         completedLines.toDouble(), | ||||
|         maximum: widget.order.lineItemCount.toDouble() | ||||
|       ), | ||||
|       leading: FaIcon(FontAwesomeIcons.clipboardCheck), | ||||
|       trailing: Text("${completedLines} / ${widget.order.lineItemCount}", style: TextStyle(color: lineColor)), | ||||
|     )); | ||||
|  | ||||
|     // TODO: total price | ||||
|  | ||||
|     if (widget.order.targetDate.isNotEmpty) { | ||||
|       tiles.add(ListTile( | ||||
|         title: Text(L10().targetDate), | ||||
|         subtitle: Text(widget.order.targetDate), | ||||
|         leading: FaIcon(FontAwesomeIcons.calendarDays), | ||||
|       )); | ||||
|     } | ||||
|  | ||||
|     // Notes tile | ||||
|     tiles.add( | ||||
|       ListTile( | ||||
|         title: Text(L10().notes), | ||||
|         leading: FaIcon(FontAwesomeIcons.noteSticky, color: COLOR_ACTION), | ||||
|         onTap: () { | ||||
|           Navigator.push( | ||||
|             context, | ||||
|             MaterialPageRoute( | ||||
|             builder: (context) => NotesWidget(widget.order) | ||||
|             ) | ||||
|           ); | ||||
|         }, | ||||
|       ) | ||||
|     ); | ||||
|  | ||||
|     // Attachments | ||||
|     tiles.add( | ||||
|       ListTile( | ||||
|         title: Text(L10().attachments), | ||||
|         leading: FaIcon(FontAwesomeIcons.fileLines, color: COLOR_ACTION), | ||||
|         trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null, | ||||
|         onTap: () { | ||||
|           Navigator.push( | ||||
|           context, | ||||
|           MaterialPageRoute( | ||||
|             builder: (context) => AttachmentWidget( | ||||
|             InvenTreeSalesOrderAttachment(), | ||||
|             widget.order.pk, | ||||
|             widget.order.canEdit | ||||
|             ) | ||||
|           ) | ||||
|         ); | ||||
|         }, | ||||
|       ) | ||||
|     ); | ||||
|  | ||||
|     return tiles; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   List<Widget> getTabIcons(BuildContext context) { | ||||
|     return [ | ||||
|       Tab(text: L10().details), | ||||
|       Tab(text: L10().lineItems), | ||||
|       // TODO: Add in the "shipped items" tab | ||||
|       // Tab(text: L10().shipped) | ||||
|     ]; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   List<Widget> getTabs(BuildContext context) { | ||||
|     return [ | ||||
|       ListView(children: orderTiles(context)), | ||||
|       PaginatedSOLineList({"order": widget.order.pk.toString()}), | ||||
|       // Center(), // TODO: Delivered stock | ||||
|     ]; | ||||
|   } | ||||
|  | ||||
| } | ||||
							
								
								
									
										176
									
								
								lib/widget/order/sales_order_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								lib/widget/order/sales_order_list.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,176 @@ | ||||
|  | ||||
| import "package:flutter/material.dart"; | ||||
| import "package:flutter_speed_dial/flutter_speed_dial.dart"; | ||||
| import "package:font_awesome_flutter/font_awesome_flutter.dart"; | ||||
| import "package:inventree/inventree/sales_order.dart"; | ||||
| import "package:inventree/widget/order/sales_order_detail.dart"; | ||||
| import "package:inventree/widget/paginator.dart"; | ||||
|  | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| import "package:inventree/l10.dart"; | ||||
|  | ||||
| import "package:inventree/api.dart"; | ||||
| import "package:inventree/inventree/company.dart"; | ||||
| import "package:inventree/inventree/model.dart"; | ||||
|  | ||||
|  | ||||
| class SalesOrderListWidget extends StatefulWidget { | ||||
|  | ||||
|   const SalesOrderListWidget({this.filters = const {}, Key? key}) : super(key: key); | ||||
|  | ||||
|   final Map<String, String> filters; | ||||
|  | ||||
|   @override | ||||
|   _SalesOrderListWidgetState createState() => _SalesOrderListWidgetState(); | ||||
|  | ||||
| } | ||||
|  | ||||
| class _SalesOrderListWidgetState extends RefreshableState<SalesOrderListWidget> { | ||||
|  | ||||
|   _SalesOrderListWidgetState(); | ||||
|  | ||||
|   @override | ||||
|   String getAppBarTitle() => L10().salesOrders; | ||||
|  | ||||
|   @override | ||||
|   List<SpeedDialChild> actionButtons(BuildContext context) { | ||||
|     List<SpeedDialChild> actions = []; | ||||
|  | ||||
|     if (InvenTreeSalesOrder().canCreate) { | ||||
|       actions.add( | ||||
|           SpeedDialChild( | ||||
|               child: FaIcon(FontAwesomeIcons.circlePlus), | ||||
|               label: L10().salesOrderCreate, | ||||
|               onTap: () { | ||||
|                 _createSalesOrder(context); | ||||
|               } | ||||
|           ) | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return actions; | ||||
|   } | ||||
|  | ||||
|   // Launch form to create a new SalesOrder | ||||
|   Future<void> _createSalesOrder(BuildContext context) async { | ||||
|     var fields = InvenTreeSalesOrder().formFields(); | ||||
|  | ||||
|     // Cannot set contact until company is locked in | ||||
|     fields.remove("contact"); | ||||
|  | ||||
|     InvenTreeSalesOrder().createForm( | ||||
|         context, | ||||
|         L10().salesOrderCreate, | ||||
|         fields: fields, | ||||
|         onSuccess: (result) async { | ||||
|           Map<String, dynamic> data = result as Map<String, dynamic>; | ||||
|  | ||||
|           if (data.containsKey("pk")) { | ||||
|             var order = InvenTreeSalesOrder.fromJson(data); | ||||
|  | ||||
|             Navigator.push( | ||||
|               context, | ||||
|               MaterialPageRoute( | ||||
|                 builder: (context) => SalesOrderDetailWidget(order) | ||||
|               ) | ||||
|             ); | ||||
|           } | ||||
|         } | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   List<SpeedDialChild> barcodeButtons(BuildContext context) { | ||||
|     // TODO: return custom barcode actions | ||||
|     return []; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget getBody(BuildContext context) { | ||||
|     return PaginatedSalesOrderList(widget.filters); | ||||
|   } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| class PaginatedSalesOrderList extends PaginatedSearchWidget { | ||||
|  | ||||
|   const PaginatedSalesOrderList(Map<String, String> filters) : super(filters: filters); | ||||
|  | ||||
|   @override | ||||
|   String get searchTitle => L10().salesOrders; | ||||
|  | ||||
|   @override | ||||
|   _PaginatedSalesOrderListState createState() => _PaginatedSalesOrderListState(); | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| class _PaginatedSalesOrderListState extends PaginatedSearchState<PaginatedSalesOrderList> { | ||||
|  | ||||
|   _PaginatedSalesOrderListState() : super(); | ||||
|  | ||||
|   @override | ||||
|   String get prefix => "so_"; | ||||
|  | ||||
|   @override | ||||
|   Map<String, String> get orderingOptions => { | ||||
|     "reference": L10().reference, | ||||
|     "status": L10().status, | ||||
|     "target_date": L10().targetDate, | ||||
|     "customer__name": L10().customer, | ||||
|   }; | ||||
|  | ||||
|   @override | ||||
|   Map<String, Map<String, dynamic>> get filterOptions => { | ||||
|     "outstanding": { | ||||
|       "label": L10().outstanding, | ||||
|       "help_text": L10().outstandingOrderDetail, | ||||
|       "tristate": true, | ||||
|     }, | ||||
|     "overdue": { | ||||
|       "label": L10().overdue, | ||||
|       "help_text": L10().overdueDetail, | ||||
|       "tristate": true, | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   @override | ||||
|   Future<InvenTreePageResponse?> requestPage(int limit, int offset, Map<String, String> params) async { | ||||
|  | ||||
|     await InvenTreeAPI().SalesOrderStatus.load(); | ||||
|     final page = await InvenTreeSalesOrder().listPaginated(limit, offset, filters: params); | ||||
|  | ||||
|     return page; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget buildItem(BuildContext context, InvenTreeModel model) { | ||||
|  | ||||
|     InvenTreeSalesOrder order = model as InvenTreeSalesOrder; | ||||
|  | ||||
|     InvenTreeCompany? customer = order.customer; | ||||
|  | ||||
|     return ListTile( | ||||
|       title: Text(order.reference), | ||||
|       subtitle: Text(order.description), | ||||
|       leading: customer == null ? null : InvenTreeAPI().getThumbnail(customer.thumbnail), | ||||
|       trailing: Text( | ||||
|         InvenTreeAPI().SalesOrderStatus.label(order.status), | ||||
|         style: TextStyle( | ||||
|           color: InvenTreeAPI().SalesOrderStatus.color(order.status), | ||||
|         ) | ||||
|       ), | ||||
|       onTap: () async { | ||||
|         Navigator.push( | ||||
|           context, | ||||
|           MaterialPageRoute( | ||||
|             builder: (context) => SalesOrderDetailWidget(order) | ||||
|           ) | ||||
|         ); | ||||
|       } | ||||
|     ); | ||||
|  | ||||
|   } | ||||
|  | ||||
| } | ||||
							
								
								
									
										164
									
								
								lib/widget/order/so_line_detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								lib/widget/order/so_line_detail.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,164 @@ | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Widget for displaying detail view of a single SalesOrderLineItem | ||||
|  */ | ||||
| import "package:flutter/material.dart"; | ||||
| import "package:flutter_speed_dial/flutter_speed_dial.dart"; | ||||
| import "package:font_awesome_flutter/font_awesome_flutter.dart"; | ||||
|  | ||||
| import "package:inventree/app_colors.dart"; | ||||
| import "package:inventree/l10.dart"; | ||||
| import "package:inventree/inventree/part.dart"; | ||||
| import "package:inventree/inventree/sales_order.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| import "package:inventree/widget/progress.dart"; | ||||
| import "package:inventree/widget/part/part_detail.dart"; | ||||
|  | ||||
| import "package:inventree/helpers.dart"; | ||||
| import "package:inventree/widget/snacks.dart"; | ||||
|  | ||||
|  | ||||
| class SoLineDetailWidget extends StatefulWidget { | ||||
|  | ||||
|   const SoLineDetailWidget(this.item, {Key? key}) : super(key: key); | ||||
|  | ||||
|   final InvenTreeSOLineItem item; | ||||
|  | ||||
|   @override | ||||
|   _SOLineDetailWidgetState createState() => _SOLineDetailWidgetState(); | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| class _SOLineDetailWidgetState extends RefreshableState<SoLineDetailWidget> { | ||||
|  | ||||
|   _SOLineDetailWidgetState(); | ||||
|  | ||||
|   @override | ||||
|   String getAppBarTitle() => L10().lineItem; | ||||
|  | ||||
|   @override | ||||
|   List<Widget> appBarActions(BuildContext context) { | ||||
|     List<Widget> actions = []; | ||||
|  | ||||
|     if (widget.item.canEdit) { | ||||
|       actions.add( | ||||
|         IconButton( | ||||
|             icon: Icon(Icons.edit_square), | ||||
|             onPressed: () { | ||||
|               _editLineItem(context); | ||||
|             }), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return actions; | ||||
|   } | ||||
|  | ||||
|   Future<void> _editLineItem(BuildContext context) async { | ||||
|     var fields = widget.item.formFields(); | ||||
|  | ||||
|     // Prevent editing of the line item | ||||
|     if (widget.item.shipped > 0) { | ||||
|       fields["part"]?["hidden"] = true; | ||||
|     } | ||||
|  | ||||
|     widget.item.editForm( | ||||
|       context, | ||||
|       L10().editLineItem, | ||||
|       fields: fields, | ||||
|       onSuccess: (data) async { | ||||
|         refresh(context); | ||||
|         showSnackIcon(L10().lineItemUpdated, success: true); | ||||
|       } | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   List<SpeedDialChild> actionButtons(BuildContext context) { | ||||
|     // TODO | ||||
|     return []; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<void> request(BuildContext context) async { | ||||
|     await widget.item.reload(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   List<Widget> getTiles(BuildContext context) { | ||||
|     List<Widget> tiles = []; | ||||
|  | ||||
|     // Reference to the part | ||||
|     tiles.add( | ||||
|       ListTile( | ||||
|         title: Text(L10().part), | ||||
|         subtitle: Text(widget.item.partName), | ||||
|         leading: FaIcon(FontAwesomeIcons.shapes, color: COLOR_ACTION), | ||||
|         trailing: api.getThumbnail(widget.item.partImage), | ||||
|         onTap: () async { | ||||
|           showLoadingOverlay(context); | ||||
|           var part = await InvenTreePart().get(widget.item.partId); | ||||
|           hideLoadingOverlay(); | ||||
|  | ||||
|           if (part is InvenTreePart) { | ||||
|             Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part))); | ||||
|           } | ||||
|         } | ||||
|       ) | ||||
|     ); | ||||
|  | ||||
|     // Shipped quantity | ||||
|     tiles.add( | ||||
|       ListTile( | ||||
|         title: Text(L10().shipped), | ||||
|         subtitle: ProgressBar(widget.item.progressRatio), | ||||
|         trailing: Text( | ||||
|           widget.item.progressString, | ||||
|           style: TextStyle( | ||||
|             color: widget.item.isComplete ? COLOR_SUCCESS : COLOR_WARNING | ||||
|           ), | ||||
|         ), | ||||
|         leading: FaIcon(FontAwesomeIcons.truck) | ||||
|       ) | ||||
|     ); | ||||
|  | ||||
|     // Reference | ||||
|     if (widget.item.reference.isNotEmpty) { | ||||
|       tiles.add( | ||||
|         ListTile( | ||||
|           title: Text(L10().reference), | ||||
|           subtitle: Text(widget.item.reference), | ||||
|           leading: FaIcon(FontAwesomeIcons.hashtag) | ||||
|         ) | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     // Note | ||||
|     if (widget.item.notes.isNotEmpty) { | ||||
|       tiles.add( | ||||
|           ListTile( | ||||
|             title: Text(L10().notes), | ||||
|             subtitle: Text(widget.item.notes), | ||||
|             leading: FaIcon(FontAwesomeIcons.noteSticky), | ||||
|           ) | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     // External link | ||||
|     if (widget.item.link.isNotEmpty) { | ||||
|       tiles.add( | ||||
|           ListTile( | ||||
|             title: Text(L10().link), | ||||
|             subtitle: Text(widget.item.link), | ||||
|             leading: FaIcon(FontAwesomeIcons.link, color: COLOR_ACTION), | ||||
|             onTap: () async { | ||||
|               await openLink(widget.item.link); | ||||
|             }, | ||||
|           ) | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return tiles; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										86
									
								
								lib/widget/order/so_line_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								lib/widget/order/so_line_list.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| import "package:flutter/material.dart"; | ||||
| import "package:inventree/l10.dart"; | ||||
| import "package:inventree/widget/order/so_line_detail.dart"; | ||||
| import "package:inventree/widget/paginator.dart"; | ||||
| import "package:inventree/inventree/model.dart"; | ||||
| import "package:inventree/inventree/sales_order.dart"; | ||||
| import "package:inventree/inventree/part.dart"; | ||||
| import "package:inventree/api.dart"; | ||||
| import "package:inventree/app_colors.dart"; | ||||
| import "package:inventree/widget/progress.dart"; | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Paginated widget class for displaying a list of sales order line items | ||||
|  */ | ||||
|  | ||||
| class PaginatedSOLineList extends PaginatedSearchWidget { | ||||
|   const PaginatedSOLineList(Map<String, String> filters) : super(filters: filters); | ||||
|  | ||||
|   @override | ||||
|   String get searchTitle => L10().lineItems; | ||||
|  | ||||
|   @override | ||||
|   _PaginatedSOLineListState createState() => _PaginatedSOLineListState(); | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * State class for PaginatedSOLineList | ||||
|  */ | ||||
| class _PaginatedSOLineListState extends PaginatedSearchState<PaginatedSOLineList> { | ||||
|  | ||||
|   _PaginatedSOLineListState() : super(); | ||||
|  | ||||
|   @override | ||||
|   String get prefix => "so_line_"; | ||||
|  | ||||
|   @override | ||||
|   Map<String, String> get orderingOptions => { | ||||
|     "part": L10().part, | ||||
|     "quantity": L10().quantity, | ||||
|   }; | ||||
|  | ||||
|   @override | ||||
|   Map<String, Map<String, dynamic>> get filterOptions => { | ||||
|  | ||||
|   }; | ||||
|  | ||||
|   @override | ||||
|   Future<InvenTreePageResponse?> requestPage(int limit, int offset, Map<String, String> params) async { | ||||
|     final page = await InvenTreeSOLineItem().listPaginated(limit, offset, filters: params); | ||||
|     return page; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget buildItem(BuildContext context, InvenTreeModel model) { | ||||
|     InvenTreeSOLineItem item = model as InvenTreeSOLineItem; | ||||
|     InvenTreePart? part = item.part; | ||||
|  | ||||
|     if (part != null) { | ||||
|       return ListTile( | ||||
|         title: Text(part.name), | ||||
|         subtitle: Text(part.description), | ||||
|         leading: InvenTreeAPI().getThumbnail(part.thumbnail), | ||||
|         trailing: Text(item.progressString), | ||||
|         onTap: () async { | ||||
|           showLoadingOverlay(context); | ||||
|           await item.reload(); | ||||
|           hideLoadingOverlay(); | ||||
|           Navigator.push( | ||||
|             context, | ||||
|             MaterialPageRoute( | ||||
|               builder: (context) => SoLineDetailWidget(item)) | ||||
|           ); | ||||
|         } | ||||
|       ); | ||||
|     } else { | ||||
|       return ListTile( | ||||
|         title: Text(L10().error), | ||||
|         subtitle: Text("Missing part detail", style: TextStyle(color: COLOR_DANGER)), | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| } | ||||
| @@ -11,7 +11,7 @@ import "package:inventree/inventree/model.dart"; | ||||
| import "package:inventree/inventree/part.dart"; | ||||
| 
 | ||||
| import "package:inventree/widget/paginator.dart"; | ||||
| import "package:inventree/widget/part_detail.dart"; | ||||
| import "package:inventree/widget/part/part_detail.dart"; | ||||
| import "package:inventree/widget/progress.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| 
 | ||||
| @@ -7,11 +7,11 @@ import "package:inventree/l10.dart"; | ||||
| 
 | ||||
| import "package:inventree/inventree/part.dart"; | ||||
| 
 | ||||
| import "package:inventree/widget/category_list.dart"; | ||||
| import "package:inventree/widget/part_list.dart"; | ||||
| import "package:inventree/widget/part/category_list.dart"; | ||||
| import "package:inventree/widget/part/part_list.dart"; | ||||
| import "package:inventree/widget/progress.dart"; | ||||
| import "package:inventree/widget/snacks.dart"; | ||||
| import "package:inventree/widget/part_detail.dart"; | ||||
| import "package:inventree/widget/part/part_detail.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| 
 | ||||
| 
 | ||||
| @@ -2,7 +2,7 @@ import "package:flutter/material.dart"; | ||||
| 
 | ||||
| import "package:inventree/inventree/model.dart"; | ||||
| import "package:inventree/inventree/part.dart"; | ||||
| import "package:inventree/widget/category_display.dart"; | ||||
| import "package:inventree/widget/part/category_display.dart"; | ||||
| import "package:inventree/widget/paginator.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| 
 | ||||
| @@ -15,18 +15,18 @@ import "package:inventree/labels.dart"; | ||||
| import "package:inventree/preferences.dart"; | ||||
| 
 | ||||
| import "package:inventree/widget/attachment_widget.dart"; | ||||
| import "package:inventree/widget/bom_list.dart"; | ||||
| import "package:inventree/widget/part_list.dart"; | ||||
| import "package:inventree/widget/part/bom_list.dart"; | ||||
| import "package:inventree/widget/part/part_list.dart"; | ||||
| import "package:inventree/widget/notes_widget.dart"; | ||||
| import "package:inventree/widget/part_parameter_widget.dart"; | ||||
| import "package:inventree/widget/part/part_parameter_widget.dart"; | ||||
| import "package:inventree/widget/progress.dart"; | ||||
| import "package:inventree/widget/category_display.dart"; | ||||
| import "package:inventree/widget/part/category_display.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| import "package:inventree/widget/part_image_widget.dart"; | ||||
| import "package:inventree/widget/part/part_image_widget.dart"; | ||||
| import "package:inventree/widget/snacks.dart"; | ||||
| import "package:inventree/widget/stock_detail.dart"; | ||||
| import "package:inventree/widget/stock_list.dart"; | ||||
| import "package:inventree/widget/supplier_part_list.dart"; | ||||
| import "package:inventree/widget/stock/stock_detail.dart"; | ||||
| import "package:inventree/widget/stock/stock_list.dart"; | ||||
| import "package:inventree/widget/company/supplier_part_list.dart"; | ||||
| 
 | ||||
| 
 | ||||
| /* | ||||
| @@ -634,7 +634,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> { | ||||
|     fields.remove("serial"); | ||||
| 
 | ||||
|     // Hide the "part" field | ||||
|     fields["part"]["hidden"] = true; | ||||
|     fields["part"]?["hidden"] = true; | ||||
| 
 | ||||
|     int? default_location = part.defaultLocation; | ||||
| 
 | ||||
| @@ -7,7 +7,7 @@ import "package:inventree/inventree/model.dart"; | ||||
| import "package:inventree/inventree/part.dart"; | ||||
| 
 | ||||
| import "package:inventree/widget/paginator.dart"; | ||||
| import "package:inventree/widget/part_detail.dart"; | ||||
| import "package:inventree/widget/part/part_detail.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| 
 | ||||
| 
 | ||||
| @@ -7,7 +7,7 @@ import "package:inventree/api.dart"; | ||||
| import "package:flutter/material.dart"; | ||||
| import "package:inventree/inventree/part.dart"; | ||||
| import "package:inventree/inventree/company.dart"; | ||||
| import "package:inventree/widget/company_detail.dart"; | ||||
| import "package:inventree/widget/company/company_detail.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| 
 | ||||
| class PartSupplierWidget extends StatefulWidget { | ||||
| @@ -2,6 +2,34 @@ | ||||
|  | ||||
| import "package:flutter/material.dart"; | ||||
| import "package:flutter_overlay_loader/flutter_overlay_loader.dart"; | ||||
| import "package:inventree/app_colors.dart"; | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * A simplified linear progress bar widget, | ||||
|  * with standardized color depiction | ||||
|  */ | ||||
| Widget ProgressBar( | ||||
|   double value, | ||||
| { | ||||
|   double maximum = 1.0 | ||||
| }) { | ||||
|  | ||||
|   double v = 0; | ||||
|  | ||||
|   if (value <= 0 || maximum <= 0) { | ||||
|     v = 0; | ||||
|   } else { | ||||
|     v = value / maximum; | ||||
|   } | ||||
|  | ||||
|   return LinearProgressIndicator( | ||||
|     value: v, | ||||
|     backgroundColor: Colors.grey, | ||||
|     color: v >= 1 ? COLOR_SUCCESS : COLOR_WARNING, | ||||
|   ); | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Construct a circular progress indicator | ||||
|   | ||||
| @@ -11,13 +11,13 @@ import "package:inventree/inventree/part.dart"; | ||||
| import "package:inventree/inventree/purchase_order.dart"; | ||||
| import "package:inventree/inventree/stock.dart"; | ||||
|  | ||||
| import "package:inventree/widget/part_list.dart"; | ||||
| import "package:inventree/widget/purchase_order_list.dart"; | ||||
| import "package:inventree/widget/part/part_list.dart"; | ||||
| import "package:inventree/widget/order/purchase_order_list.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| import "package:inventree/widget/stock_list.dart"; | ||||
| import "package:inventree/widget/category_list.dart"; | ||||
| import "package:inventree/widget/company_list.dart"; | ||||
| import "package:inventree/widget/location_list.dart"; | ||||
| import "package:inventree/widget/stock/stock_list.dart"; | ||||
| import "package:inventree/widget/part/category_list.dart"; | ||||
| import "package:inventree/widget/company/company_list.dart"; | ||||
| import "package:inventree/widget/stock/location_list.dart"; | ||||
|  | ||||
|  | ||||
| // Widget for performing database-wide search | ||||
|   | ||||
| @@ -10,12 +10,12 @@ import "package:inventree/l10.dart"; | ||||
| import "package:inventree/inventree/stock.dart"; | ||||
| import "package:inventree/preferences.dart"; | ||||
| 
 | ||||
| import "package:inventree/widget/location_list.dart"; | ||||
| import "package:inventree/widget/stock/location_list.dart"; | ||||
| import "package:inventree/widget/progress.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| import "package:inventree/widget/snacks.dart"; | ||||
| import "package:inventree/widget/stock_detail.dart"; | ||||
| import "package:inventree/widget/stock_list.dart"; | ||||
| import "package:inventree/widget/stock/stock_detail.dart"; | ||||
| import "package:inventree/widget/stock/stock_list.dart"; | ||||
| import "package:inventree/labels.dart"; | ||||
| 
 | ||||
| 
 | ||||
| @@ -2,7 +2,7 @@ import "package:flutter/material.dart"; | ||||
| 
 | ||||
| import "package:inventree/inventree/model.dart"; | ||||
| import "package:inventree/inventree/stock.dart"; | ||||
| import "package:inventree/widget/location_display.dart"; | ||||
| import "package:inventree/widget/stock/location_display.dart"; | ||||
| import "package:inventree/widget/paginator.dart"; | ||||
| 
 | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| @@ -16,16 +16,16 @@ import "package:inventree/inventree/company.dart"; | ||||
| import "package:inventree/inventree/stock.dart"; | ||||
| import "package:inventree/inventree/part.dart"; | ||||
| 
 | ||||
| import "package:inventree/widget/supplier_part_detail.dart"; | ||||
| import "package:inventree/widget/company/supplier_part_detail.dart"; | ||||
| import "package:inventree/widget/dialogs.dart"; | ||||
| import "package:inventree/widget/attachment_widget.dart"; | ||||
| import "package:inventree/widget/location_display.dart"; | ||||
| import "package:inventree/widget/part_detail.dart"; | ||||
| import "package:inventree/widget/stock/location_display.dart"; | ||||
| import "package:inventree/widget/part/part_detail.dart"; | ||||
| import "package:inventree/widget/progress.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| import "package:inventree/widget/snacks.dart"; | ||||
| import "package:inventree/widget/stock_item_history.dart"; | ||||
| import "package:inventree/widget/stock_item_test_results.dart"; | ||||
| import "package:inventree/widget/stock/stock_item_history.dart"; | ||||
| import "package:inventree/widget/stock/stock_item_test_results.dart"; | ||||
| import "package:inventree/widget/notes_widget.dart"; | ||||
| 
 | ||||
| 
 | ||||
| @@ -5,7 +5,7 @@ import "package:inventree/inventree/stock.dart"; | ||||
| import "package:inventree/widget/paginator.dart"; | ||||
| import "package:inventree/widget/refreshable_state.dart"; | ||||
| import "package:inventree/l10.dart"; | ||||
| import "package:inventree/widget/stock_detail.dart"; | ||||
| import "package:inventree/widget/stock/stock_detail.dart"; | ||||
| import "package:inventree/api.dart"; | ||||
| 
 | ||||
| 
 | ||||
		Reference in New Issue
	
	Block a user