diff --git a/lib/api.dart b/lib/api.dart index b8a2d7b1..28f85f39 100644 --- a/lib/api.dart +++ b/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 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; diff --git a/lib/barcode/barcode.dart b/lib/barcode/barcode.dart index df110e6f..4ea044e1 100644 --- a/lib/barcode/barcode.dart +++ b/lib/barcode/barcode.dart @@ -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"; /* diff --git a/lib/generated/i18n.dart b/lib/generated/i18n.dart index 09177b85..ad002d5a 100644 --- a/lib/generated/i18n.dart +++ b/lib/generated/i18n.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 diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart index 032ee41e..4ebbc987 100644 --- a/lib/inventree/company.dart +++ b/lib/inventree/company.dart @@ -22,7 +22,7 @@ class InvenTreeCompany extends InvenTreeModel { List get rolesRequired => ["purchase_order", "sales_order", "return_order"]; @override - Map formFields() { + Map> formFields() { return { "name": {}, "description": {}, @@ -121,8 +121,8 @@ class InvenTreeSupplierPart extends InvenTreeModel { List get rolesRequired => ["part", "purchase_order"]; @override - Map formFields() { - Map fields = { + Map> formFields() { + Map> fields = { "supplier": {}, "SKU": {}, "link": {}, diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index abb7ec6b..fd4c29e5 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -230,7 +230,7 @@ class InvenTreeModel { // Fields for editing / creating this model // Override per-model - Map formFields() { + Map> formFields() { return {}; } diff --git a/lib/inventree/orders.dart b/lib/inventree/orders.dart new file mode 100644 index 00000000..0595b2d8 --- /dev/null +++ b/lib/inventree/orders.dart @@ -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 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 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); + } + } + + 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"); +} \ No newline at end of file diff --git a/lib/inventree/part.dart b/lib/inventree/part.dart index e335a119..e44494e1 100644 --- a/lib/inventree/part.dart +++ b/lib/inventree/part.dart @@ -27,9 +27,9 @@ class InvenTreePartCategory extends InvenTreeModel { List get rolesRequired => ["part_category"]; @override - Map formFields() { + Map> formFields() { - Map fields = { + Map> fields = { "name": {}, "description": {}, "parent": {}, @@ -140,9 +140,9 @@ class InvenTreePartParameter extends InvenTreeModel { InvenTreeModel createFromJson(Map json) => InvenTreePartParameter.fromJson(json); @override - Map formFields() { + Map> formFields() { - Map fields = { + Map> fields = { "header": { "type": "string", "read_only": true, @@ -200,7 +200,7 @@ class InvenTreePart extends InvenTreeModel { List get rolesRequired => ["part"]; @override - Map formFields() { + Map> formFields() { return { "name": {}, "description": {}, diff --git a/lib/inventree/project_code.dart b/lib/inventree/project_code.dart index 8e2c75b6..07a0c19d 100644 --- a/lib/inventree/project_code.dart +++ b/lib/inventree/project_code.dart @@ -17,7 +17,7 @@ class InvenTreeProjectCode extends InvenTreeModel { String get URL => "project-code/"; @override - Map formFields() { + Map> formFields() { return { "code": {}, "description": {}, diff --git a/lib/inventree/purchase_order.dart b/lib/inventree/purchase_order.dart index defb9f8a..80708948 100644 --- a/lib/inventree/purchase_order.dart +++ b/lib/inventree/purchase_order.dart @@ -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 json) : super.fromJson(json); + @override + InvenTreeModel createFromJson(Map 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 formFields() { - var fields = { + Map> formFields() { + Map> 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> getLineItems() async { @@ -162,9 +111,6 @@ class InvenTreePurchaseOrder extends InvenTreeModel { return items; } - @override - InvenTreeModel createFromJson(Map json) => InvenTreePurchaseOrder.fromJson(json); - /// Mark this order as "placed" / "issued" Future 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 json) : super.fromJson(json); + @override + InvenTreeModel createFromJson(Map json) => InvenTreePOLineItem.fromJson(json); + @override String get URL => "order/po-line/"; @@ -198,7 +147,7 @@ class InvenTreePOLineItem extends InvenTreeModel { List get rolesRequired => ["purchase_order"]; @override - Map formFields() { + Map> 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); - } - } - - 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 get destinationDetail => getMap("destination_detail"); - - @override - InvenTreeModel createFromJson(Map 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 { diff --git a/lib/inventree/sales_order.dart b/lib/inventree/sales_order.dart new file mode 100644 index 00000000..1225cb86 --- /dev/null +++ b/lib/inventree/sales_order.dart @@ -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 json) : super.fromJson(json); + + @override + InvenTreeModel createFromJson(Map json) => InvenTreeSalesOrder.fromJson(json); + + @override + String get URL => "order/so/"; + + @override + List get rolesRequired => ["sales_order"]; + + @override + Map> formFields() { + Map> 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 defaultGetFilters() { + return { + "customer_detail": "true", + }; + } + + @override + Map 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 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 json) : super.fromJson(json); + + @override + InvenTreeModel createFromJson(Map json) => InvenTreeSOLineItem.fromJson(json); + + @override + String get URL => "order/so-line/"; + + @override + List get rolesRequired => ["sales_order"]; + + @override + Map> formFields() { + return { + "order": { + "hidden": true, + }, + "part": {}, + "quantity": {}, + "reference": {}, + "notes": {}, + "link": {}, + }; + } + + @override + Map defaultGetFilters() { + return { + "part_detail": "true", + }; + } + + @override + Map 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 json) : super.fromJson(json); + + @override + InvenTreeModel createFromJson(Map json) => InvenTreeSalesOrderAttachment.fromJson(json); + + @override + String get REFERENCE_FIELD => "order"; + + @override + String get URL => "order/po/attachment/"; + +} diff --git a/lib/inventree/status_codes.dart b/lib/inventree/status_codes.dart index 8edb4ea0..9b75280d 100644 --- a/lib/inventree/status_codes.dart +++ b/lib/inventree/status_codes.dart @@ -105,6 +105,24 @@ class InvenTreeStatusCode { } } + // Return the 'name' (untranslated) associated with a given status code + String name(int status) { + Map _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 names) { + return names.contains(name(code)); + } + // Return the 'color' associated with a given status code Color color(int status) { Map _entry = entry(status); diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index 22113d4e..4f455fe8 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -27,7 +27,7 @@ class InvenTreeStockItemTestResult extends InvenTreeModel { List get rolesRequired => ["stock"]; @override - Map formFields() { + Map> formFields() { return { "stock_item": {"hidden": true}, "test": {}, @@ -158,8 +158,8 @@ class InvenTreeStockItem extends InvenTreeModel { String get WEB_URL => "stock/item/"; @override - Map formFields() { - return { + Map> formFields() { + Map> 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 formFields() { - Map fields = { + Map> formFields() { + Map> fields = { "name": {}, "description": {}, "parent": {}, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index e1c1f2ea..fb8f91c8 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -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": {}, diff --git a/lib/preferences.dart b/lib/preferences.dart index 30105978..581a5fd8 100644 --- a/lib/preferences.dart +++ b/lib/preferences.dart @@ -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"; diff --git a/lib/settings/home_settings.dart b/lib/settings/home_settings.dart index 926f31db..c1bee1f0 100644 --- a/lib/settings/home_settings.dart +++ b/lib/settings/home_settings.dart @@ -21,6 +21,7 @@ class _HomeScreenSettingsState extends State { // 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 { 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 { }, ), ), + 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 { }, ), ), + */ ListTile( title: Text(L10().homeShowCustomers), subtitle: Text(L10().homeShowCustomersDescription), @@ -130,7 +147,6 @@ class _HomeScreenSettingsState extends State { }, ), ), - */ ] ) ) diff --git a/lib/widget/company_detail.dart b/lib/widget/company/company_detail.dart similarity index 64% rename from lib/widget/company_detail.dart rename to lib/widget/company/company_detail.dart index 15684a16..bf83ee4d 100644 --- a/lib/widget/company_detail.dart +++ b/lib/widget/company/company_detail.dart @@ -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 { _CompanyDetailState(); - List outstandingOrders = []; - int supplierPartCount = 0; + int outstandingPurchaseOrders = 0; + int outstandingSalesOrders = 0; + int attachmentCount = 0; @override @@ -68,11 +73,87 @@ class _CompanyDetailState extends RefreshableState { List actionButtons(BuildContext context) { List 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 _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 data = result as Map; + + if (data.containsKey("pk")) { + var order = InvenTreeSalesOrder.fromJson(data); + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SalesOrderDetailWidget(order) + ) + ); + } + } + ); + } + + Future _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 data = result as Map; + + if (data.containsKey("pk")) { + var order = InvenTreePurchaseOrder.fromJson(data); + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PurchaseOrderDetailWidget(order) + ) + ); + } + } + ); + } + @override Future request(BuildContext context) async { final bool result = await widget.company.reload(); @@ -83,11 +164,18 @@ class _CompanyDetailState extends RefreshableState { 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 { 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 { } 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) { diff --git a/lib/widget/company_list.dart b/lib/widget/company/company_list.dart similarity index 97% rename from lib/widget/company_list.dart rename to lib/widget/company/company_list.dart index a19762c2..b68dffd0 100644 --- a/lib/widget/company_list.dart +++ b/lib/widget/company/company_list.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/company_detail.dart"; +import "package:inventree/widget/company/company_detail.dart"; /* diff --git a/lib/widget/supplier_part_detail.dart b/lib/widget/company/supplier_part_detail.dart similarity index 98% rename from lib/widget/supplier_part_detail.dart rename to lib/widget/company/supplier_part_detail.dart index fe39c803..6ca43cb2 100644 --- a/lib/widget/supplier_part_detail.dart +++ b/lib/widget/company/supplier_part_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"; diff --git a/lib/widget/supplier_part_list.dart b/lib/widget/company/supplier_part_list.dart similarity index 97% rename from lib/widget/supplier_part_list.dart rename to lib/widget/company/supplier_part_list.dart index 3674f66c..5f412274 100644 --- a/lib/widget/supplier_part_list.dart +++ b/lib/widget/company/supplier_part_list.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"; /* diff --git a/lib/widget/drawer.dart b/lib/widget/drawer.dart index 2ca8d88e..f38ad337 100644 --- a/lib/widget/drawer.dart +++ b/lib/widget/drawer.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()); } diff --git a/lib/widget/home.dart b/lib/widget/home.dart index b62daf77..e9034a7b 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -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 with BaseWidgetPr final homeKey = GlobalKey(); bool homeShowPo = false; + bool homeShowSo = false; bool homeShowSubscribed = false; bool homeShowManufacturers = false; bool homeShowCustomers = false; @@ -97,6 +103,17 @@ class _InvenTreeHomePageState extends State 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 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 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 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 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 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 with BaseWidgetPr } )); } - + */ // Customers if (homeShowCustomers) { tiles.add(_listTile( @@ -289,7 +322,6 @@ class _InvenTreeHomePageState extends State with BaseWidgetPr } )); } - */ return tiles; } diff --git a/lib/widget/po_line_detail.dart b/lib/widget/order/po_line_detail.dart similarity index 89% rename from lib/widget/po_line_detail.dart rename to lib/widget/order/po_line_detail.dart index b8ac8aaa..d5510d93 100644 --- a/lib/widget/po_line_detail.dart +++ b/lib/widget/order/po_line_detail.dart @@ -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 { 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 { ) ); - // 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( diff --git a/lib/widget/po_line_list.dart b/lib/widget/order/po_line_list.dart similarity index 97% rename from lib/widget/po_line_list.dart rename to lib/widget/order/po_line_list.dart index 27827f81..e7f53d4b 100644 --- a/lib/widget/po_line_list.dart +++ b/lib/widget/order/po_line_list.dart @@ -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"; /* diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/order/purchase_order_detail.dart similarity index 74% rename from lib/widget/purchase_order_detail.dart rename to lib/widget/order/purchase_order_detail.dart index f03d4f0b..73fa4eb2 100644 --- a/lib/widget/purchase_order_detail.dart +++ b/lib/widget/order/purchase_order_detail.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 { - _PurchaseOrderDetailState(this.order); - - final InvenTreePurchaseOrder order; - + _PurchaseOrderDetailState(); + List lines = []; int completedLines = 0; @@ -52,7 +53,7 @@ class _PurchaseOrderDetailState extends RefreshableState appBarActions(BuildContext context) { List actions = []; - if (order.canEdit) { + if (widget.order.canEdit) { actions.add( IconButton( icon: Icon(Icons.edit_square), @@ -71,8 +72,8 @@ class _PurchaseOrderDetailState extends RefreshableState actionButtons(BuildContext context) { List 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 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 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 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 NotesWidget(order) + builder: (context) => NotesWidget(widget.order) ) ); }, @@ -329,8 +338,8 @@ class _PurchaseOrderDetailState extends RefreshableState AttachmentWidget( InvenTreePurchaseOrderAttachment(), - order.pk, - order.canEdit + widget.order.pk, + widget.order.canEdit ) ) ); @@ -355,9 +364,9 @@ class _PurchaseOrderDetailState extends RefreshableState 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()}), ]; } diff --git a/lib/widget/purchase_order_list.dart b/lib/widget/order/purchase_order_list.dart similarity index 90% rename from lib/widget/purchase_order_list.dart rename to lib/widget/order/purchase_order_list.dart index 44046564..1eb851d9 100644 --- a/lib/widget/purchase_order_list.dart +++ b/lib/widget/order/purchase_order_list.dart @@ -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 filters; @override - _PurchaseOrderListWidgetState createState() => _PurchaseOrderListWidgetState(filters); + _PurchaseOrderListWidgetState createState() => _PurchaseOrderListWidgetState(); } class _PurchaseOrderListWidgetState extends RefreshableState { - _PurchaseOrderListWidgetState(this.filters); - - final Map filters; + _PurchaseOrderListWidgetState(); @override String getAppBarTitle() => L10().purchaseOrders; @@ -45,7 +43,7 @@ class _PurchaseOrderListWidgetState extends RefreshableState createPurchaseOrder(BuildContext context) async { + // Launch form to create a new PurchaseOrder + Future _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 _SalesOrderDetailState(); +} + + +class _SalesOrderDetailState extends RefreshableState { + + _SalesOrderDetailState(); + + List lines = []; + + bool supportsProjectCodes = false; + int completedLines = 0; + int attachmentCount = 0; + + @override + String getAppBarTitle() => L10().salesOrder; + + @override + List appBarActions(BuildContext context) { + List 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 _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 actionButtons(BuildContext context) { + List 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 barcodeButtons(BuildContext context) { + List actions = []; + + // TODO + + return actions; + } + + @override + Future 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 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 orderTiles(BuildContext context) { + + List 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 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 getTabs(BuildContext context) { + return [ + ListView(children: orderTiles(context)), + PaginatedSOLineList({"order": widget.order.pk.toString()}), + // Center(), // TODO: Delivered stock + ]; + } + +} \ No newline at end of file diff --git a/lib/widget/order/sales_order_list.dart b/lib/widget/order/sales_order_list.dart new file mode 100644 index 00000000..a97de809 --- /dev/null +++ b/lib/widget/order/sales_order_list.dart @@ -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 filters; + + @override + _SalesOrderListWidgetState createState() => _SalesOrderListWidgetState(); + +} + +class _SalesOrderListWidgetState extends RefreshableState { + + _SalesOrderListWidgetState(); + + @override + String getAppBarTitle() => L10().salesOrders; + + @override + List actionButtons(BuildContext context) { + List 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 _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 data = result as Map; + + if (data.containsKey("pk")) { + var order = InvenTreeSalesOrder.fromJson(data); + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SalesOrderDetailWidget(order) + ) + ); + } + } + ); + } + + @override + List 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 filters) : super(filters: filters); + + @override + String get searchTitle => L10().salesOrders; + + @override + _PaginatedSalesOrderListState createState() => _PaginatedSalesOrderListState(); + +} + + +class _PaginatedSalesOrderListState extends PaginatedSearchState { + + _PaginatedSalesOrderListState() : super(); + + @override + String get prefix => "so_"; + + @override + Map get orderingOptions => { + "reference": L10().reference, + "status": L10().status, + "target_date": L10().targetDate, + "customer__name": L10().customer, + }; + + @override + Map> get filterOptions => { + "outstanding": { + "label": L10().outstanding, + "help_text": L10().outstandingOrderDetail, + "tristate": true, + }, + "overdue": { + "label": L10().overdue, + "help_text": L10().overdueDetail, + "tristate": true, + } + }; + + @override + Future requestPage(int limit, int offset, Map 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) + ) + ); + } + ); + + } + +} \ No newline at end of file diff --git a/lib/widget/order/so_line_detail.dart b/lib/widget/order/so_line_detail.dart new file mode 100644 index 00000000..e0ddd898 --- /dev/null +++ b/lib/widget/order/so_line_detail.dart @@ -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 { + + _SOLineDetailWidgetState(); + + @override + String getAppBarTitle() => L10().lineItem; + + @override + List appBarActions(BuildContext context) { + List actions = []; + + if (widget.item.canEdit) { + actions.add( + IconButton( + icon: Icon(Icons.edit_square), + onPressed: () { + _editLineItem(context); + }), + ); + } + + return actions; + } + + Future _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 actionButtons(BuildContext context) { + // TODO + return []; + } + + @override + Future request(BuildContext context) async { + await widget.item.reload(); + } + + @override + List getTiles(BuildContext context) { + List 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; + } +} \ No newline at end of file diff --git a/lib/widget/order/so_line_list.dart b/lib/widget/order/so_line_list.dart new file mode 100644 index 00000000..6d7e1acd --- /dev/null +++ b/lib/widget/order/so_line_list.dart @@ -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 filters) : super(filters: filters); + + @override + String get searchTitle => L10().lineItems; + + @override + _PaginatedSOLineListState createState() => _PaginatedSOLineListState(); + +} + + +/* + * State class for PaginatedSOLineList + */ +class _PaginatedSOLineListState extends PaginatedSearchState { + + _PaginatedSOLineListState() : super(); + + @override + String get prefix => "so_line_"; + + @override + Map get orderingOptions => { + "part": L10().part, + "quantity": L10().quantity, + }; + + @override + Map> get filterOptions => { + + }; + + @override + Future requestPage(int limit, int offset, Map 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)), + ); + } + } + +} diff --git a/lib/widget/bom_list.dart b/lib/widget/part/bom_list.dart similarity index 98% rename from lib/widget/bom_list.dart rename to lib/widget/part/bom_list.dart index a39b2773..d9e16bb3 100644 --- a/lib/widget/bom_list.dart +++ b/lib/widget/part/bom_list.dart @@ -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"; diff --git a/lib/widget/category_display.dart b/lib/widget/part/category_display.dart similarity index 97% rename from lib/widget/category_display.dart rename to lib/widget/part/category_display.dart index 261840af..c399cef5 100644 --- a/lib/widget/category_display.dart +++ b/lib/widget/part/category_display.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"; diff --git a/lib/widget/category_list.dart b/lib/widget/part/category_list.dart similarity index 97% rename from lib/widget/category_list.dart rename to lib/widget/part/category_list.dart index c1c95514..a2031027 100644 --- a/lib/widget/category_list.dart +++ b/lib/widget/part/category_list.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"; diff --git a/lib/widget/part_detail.dart b/lib/widget/part/part_detail.dart similarity index 97% rename from lib/widget/part_detail.dart rename to lib/widget/part/part_detail.dart index 1079fb18..8009ee68 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part/part_detail.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 { fields.remove("serial"); // Hide the "part" field - fields["part"]["hidden"] = true; + fields["part"]?["hidden"] = true; int? default_location = part.defaultLocation; diff --git a/lib/widget/part_image_widget.dart b/lib/widget/part/part_image_widget.dart similarity index 100% rename from lib/widget/part_image_widget.dart rename to lib/widget/part/part_image_widget.dart diff --git a/lib/widget/part_list.dart b/lib/widget/part/part_list.dart similarity index 98% rename from lib/widget/part_list.dart rename to lib/widget/part/part_list.dart index 6def9b1b..80dbdbd7 100644 --- a/lib/widget/part_list.dart +++ b/lib/widget/part/part_list.dart @@ -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"; diff --git a/lib/widget/part_parameter_widget.dart b/lib/widget/part/part_parameter_widget.dart similarity index 100% rename from lib/widget/part_parameter_widget.dart rename to lib/widget/part/part_parameter_widget.dart diff --git a/lib/widget/part_suppliers.dart b/lib/widget/part/part_suppliers.dart similarity index 96% rename from lib/widget/part_suppliers.dart rename to lib/widget/part/part_suppliers.dart index b9fb05ac..ae3788d3 100644 --- a/lib/widget/part_suppliers.dart +++ b/lib/widget/part/part_suppliers.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 { diff --git a/lib/widget/progress.dart b/lib/widget/progress.dart index 84174690..ff13ceea 100644 --- a/lib/widget/progress.dart +++ b/lib/widget/progress.dart @@ -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 diff --git a/lib/widget/search.dart b/lib/widget/search.dart index a719633d..a899c83c 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -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 diff --git a/lib/widget/location_display.dart b/lib/widget/stock/location_display.dart similarity index 98% rename from lib/widget/location_display.dart rename to lib/widget/stock/location_display.dart index 4e89f86a..e44e726e 100644 --- a/lib/widget/location_display.dart +++ b/lib/widget/stock/location_display.dart @@ -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"; diff --git a/lib/widget/location_list.dart b/lib/widget/stock/location_list.dart similarity index 97% rename from lib/widget/location_list.dart rename to lib/widget/stock/location_list.dart index fe396593..a8bc2fe9 100644 --- a/lib/widget/location_list.dart +++ b/lib/widget/stock/location_list.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"; diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock/stock_detail.dart similarity index 98% rename from lib/widget/stock_detail.dart rename to lib/widget/stock/stock_detail.dart index b917e01a..d9b4edf2 100644 --- a/lib/widget/stock_detail.dart +++ b/lib/widget/stock/stock_detail.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"; diff --git a/lib/widget/stock_item_history.dart b/lib/widget/stock/stock_item_history.dart similarity index 100% rename from lib/widget/stock_item_history.dart rename to lib/widget/stock/stock_item_history.dart diff --git a/lib/widget/stock_item_test_results.dart b/lib/widget/stock/stock_item_test_results.dart similarity index 100% rename from lib/widget/stock_item_test_results.dart rename to lib/widget/stock/stock_item_test_results.dart diff --git a/lib/widget/stock_list.dart b/lib/widget/stock/stock_list.dart similarity index 98% rename from lib/widget/stock_list.dart rename to lib/widget/stock/stock_list.dart index 3fa474ab..c24c35ca 100644 --- a/lib/widget/stock_list.dart +++ b/lib/widget/stock/stock_list.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";