2
0
mirror of https://github.com/inventree/inventree-app.git synced 2025-04-28 13:36:50 +00:00

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
This commit is contained in:
Oliver 2023-04-21 21:12:22 +10:00 committed by GitHub
parent 95573a2784
commit e23a8b4d5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 383 additions and 299 deletions

View File

@ -1,6 +1,7 @@
### 0.11.6 - April 2023 ### 0.11.6 - April 2023
--- ---
- Add support for Project Codes
- Fix action button colors - Fix action button colors
- Added Norwegian translations - Added Norwegian translations
- Fix serial number field when creating stock item - Fix serial number field when creating stock item

View File

@ -300,6 +300,9 @@ class InvenTreeAPI {
// Order barcodes API v107 or newer // Order barcodes API v107 or newer
bool get supportsOrderBarcodes => isConnected() && apiVersion >= 107; bool get supportsOrderBarcodes => isConnected() && apiVersion >= 107;
// Project codes require v109 or newer
bool get supportsProjectCodes => isConnected() && apiVersion >= 109;
// Are plugins enabled on the server? // Are plugins enabled on the server?
bool _pluginsEnabled = false; bool _pluginsEnabled = false;
@ -1362,6 +1365,12 @@ class InvenTreeAPI {
} }
} }
// Return a boolean global setting value
Future<bool> getGlobalBooleanSetting(String key) async {
String value = await getGlobalSetting(key);
return value.toLowerCase() == "true";
}
Future<String> getUserSetting(String key) async { Future<String> getUserSetting(String key) async {
if (!supportsSettings) return ""; if (!supportsSettings) return "";
@ -1382,6 +1391,12 @@ class InvenTreeAPI {
} }
} }
// Return a boolean user setting value
Future<bool> 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 * Send a request to the server to locate / identify either a StockItem or StockLocation
*/ */

View File

