From e23a8b4d5efefe76b969dfc7539c25ae56db3a5b Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 21 Apr 2023 21:12:22 +1000 Subject: [PATCH] Project code support (#336) * Determine if project codes are supported * Add helpers for boolean functions * Adds helper methods for generic "model" class - Will allow us to do some good refactoring * Refactor the refactor * Add debug support and getMap function * Major refactoring for model data accessors * Handle null values * Add sentry reporting if key is used incorrectly * Fix typo * Refactor createFromJson function * Add model for ProjectCode * Display and edit project code for purchase orders --- assets/release_notes.md | 1 + lib/api.dart | 15 +++ lib/api_form.dart | 8 ++ lib/inventree/bom.dart | 14 ++- lib/inventree/company.dart | 94 ++++++++--------- lib/inventree/model.dart | 145 ++++++++++++++++++++------ lib/inventree/notification.dart | 4 +- lib/inventree/part.dart | 139 ++++++++++-------------- lib/inventree/project_code.dart | 28 +++++ lib/inventree/purchase_order.dart | 76 +++++++------- lib/inventree/stock.dart | 135 +++++++++--------------- lib/l10n/app_en.arb | 3 + lib/widget/purchase_order_detail.dart | 20 ++++ 13 files changed, 383 insertions(+), 299 deletions(-) create mode 100644 lib/inventree/project_code.dart diff --git a/assets/release_notes.md b/assets/release_notes.md index 20e102f0..1d67fe51 100644 --- a/assets/release_notes.md +++ b/assets/release_notes.md @@ -1,6 +1,7 @@ ### 0.11.6 - April 2023 --- +- Add support for Project Codes - Fix action button colors - Added Norwegian translations - Fix serial number field when creating stock item diff --git a/lib/api.dart b/lib/api.dart index 0950eeb1..7a41572d 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -300,6 +300,9 @@ class InvenTreeAPI { // Order barcodes API v107 or newer bool get supportsOrderBarcodes => isConnected() && apiVersion >= 107; + // Project codes require v109 or newer + bool get supportsProjectCodes => isConnected() && apiVersion >= 109; + // Are plugins enabled on the server? bool _pluginsEnabled = false; @@ -1362,6 +1365,12 @@ class InvenTreeAPI { } } + // Return a boolean global setting value + Future getGlobalBooleanSetting(String key) async { + String value = await getGlobalSetting(key); + return value.toLowerCase() == "true"; + } + Future getUserSetting(String key) async { if (!supportsSettings) return ""; @@ -1382,6 +1391,12 @@ class InvenTreeAPI { } } + // Return a boolean user setting value + Future getUserBooleanSetting(String key) async { + String value = await getUserSetting(key); + return value.toLowerCase() == "true"; + } + /* * Send a request to the server to locate / identify either a StockItem or StockLocation */ diff --git a/lib/api_form.dart b/lib/api_form.dart index 1b723604..d08d29a3 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -15,6 +15,7 @@ import "package:inventree/l10.dart"; import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/part.dart"; +import "package:inventree/inventree/project_code.dart"; import "package:inventree/inventree/sentry.dart"; import "package:inventree/inventree/stock.dart"; @@ -667,6 +668,13 @@ class APIFormField { height: 40 ) ); + case "projectcode": + var project_code = InvenTreeProjectCode.fromJson(data); + return ListTile( + title: Text(project_code.code), + subtitle: Text(project_code.description), + leading: FaIcon(FontAwesomeIcons.list) + ); default: return ListTile( title: Text( diff --git a/lib/inventree/bom.dart b/lib/inventree/bom.dart index ec326fef..cf708c6f 100644 --- a/lib/inventree/bom.dart +++ b/lib/inventree/bom.dart @@ -12,9 +12,7 @@ class InvenTreeBomItem extends InvenTreeModel { InvenTreeBomItem.fromJson(Map json) : super.fromJson(json); @override - InvenTreeModel createFromJson(Map json) { - return InvenTreeBomItem.fromJson(json); - } + InvenTreeModel createFromJson(Map json) => InvenTreeBomItem.fromJson(json); @override String get URL => "bom/"; @@ -36,13 +34,13 @@ class InvenTreeBomItem extends InvenTreeModel { } // Extract the 'reference' value associated with this BomItem - String get reference => (jsondata["reference"] ?? "") as String; - + String get reference => getString("reference"); + // Extract the 'quantity' value associated with this BomItem - double get quantity => double.tryParse(jsondata["quantity"].toString()) ?? 0; + double get quantity => getDouble("quantity"); // Extract the ID of the related part - int get partId => int.tryParse(jsondata["part"].toString()) ?? -1; + int get partId => getInt("part"); // Return a Part instance for the referenced part InvenTreePart? get part { @@ -69,5 +67,5 @@ class InvenTreeBomItem extends InvenTreeModel { } // Extract the ID of the related sub-part - int get subPartId => int.tryParse(jsondata["sub_part"].toString()) ?? -1; + int get subPartId => getInt("sub_part"); } \ No newline at end of file diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart index 7af1229a..78f83d39 100644 --- a/lib/inventree/company.dart +++ b/lib/inventree/company.dart @@ -38,22 +38,22 @@ class InvenTreeCompany extends InvenTreeModel { String get thumbnail => (jsondata["thumbnail"] ?? jsondata["image"] ?? InvenTreeAPI.staticThumb) as String; - String get website => (jsondata["website"] ?? "") as String; + String get website => getString("website"); + + String get phone => getString("phone"); - String get phone => (jsondata["phone"] ?? "") as String; + String get email => getString("email"); - String get email => (jsondata["email"] ?? "") as String; + bool get isSupplier => getBool("is_supplier"); - bool get isSupplier => (jsondata["is_supplier"] ?? false) as bool; + bool get isManufacturer => getBool("is_manufacturer"); - bool get isManufacturer => (jsondata["is_manufacturer"] ?? false) as bool; - - bool get isCustomer => (jsondata["is_customer"] ?? false) as bool; - - int get partSuppliedCount => (jsondata["parts_supplied"] ?? 0) as int; - - int get partManufacturedCount => (jsondata["parts_manufactured"] ?? 0) as int; + bool get isCustomer => getBool("is_customer"); + int get partSuppliedCount => getInt("part_supplied"); + + int get partManufacturedCount => getInt("parts_manufactured"); + // Request a list of purchase orders against this company Future> getPurchaseOrders({bool? outstanding}) async { @@ -81,11 +81,7 @@ class InvenTreeCompany extends InvenTreeModel { } @override - InvenTreeModel createFromJson(Map json) { - var company = InvenTreeCompany.fromJson(json); - - return company; - } + InvenTreeModel createFromJson(Map json) => InvenTreeCompany.fromJson(json); } @@ -154,40 +150,36 @@ class InvenTreeSupplierPart extends InvenTreeModel { return _filters(); } - int get manufacturerId => (jsondata["manufacturer_detail"]["pk"] ?? -1) as int; - - String get manufacturerName => (jsondata["manufacturer_detail"]?["name"] ?? "") as String; - - String get MPN => (jsondata["manufacturer_part_detail"]?["MPN"] ?? "") as String; - + int get manufacturerId => getInt("pk", subKey: "manufacturer_detail"); + + String get manufacturerName => getString("name", subKey: "manufacturer_detail"); + + String get MPN => getString("MPN", subKey: "manufacturer_part_detail"); + String get manufacturerImage => (jsondata["manufacturer_detail"]?["image"] ?? jsondata["manufacturer_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; - int get manufacturerPartId => (jsondata["manufacturer_part"] ?? -1) as int; - - int get supplierId => (jsondata["supplier"] ?? -1) as int; - - String get supplierName => (jsondata["supplier_detail"]?["name"] ?? "") as String; - + int get manufacturerPartId => getInt("manufacturer_part"); + + int get supplierId => getInt("supplier"); + + String get supplierName => getString("name", subKey: "supplier_detail"); + String get supplierImage => (jsondata["supplier_detail"]?["image"] ?? jsondata["supplier_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; - String get SKU => (jsondata["SKU"] ?? "") as String; - - int get partId => (jsondata["part"] ?? -1) as int; - + String get SKU => getString("SKU"); + + int get partId => getInt("part"); + String get partImage => (jsondata["part_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; - String get partName => (jsondata["part_detail"]?["full_name"] ?? "") as String; - - String get partDescription => (jsondata["part_detail"]?["description"] ?? "") as String; - - String get note => (jsondata["note"] ?? "") as String; + String get partName => getString("full_name", subKey: "part_detail"); + + String get partDescription => getString("description", subKey: "part_detail"); + + String get note => getString("note"); @override - InvenTreeModel createFromJson(Map json) { - var part = InvenTreeSupplierPart.fromJson(json); - - return part; - } + InvenTreeModel createFromJson(Map json) => InvenTreeSupplierPart.fromJson(json); } @@ -207,16 +199,12 @@ class InvenTreeManufacturerPart extends InvenTreeModel { }; } - int get partId => (jsondata["part"] ?? -1) as int; - - int get manufacturerId => (jsondata["manufacturer"] ?? -1) as int; - - String get MPN => (jsondata["MPN"] ?? "") as String; - + int get partId => getInt("part"); + + int get manufacturerId => getInt("manufacturer"); + + String get MPN => getString("MPN"); + @override - InvenTreeModel createFromJson(Map json) { - var part = InvenTreeManufacturerPart.fromJson(json); - - return part; - } + InvenTreeModel createFromJson(Map json) => InvenTreeManufacturerPart.fromJson(json); } diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index 576329fd..97b2f162 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -63,6 +63,96 @@ class InvenTreeModel { // Note: If the WEB_URL is the same (except for /api/) as URL then just leave blank String get WEB_URL => ""; + // Helper function to set a value in the JSON data + void setValue(String key, dynamic value) { + jsondata[key] = value; + } + + // return a dynamic value from the JSON data + // optionally we can specifiy a "subKey" to get a value from a sub-dictionary + dynamic getValue(String key, {dynamic backup, String subKey = ""}) { + Map data = jsondata; + + // If a subKey is specified, we need to dig deeper into the JSON data + if (subKey.isNotEmpty) { + + if (!data.containsKey(subKey)) { + debug("JSON data does not contain subKey '$subKey' for key '$key'"); + return backup; + } + + dynamic sub_data = data[subKey]; + + if (sub_data is Map) { + data = (data[subKey] ?? {}) as Map; + } + + } + + if (data.containsKey(key)) { + return data[key]; + } else { + debug("JSON data does not contain key '$key' (subKey '${subKey}')"); + return backup; + } + } + + // Helper function to get sub-map from JSON data + Map getMap(String key, {Map backup = const {}, String subKey = ""}) { + dynamic value = getValue(key, backup: backup, subKey: subKey); + + if (value == null) { + return backup; + } + + return value as Map; + } + + // Helper function to get string value from JSON data + String getString(String key, {String backup = "", String subKey = ""}) { + dynamic value = getValue(key, backup: backup, subKey: subKey); + + if (value == null) { + return backup; + } + + return value.toString(); + } + + // Helper function to get integer value from JSON data + int getInt(String key, {int backup = -1, String subKey = ""}) { + dynamic value = getValue(key, backup: backup, subKey: subKey); + + if (value == null) { + return backup; + } + + return int.tryParse(value.toString()) ?? backup; + } + + // Helper function to get double value from JSON data + double getDouble(String key, {double backup = 0.0, String subKey = ""}) { + dynamic value = getValue(key, backup: backup, subKey: subKey); + + if (value == null) { + return backup; + } + + return double.tryParse(value.toString()) ?? backup; + } + + // Helper function to get boolean value from json data + bool getBool(String key, {bool backup = false, String subKey = ""}) { + dynamic value = getValue(key, backup: backup, subKey: subKey); + + if (value == null) { + return backup; + } + + return value.toString().toLowerCase() == "true"; + } + + // Return the InvenTree web server URL for this object String get webUrl { if (api.isConnected()) { @@ -191,16 +281,16 @@ class InvenTreeModel { // Accessor for the API InvenTreeAPI get api => InvenTreeAPI(); - int get pk => (jsondata["pk"] ?? -1) as int; - + int get pk => getInt("pk"); + // Some common accessors - String get name => (jsondata["name"] ?? "") as String; + String get name => getString("name"); - String get description => (jsondata["description"] ?? "") as String; + String get description => getString("description"); + + String get notes => getString("notes"); - String get notes => (jsondata["notes"] ?? "") as String; - - int get parentId => (jsondata["parent"] ?? -1) as int; + int get parentId => getInt("parent"); // Legacy API provided external link as "URL", while newer API uses "link" String get link => (jsondata["link"] ?? jsondata["URL"] ?? "") as String; @@ -297,15 +387,10 @@ class InvenTreeModel { } } - String get keywords => (jsondata["keywords"] ?? "") as String; - + String get keywords => getString("keywords"); + // Create a new object from JSON data (not a constructor!) - InvenTreeModel createFromJson(Map json) { - - var obj = InvenTreeModel.fromJson(json); - - return obj; - } + InvenTreeModel createFromJson(Map json) => InvenTreeModel.fromJson(json); // Return the API detail endpoint for this Model object String get url => "${URL}/${pk}/".replaceAll("//", "/"); @@ -746,9 +831,7 @@ class InvenTreePlugin extends InvenTreeModel { InvenTreePlugin.fromJson(Map json) : super.fromJson(json); @override - InvenTreeModel createFromJson(Map json) { - return InvenTreePlugin.fromJson(json); - } + InvenTreeModel createFromJson(Map json) => InvenTreePlugin.fromJson(json); @override String get URL { @@ -765,10 +848,10 @@ class InvenTreePlugin extends InvenTreeModel { } } - String get key => (jsondata["key"] ?? "") as String; - - bool get active => (jsondata["active"] ?? false) as bool; - + String get key => getString("key"); + + bool get active => getBool("active"); + // Return the metadata struct for this plugin Map get _meta => (jsondata["meta"] ?? {}) as Map; @@ -803,11 +886,11 @@ class InvenTreeGlobalSetting extends InvenTreeModel { @override String get URL => "settings/global/"; - String get key => (jsondata["key"] ?? "") as String; - - String get value => (jsondata["value"] ?? "") as String; - - String get type => (jsondata["type"] ?? "") as String; + String get key => getString("key"); + + String get value => getString("value"); + + String get type => getString("type"); } @@ -836,8 +919,8 @@ class InvenTreeAttachment extends InvenTreeModel { // Override this reference field for any subclasses String get REFERENCE_FIELD => ""; - String get attachment => (jsondata["attachment"] ?? "") as String; - + String get attachment => getString("attachment"); + // Return the filename of the attachment String get filename { return attachment.split("/").last; @@ -874,8 +957,8 @@ class InvenTreeAttachment extends InvenTreeModel { return FontAwesomeIcons.fileLines; } - String get comment => (jsondata["comment"] ?? "") as String; - + String get comment => getString("comment"); + DateTime? get uploadDate { if (jsondata.containsKey("upload_date")) { return DateTime.tryParse((jsondata["upload_date"] ?? "") as String); diff --git a/lib/inventree/notification.dart b/lib/inventree/notification.dart index a533319b..ae79fa2c 100644 --- a/lib/inventree/notification.dart +++ b/lib/inventree/notification.dart @@ -27,8 +27,8 @@ class InvenTreeNotification extends InvenTreeModel { }; } - String get message => (jsondata["message"] ?? "") as String; - + String get message => getString("message"); + DateTime? get creationDate { if (jsondata.containsKey("creation")) { return DateTime.tryParse((jsondata["creation"] ?? "") as String); diff --git a/lib/inventree/part.dart b/lib/inventree/part.dart index e2ef2d91..b476937a 100644 --- a/lib/inventree/part.dart +++ b/lib/inventree/part.dart @@ -43,8 +43,8 @@ class InvenTreePartCategory extends InvenTreeModel { return fields; } - String get pathstring => (jsondata["pathstring"] ?? "") as String; - + String get pathstring => getString("pathstring"); + String get parentPathString { List psplit = pathstring.split("/"); @@ -67,11 +67,7 @@ class InvenTreePartCategory extends InvenTreeModel { int get partcount => (jsondata["part_count"] ?? jsondata["parts"] ?? 0) as int; @override - InvenTreeModel createFromJson(Map json) { - var cat = InvenTreePartCategory.fromJson(json); - - return cat; - } + InvenTreeModel createFromJson(Map json) => InvenTreePartCategory.fromJson(json); } @@ -87,22 +83,18 @@ class InvenTreePartTestTemplate extends InvenTreeModel { @override String get URL => "part/test-template/"; - String get key => (jsondata["key"] ?? "") as String; + String get key => getString("key"); - String get testName => (jsondata["test_name"] ?? "") as String; + String get testName => getString("test_name"); - bool get required => (jsondata["required"] ?? false) as bool; + bool get required => getBool("required"); + + bool get requiresValue => getBool("requires_value"); - bool get requiresValue => (jsondata["requires_value"] ?? false) as bool; - - bool get requiresAttachment => (jsondata["requires_attachment"] ?? false) as bool; + bool get requiresAttachment => getBool("requires_attachment"); @override - InvenTreeModel createFromJson(Map json) { - var template = InvenTreePartTestTemplate.fromJson(json); - - return template; - } + InvenTreeModel createFromJson(Map json) => InvenTreePartTestTemplate.fromJson(json); bool passFailStatus() { @@ -142,9 +134,7 @@ class InvenTreePartParameter extends InvenTreeModel { String get URL => "part/parameter/"; @override - InvenTreeModel createFromJson(Map json) { - return InvenTreePartParameter.fromJson(json); - } + InvenTreeModel createFromJson(Map json) => InvenTreePartParameter.fromJson(json); @override Map formFields() { @@ -152,13 +142,13 @@ class InvenTreePartParameter extends InvenTreeModel { } @override - String get name => (jsondata["template_detail"]?["name"] ?? "") as String; + String get name => getString("name", subKey: "template_detail"); @override - String get description => (jsondata["template_detail"]?["description"] ?? "") as String; - - String get value => jsondata["data"] as String; - + String get description => getString("description", subKey: "template_detail"); + + String get value => getString("data"); + String get valueString { String v = value; @@ -170,7 +160,7 @@ class InvenTreePartParameter extends InvenTreeModel { return v; } - String get units => (jsondata["template_detail"]?["units"] ?? "") as String; + String get units => getString("units", subKey: "template_detail"); } /* @@ -254,8 +244,8 @@ class InvenTreePart extends InvenTreeModel { }); } - int get supplierCount => (jsondata["suppliers"] ?? 0) as int; - + int get supplierCount => getInt("suppliers", backup: 0); + // Request supplier parts for this part Future> getSupplierParts() async { List _supplierParts = []; @@ -301,40 +291,26 @@ class InvenTreePart extends InvenTreeModel { int? get defaultLocation => jsondata["default_location"] as int?; - // Get the number of stock on order for this Part - double get onOrder => double.tryParse(jsondata["ordering"].toString()) ?? 0; + double get onOrder => getDouble("ordering"); - String get onOrderString { + String get onOrderString => simpleNumberString(onOrder); - return simpleNumberString(onOrder); + double get inStock => getDouble("in_stock"); + + String get inStockString => simpleNumberString(inStock); + + // Get the 'available stock' for this Part + double get unallocatedStock { + + // Note that the 'available_stock' was not added until API v35 + if (jsondata.containsKey("unallocated_stock")) { + return double.tryParse(jsondata["unallocated_stock"].toString()) ?? 0; + } else { + return inStock; } + } - // Get the stock count for this Part - double get inStock => double.tryParse(jsondata["in_stock"].toString()) ?? 0; - - String get inStockString { - - String q = simpleNumberString(inStock); - - return q; - } - - // Get the 'available stock' for this Part - double get unallocatedStock { - - // Note that the 'available_stock' was not added until API v35 - if (jsondata.containsKey("unallocated_stock")) { - return double.tryParse(jsondata["unallocated_stock"].toString()) ?? 0; - } else { - return inStock; - } - } - - String get unallocatedStockString { - String q = simpleNumberString(unallocatedStock); - - return q; - } + String get unallocatedStockString => simpleNumberString(unallocatedStock); String stockString({bool includeUnits = true}) { String q = unallocatedStockString; @@ -350,39 +326,39 @@ class InvenTreePart extends InvenTreeModel { return q; } - String get units => (jsondata["units"] ?? "") as String; + String get units => getString("units"); // Get the ID of the Part that this part is a variant of (or null) int? get variantOf => jsondata["variant_of"] as int?; // Get the number of units being build for this Part - double get building => double.tryParse(jsondata["building"].toString()) ?? 0; + double get building => getDouble("building"); // Get the number of BOMs this Part is used in (if it is a component) - int get usedInCount => (jsondata["used_in"] ?? 0) as int; + int get usedInCount => jsondata.containsKey("used_in") ? getInt("used_in", backup: 0) : 0; - bool get isAssembly => (jsondata["assembly"] ?? false) as bool; + bool get isAssembly => getBool("assembly"); - bool get isComponent => (jsondata["component"] ?? false) as bool; + bool get isComponent => getBool("component"); - bool get isPurchaseable => (jsondata["purchaseable"] ?? false) as bool; + bool get isPurchaseable => getBool("purchaseable"); - bool get isSalable => (jsondata["salable"] ?? false) as bool; + bool get isSalable => getBool("salable"); - bool get isActive => (jsondata["active"] ?? false) as bool; + bool get isActive => getBool("active"); - bool get isVirtual => (jsondata["virtual"] ?? false) as bool; + bool get isVirtual => getBool("virtual"); - bool get isTrackable => (jsondata["trackable"] ?? false) as bool; + bool get isTrackable => getBool("trackable"); // Get the IPN (internal part number) for the Part instance - String get IPN => (jsondata["IPN"] ?? "") as String; + String get IPN => getString("IPN"); // Get the revision string for the Part instance - String get revision => (jsondata["revision"] ?? "") as String; + String get revision => getString("revision"); // Get the category ID for the Part instance (or "null" if does not exist) - int get categoryId => (jsondata["category"] ?? -1) as int; + int get categoryId => getInt("category"); // Get the category name for the Part instance String get categoryName { @@ -404,15 +380,15 @@ class InvenTreePart extends InvenTreeModel { return (jsondata["category_detail"]?["description"] ?? "") as String; } // Get the image URL for the Part instance - String get _image => (jsondata["image"] ?? "") as String; + String get _image => getString("image"); // Get the thumbnail URL for the Part instance - String get _thumbnail => (jsondata["thumbnail"] ?? "") as String; + String get _thumbnail => getString("thumbnail"); // Return the fully-qualified name for the Part instance String get fullname { - String fn = (jsondata["full_name"] ?? "") as String; + String fn = getString("full_name"); if (fn.isNotEmpty) return fn; @@ -456,15 +432,10 @@ class InvenTreePart extends InvenTreeModel { } // Return the "starred" status of this part - bool get starred => (jsondata["starred"] ?? false) as bool; + bool get starred => getBool("starred"); @override - InvenTreeModel createFromJson(Map json) { - - var part = InvenTreePart.fromJson(json); - - return part; - } + InvenTreeModel createFromJson(Map json) => InvenTreePart.fromJson(json); } /* @@ -483,8 +454,6 @@ class InvenTreePartAttachment extends InvenTreeAttachment { String get URL => "part/attachment/"; @override - InvenTreeModel createFromJson(Map json) { - return InvenTreePartAttachment.fromJson(json); - } + InvenTreeModel createFromJson(Map json) => InvenTreePartAttachment.fromJson(json); } diff --git a/lib/inventree/project_code.dart b/lib/inventree/project_code.dart new file mode 100644 index 00000000..8e2c75b6 --- /dev/null +++ b/lib/inventree/project_code.dart @@ -0,0 +1,28 @@ +import "package:inventree/inventree/model.dart"; + + +/* + * Class representing the ProjectCode database model + */ +class InvenTreeProjectCode extends InvenTreeModel { + + InvenTreeProjectCode() : super(); + + InvenTreeProjectCode.fromJson(Map json) : super.fromJson(json); + + @override + InvenTreeModel createFromJson(Map json) => InvenTreeProjectCode.fromJson(json); + + @override + String get URL => "project-code/"; + + @override + Map formFields() { + return { + "code": {}, + "description": {}, + }; + } + + String get code => getString("code"); +} diff --git a/lib/inventree/purchase_order.dart b/lib/inventree/purchase_order.dart index 53a297a0..e22d009f 100644 --- a/lib/inventree/purchase_order.dart +++ b/lib/inventree/purchase_order.dart @@ -34,6 +34,7 @@ class InvenTreePurchaseOrder extends InvenTreeModel { }, "supplier_reference": {}, "description": {}, + "project_code": {}, "target_date": {}, "link": {}, "responsible": {}, @@ -59,23 +60,32 @@ class InvenTreePurchaseOrder extends InvenTreeModel { }; } - String get issueDate => (jsondata["issue_date"] ?? "") as String; + String get issueDate => getString("issue_date"); - String get completeDate => (jsondata["complete_date"] ?? "") as String; + String get completeDate => getString("complete_date"); - String get creationDate => (jsondata["creation_date"] ?? "") as String; + String get creationDate => getString("creation_date"); - String get targetDate => (jsondata["target_date"] ?? "") as String; + String get targetDate => getString("target_date"); - int get lineItemCount => (jsondata["line_items"] ?? 0) as int; + int get lineItemCount => getInt("line_items", backup: 0); + + bool get overdue => getBool("overdue"); - bool get overdue => (jsondata["overdue"] ?? false) as bool; + String get reference => getString("reference"); - String get reference => (jsondata["reference"] ?? "") as String; + int get responsibleId => getInt("responsible"); - int get responsibleId => (jsondata["responsible"] ?? -1) as int; + int get supplierId => getInt("supplier"); - int get supplierId => (jsondata["supplier"] ?? -1) as int; + // 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 { @@ -88,11 +98,11 @@ class InvenTreePurchaseOrder extends InvenTreeModel { } } - String get supplierReference => (jsondata["supplier_reference"] ?? "") as String; + String get supplierReference => getString("supplier_reference"); - int get status => (jsondata["status"] ?? -1) as int; + int get status => getInt("status"); - String get statusText => (jsondata["status_text"] ?? "") as String; + String get statusText => getString("status_text"); bool get isOpen => status == PO_STATUS_PENDING || status == PO_STATUS_PLACED; @@ -103,7 +113,7 @@ class InvenTreePurchaseOrder extends InvenTreeModel { bool get isFailed => status == PO_STATUS_CANCELLED || status == PO_STATUS_LOST || status == PO_STATUS_RETURNED; double? get totalPrice { - String price = (jsondata["total_price"] ?? "") as String; + String price = getString("total_price"); if (price.isEmpty) { return null; @@ -112,7 +122,7 @@ class InvenTreePurchaseOrder extends InvenTreeModel { } } - String get totalPriceCurrency => (jsondata["total_price_currency"] ?? "") as String; + String get totalPriceCurrency => getString("total_price_currency"); Future> getLineItems() async { @@ -134,9 +144,7 @@ class InvenTreePurchaseOrder extends InvenTreeModel { } @override - InvenTreeModel createFromJson(Map json) { - return InvenTreePurchaseOrder.fromJson(json); - } + InvenTreeModel createFromJson(Map json) => InvenTreePurchaseOrder.fromJson(json); /// Mark this order as "placed" / "issued" Future issueOrder() async { @@ -199,17 +207,17 @@ class InvenTreePOLineItem extends InvenTreeModel { bool get isComplete => received >= quantity; - double get quantity => (jsondata["quantity"] ?? 0) as double; + double get quantity => getDouble("quantity"); - double get received => (jsondata["received"] ?? 0) as double; + double get received => getDouble("received"); double get outstanding => quantity - received; - String get reference => (jsondata["reference"] ?? "") as String; + String get reference => getString("reference"); - int get orderId => (jsondata["order"] ?? -1) as int; + int get orderId => getInt("order"); - int get supplierPartId => (jsondata["part"] ?? -1) as int; + int get supplierPartId => getInt("part"); InvenTreePart? get part { dynamic part_detail = jsondata["part_detail"]; @@ -232,20 +240,19 @@ class InvenTreePOLineItem extends InvenTreeModel { } } - double get purchasePrice => double.parse((jsondata["purchase_price"] ?? "") as String); + double get purchasePrice => getDouble("purchase_price"); + + String get purchasePriceCurrency => getString("purchase_price_currency"); - String get purchasePriceCurrency => (jsondata["purchase_price_currency"] ?? "") as String; + String get purchasePriceString => getString("purchase_price_string"); - String get purchasePriceString => (jsondata["purchase_price_string"] ?? "") as String; - - int get destination => (jsondata["destination"] ?? -1) as int; - - Map get destinationDetail => (jsondata["destination_detail"] ?? {}) as Map; + int get destination => getInt("destination"); + Map get destinationDetail => getMap("destination_detail"); + @override - InvenTreeModel createFromJson(Map json) { - return InvenTreePOLineItem.fromJson(json); - } + InvenTreeModel createFromJson(Map json) => InvenTreePOLineItem.fromJson(json); + } /* @@ -264,7 +271,6 @@ class InvenTreePurchaseOrderAttachment extends InvenTreeAttachment { String get URL => "order/po/attachment/"; @override - InvenTreeModel createFromJson(Map json) { - return InvenTreePurchaseOrderAttachment.fromJson(json); - } + InvenTreeModel createFromJson(Map json) => InvenTreePurchaseOrderAttachment.fromJson(json); + } diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index 10b54484..4a026172 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -35,18 +35,18 @@ class InvenTreeStockItemTestResult extends InvenTreeModel { }; } - String get key => (jsondata["key"] ?? "") as String; - - String get testName => (jsondata["test"] ?? "") as String; - - bool get result => (jsondata["result"] ?? false) as bool; - - String get value => (jsondata["value"] ?? "") as String; - - String get attachment => (jsondata["attachment"] ?? "") as String; - - String get date => (jsondata["date"] ?? "") as String; + String get key => getString("key"); + + String get testName => getString("test"); + bool get result => getBool("result"); + + String get value => getString("value"); + + String get attachment => getString("attachment"); + + String get date => getString("date"); + @override InvenTreeStockItemTestResult createFromJson(Map json) { var result = InvenTreeStockItemTestResult.fromJson(json); @@ -63,9 +63,7 @@ class InvenTreeStockItemHistory extends InvenTreeModel { InvenTreeStockItemHistory.fromJson(Map json) : super.fromJson(json); @override - InvenTreeModel createFromJson(Map json) { - return InvenTreeStockItemHistory.fromJson(json); - } + InvenTreeModel createFromJson(Map json) => InvenTreeStockItemHistory.fromJson(json); @override String get URL => "stock/track/"; @@ -98,16 +96,10 @@ class InvenTreeStockItemHistory extends InvenTreeModel { return DateFormat("yyyy-MM-dd").format(d); } - String get label => (jsondata["label"] ?? "") as String; - + String get label => getString("label"); + // Return the "deltas" associated with this historical object - Map get deltas { - if (jsondata.containsKey("deltas")) { - return jsondata["deltas"] as Map; - } else { - return {}; - } - } + Map get deltas => getMap("deltas"); // Return the quantity string for this historical object String get quantityString { @@ -122,12 +114,13 @@ class InvenTreeStockItemHistory extends InvenTreeModel { } } - String get userString { - return (jsondata["user_detail"]?["username"] ?? "") as String; - } + String get userString => getString("username", subKey: "user_detail"); } +/* + * Class representing a StockItem database instance + */ class InvenTreeStockItem extends InvenTreeModel { InvenTreeStockItem() : super(); @@ -237,16 +230,16 @@ class InvenTreeStockItem extends InvenTreeModel { }); } - int get status => (jsondata["status"] ?? -1) as int; + int get status => getInt("status"); + + String get packaging => getString("packaging"); - String get packaging => (jsondata["packaging"] ?? "") as String; + String get batch => getString("batch"); - String get batch => (jsondata["batch"] ?? "") as String; - - int get partId => (jsondata["part"] ?? -1) as int; + int get partId => getInt("part"); double? get purchasePrice { - String pp = (jsondata["purchase_price"] ?? "") as String; + String pp = getString("purchase_price"); if (pp.isEmpty) { return null; @@ -255,18 +248,18 @@ class InvenTreeStockItem extends InvenTreeModel { } } - String get purchasePriceCurrency => (jsondata["purchase_price_currency"] ?? "") as String; + String get purchasePriceCurrency => getString("purchase_price_currency"); bool get hasPurchasePrice { double? pp = purchasePrice; return pp != null && pp > 0; } - int get purchaseOrderId => (jsondata["purchase_order"] ?? -1) as int; + int get purchaseOrderId => getInt("purchase_order"); - int get trackingItemCount => (jsondata["tracking_items"] ?? 0) as int; - - bool get isBuilding => (jsondata["is_building"] ?? false) as bool; + int get trackingItemCount => getInt("tracking_items", backup: 0); + + bool get isBuilding => getBool("is_building"); // Date of last update DateTime? get updatedDate { @@ -320,7 +313,7 @@ class InvenTreeStockItem extends InvenTreeModel { // Backup if first value fails if (nm.isEmpty) { - nm = (jsondata["part__name"] ?? "") as String; + nm = getString("part__name"); } return nm; @@ -335,7 +328,7 @@ class InvenTreeStockItem extends InvenTreeModel { } if (desc.isEmpty) { - desc = (jsondata["part__description"] ?? "") as String; + desc = getString("part__description"); } return desc; @@ -349,7 +342,7 @@ class InvenTreeStockItem extends InvenTreeModel { } if (img.isEmpty) { - img = (jsondata["part__thumbnail"] ?? "") as String; + img = getString("part__thumbnail"); } return img; @@ -371,7 +364,7 @@ class InvenTreeStockItem extends InvenTreeModel { // Try a different approach if (thumb.isEmpty) { - thumb = (jsondata["part__thumbnail"] ?? "") as String; + thumb = getString("part__thumbnail"); } // Still no thumbnail? Use the "no image" image @@ -380,7 +373,7 @@ class InvenTreeStockItem extends InvenTreeModel { return thumb; } - int get supplierPartId => (jsondata["supplier_part"] ?? -1) as int; + int get supplierPartId => getInt("supplier_part"); String get supplierImage { String thumb = ""; @@ -394,33 +387,15 @@ class InvenTreeStockItem extends InvenTreeModel { return thumb; } - String get supplierName { - String sname = ""; + String get supplierName => getString("supplier_name", subKey: "supplier_detail"); - if (jsondata.containsKey("supplier_detail")) { - sname = (jsondata["supplier_detail"]["supplier_name"] ?? "") as String; - } + String get units => getString("units", subKey: "part_detail"); - return sname; - } + String get supplierSKU => getString("SKU", subKey: "supplier_part_detail"); - String get units { - return (jsondata["part_detail"]?["units"] ?? "") as String; - } + String get serialNumber => getString("serial"); - String get supplierSKU { - String sku = ""; - - if (jsondata.containsKey("supplier_part_detail")) { - sku = (jsondata["supplier_part_detail"]["SKU"] ?? "") as String; - } - - return sku; - } - - String get serialNumber => (jsondata["serial"] ?? "") as String; - - double get quantity => double.tryParse(jsondata["quantity"].toString()) ?? 0; + double get quantity => getDouble("quantity"); String quantityString({bool includeUnits = false}){ @@ -440,11 +415,11 @@ class InvenTreeStockItem extends InvenTreeModel { return q; } - double get allocated => double.tryParse(jsondata["allocated"].toString()) ?? 0; + double get allocated => getDouble("allocated"); double get available => quantity - allocated; - int get locationId => (jsondata["location"] ?? -1) as int; + int get locationId => getInt("location"); bool isSerialized() => serialNumber.isNotEmpty && quantity.toInt() == 1; @@ -459,15 +434,14 @@ class InvenTreeStockItem extends InvenTreeModel { } String get locationName { - String loc = ""; if (locationId == -1 || !jsondata.containsKey("location_detail")) return "Unknown Location"; - loc = (jsondata["location_detail"]["name"] ?? "") as String; + String loc = getString("name", subKey: "location_detail"); // Old-style name if (loc.isEmpty) { - loc = (jsondata["location__name"] ?? "") as String; + loc = getString("location__name"); } return loc; @@ -477,8 +451,7 @@ class InvenTreeStockItem extends InvenTreeModel { if (locationId == -1 || !jsondata.containsKey("location_detail")) return L10().locationNotSet; - String _loc = (jsondata["location_detail"]["pathstring"] ?? "") as String; - + String _loc = getString("pathstring", subKey: "location_detail"); if (_loc.isNotEmpty) { return _loc; } else { @@ -497,9 +470,7 @@ class InvenTreeStockItem extends InvenTreeModel { } @override - InvenTreeModel createFromJson(Map json) { - return InvenTreeStockItem.fromJson(json); - } + InvenTreeModel createFromJson(Map json) => InvenTreeStockItem.fromJson(json); /* * Perform stocktake action: @@ -601,9 +572,7 @@ class InvenTreeStockItemAttachment extends InvenTreeAttachment { String get URL => "stock/attachment/"; @override - InvenTreeModel createFromJson(Map json) { - return InvenTreeStockItemAttachment.fromJson(json); - } + InvenTreeModel createFromJson(Map json) => InvenTreeStockItemAttachment.fromJson(json); } @@ -620,7 +589,7 @@ class InvenTreeStockLocation extends InvenTreeModel { @override List get rolesRequired => ["stock_location"]; - String get pathstring => (jsondata["pathstring"] ?? "") as String; + String get pathstring => getString("pathstring"); @override Map formFields() { @@ -658,10 +627,6 @@ class InvenTreeStockLocation extends InvenTreeModel { int get itemcount => (jsondata["items"] ?? 0) as int; @override - InvenTreeModel createFromJson(Map json) { + InvenTreeModel createFromJson(Map json) => InvenTreeStockLocation.fromJson(json); - var loc = InvenTreeStockLocation.fromJson(json); - - return loc; - } -} \ No newline at end of file +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 66ded8ba..9802d72b 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -759,6 +759,9 @@ "profileTapToCreate": "Tap to create or select a profile", "@profileTapToCreate": {}, + "projectCode": "Project Code", + "@projectCode": {}, + "purchaseOrder": "Purchase Order", "@purchaseOrder": {}, diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/purchase_order_detail.dart index 1e405f07..e6017c57 100644 --- a/lib/widget/purchase_order_detail.dart +++ b/lib/widget/purchase_order_detail.dart @@ -43,6 +43,8 @@ class _PurchaseOrderDetailState extends RefreshableState L10().purchaseOrder; @@ -139,6 +141,8 @@ class _PurchaseOrderDetailState extends RefreshableState editOrder(BuildContext context) async { var fields = order.formFields(); + + // Cannot edit supplier field from here fields.remove("supplier"); + // Contact model not supported by server if (!api.supportsContactModel) { fields.remove("contact"); } + // ProjectCode model not supported by server + if (!supportProjectCodes) { + fields.remove("project_code"); + } + order.editForm( context, L10().purchaseOrderEdit, @@ -202,6 +214,14 @@ class _PurchaseOrderDetailState extends RefreshableState