@ -15,6 +15,7 @@ import "package:inventree/l10.dart";
import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/company.dart";
import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/part.dart";
import "package:inventree/inventree/project_code.dart";
import "package:inventree/inventree/sentry.dart"; import "package:inventree/inventree/sentry.dart";
import "package:inventree/inventree/stock.dart"; import "package:inventree/inventree/stock.dart";
@ -667,6 +668,13 @@ class APIFormField {
height: 40 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: default:
return ListTile( return ListTile(
title: Text( title: Text(

View File

@ -12,9 +12,7 @@ class InvenTreeBomItem extends InvenTreeModel {
InvenTreeBomItem.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreeBomItem.fromJson(Map<String, dynamic> json) : super.fromJson(json);
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeBomItem.fromJson(json);
return InvenTreeBomItem.fromJson(json);
}
@override @override
String get URL => "bom/"; String get URL => "bom/";
@ -36,13 +34,13 @@ class InvenTreeBomItem extends InvenTreeModel {
} }
// Extract the 'reference' value associated with this BomItem // 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 // 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 // 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 // Return a Part instance for the referenced part
InvenTreePart? get part { InvenTreePart? get part {
@ -69,5 +67,5 @@ class InvenTreeBomItem extends InvenTreeModel {
} }
// Extract the ID of the related sub-part // Extract the ID of the related sub-part
int get subPartId => int.tryParse(jsondata["sub_part"].toString()) ?? -1; int get subPartId => getInt("sub_part");
} }

View File

@ -38,21 +38,21 @@ class InvenTreeCompany extends InvenTreeModel {
String get thumbnail => (jsondata["thumbnail"] ?? jsondata["image"] ?? InvenTreeAPI.staticThumb) as String; 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 => (jsondata["phone"] ?? "") as String; String get phone => getString("phone");
String get email => (jsondata["email"] ?? "") as String; String get email => getString("email");
bool get isSupplier => (jsondata["is_supplier"] ?? false) as bool; bool get isSupplier => getBool("is_supplier");
bool get isManufacturer => (jsondata["is_manufacturer"] ?? false) as bool; bool get isManufacturer => getBool("is_manufacturer");
bool get isCustomer => (jsondata["is_customer"] ?? false) as bool; bool get isCustomer => getBool("is_customer");
int get partSuppliedCount => (jsondata["parts_supplied"] ?? 0) as int; int get partSuppliedCount => getInt("part_supplied");
int get partManufacturedCount => (jsondata["parts_manufactured"] ?? 0) as int; int get partManufacturedCount => getInt("parts_manufactured");
// Request a list of purchase orders against this company // Request a list of purchase orders against this company
Future<List<InvenTreePurchaseOrder>> getPurchaseOrders({bool? outstanding}) async { Future<List<InvenTreePurchaseOrder>> getPurchaseOrders({bool? outstanding}) async {
@ -81,11 +81,7 @@ class InvenTreeCompany extends InvenTreeModel {
} }
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeCompany.fromJson(json);
var company = InvenTreeCompany.fromJson(json);
return company;
}
} }
@ -154,40 +150,36 @@ class InvenTreeSupplierPart extends InvenTreeModel {
return _filters(); return _filters();
} }
int get manufacturerId => (jsondata["manufacturer_detail"]["pk"] ?? -1) as int; int get manufacturerId => getInt("pk", subKey: "manufacturer_detail");
String get manufacturerName => (jsondata["manufacturer_detail"]?["name"] ?? "") as String; String get manufacturerName => getString("name", subKey: "manufacturer_detail");
String get MPN => (jsondata["manufacturer_part_detail"]?["MPN"] ?? "") as String; String get MPN => getString("MPN", subKey: "manufacturer_part_detail");
String get manufacturerImage => (jsondata["manufacturer_detail"]?["image"] ?? jsondata["manufacturer_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; 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 manufacturerPartId => getInt("manufacturer_part");
int get supplierId => (jsondata["supplier"] ?? -1) as int; int get supplierId => getInt("supplier");
String get supplierName => (jsondata["supplier_detail"]?["name"] ?? "") as String; 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 supplierImage => (jsondata["supplier_detail"]?["image"] ?? jsondata["supplier_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String;
String get SKU => (jsondata["SKU"] ?? "") as String; String get SKU => getString("SKU");
int get partId => (jsondata["part"] ?? -1) as int; int get partId => getInt("part");
String get partImage => (jsondata["part_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; String get partImage => (jsondata["part_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb) as String;
String get partName => (jsondata["part_detail"]?["full_name"] ?? "") as String; String get partName => getString("full_name", subKey: "part_detail");
String get partDescription => (jsondata["part_detail"]?["description"] ?? "") as String; String get partDescription => getString("description", subKey: "part_detail");
String get note => (jsondata["note"] ?? "") as String; String get note => getString("note");
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeSupplierPart.fromJson(json);
var part = InvenTreeSupplierPart.fromJson(json);
return part;
}
} }
@ -207,16 +199,12 @@ class InvenTreeManufacturerPart extends InvenTreeModel {
}; };
} }
int get partId => (jsondata["part"] ?? -1) as int; int get partId => getInt("part");
int get manufacturerId => (jsondata["manufacturer"] ?? -1) as int; int get manufacturerId => getInt("manufacturer");
String get MPN => (jsondata["MPN"] ?? "") as String; String get MPN => getString("MPN");
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeManufacturerPart.fromJson(json);
var part = InvenTreeManufacturerPart.fromJson(json);
return part;
}
} }

View File

@ -63,6 +63,96 @@ class InvenTreeModel {
// Note: If the WEB_URL is the same (except for /api/) as URL then just leave blank // Note: If the WEB_URL is the same (except for /api/) as URL then just leave blank
String get WEB_URL => ""; 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<String, dynamic> 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<String, dynamic>) {
data = (data[subKey] ?? {}) as Map<String, dynamic>;
}
}
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<String, dynamic> getMap(String key, {Map<String, dynamic> backup = const {}, String subKey = ""}) {
dynamic value = getValue(key, backup: backup, subKey: subKey);
if (value == null) {
return backup;
}
return value as Map<String, dynamic>;
}
// 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 { String get webUrl {
if (api.isConnected()) { if (api.isConnected()) {
@ -191,16 +281,16 @@ class InvenTreeModel {
// Accessor for the API // Accessor for the API
InvenTreeAPI get api => InvenTreeAPI(); InvenTreeAPI get api => InvenTreeAPI();
int get pk => (jsondata["pk"] ?? -1) as int; int get pk => getInt("pk");
// Some common accessors // 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 => (jsondata["notes"] ?? "") as String; String get notes => getString("notes");
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" // Legacy API provided external link as "URL", while newer API uses "link"
String get link => (jsondata["link"] ?? jsondata["URL"] ?? "") as String; 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!) // Create a new object from JSON data (not a constructor!)
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeModel.fromJson(json);
var obj = InvenTreeModel.fromJson(json);
return obj;
}
// Return the API detail endpoint for this Model object // Return the API detail endpoint for this Model object
String get url => "${URL}/${pk}/".replaceAll("//", "/"); String get url => "${URL}/${pk}/".replaceAll("//", "/");
@ -746,9 +831,7 @@ class InvenTreePlugin extends InvenTreeModel {
InvenTreePlugin.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreePlugin.fromJson(Map<String, dynamic> json) : super.fromJson(json);
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePlugin.fromJson(json);
return InvenTreePlugin.fromJson(json);
}
@override @override
String get URL { String get URL {
@ -765,9 +848,9 @@ class InvenTreePlugin extends InvenTreeModel {
} }
} }
String get key => (jsondata["key"] ?? "") as String; String get key => getString("key");
bool get active => (jsondata["active"] ?? false) as bool; bool get active => getBool("active");
// Return the metadata struct for this plugin // Return the metadata struct for this plugin
Map<String, dynamic> get _meta => (jsondata["meta"] ?? {}) as Map<String, dynamic>; Map<String, dynamic> get _meta => (jsondata["meta"] ?? {}) as Map<String, dynamic>;
@ -803,11 +886,11 @@ class InvenTreeGlobalSetting extends InvenTreeModel {
@override @override
String get URL => "settings/global/"; String get URL => "settings/global/";
String get key => (jsondata["key"] ?? "") as String; String get key => getString("key");
String get value => (jsondata["value"] ?? "") as String; String get value => getString("value");
String get type => (jsondata["type"] ?? "") as String; String get type => getString("type");
} }
@ -836,7 +919,7 @@ class InvenTreeAttachment extends InvenTreeModel {
// Override this reference field for any subclasses // Override this reference field for any subclasses
String get REFERENCE_FIELD => ""; String get REFERENCE_FIELD => "";
String get attachment => (jsondata["attachment"] ?? "") as String; String get attachment => getString("attachment");
// Return the filename of the attachment // Return the filename of the attachment
String get filename { String get filename {
@ -874,7 +957,7 @@ class InvenTreeAttachment extends InvenTreeModel {
return FontAwesomeIcons.fileLines; return FontAwesomeIcons.fileLines;
} }
String get comment => (jsondata["comment"] ?? "") as String; String get comment => getString("comment");
DateTime? get uploadDate { DateTime? get uploadDate {
if (jsondata.containsKey("upload_date")) { if (jsondata.containsKey("upload_date")) {

View File

@ -27,7 +27,7 @@ class InvenTreeNotification extends InvenTreeModel {
}; };
} }
String get message => (jsondata["message"] ?? "") as String; String get message => getString("message");
DateTime? get creationDate { DateTime? get creationDate {
if (jsondata.containsKey("creation")) { if (jsondata.containsKey("creation")) {

View File

@ -43,7 +43,7 @@ class InvenTreePartCategory extends InvenTreeModel {
return fields; return fields;
} }
String get pathstring => (jsondata["pathstring"] ?? "") as String; String get pathstring => getString("pathstring");
String get parentPathString { String get parentPathString {
@ -67,11 +67,7 @@ class InvenTreePartCategory extends InvenTreeModel {
int get partcount => (jsondata["part_count"] ?? jsondata["parts"] ?? 0) as int; int get partcount => (jsondata["part_count"] ?? jsondata["parts"] ?? 0) as int;
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePartCategory.fromJson(json);
var cat = InvenTreePartCategory.fromJson(json);
return cat;
}
} }
@ -87,22 +83,18 @@ class InvenTreePartTestTemplate extends InvenTreeModel {
@override @override
String get URL => "part/test-template/"; 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 => (jsondata["requires_value"] ?? false) as bool; bool get requiresValue => getBool("requires_value");
bool get requiresAttachment => (jsondata["requires_attachment"] ?? false) as bool; bool get requiresAttachment => getBool("requires_attachment");
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePartTestTemplate.fromJson(json);
var template = InvenTreePartTestTemplate.fromJson(json);
return template;
}
bool passFailStatus() { bool passFailStatus() {
@ -142,9 +134,7 @@ class InvenTreePartParameter extends InvenTreeModel {
String get URL => "part/parameter/"; String get URL => "part/parameter/";
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePartParameter.fromJson(json);
return InvenTreePartParameter.fromJson(json);
}
@override @override
Map<String, dynamic> formFields() { Map<String, dynamic> formFields() {
@ -152,12 +142,12 @@ class InvenTreePartParameter extends InvenTreeModel {
} }
@override @override
String get name => (jsondata["template_detail"]?["name"] ?? "") as String; String get name => getString("name", subKey: "template_detail");
@override @override
String get description => (jsondata["template_detail"]?["description"] ?? "") as String; String get description => getString("description", subKey: "template_detail");
String get value => jsondata["data"] as String; String get value => getString("data");
String get valueString { String get valueString {
String v = value; String v = value;
@ -170,7 +160,7 @@ class InvenTreePartParameter extends InvenTreeModel {
return v; return v;
} }
String get units => (jsondata["template_detail"]?["units"] ?? "") as String; String get units => getString("units", subKey: "template_detail");
} }
/* /*
@ -254,7 +244,7 @@ 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 // Request supplier parts for this part
Future<List<InvenTreeSupplierPart>> getSupplierParts() async { Future<List<InvenTreeSupplierPart>> getSupplierParts() async {
@ -301,23 +291,13 @@ class InvenTreePart extends InvenTreeModel {
int? get defaultLocation => jsondata["default_location"] as int?; int? get defaultLocation => jsondata["default_location"] as int?;
// Get the number of stock on order for this Part double get onOrder => getDouble("ordering");
double get onOrder => double.tryParse(jsondata["ordering"].toString()) ?? 0;
String get onOrderString { String get onOrderString => simpleNumberString(onOrder);
return simpleNumberString(onOrder); double get inStock => getDouble("in_stock");
}
// Get the stock count for this Part String get inStockString => simpleNumberString(inStock);
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 // Get the 'available stock' for this Part
double get unallocatedStock { double get unallocatedStock {
@ -330,11 +310,7 @@ class InvenTreePart extends InvenTreeModel {
} }
} }
String get unallocatedStockString { String get unallocatedStockString => simpleNumberString(unallocatedStock);
String q = simpleNumberString(unallocatedStock);
return q;
}
String stockString({bool includeUnits = true}) { String stockString({bool includeUnits = true}) {
String q = unallocatedStockString; String q = unallocatedStockString;
@ -350,39 +326,39 @@ class InvenTreePart extends InvenTreeModel {
return q; 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) // Get the ID of the Part that this part is a variant of (or null)
int? get variantOf => jsondata["variant_of"] as int?; int? get variantOf => jsondata["variant_of"] as int?;
// Get the number of units being build for this Part // 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) // 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 // 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 // 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) // 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 // Get the category name for the Part instance
String get categoryName { String get categoryName {
@ -404,15 +380,15 @@ class InvenTreePart extends InvenTreeModel {
return (jsondata["category_detail"]?["description"] ?? "") as String; return (jsondata["category_detail"]?["description"] ?? "") as String;
} }
// Get the image URL for the Part instance // 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 // 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 // Return the fully-qualified name for the Part instance
String get fullname { String get fullname {
String fn = (jsondata["full_name"] ?? "") as String; String fn = getString("full_name");
if (fn.isNotEmpty) return fn; if (fn.isNotEmpty) return fn;
@ -456,15 +432,10 @@ class InvenTreePart extends InvenTreeModel {
} }
// Return the "starred" status of this part // Return the "starred" status of this part
bool get starred => (jsondata["starred"] ?? false) as bool; bool get starred => getBool("starred");
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePart.fromJson(json);
var part = InvenTreePart.fromJson(json);
return part;
}
} }
/* /*
@ -483,8 +454,6 @@ class InvenTreePartAttachment extends InvenTreeAttachment {
String get URL => "part/attachment/"; String get URL => "part/attachment/";
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePartAttachment.fromJson(json);
return InvenTreePartAttachment.fromJson(json);
}
} }

View File

@ -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<String, dynamic> json) : super.fromJson(json);
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeProjectCode.fromJson(json);
@override
String get URL => "project-code/";
@override
Map<String, dynamic> formFields() {
return {
"code": {},
"description": {},
};
}
String get code => getString("code");
}

View File

@ -34,6 +34,7 @@ class InvenTreePurchaseOrder extends InvenTreeModel {
}, },
"supplier_reference": {}, "supplier_reference": {},
"description": {}, "description": {},
"project_code": {},
"target_date": {}, "target_date": {},
"link": {}, "link": {},
"responsible": {}, "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 => (jsondata["overdue"] ?? false) as bool; bool get overdue => getBool("overdue");
String get reference => (jsondata["reference"] ?? "") as String; String get reference => getString("reference");
int get responsibleId => (jsondata["responsible"] ?? -1) as int; int get responsibleId => getInt("responsible");
int get supplierId => (jsondata["supplier"] ?? -1) as int; 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 { 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; 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; bool get isFailed => status == PO_STATUS_CANCELLED || status == PO_STATUS_LOST || status == PO_STATUS_RETURNED;
double? get totalPrice { double? get totalPrice {
String price = (jsondata["total_price"] ?? "") as String; String price = getString("total_price");
if (price.isEmpty) { if (price.isEmpty) {
return null; 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<List<InvenTreePOLineItem>> getLineItems() async { Future<List<InvenTreePOLineItem>> getLineItems() async {
@ -134,9 +144,7 @@ class InvenTreePurchaseOrder extends InvenTreeModel {
} }
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePurchaseOrder.fromJson(json);
return InvenTreePurchaseOrder.fromJson(json);
}
/// Mark this order as "placed" / "issued" /// Mark this order as "placed" / "issued"
Future<void> issueOrder() async { Future<void> issueOrder() async {
@ -199,17 +207,17 @@ class InvenTreePOLineItem extends InvenTreeModel {
bool get isComplete => received >= quantity; 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; 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 { InvenTreePart? get part {
dynamic part_detail = jsondata["part_detail"]; 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 => (jsondata["purchase_price_currency"] ?? "") as String; String get purchasePriceCurrency => getString("purchase_price_currency");
String get purchasePriceString => (jsondata["purchase_price_string"] ?? "") as String; String get purchasePriceString => getString("purchase_price_string");
int get destination => (jsondata["destination"] ?? -1) as int; int get destination => getInt("destination");
Map<String, dynamic> get destinationDetail => (jsondata["destination_detail"] ?? {}) as Map<String, dynamic>; Map<String, dynamic> get destinationDetail => getMap("destination_detail");
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePOLineItem.fromJson(json);
return InvenTreePOLineItem.fromJson(json);
}
} }
/* /*
@ -264,7 +271,6 @@ class InvenTreePurchaseOrderAttachment extends InvenTreeAttachment {
String get URL => "order/po/attachment/"; String get URL => "order/po/attachment/";
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePurchaseOrderAttachment.fromJson(json);
return InvenTreePurchaseOrderAttachment.fromJson(json);
}
} }

View File

@ -35,17 +35,17 @@ class InvenTreeStockItemTestResult extends InvenTreeModel {
}; };
} }
String get key => (jsondata["key"] ?? "") as String; String get key => getString("key");
String get testName => (jsondata["test"] ?? "") as String; String get testName => getString("test");
bool get result => (jsondata["result"] ?? false) as bool; bool get result => getBool("result");
String get value => (jsondata["value"] ?? "") as String; String get value => getString("value");
String get attachment => (jsondata["attachment"] ?? "") as String; String get attachment => getString("attachment");
String get date => (jsondata["date"] ?? "") as String; String get date => getString("date");
@override @override
InvenTreeStockItemTestResult createFromJson(Map<String, dynamic> json) { InvenTreeStockItemTestResult createFromJson(Map<String, dynamic> json) {
@ -63,9 +63,7 @@ class InvenTreeStockItemHistory extends InvenTreeModel {
InvenTreeStockItemHistory.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreeStockItemHistory.fromJson(Map<String, dynamic> json) : super.fromJson(json);
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeStockItemHistory.fromJson(json);
return InvenTreeStockItemHistory.fromJson(json);
}
@override @override
String get URL => "stock/track/"; String get URL => "stock/track/";
@ -98,16 +96,10 @@ class InvenTreeStockItemHistory extends InvenTreeModel {
return DateFormat("yyyy-MM-dd").format(d); 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 // Return the "deltas" associated with this historical object
Map<String, dynamic> get deltas { Map<String, dynamic> get deltas => getMap("deltas");
if (jsondata.containsKey("deltas")) {
return jsondata["deltas"] as Map<String, dynamic>;
} else {
return {};
}
}
// Return the quantity string for this historical object // Return the quantity string for this historical object
String get quantityString { String get quantityString {
@ -122,12 +114,13 @@ class InvenTreeStockItemHistory extends InvenTreeModel {
} }
} }
String get userString { String get userString => getString("username", subKey: "user_detail");
return (jsondata["user_detail"]?["username"] ?? "") as String;
}
} }
/*
* Class representing a StockItem database instance
*/
class InvenTreeStockItem extends InvenTreeModel { class InvenTreeStockItem extends InvenTreeModel {
InvenTreeStockItem() : super(); 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 => (jsondata["packaging"] ?? "") as String; String get packaging => getString("packaging");
String get batch => (jsondata["batch"] ?? "") as String; String get batch => getString("batch");
int get partId => (jsondata["part"] ?? -1) as int; int get partId => getInt("part");
double? get purchasePrice { double? get purchasePrice {
String pp = (jsondata["purchase_price"] ?? "") as String; String pp = getString("purchase_price");
if (pp.isEmpty) { if (pp.isEmpty) {
return null; 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 { bool get hasPurchasePrice {
double? pp = purchasePrice; double? pp = purchasePrice;
return pp != null && pp > 0; 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; int get trackingItemCount => getInt("tracking_items", backup: 0);
bool get isBuilding => (jsondata["is_building"] ?? false) as bool; bool get isBuilding => getBool("is_building");
// Date of last update // Date of last update
DateTime? get updatedDate { DateTime? get updatedDate {
@ -320,7 +313,7 @@ class InvenTreeStockItem extends InvenTreeModel {
// Backup if first value fails // Backup if first value fails
if (nm.isEmpty) { if (nm.isEmpty) {
nm = (jsondata["part__name"] ?? "") as String; nm = getString("part__name");
} }
return nm; return nm;
@ -335,7 +328,7 @@ class InvenTreeStockItem extends InvenTreeModel {
} }
if (desc.isEmpty) { if (desc.isEmpty) {
desc = (jsondata["part__description"] ?? "") as String; desc = getString("part__description");
} }
return desc; return desc;
@ -349,7 +342,7 @@ class InvenTreeStockItem extends InvenTreeModel {
} }
if (img.isEmpty) { if (img.isEmpty) {
img = (jsondata["part__thumbnail"] ?? "") as String; img = getString("part__thumbnail");
} }
return img; return img;
@ -371,7 +364,7 @@ class InvenTreeStockItem extends InvenTreeModel {
// Try a different approach // Try a different approach
if (thumb.isEmpty) { if (thumb.isEmpty) {
thumb = (jsondata["part__thumbnail"] ?? "") as String; thumb = getString("part__thumbnail");
} }
// Still no thumbnail? Use the "no image" image // Still no thumbnail? Use the "no image" image
@ -380,7 +373,7 @@ class InvenTreeStockItem extends InvenTreeModel {
return thumb; return thumb;
} }
int get supplierPartId => (jsondata["supplier_part"] ?? -1) as int; int get supplierPartId => getInt("supplier_part");
String get supplierImage { String get supplierImage {
String thumb = ""; String thumb = "";
@ -394,33 +387,15 @@ class InvenTreeStockItem extends InvenTreeModel {
return thumb; return thumb;
} }
String get supplierName { String get supplierName => getString("supplier_name", subKey: "supplier_detail");
String sname = "";
if (jsondata.containsKey("supplier_detail")) { String get units => getString("units", subKey: "part_detail");
sname = (jsondata["supplier_detail"]["supplier_name"] ?? "") as String;
}
return sname; String get supplierSKU => getString("SKU", subKey: "supplier_part_detail");
}
String get units { String get serialNumber => getString("serial");
return (jsondata["part_detail"]?["units"] ?? "") as String;
}
String get supplierSKU { double get quantity => getDouble("quantity");
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;
String quantityString({bool includeUnits = false}){ String quantityString({bool includeUnits = false}){
@ -440,11 +415,11 @@ class InvenTreeStockItem extends InvenTreeModel {
return q; return q;
} }
double get allocated => double.tryParse(jsondata["allocated"].toString()) ?? 0; double get allocated => getDouble("allocated");
double get available => quantity - 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; bool isSerialized() => serialNumber.isNotEmpty && quantity.toInt() == 1;
@ -459,15 +434,14 @@ class InvenTreeStockItem extends InvenTreeModel {
} }
String get locationName { String get locationName {
String loc = "";
if (locationId == -1 || !jsondata.containsKey("location_detail")) return "Unknown Location"; 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 // Old-style name
if (loc.isEmpty) { if (loc.isEmpty) {
loc = (jsondata["location__name"] ?? "") as String; loc = getString("location__name");
} }
return loc; return loc;
@ -477,8 +451,7 @@ class InvenTreeStockItem extends InvenTreeModel {
if (locationId == -1 || !jsondata.containsKey("location_detail")) return L10().locationNotSet; 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) { if (_loc.isNotEmpty) {
return _loc; return _loc;
} else { } else {
@ -497,9 +470,7 @@ class InvenTreeStockItem extends InvenTreeModel {
} }
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeStockItem.fromJson(json);
return InvenTreeStockItem.fromJson(json);
}
/* /*
* Perform stocktake action: * Perform stocktake action:
@ -601,9 +572,7 @@ class InvenTreeStockItemAttachment extends InvenTreeAttachment {
String get URL => "stock/attachment/"; String get URL => "stock/attachment/";
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeStockItemAttachment.fromJson(json);
return InvenTreeStockItemAttachment.fromJson(json);
}
} }
@ -620,7 +589,7 @@ class InvenTreeStockLocation extends InvenTreeModel {
@override @override
List<String> get rolesRequired => ["stock_location"]; List<String> get rolesRequired => ["stock_location"];
String get pathstring => (jsondata["pathstring"] ?? "") as String; String get pathstring => getString("pathstring");
@override @override
Map<String, dynamic> formFields() { Map<String, dynamic> formFields() {
@ -658,10 +627,6 @@ class InvenTreeStockLocation extends InvenTreeModel {
int get itemcount => (jsondata["items"] ?? 0) as int; int get itemcount => (jsondata["items"] ?? 0) as int;
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeStockLocation.fromJson(json);
var loc = InvenTreeStockLocation.fromJson(json);
return loc;
}
} }

View File

@ -759,6 +759,9 @@
"profileTapToCreate": "Tap to create or select a profile", "profileTapToCreate": "Tap to create or select a profile",
"@profileTapToCreate": {}, "@profileTapToCreate": {},
"projectCode": "Project Code",
"@projectCode": {},
"purchaseOrder": "Purchase Order", "purchaseOrder": "Purchase Order",
"@purchaseOrder": {}, "@purchaseOrder": {},

View File

@ -43,6 +43,8 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
int attachmentCount = 0; int attachmentCount = 0;
bool supportProjectCodes = false;
@override @override
String getAppBarTitle() => L10().purchaseOrder; String getAppBarTitle() => L10().purchaseOrder;
@ -139,6 +141,8 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
lines = await order.getLineItems(); lines = await order.getLineItems();
supportProjectCodes = api.supportsProjectCodes && await api.getGlobalBooleanSetting("PROJECT_CODES_ENABLED");
completedLines = 0; completedLines = 0;
for (var line in lines) { for (var line in lines) {
@ -157,12 +161,20 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
// Edit the currently displayed PurchaseOrder // Edit the currently displayed PurchaseOrder
Future <void> editOrder(BuildContext context) async { Future <void> editOrder(BuildContext context) async {
var fields = order.formFields(); var fields = order.formFields();
// Cannot edit supplier field from here
fields.remove("supplier"); fields.remove("supplier");
// Contact model not supported by server
if (!api.supportsContactModel) { if (!api.supportsContactModel) {
fields.remove("contact"); fields.remove("contact");
} }
// ProjectCode model not supported by server
if (!supportProjectCodes) {
fields.remove("project_code");
}
order.editForm( order.editForm(
context, context,
L10().purchaseOrderEdit, L10().purchaseOrderEdit,
@ -202,6 +214,14 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
tiles.add(headerTile(context)); tiles.add(headerTile(context));
if (supportProjectCodes && order.hasProjectCode) {
tiles.add(ListTile(
title: Text(L10().projectCode),
subtitle: Text("${order.projectCode} - ${order.projectCodeDescription}"),
leading: FaIcon(FontAwesomeIcons.list),
));
}
if (supplier != null) { if (supplier != null) {
tiles.add(ListTile( tiles.add(ListTile(
title: Text(L10().supplier), title: Text(L10().supplier),