2
0
mirror of https://github.com/inventree/inventree-app.git synced 2025-04-27 21:16:48 +00:00

Sales order support (#438)

* Add new models for SalesOrder

- Create generic Order and OrderLine models with common functionality

* Refactor

- Move some widgets around
- Cleanup directory structure

* Add link to home screen and nav drawer

* Add SalesOrder list widget

* Linting fixes

* Fix string

* Refactor PurchaseOrderDetailWidget

* Tweaks to existing code

* linting

* Fixes for drawer widget

* Add "detail" page for SalesOrder

* Add more tiles to SalesOrder detail

* Allow editing of salesorder

* add list filters for sales orders

* Display list of line items

* Customer updates

- Display customer icon on home screen
- Fetch sales orders for customer detail page

* Cleanup company detail view

* Create new sales order from list

* Stricter typing for formFields method

* Create new PurchaseOrder and SalesOrder from company deatil

* Status code updates

- Add function for name comparison
- Remove hard-coded values

* Update view permission checks for home widget

* Add ability to manually add SalesOrderLineItem

* Add nice progress bar widgets

* Display detail view for sales order line item

* edit SalesOrderLineItem

* Fix unused import

* Hide "shipped items" tab

- Will be added in a future update
This commit is contained in:
Oliver 2023-11-12 23:13:22 +11:00 committed by GitHub
parent c1c0d46957
commit bdd5470e68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1565 additions and 284 deletions

View File

@ -620,6 +620,8 @@ class InvenTreeAPI {
_globalSettings.clear(); _globalSettings.clear();
_userSettings.clear(); _userSettings.clear();
roles.clear();
_plugins.clear();
serverInfo.clear(); serverInfo.clear();
_connectionStatusChanged(); _connectionStatusChanged();
} }
@ -672,6 +674,8 @@ class InvenTreeAPI {
_connectionStatusChanged(); _connectionStatusChanged();
fetchStatusCodeData();
return _connected; return _connected;
} }
@ -735,6 +739,10 @@ class InvenTreeAPI {
*/ */
bool checkPermission(String role, String permission) { bool checkPermission(String role, String permission) {
if (!_connected) {
return false;
}
// If we do not have enough information, assume permission is allowed // If we do not have enough information, assume permission is allowed
if (roles.isEmpty) { if (roles.isEmpty) {
debug("checkPermission - no roles defined!"); debug("checkPermission - no roles defined!");
@ -1624,11 +1632,20 @@ class InvenTreeAPI {
InvenTreeStatusCode get StockHistoryStatus => _get_status_class("stock/track/status/"); InvenTreeStatusCode get StockHistoryStatus => _get_status_class("stock/track/status/");
InvenTreeStatusCode get StockStatus => _get_status_class("stock/status/"); InvenTreeStatusCode get StockStatus => _get_status_class("stock/status/");
InvenTreeStatusCode get PurchaseOrderStatus => _get_status_class("order/po/status/"); InvenTreeStatusCode get PurchaseOrderStatus => _get_status_class("order/po/status/");
InvenTreeStatusCode get SalesOrderStatus => _get_status_class("order/so/status/");
void clearStatusCodeData() { void clearStatusCodeData() {
StockHistoryStatus.data.clear(); StockHistoryStatus.data.clear();
StockStatus.data.clear(); StockStatus.data.clear();
PurchaseOrderStatus.data.clear(); PurchaseOrderStatus.data.clear();
SalesOrderStatus.data.clear();
}
Future<void> fetchStatusCodeData({bool forceReload = true}) async {
StockHistoryStatus.load(forceReload: forceReload);
StockStatus.load(forceReload: forceReload);
PurchaseOrderStatus.load(forceReload: forceReload);
SalesOrderStatus.load(forceReload: forceReload);
} }
int notification_counter = 0; int notification_counter = 0;

View File

@ -23,13 +23,13 @@ import "package:inventree/inventree/purchase_order.dart";
import "package:inventree/inventree/stock.dart"; import "package:inventree/inventree/stock.dart";
import "package:inventree/widget/dialogs.dart"; import "package:inventree/widget/dialogs.dart";
import "package:inventree/widget/location_display.dart"; import "package:inventree/widget/stock/location_display.dart";
import "package:inventree/widget/part_detail.dart"; import "package:inventree/widget/part/part_detail.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/widget/refreshable_state.dart";
import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/snacks.dart";
import "package:inventree/widget/stock_detail.dart"; import "package:inventree/widget/stock/stock_detail.dart";
import "package:inventree/widget/supplier_part_detail.dart"; import "package:inventree/widget/company/supplier_part_detail.dart";
/* /*

View File

@ -1,8 +1,8 @@
import 'dart:async'; import "dart:async';
import 'package:flutter/foundation.dart'; import "package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import "package:flutter/material.dart';
// ignore_for_file: non_constant_identifier_names // ignore_for_file: non_constant_identifier_names
// ignore_for_file: camel_case_types // ignore_for_file: camel_case_types
// ignore_for_file: prefer_single_quotes // ignore_for_file: prefer_single_quotes

View File

@ -22,7 +22,7 @@ class InvenTreeCompany extends InvenTreeModel {
List<String> get rolesRequired => ["purchase_order", "sales_order", "return_order"]; List<String> get rolesRequired => ["purchase_order", "sales_order", "return_order"];
@override @override
Map<String, dynamic> formFields() { Map<String, Map<String, dynamic>> formFields() {
return { return {
"name": {}, "name": {},
"description": {}, "description": {},
@ -121,8 +121,8 @@ class InvenTreeSupplierPart extends InvenTreeModel {
List<String> get rolesRequired => ["part", "purchase_order"]; List<String> get rolesRequired => ["part", "purchase_order"];
@override @override
Map<String, dynamic> formFields() { Map<String, Map<String, dynamic>> formFields() {
Map<String, dynamic> fields = { Map<String, Map<String, dynamic>> fields = {
"supplier": {}, "supplier": {},
"SKU": {}, "SKU": {},
"link": {}, "link": {},

View File

@ -230,7 +230,7 @@ class InvenTreeModel {
// Fields for editing / creating this model // Fields for editing / creating this model
// Override per-model // Override per-model
Map<String, dynamic> formFields() { Map<String, Map<String, dynamic>> formFields() {
return {}; return {};
} }

107
lib/inventree/orders.dart Normal file
View File

@ -0,0 +1,107 @@
/*
* Base model for various "orders" which share common properties
*/
import "package:inventree/inventree/model.dart";
import "package:inventree/inventree/part.dart";
/*
* Generic class representing an "order"
*/
class InvenTreeOrder extends InvenTreeModel {
InvenTreeOrder() : super();
InvenTreeOrder.fromJson(Map<String, dynamic> json) : super.fromJson(json);
String get issueDate => getString("issue_date");
String get completeDate => getString("complete_date");
String get creationDate => getString("creation_date");
String get targetDate => getString("target_date");
int get lineItemCount => getInt("line_items", backup: 0);
bool get overdue => getBool("overdue");
String get reference => getString("reference");
int get responsibleId => getInt("responsible");
// Project code information
int get projectCodeId => getInt("project_code");
String get projectCode => getString("code", subKey: "project_code_detail");
String get projectCodeDescription => getString("description", subKey: "project_code_detail");
bool get hasProjectCode => projectCode.isNotEmpty;
int get status => getInt("status");
String get statusText => getString("status_text");
double? get totalPrice {
String price = getString("total_price");
if (price.isEmpty) {
return null;
} else {
return double.tryParse(price);
}
}
// Return the currency for this order
// Note that the nomenclature in the API changed at some point
String get totalPriceCurrency {
if (jsondata.containsKey("order_currency")) {
return getString("order_currency");
} else if (jsondata.containsKey("total_price_currency")) {
return getString("total_price_currency");
} else {
return "";
}
}
}
/*
* Generic class representing an "order line"
*/
class InvenTreeOrderLine extends InvenTreeModel {
InvenTreeOrderLine() : super();
InvenTreeOrderLine.fromJson(Map<String, dynamic> json) : super.fromJson(json);
bool get overdue => getBool("overdue");
double get quantity => getDouble("quantity");
String get reference => getString("reference");
int get orderId => getInt("order");
InvenTreePart? get part {
dynamic part_detail = jsondata["part_detail"];
if (part_detail == null) {
return null;
} else {
return InvenTreePart.fromJson(part_detail as Map<String, dynamic>);
}
}
int get partId => getInt("pk", subKey: "part_detail");
String get partName => getString("name", subKey: "part_detail");
String get partImage => getString("thumbnail", subKey: "part_detail");
// TODO: Perhaps parse this as an actual date?
String get targetDate => getString("target_date");
}

View File

@ -27,9 +27,9 @@ class InvenTreePartCategory extends InvenTreeModel {
List<String> get rolesRequired => ["part_category"]; List<String> get rolesRequired => ["part_category"];
@override @override
Map<String, dynamic> formFields() { Map<String, Map<String, dynamic>> formFields() {
Map<String, dynamic> fields = { Map<String, Map<String, dynamic>> fields = {
"name": {}, "name": {},
"description": {}, "description": {},
"parent": {}, "parent": {},
@ -140,9 +140,9 @@ class InvenTreePartParameter extends InvenTreeModel {
InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePartParameter.fromJson(json); InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePartParameter.fromJson(json);
@override @override
Map<String, dynamic> formFields() { Map<String, Map<String, dynamic>> formFields() {
Map<String, dynamic> fields = { Map<String, Map<String, dynamic>> fields = {
"header": { "header": {
"type": "string", "type": "string",
"read_only": true, "read_only": true,
@ -200,7 +200,7 @@ class InvenTreePart extends InvenTreeModel {
List<String> get rolesRequired => ["part"]; List<String> get rolesRequired => ["part"];
@override @override
Map<String, dynamic> formFields() { Map<String, Map<String, dynamic>> formFields() {
return { return {
"name": {}, "name": {},
"description": {}, "description": {},

View File

@ -17,7 +17,7 @@ class InvenTreeProjectCode extends InvenTreeModel {
String get URL => "project-code/"; String get URL => "project-code/";
@override @override
Map<String, dynamic> formFields() { Map<String, Map<String, dynamic>> formFields() {
return { return {
"code": {}, "code": {},
"description": {}, "description": {},

View File

@ -1,22 +1,22 @@
import "package:inventree/api.dart"; import "package:inventree/api.dart";
import "package:inventree/helpers.dart"; import "package:inventree/helpers.dart";
import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/company.dart";
import "package:inventree/inventree/part.dart";
import "package:inventree/inventree/model.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() : super();
InvenTreePurchaseOrder.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreePurchaseOrder.fromJson(Map<String, dynamic> json) : super.fromJson(json);
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePurchaseOrder.fromJson(json);
@override @override
String get URL => "order/po/"; String get URL => "order/po/";
@ -26,8 +26,8 @@ class InvenTreePurchaseOrder extends InvenTreeModel {
String get receive_url => "${url}receive/"; String get receive_url => "${url}receive/";
@override @override
Map<String, dynamic> formFields() { Map<String, Map<String, dynamic>> formFields() {
var fields = { Map<String, Map<String, dynamic>> fields = {
"reference": {}, "reference": {},
"supplier": { "supplier": {
"filters": { "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"); 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 {
dynamic supplier_detail = jsondata["supplier_detail"]; dynamic supplier_detail = jsondata["supplier_detail"];
@ -109,39 +84,13 @@ class InvenTreePurchaseOrder extends InvenTreeModel {
String get supplierReference => getString("supplier_reference"); 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 isFailed => api.PurchaseOrderStatus.isNameIn(status, ["CANCELLED", "LOST", "RETURNED"]);
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 "";
}
}
Future<List<InvenTreePOLineItem>> getLineItems() async { Future<List<InvenTreePOLineItem>> getLineItems() async {
@ -162,9 +111,6 @@ class InvenTreePurchaseOrder extends InvenTreeModel {
return items; return items;
} }
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePurchaseOrder.fromJson(json);
/// Mark this order as "placed" / "issued" /// Mark this order as "placed" / "issued"
Future<void> issueOrder() async { Future<void> issueOrder() async {
// Order can only be placed when the order is 'pending' // 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() : super();
InvenTreePOLineItem.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreePOLineItem.fromJson(Map<String, dynamic> json) : super.fromJson(json);
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePOLineItem.fromJson(json);
@override @override
String get URL => "order/po-line/"; String get URL => "order/po-line/";
@ -198,7 +147,7 @@ class InvenTreePOLineItem extends InvenTreeModel {
List<String> get rolesRequired => ["purchase_order"]; List<String> get rolesRequired => ["purchase_order"];
@override @override
Map<String, dynamic> formFields() { Map<String, Map<String, dynamic>> formFields() {
return { return {
"part": { "part": {
// We cannot edit the supplier part field here // 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; 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); String get progressString => simpleNumberString(received) + " / " + simpleNumberString(quantity);
double get outstanding => quantity - received; double get outstanding => quantity - received;
String get reference => getString("reference");
int get orderId => getInt("order");
int get supplierPartId => getInt("part"); int get supplierPartId => getInt("part");
InvenTreePart? get part {
dynamic part_detail = jsondata["part_detail"];
if (part_detail == null) {
return null;
} else {
return InvenTreePart.fromJson(part_detail as Map<String, dynamic>);
}
}
int get partId => getInt("pk", subKey: "part_detail");
String get partName => getString("name", subKey: "part_detail");
String get partImage => getString("thumbnail", subKey: "part_detail");
InvenTreeSupplierPart? get supplierPart { InvenTreeSupplierPart? get supplierPart {
dynamic detail = jsondata["supplier_part_detail"]; dynamic detail = jsondata["supplier_part_detail"];
@ -281,19 +216,13 @@ class InvenTreePOLineItem extends InvenTreeModel {
String get purchasePriceCurrency => getString("purchase_price_currency"); String get purchasePriceCurrency => getString("purchase_price_currency");
String get purchasePriceString => getString("purchase_price_string");
int get destination => getInt("destination"); int get destination => getInt("destination");
Map<String, dynamic> get destinationDetail => getMap("destination_detail"); Map<String, dynamic> get destinationDetail => getMap("destination_detail");
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreePOLineItem.fromJson(json);
} }
/* /*
* Class representing an attachment file against a StockItem object * Class representing an attachment file against a PurchaseOrder object
*/ */
class InvenTreePurchaseOrderAttachment extends InvenTreeAttachment { class InvenTreePurchaseOrderAttachment extends InvenTreeAttachment {

View File

@ -0,0 +1,190 @@
import "package:inventree/helpers.dart";
import "package:inventree/inventree/company.dart";
import "package:inventree/inventree/model.dart";
import "package:inventree/inventree/orders.dart";
import "package:inventree/api.dart";
/*
* Class representing an individual SalesOrder
*/
class InvenTreeSalesOrder extends InvenTreeOrder {
InvenTreeSalesOrder() : super();
InvenTreeSalesOrder.fromJson(Map<String, dynamic> json) : super.fromJson(json);
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeSalesOrder.fromJson(json);
@override
String get URL => "order/so/";
@override
List<String> get rolesRequired => ["sales_order"];
@override
Map<String, Map<String, dynamic>> formFields() {
Map<String, Map<String, dynamic>> fields = {
"reference": {},
"customer": {
"filters": {
"is_customer": true,
}
},
"customer_reference": {},
"description": {},
"project_code": {},
"target_date": {},
"link": {},
"responsible": {},
"contact": {
"filters": {
"company": customerId,
}
}
};
if (!InvenTreeAPI().supportsProjectCodes) {
fields.remove("project_code");
}
if (!InvenTreeAPI().supportsContactModel) {
fields.remove("contact");
}
return fields;
}
@override
Map<String, String> defaultGetFilters() {
return {
"customer_detail": "true",
};
}
@override
Map<String, String> defaultListFilters() {
return {
"customer_detail": "true",
};
}
int get customerId => getInt("customer");
InvenTreeCompany? get customer {
dynamic customer_detail = jsondata["customer_detail"];
if (customer_detail == null) {
return null;
} else {
return InvenTreeCompany.fromJson(customer_detail as Map<String, dynamic>);
}
}
String get customerReference => getString("customer_reference");
bool get isOpen => api.SalesOrderStatus.isNameIn(status, ["PENDING", "IN_PROGRESS"]);
bool get isComplete => api.SalesOrderStatus.isNameIn(status, ["SHIPPED"]);
}
/*
* Class representing an individual line item in a SalesOrder
*/
class InvenTreeSOLineItem extends InvenTreeOrderLine {
InvenTreeSOLineItem() : super();
InvenTreeSOLineItem.fromJson(Map<String, dynamic> json) : super.fromJson(json);
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeSOLineItem.fromJson(json);
@override
String get URL => "order/so-line/";
@override
List<String> get rolesRequired => ["sales_order"];
@override
Map<String, Map<String, dynamic>> formFields() {
return {
"order": {
"hidden": true,
},
"part": {},
"quantity": {},
"reference": {},
"notes": {},
"link": {},
};
}
@override
Map<String, String> defaultGetFilters() {
return {
"part_detail": "true",
};
}
@override
Map<String, String> defaultListFilters() {
return {
"part_detail": "true",
};
}
double get allocated => getDouble("allocated");
bool get isAllocated => allocated >= quantity;
double get shipped => getDouble("shipped");
double get outstanding => quantity - shipped;
double get progressRatio {
if (quantity <= 0 || shipped <= 0) {
return 0;
}
return shipped / quantity;
}
String get progressString => simpleNumberString(shipped) + " / " + simpleNumberString(quantity);
bool get isComplete => shipped >= quantity;
double get available => getDouble("available_stock") + getDouble("available_variant_stock");
double get salePrice => getDouble("sale_price");
String get salePriceCurrency => getString("sale_price_currency");
}
/*
* Class representing an attachment file against a SalesOrder object
*/
class InvenTreeSalesOrderAttachment extends InvenTreeAttachment {
InvenTreeSalesOrderAttachment() : super();
InvenTreeSalesOrderAttachment.fromJson(Map<String, dynamic> json) : super.fromJson(json);
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) => InvenTreeSalesOrderAttachment.fromJson(json);
@override
String get REFERENCE_FIELD => "order";
@override
String get URL => "order/po/attachment/";
}

View File

@ -105,6 +105,24 @@ class InvenTreeStatusCode {
} }
} }
// Return the 'name' (untranslated) associated with a given status code
String name(int status) {
Map<String, dynamic> _entry = entry(status);
String _name = (_entry["name"] ?? "") as String;
if (_name.isEmpty) {
debug("No match for status code ${status} at '${URL}'");
}
return _name;
}
// Test if the name associated with the given code is in the provided list
bool isNameIn(int code, List<String> names) {
return names.contains(name(code));
}
// Return the 'color' associated with a given status code // Return the 'color' associated with a given status code
Color color(int status) { Color color(int status) {
Map<String, dynamic> _entry = entry(status); Map<String, dynamic> _entry = entry(status);

View File

@ -27,7 +27,7 @@ class InvenTreeStockItemTestResult extends InvenTreeModel {
List<String> get rolesRequired => ["stock"]; List<String> get rolesRequired => ["stock"];
@override @override
Map<String, dynamic> formFields() { Map<String, Map<String, dynamic>> formFields() {
return { return {
"stock_item": {"hidden": true}, "stock_item": {"hidden": true},
"test": {}, "test": {},
@ -158,8 +158,8 @@ class InvenTreeStockItem extends InvenTreeModel {
String get WEB_URL => "stock/item/"; String get WEB_URL => "stock/item/";
@override @override
Map<String, dynamic> formFields() { Map<String, Map<String, dynamic>> formFields() {
return { Map<String, Map<String, dynamic>> fields = {
"part": {}, "part": {},
"location": {}, "location": {},
"quantity": {}, "quantity": {},
@ -175,6 +175,8 @@ class InvenTreeStockItem extends InvenTreeModel {
"packaging": {}, "packaging": {},
"link": {}, "link": {},
}; };
return fields;
} }
@override @override
@ -609,8 +611,8 @@ class InvenTreeStockLocation extends InvenTreeModel {
String get pathstring => getString("pathstring"); String get pathstring => getString("pathstring");
@override @override
Map<String, dynamic> formFields() { Map<String, Map<String, dynamic>> formFields() {
Map<String, dynamic> fields = { Map<String, Map<String, dynamic>> fields = {
"name": {}, "name": {},
"description": {}, "description": {},
"parent": {}, "parent": {},

View File

@ -235,9 +235,15 @@
"credits": "Credits", "credits": "Credits",
"@credits": {}, "@credits": {},
"customer": "Customer",
"@customer": {},
"customers": "Customers", "customers": "Customers",
"@customers": {}, "@customers": {},
"customerReference": "Customer Reference",
"@customerReference": {},
"damaged": "Damaged", "damaged": "Damaged",
"@damaged": {}, "@damaged": {},
@ -440,15 +446,21 @@
"homeShowPo": "Show Purchase Orders", "homeShowPo": "Show Purchase Orders",
"@homeShowPo": {}, "@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": "Subscribed Parts",
"@homeShowSubscribed": {}, "@homeShowSubscribed": {},
"homeShowSubscribedDescription": "Show subscribed parts on home screen", "homeShowSubscribedDescription": "Show subscribed parts on home screen",
"@homeShowSubscsribedDescription": {}, "@homeShowSubscsribedDescription": {},
"homeShowPoDescription": "Show purchase order button on home screen",
"@homeShowPoDescription": {},
"homeShowSuppliers": "Show Suppliers", "homeShowSuppliers": "Show Suppliers",
"@homeShowSuppliers": {}, "@homeShowSuppliers": {},
@ -576,6 +588,9 @@
"level": "Level", "level": "Level",
"@level": {}, "@level": {},
"lineItemAdd": "Add Line Item",
"@lineItemAdd": {},
"lineItem": "Line Item", "lineItem": "Line Item",
"@lineItem": {}, "@lineItem": {},
@ -687,9 +702,15 @@
"outstanding": "Outstanding", "outstanding": "Outstanding",
"@outstanding": {}, "@outstanding": {},
"outstandingOrderDetail": "Show outstanding items", "outstandingOrderDetail": "Show outstanding orders",
"@outstandingOrderDetail": {}, "@outstandingOrderDetail": {},
"overdue": "Overdue",
"@overdue": {},
"overdueDetail": "Show overdue orders",
"@overdueDetail": {},
"packaging": "Packaging", "packaging": "Packaging",
"@packaging": {}, "@packaging": {},
@ -997,9 +1018,21 @@
"returned": "Returned", "returned": "Returned",
"@returned": {}, "@returned": {},
"salesOrder": "Sales Order",
"@salesOrder": {},
"salesOrders": "Sales Orders", "salesOrders": "Sales Orders",
"@salesOrders": {}, "@salesOrders": {},
"salesOrderCreate": "New Sales Order",
"@saleOrderCreate": {},
"salesOrderEdit": "Edit Sales Order",
"@salesOrderEdit": {},
"salesOrderUpdated": "Sales order updated",
"@salesOrderUpdated": {},
"save": "Save", "save": "Save",
"@save": { "@save": {
"description": "Save" "description": "Save"
@ -1125,6 +1158,9 @@
"serverNotSelected": "Server not selected", "serverNotSelected": "Server not selected",
"@serverNotSelected": {}, "@serverNotSelected": {},
"shipped": "Shipped",
"@shipped": {},
"sku": "SKU", "sku": "SKU",
"@sku": {}, "@sku": {},

View File

@ -11,6 +11,7 @@ import "package:path/path.dart";
// Settings key values // Settings key values
const String INV_HOME_SHOW_SUBSCRIBED = "homeShowSubscribed"; const String INV_HOME_SHOW_SUBSCRIBED = "homeShowSubscribed";
const String INV_HOME_SHOW_PO = "homeShowPo"; 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_MANUFACTURERS = "homeShowManufacturers";
const String INV_HOME_SHOW_CUSTOMERS = "homeShowCustomers"; const String INV_HOME_SHOW_CUSTOMERS = "homeShowCustomers";
const String INV_HOME_SHOW_SUPPLIERS = "homeShowSuppliers"; const String INV_HOME_SHOW_SUPPLIERS = "homeShowSuppliers";

View File

@ -21,6 +21,7 @@ class _HomeScreenSettingsState extends State<HomeScreenSettingsWidget> {
// Home screen settings // Home screen settings
bool homeShowSubscribed = true; bool homeShowSubscribed = true;
bool homeShowPo = true; bool homeShowPo = true;
bool homeShowSo = true;
bool homeShowSuppliers = true; bool homeShowSuppliers = true;
bool homeShowManufacturers = true; bool homeShowManufacturers = true;
bool homeShowCustomers = true; bool homeShowCustomers = true;
@ -38,6 +39,7 @@ class _HomeScreenSettingsState extends State<HomeScreenSettingsWidget> {
homeShowSubscribed = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_SUBSCRIBED, true) as bool; homeShowSubscribed = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_SUBSCRIBED, true) as bool;
homeShowPo = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_PO, 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; homeShowManufacturers = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_MANUFACTURERS, true) as bool;
homeShowCustomers = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_CUSTOMERS, 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; homeShowSuppliers = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_SUPPLIERS, true) as bool;
@ -85,6 +87,20 @@ class _HomeScreenSettingsState extends State<HomeScreenSettingsWidget> {
}, },
), ),
), ),
ListTile(
title: Text(L10().homeShowSo),
subtitle: Text(L10().homeShowSoDescription),
leading: FaIcon(FontAwesomeIcons.truck),
trailing: Switch(
value: homeShowSo,
onChanged: (bool value) {
InvenTreeSettingsManager().setValue(INV_HOME_SHOW_SO, value);
setState(() {
homeShowSo = value;
});
},
),
),
ListTile( ListTile(
title: Text(L10().homeShowSuppliers), title: Text(L10().homeShowSuppliers),
subtitle: Text(L10().homeShowSuppliersDescription), subtitle: Text(L10().homeShowSuppliersDescription),
@ -116,6 +132,7 @@ class _HomeScreenSettingsState extends State<HomeScreenSettingsWidget> {
}, },
), ),
), ),
*/
ListTile( ListTile(
title: Text(L10().homeShowCustomers), title: Text(L10().homeShowCustomers),
subtitle: Text(L10().homeShowCustomersDescription), subtitle: Text(L10().homeShowCustomersDescription),
@ -130,7 +147,6 @@ class _HomeScreenSettingsState extends State<HomeScreenSettingsWidget> {
}, },
), ),
), ),
*/
] ]
) )
) )

View File

@ -9,12 +9,16 @@ import "package:inventree/helpers.dart";
import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/company.dart";
import "package:inventree/inventree/purchase_order.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/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/refreshable_state.dart";
import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/snacks.dart";
import "package:inventree/widget/supplier_part_list.dart"; import "package:inventree/widget/company/supplier_part_list.dart";
import "package:inventree/widget/order/sales_order_detail.dart";
import "package:inventree/widget/order/purchase_order_detail.dart";
/* /*
@ -36,10 +40,11 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
_CompanyDetailState(); _CompanyDetailState();
List<InvenTreePurchaseOrder> outstandingOrders = [];
int supplierPartCount = 0; int supplierPartCount = 0;
int outstandingPurchaseOrders = 0;
int outstandingSalesOrders = 0;
int attachmentCount = 0; int attachmentCount = 0;
@override @override
@ -68,11 +73,87 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
List<SpeedDialChild> actionButtons(BuildContext context) { List<SpeedDialChild> actionButtons(BuildContext context) {
List<SpeedDialChild> actions = []; List<SpeedDialChild> actions = [];
// TODO - Actions for this company if (widget.company.isCustomer && InvenTreeSalesOrder().canCreate) {
actions.add(SpeedDialChild(
child: FaIcon(FontAwesomeIcons.truck),
label: L10().salesOrderCreate,
onTap: () async {
_createSalesOrder(context);
}
));
}
if (widget.company.isSupplier && InvenTreePurchaseOrder().canCreate) {
actions.add(SpeedDialChild(
child: FaIcon(FontAwesomeIcons.cartShopping),
label: L10().purchaseOrderCreate,
onTap: () async {
_createPurchaseOrder(context);
}
));
}
return actions; return actions;
} }
Future<void> _createSalesOrder(BuildContext context) async {
var fields = InvenTreeSalesOrder().formFields();
// Cannot set contact until company is locked in
fields.remove("contact");
fields["customer"]?["value"] = widget.company.pk;
InvenTreeSalesOrder().createForm(
context,
L10().salesOrderCreate,
fields: fields,
onSuccess: (result) async {
Map<String, dynamic> data = result as Map<String, dynamic>;
if (data.containsKey("pk")) {
var order = InvenTreeSalesOrder.fromJson(data);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SalesOrderDetailWidget(order)
)
);
}
}
);
}
Future<void> _createPurchaseOrder(BuildContext context) async {
var fields = InvenTreePurchaseOrder().formFields();
// Cannot set contact until company is locked in
fields.remove("contact");
fields["supplier"]?["value"] = widget.company.pk;
InvenTreePurchaseOrder().createForm(
context,
L10().purchaseOrderCreate,
fields: fields,
onSuccess: (result) async {
Map<String, dynamic> data = result as Map<String, dynamic>;
if (data.containsKey("pk")) {
var order = InvenTreePurchaseOrder.fromJson(data);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PurchaseOrderDetailWidget(order)
)
);
}
}
);
}
@override @override
Future<void> request(BuildContext context) async { Future<void> request(BuildContext context) async {
final bool result = await widget.company.reload(); final bool result = await widget.company.reload();
@ -83,11 +164,18 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
return; return;
} }
if (widget.company.isSupplier) { outstandingPurchaseOrders = widget.company.isSupplier ?
outstandingOrders = await InvenTreePurchaseOrder().count(filters: {
await widget.company.getPurchaseOrders(outstanding: true); "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( InvenTreeSupplierPart().count(
filters: { filters: {
"supplier": widget.company.pk.toString() "supplier": widget.company.pk.toString()
@ -224,7 +312,7 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
ListTile( ListTile(
title: Text(L10().purchaseOrders), title: Text(L10().purchaseOrders),
leading: FaIcon(FontAwesomeIcons.cartShopping, color: COLOR_ACTION), leading: FaIcon(FontAwesomeIcons.cartShopping, color: COLOR_ACTION),
trailing: Text("${outstandingOrders.length}"), trailing: Text("${outstandingPurchaseOrders}"),
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
@ -257,7 +345,25 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
} }
if (widget.company.isCustomer) { 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) { if (widget.company.notes.isNotEmpty) {

View File

@ -8,7 +8,7 @@ import "package:inventree/inventree/model.dart";
import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/paginator.dart";
import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/refreshable_state.dart";
import "package:inventree/widget/company_detail.dart"; import "package:inventree/widget/company/company_detail.dart";
/* /*

View File

@ -10,8 +10,8 @@ import "package:inventree/l10.dart";
import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/part.dart";
import "package:inventree/inventree/company.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/part_detail.dart"; import "package:inventree/widget/part/part_detail.dart";
import "package:inventree/widget/progress.dart"; import "package:inventree/widget/progress.dart";
import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/refreshable_state.dart";
import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/snacks.dart";

View File

@ -8,7 +8,7 @@ import "package:inventree/inventree/model.dart";
import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/paginator.dart";
import "package:inventree/widget/refreshable_state.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";
/* /*

View File

@ -3,15 +3,17 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:inventree/api.dart"; import "package:inventree/api.dart";
import "package:inventree/app_colors.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/purchase_order.dart";
import "package:inventree/inventree/sales_order.dart";
import "package:inventree/inventree/stock.dart"; import "package:inventree/inventree/stock.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
import "package:inventree/settings/settings.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/notifications.dart";
import "package:inventree/widget/purchase_order_list.dart"; import "package:inventree/widget/order/purchase_order_list.dart";
import "package:inventree/widget/location_display.dart"; import "package:inventree/widget/stock/location_display.dart";
/* /*
@ -28,6 +30,10 @@ class InvenTreeDrawer extends StatelessWidget {
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
bool _checkConnection() {
return InvenTreeAPI().checkConnection();
}
/* /*
* Return to the 'home' screen. * Return to the 'home' screen.
* This will empty the navigation stack. * This will empty the navigation stack.
@ -43,37 +49,63 @@ class InvenTreeDrawer extends StatelessWidget {
// Load "parts" page // Load "parts" page
void _parts() { void _parts() {
_closeDrawer(); _closeDrawer();
Navigator.push(
context, if (_checkConnection()) {
MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null)) Navigator.push(
); context,
MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null))
);
}
} }
// Load "stock" page // Load "stock" page
void _stock() { void _stock() {
_closeDrawer(); _closeDrawer();
Navigator.push(
context, if (_checkConnection()) {
MaterialPageRoute(builder: (context) => LocationDisplayWidget(null)) 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 // Load "purchase orders" page
void _purchaseOrders() { void _purchaseOrders() {
_closeDrawer(); _closeDrawer();
Navigator.push( if (_checkConnection()) {
context, Navigator.push(
MaterialPageRoute( context,
builder: (context) => PurchaseOrderListWidget(filters: {}) MaterialPageRoute(
) builder: (context) => PurchaseOrderListWidget(filters: {})
); )
);
}
} }
// Load notifications screen // Load notifications screen
void _notifications() { void _notifications() {
_closeDrawer(); _closeDrawer();
Navigator.push(context, MaterialPageRoute(builder: (context) => NotificationWidget()));
if (_checkConnection()) {
Navigator.push(context,
MaterialPageRoute(builder: (context) => NotificationWidget()));
}
} }
// Load settings widget // Load settings widget
@ -98,7 +130,7 @@ class InvenTreeDrawer extends StatelessWidget {
tiles.add(Divider()); tiles.add(Divider());
if (InvenTreeCompany().canView) { if (InvenTreePart().canView) {
tiles.add( tiles.add(
ListTile( ListTile(
title: Text(L10().parts), 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) { if (tiles.length > 2) {
tiles.add(Divider()); tiles.add(Divider());
} }

View File

@ -6,20 +6,25 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:inventree/api.dart"; import "package:inventree/api.dart";
import "package:inventree/app_colors.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/preferences.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
import "package:inventree/settings/select_server.dart"; import "package:inventree/settings/select_server.dart";
import "package:inventree/user_profile.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/drawer.dart";
import "package:inventree/widget/location_display.dart"; import "package:inventree/widget/stock/location_display.dart";
import "package:inventree/widget/part_list.dart"; import "package:inventree/widget/part/part_list.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/refreshable_state.dart";
import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/snacks.dart";
import "package:inventree/widget/spinner.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 { class InvenTreeHomePage extends StatefulWidget {
@ -53,6 +58,7 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr
final homeKey = GlobalKey<ScaffoldState>(); final homeKey = GlobalKey<ScaffoldState>();
bool homeShowPo = false; bool homeShowPo = false;
bool homeShowSo = false;
bool homeShowSubscribed = false; bool homeShowSubscribed = false;
bool homeShowManufacturers = false; bool homeShowManufacturers = false;
bool homeShowCustomers = false; bool homeShowCustomers = false;
@ -97,6 +103,17 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr
); );
} }
void _showSalesOrders(BuildContext context) {
if (!InvenTreeAPI().checkConnection()) return;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SalesOrderListWidget(filters: {})
)
);
}
void _showSuppliers(BuildContext context) { void _showSuppliers(BuildContext context) {
if (!InvenTreeAPI().checkConnection()) return; if (!InvenTreeAPI().checkConnection()) return;
@ -110,12 +127,12 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr
Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().manufacturers, {"is_manufacturer": "true"}))); Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().manufacturers, {"is_manufacturer": "true"})));
} }
*/
void _showCustomers(BuildContext context) { void _showCustomers(BuildContext context) {
if (!InvenTreeAPI().checkConnection()) return; if (!InvenTreeAPI().checkConnection()) return;
Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().customers, {"is_customer": "true"}))); Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().customers, {"is_customer": "true"})));
} }
*/
void _selectProfile() { void _selectProfile() {
Navigator.push( Navigator.push(
@ -130,6 +147,7 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr
homeShowSubscribed = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_SUBSCRIBED, true) as bool; homeShowSubscribed = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_SUBSCRIBED, true) as bool;
homeShowPo = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_PO, 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; homeShowManufacturers = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_MANUFACTURERS, true) as bool;
homeShowCustomers = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_CUSTOMERS, 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; homeShowSuppliers = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_SUPPLIERS, true) as bool;
@ -207,17 +225,19 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr
]; ];
// Parts // Parts
tiles.add(_listTile( if (InvenTreePart().canView) {
context, tiles.add(_listTile(
L10().parts, context,
FontAwesomeIcons.shapes, L10().parts,
callback: () { FontAwesomeIcons.shapes,
_showParts(context); callback: () {
}, _showParts(context);
)); },
));
}
// Starred parts // Starred parts
if (homeShowSubscribed) { if (homeShowSubscribed && InvenTreePart().canView) {
tiles.add(_listTile( tiles.add(_listTile(
context, context,
L10().partsStarred, L10().partsStarred,
@ -229,17 +249,19 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr
} }
// Stock button // Stock button
tiles.add(_listTile( if (InvenTreeStockItem().canView) {
context, tiles.add(_listTile(
L10().stock, context,
FontAwesomeIcons.boxesStacked, L10().stock,
callback: () { FontAwesomeIcons.boxesStacked,
_showStock(context); callback: () {
} _showStock(context);
)); }
));
}
// Purchase orders // Purchase orders
if (homeShowPo) { if (homeShowPo && InvenTreePurchaseOrder().canView) {
tiles.add(_listTile( tiles.add(_listTile(
context, context,
L10().purchaseOrders, L10().purchaseOrders,
@ -250,8 +272,19 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr
)); ));
} }
if (homeShowSo && InvenTreeSalesOrder().canView) {
tiles.add(_listTile(
context,
L10().salesOrders,
FontAwesomeIcons.truck,
callback: () {
_showSalesOrders(context);
}
));
}
// Suppliers // Suppliers
if (homeShowSuppliers) { if (homeShowSuppliers && InvenTreePurchaseOrder().canView) {
tiles.add(_listTile( tiles.add(_listTile(
context, context,
L10().suppliers, L10().suppliers,
@ -277,7 +310,7 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr
} }
)); ));
} }
*/
// Customers // Customers
if (homeShowCustomers) { if (homeShowCustomers) {
tiles.add(_listTile( tiles.add(_listTile(
@ -289,7 +322,6 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetPr
} }
)); ));
} }
*/
return tiles; return tiles;
} }

View File

@ -7,17 +7,17 @@ import "package:inventree/app_colors.dart";
import "package:inventree/helpers.dart"; import "package:inventree/helpers.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
import "package:inventree/widget/progress.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/widget/refreshable_state.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/purchase_order.dart"; import "package:inventree/inventree/purchase_order.dart";
import "package:inventree/widget/snacks.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 { class POLineDetailWidget extends StatefulWidget {
@ -171,7 +171,6 @@ class _POLineDetailWidgetState extends RefreshableState<POLineDetailWidget> {
trailing: api.getThumbnail(widget.item.partImage), trailing: api.getThumbnail(widget.item.partImage),
onTap: () async { onTap: () async {
showLoadingOverlay(context); showLoadingOverlay(context);
print("part id: ${widget.item.partId}");
var part = await InvenTreePart().get(widget.item.partId); var part = await InvenTreePart().get(widget.item.partId);
hideLoadingOverlay(); hideLoadingOverlay();
@ -200,16 +199,32 @@ class _POLineDetailWidgetState extends RefreshableState<POLineDetailWidget> {
) )
); );
// Recevied // Received quantity
tiles.add( tiles.add(
ListTile( ListTile(
title: Text(L10().received), title: Text(L10().received),
subtitle: Text(widget.item.received.toString()), subtitle: ProgressBar(widget.item.progressRatio),
trailing: Text(widget.item.progressString, style: TextStyle(color: widget.item.isComplete ? COLOR_SUCCESS: COLOR_WARNING)), trailing: Text(
widget.item.progressString,
style: TextStyle(
color: widget.item.isComplete ? COLOR_SUCCESS: COLOR_WARNING
)
),
leading: FaIcon(FontAwesomeIcons.boxOpen), 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 // Pricing information
tiles.add( tiles.add(
ListTile( ListTile(

View File

@ -9,7 +9,7 @@ import "package:inventree/inventree/model.dart";
import "package:inventree/inventree/purchase_order.dart"; import "package:inventree/inventree/purchase_order.dart";
import "package:inventree/widget/paginator.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"; import "package:inventree/widget/progress.dart";
/* /*

View File

@ -2,9 +2,8 @@ import "package:flutter/material.dart";
import "package:flutter_speed_dial/flutter_speed_dial.dart"; import "package:flutter_speed_dial/flutter_speed_dial.dart";
import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:inventree/widget/dialogs.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/app_colors.dart";
import "package:inventree/barcode/barcode.dart"; import "package:inventree/barcode/barcode.dart";
import "package:inventree/helpers.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/company.dart";
import "package:inventree/inventree/purchase_order.dart"; import "package:inventree/inventree/purchase_order.dart";
import "package:inventree/widget/attachment_widget.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/notes_widget.dart";
import "package:inventree/widget/progress.dart";
import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/refreshable_state.dart";
import "package:inventree/widget/snacks.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 { class PurchaseOrderDetailWidget extends StatefulWidget {
const PurchaseOrderDetailWidget(this.order, {Key? key}): super(key: key); const PurchaseOrderDetailWidget(this.order, {Key? key}): super(key: key);
@ -27,16 +30,14 @@ class PurchaseOrderDetailWidget extends StatefulWidget {
final InvenTreePurchaseOrder order; final InvenTreePurchaseOrder order;
@override @override
_PurchaseOrderDetailState createState() => _PurchaseOrderDetailState(order); _PurchaseOrderDetailState createState() => _PurchaseOrderDetailState();
} }
class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidget> { class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidget> {
_PurchaseOrderDetailState(this.order); _PurchaseOrderDetailState();
final InvenTreePurchaseOrder order;
List<InvenTreePOLineItem> lines = []; List<InvenTreePOLineItem> lines = [];
int completedLines = 0; int completedLines = 0;
@ -52,7 +53,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
List<Widget> appBarActions(BuildContext context) { List<Widget> appBarActions(BuildContext context) {
List<Widget> actions = []; List<Widget> actions = [];
if (order.canEdit) { if (widget.order.canEdit) {
actions.add( actions.add(
IconButton( IconButton(
icon: Icon(Icons.edit_square), icon: Icon(Icons.edit_square),
@ -71,8 +72,8 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
List<SpeedDialChild> actionButtons(BuildContext context) { List<SpeedDialChild> actionButtons(BuildContext context) {
List<SpeedDialChild> actions = []; List<SpeedDialChild> actions = [];
if (order.canCreate) { if (widget.order.canCreate) {
if (order.isPending) { if (widget.order.isPending) {
actions.add( actions.add(
SpeedDialChild( SpeedDialChild(
child: FaIcon(FontAwesomeIcons.paperPlane, color: Colors.blue), child: FaIcon(FontAwesomeIcons.paperPlane, color: Colors.blue),
@ -84,7 +85,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
); );
} }
if (order.isOpen) { if (widget.order.isOpen) {
actions.add( actions.add(
SpeedDialChild( SpeedDialChild(
child: FaIcon(FontAwesomeIcons.circleXmark, color: Colors.red), child: FaIcon(FontAwesomeIcons.circleXmark, color: Colors.red),
@ -109,7 +110,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
color: Colors.blue, color: Colors.blue,
acceptText: L10().issue, acceptText: L10().issue,
onAccept: () async { onAccept: () async {
await order.issueOrder().then((dynamic) { await widget.order.issueOrder().then((dynamic) {
refresh(context); refresh(context);
}); });
} }
@ -125,7 +126,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
color: Colors.red, color: Colors.red,
acceptText: L10().cancel, acceptText: L10().cancel,
onAccept: () async { onAccept: () async {
await order.cancelOrder().then((dynamic) { await widget.order.cancelOrder().then((dynamic) {
print("callback"); print("callback");
refresh(context); refresh(context);
}); });
@ -145,7 +146,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
onTap:() async { onTap:() async {
scanBarcode( scanBarcode(
context, context,
handler: POReceiveBarcodeHandler(purchaseOrder: order), handler: POReceiveBarcodeHandler(purchaseOrder: widget.order),
); );
}, },
) )
@ -158,11 +159,11 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
@override @override
Future<void> request(BuildContext context) async { Future<void> request(BuildContext context) async {
await order.reload(); await widget.order.reload();
await api.PurchaseOrderStatus.load(); await api.PurchaseOrderStatus.load();
lines = await order.getLineItems(); lines = await widget.order.getLineItems();
supportProjectCodes = api.supportsProjectCodes && await api.getGlobalBooleanSetting("PROJECT_CODES_ENABLED"); supportProjectCodes = api.supportsProjectCodes && await api.getGlobalBooleanSetting("PROJECT_CODES_ENABLED");
@ -174,16 +175,20 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
} }
} }
attachmentCount = await InvenTreePurchaseOrderAttachment().count( InvenTreePurchaseOrderAttachment().count(filters: {
filters: { "order": widget.order.pk.toString()
"order": order.pk.toString() }).then((int value) {
if (mounted) {
setState(() {
attachmentCount = value;
});
} }
); });
} }
// 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 = widget.order.formFields();
// Cannot edit supplier field from here // Cannot edit supplier field from here
fields.remove("supplier"); fields.remove("supplier");
@ -198,7 +203,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
fields.remove("project_code"); fields.remove("project_code");
} }
order.editForm( widget.order.editForm(
context, context,
L10().purchaseOrderEdit, L10().purchaseOrderEdit,
fields: fields, fields: fields,
@ -211,17 +216,17 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
Widget headerTile(BuildContext context) { Widget headerTile(BuildContext context) {
InvenTreeCompany? supplier = order.supplier; InvenTreeCompany? supplier = widget.order.supplier;
return Card( return Card(
child: ListTile( child: ListTile(
title: Text(order.reference), title: Text(widget.order.reference),
subtitle: Text(order.description), subtitle: Text(widget.order.description),
leading: supplier == null ? null : InvenTreeAPI().getThumbnail(supplier.thumbnail), leading: supplier == null ? null : api.getThumbnail(supplier.thumbnail),
trailing: Text( trailing: Text(
api.PurchaseOrderStatus.label(order.status), api.PurchaseOrderStatus.label(widget.order.status),
style: TextStyle( style: TextStyle(
color: api.PurchaseOrderStatus.color(order.status) color: api.PurchaseOrderStatus.color(widget.order.status)
), ),
) )
) )
@ -233,14 +238,14 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
List<Widget> tiles = []; List<Widget> tiles = [];
InvenTreeCompany? supplier = order.supplier; InvenTreeCompany? supplier = widget.order.supplier;
tiles.add(headerTile(context)); tiles.add(headerTile(context));
if (supportProjectCodes && order.hasProjectCode) { if (supportProjectCodes && widget.order.hasProjectCode) {
tiles.add(ListTile( tiles.add(ListTile(
title: Text(L10().projectCode), title: Text(L10().projectCode),
subtitle: Text("${order.projectCode} - ${order.projectCodeDescription}"), subtitle: Text("${widget.order.projectCode} - ${widget.order.projectCodeDescription}"),
leading: FaIcon(FontAwesomeIcons.list), leading: FaIcon(FontAwesomeIcons.list),
)); ));
} }
@ -261,20 +266,24 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
)); ));
} }
if (order.supplierReference.isNotEmpty) { if (widget.order.supplierReference.isNotEmpty) {
tiles.add(ListTile( tiles.add(ListTile(
title: Text(L10().supplierReference), title: Text(L10().supplierReference),
subtitle: Text(order.supplierReference), subtitle: Text(widget.order.supplierReference),
leading: FaIcon(FontAwesomeIcons.hashtag), leading: FaIcon(FontAwesomeIcons.hashtag),
)); ));
} }
Color lineColor = completedLines < order.lineItemCount ? COLOR_WARNING : COLOR_SUCCESS; Color lineColor = completedLines < widget.order.lineItemCount ? COLOR_WARNING : COLOR_SUCCESS;
tiles.add(ListTile( tiles.add(ListTile(
title: Text(L10().lineItems), title: Text(L10().lineItems),
subtitle: ProgressBar(
completedLines.toDouble(),
maximum: widget.order.lineItemCount.toDouble(),
),
leading: FaIcon(FontAwesomeIcons.clipboardCheck), leading: FaIcon(FontAwesomeIcons.clipboardCheck),
trailing: Text("${completedLines} / ${order.lineItemCount}", style: TextStyle(color: lineColor)), trailing: Text("${completedLines} / ${widget.order.lineItemCount}", style: TextStyle(color: lineColor)),
)); ));
tiles.add(ListTile( tiles.add(ListTile(
@ -285,18 +294,18 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
), ),
)); ));
if (order.issueDate.isNotEmpty) { if (widget.order.issueDate.isNotEmpty) {
tiles.add(ListTile( tiles.add(ListTile(
title: Text(L10().issueDate), title: Text(L10().issueDate),
subtitle: Text(order.issueDate), subtitle: Text(widget.order.issueDate),
leading: FaIcon(FontAwesomeIcons.calendarDays), leading: FaIcon(FontAwesomeIcons.calendarDays),
)); ));
} }
if (order.targetDate.isNotEmpty) { if (widget.order.targetDate.isNotEmpty) {
tiles.add(ListTile( tiles.add(ListTile(
title: Text(L10().targetDate), title: Text(L10().targetDate),
subtitle: Text(order.targetDate), subtitle: Text(widget.order.targetDate),
leading: FaIcon(FontAwesomeIcons.calendarDays), leading: FaIcon(FontAwesomeIcons.calendarDays),
)); ));
} }
@ -310,7 +319,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => NotesWidget(order) builder: (context) => NotesWidget(widget.order)
) )
); );
}, },
@ -329,8 +338,8 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
MaterialPageRoute( MaterialPageRoute(
builder: (context) => AttachmentWidget( builder: (context) => AttachmentWidget(
InvenTreePurchaseOrderAttachment(), InvenTreePurchaseOrderAttachment(),
order.pk, widget.order.pk,
order.canEdit widget.order.canEdit
) )
) )
); );
@ -355,9 +364,9 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
List<Widget> getTabs(BuildContext context) { List<Widget> getTabs(BuildContext context) {
return [ return [
ListView(children: orderTiles(context)), ListView(children: orderTiles(context)),
PaginatedPOLineList({"order": order.pk.toString()}), PaginatedPOLineList({"order": widget.order.pk.toString()}),
// ListView(children: lineTiles(context)), // ListView(children: lineTiles(context)),
PaginatedStockItemList({"purchase_order": order.pk.toString()}), PaginatedStockItemList({"purchase_order": widget.order.pk.toString()}),
]; ];
} }

View File

@ -5,7 +5,7 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/company.dart";
import "package:inventree/inventree/model.dart"; import "package:inventree/inventree/model.dart";
import "package:inventree/widget/paginator.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/widget/refreshable_state.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
import "package:inventree/api.dart"; import "package:inventree/api.dart";
@ -22,15 +22,13 @@ class PurchaseOrderListWidget extends StatefulWidget {
final Map<String, String> filters; final Map<String, String> filters;
@override @override
_PurchaseOrderListWidgetState createState() => _PurchaseOrderListWidgetState(filters); _PurchaseOrderListWidgetState createState() => _PurchaseOrderListWidgetState();
} }
class _PurchaseOrderListWidgetState extends RefreshableState<PurchaseOrderListWidget> { class _PurchaseOrderListWidgetState extends RefreshableState<PurchaseOrderListWidget> {
_PurchaseOrderListWidgetState(this.filters); _PurchaseOrderListWidgetState();
final Map<String, String> filters;
@override @override
String getAppBarTitle() => L10().purchaseOrders; String getAppBarTitle() => L10().purchaseOrders;
@ -45,7 +43,7 @@ class _PurchaseOrderListWidgetState extends RefreshableState<PurchaseOrderListWi
child: FaIcon(FontAwesomeIcons.circlePlus), child: FaIcon(FontAwesomeIcons.circlePlus),
label: L10().purchaseOrderCreate, label: L10().purchaseOrderCreate,
onTap: () { onTap: () {
createPurchaseOrder(context); _createPurchaseOrder(context);
} }
) )
); );
@ -54,9 +52,11 @@ class _PurchaseOrderListWidgetState extends RefreshableState<PurchaseOrderListWi
return actions; return actions;
} }
Future<void> createPurchaseOrder(BuildContext context) async { // Launch form to create a new PurchaseOrder
Future<void> _createPurchaseOrder(BuildContext context) async {
var fields = InvenTreePurchaseOrder().formFields(); var fields = InvenTreePurchaseOrder().formFields();
// Cannot set contact until company is locked in
fields.remove("contact"); fields.remove("contact");
InvenTreePurchaseOrder().createForm( InvenTreePurchaseOrder().createForm(
@ -104,7 +104,7 @@ class _PurchaseOrderListWidgetState extends RefreshableState<PurchaseOrderListWi
@override @override
Widget getBody(BuildContext context) { Widget getBody(BuildContext context) {
return PaginatedPurchaseOrderList(filters); return PaginatedPurchaseOrderList(widget.filters);
} }
} }
@ -143,6 +143,11 @@ class _PaginatedPurchaseOrderListState extends PaginatedSearchState<PaginatedPur
"label": L10().outstanding, "label": L10().outstanding,
"help_text": L10().outstandingOrderDetail, "help_text": L10().outstandingOrderDetail,
"tristate": true, "tristate": true,
},
"overdue": {
"label": L10().overdue,
"help_text": L10().overdueDetail,
"tristate": true,
} }
}; };
@ -153,7 +158,6 @@ class _PaginatedPurchaseOrderListState extends PaginatedSearchState<PaginatedPur
final page = await InvenTreePurchaseOrder().listPaginated(limit, offset, filters: params); final page = await InvenTreePurchaseOrder().listPaginated(limit, offset, filters: params);
return page; return page;
} }
@override @override

View File

@ -0,0 +1,303 @@
import "package:flutter/material.dart";
import "package:flutter_speed_dial/flutter_speed_dial.dart";
import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:inventree/inventree/company.dart";
import "package:inventree/inventree/sales_order.dart";
import "package:inventree/widget/order/so_line_list.dart";
import "package:inventree/widget/refreshable_state.dart";
import "package:inventree/l10.dart";
import "package:inventree/app_colors.dart";
import "package:inventree/widget/attachment_widget.dart";
import "package:inventree/widget/notes_widget.dart";
import "package:inventree/widget/snacks.dart";
import "package:inventree/widget/company/company_detail.dart";
import "package:inventree/widget/progress.dart";
/*
* Widget for viewing a single SalesOrder instance
*/
class SalesOrderDetailWidget extends StatefulWidget {
const SalesOrderDetailWidget(this.order, {Key? key}) : super(key: key);
final InvenTreeSalesOrder order;
@override
_SalesOrderDetailState createState() => _SalesOrderDetailState();
}
class _SalesOrderDetailState extends RefreshableState<SalesOrderDetailWidget> {
_SalesOrderDetailState();
List<InvenTreeSOLineItem> lines = [];
bool supportsProjectCodes = false;
int completedLines = 0;
int attachmentCount = 0;
@override
String getAppBarTitle() => L10().salesOrder;
@override
List<Widget> appBarActions(BuildContext context) {
List<Widget> actions = [];
if (widget.order.canEdit) {
actions.add(
IconButton(
icon: Icon(Icons.edit_square),
onPressed: () {
editOrder(context);
},
)
);
}
return actions;
}
// Add a new line item to this sales order
Future<void> _addLineItem(BuildContext context) async {
var fields = InvenTreeSOLineItem().formFields();
fields["order"]?["value"] = widget.order.pk;
fields["order"]?["hidden"] = true;
InvenTreeSOLineItem().createForm(
context,
L10().lineItemAdd,
fields: fields,
onSuccess: (result) async {
refresh(context);
}
);
}
@override
List<SpeedDialChild> actionButtons(BuildContext context) {
List<SpeedDialChild> actions = [];
// Add line item
if (widget.order.isOpen && InvenTreeSOLineItem().canCreate) {
actions.add(
SpeedDialChild(
child: FaIcon(FontAwesomeIcons.circlePlus),
label: L10().lineItemAdd,
onTap: () async {
_addLineItem(context);
}
)
);
}
return actions;
}
@override
List<SpeedDialChild> barcodeButtons(BuildContext context) {
List<SpeedDialChild> actions = [];
// TODO
return actions;
}
@override
Future<void> request(BuildContext context) async {
await widget.order.reload();
await api.SalesOrderStatus.load();
supportsProjectCodes = api.supportsProjectCodes && await api.getGlobalBooleanSetting("PROJECT_CODES_ENABLED");
completedLines = 0;
for (var line in lines) {
if (line.isComplete) {
completedLines += 1;
}
}
InvenTreeSalesOrderAttachment().count(filters: {
"order": widget.order.pk.toString()
}).then((int value) {
if (mounted) {
setState(() {
attachmentCount = value;
});
}
});
}
// Edit the current SalesOrder instance
Future<void> editOrder(BuildContext context) async {
var fields = widget.order.formFields();
fields.remove("customer");
// Contact model not supported by server
if (!api.supportsContactModel) {
fields.remove("contact");
}
// ProjectCode model not supported by server
if (!supportsProjectCodes) {
fields.remove("project_code");
}
widget.order.editForm(
context,
L10().salesOrderEdit,
fields: fields,
onSuccess: (data) async {
refresh(context);
showSnackIcon(L10().salesOrderUpdated, success: true);
}
);
}
// Construct header tile
Widget headerTile(BuildContext context) {
InvenTreeCompany? customer = widget.order.customer;
return Card(
child: ListTile(
title: Text(widget.order.reference),
subtitle: Text(widget.order.description),
leading: customer == null ? null : api.getThumbnail(customer.thumbnail),
trailing: Text(
api.SalesOrderStatus.label(widget.order.status),
style: TextStyle(
color: api.SalesOrderStatus.color(widget.order.status)
),
),
)
);
}
List<Widget> orderTiles(BuildContext context) {
List<Widget> tiles = [
headerTile(context)
];
InvenTreeCompany? customer = widget.order.customer;
if (supportsProjectCodes && widget.order.hasProjectCode) {
tiles.add(ListTile(
title: Text(L10().projectCode),
subtitle: Text("${widget.order.projectCode} - ${widget.order.projectCodeDescription}"),
leading: FaIcon(FontAwesomeIcons.list),
));
}
if (customer != null) {
tiles.add(ListTile(
title: Text(L10().customer),
subtitle: Text(customer.name),
leading: FaIcon(FontAwesomeIcons.userTie, color: COLOR_ACTION),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CompanyDetailWidget(customer)
)
);
}
));
}
if (widget.order.customerReference.isNotEmpty) {
tiles.add(ListTile(
title: Text(L10().customerReference),
subtitle: Text(widget.order.customerReference),
leading: FaIcon(FontAwesomeIcons.hashtag),
));
}
Color lineColor = completedLines < widget.order.lineItemCount ? COLOR_WARNING : COLOR_SUCCESS;
tiles.add(ListTile(
title: Text(L10().lineItems),
subtitle: ProgressBar(
completedLines.toDouble(),
maximum: widget.order.lineItemCount.toDouble()
),
leading: FaIcon(FontAwesomeIcons.clipboardCheck),
trailing: Text("${completedLines} / ${widget.order.lineItemCount}", style: TextStyle(color: lineColor)),
));
// TODO: total price
if (widget.order.targetDate.isNotEmpty) {
tiles.add(ListTile(
title: Text(L10().targetDate),
subtitle: Text(widget.order.targetDate),
leading: FaIcon(FontAwesomeIcons.calendarDays),
));
}
// Notes tile
tiles.add(
ListTile(
title: Text(L10().notes),
leading: FaIcon(FontAwesomeIcons.noteSticky, color: COLOR_ACTION),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NotesWidget(widget.order)
)
);
},
)
);
// Attachments
tiles.add(
ListTile(
title: Text(L10().attachments),
leading: FaIcon(FontAwesomeIcons.fileLines, color: COLOR_ACTION),
trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AttachmentWidget(
InvenTreeSalesOrderAttachment(),
widget.order.pk,
widget.order.canEdit
)
)
);
},
)
);
return tiles;
}
@override
List<Widget> getTabIcons(BuildContext context) {
return [
Tab(text: L10().details),
Tab(text: L10().lineItems),
// TODO: Add in the "shipped items" tab
// Tab(text: L10().shipped)
];
}
@override
List<Widget> getTabs(BuildContext context) {
return [
ListView(children: orderTiles(context)),
PaginatedSOLineList({"order": widget.order.pk.toString()}),
// Center(), // TODO: Delivered stock
];
}
}

View File

@ -0,0 +1,176 @@
import "package:flutter/material.dart";
import "package:flutter_speed_dial/flutter_speed_dial.dart";
import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:inventree/inventree/sales_order.dart";
import "package:inventree/widget/order/sales_order_detail.dart";
import "package:inventree/widget/paginator.dart";
import "package:inventree/widget/refreshable_state.dart";
import "package:inventree/l10.dart";
import "package:inventree/api.dart";
import "package:inventree/inventree/company.dart";
import "package:inventree/inventree/model.dart";
class SalesOrderListWidget extends StatefulWidget {
const SalesOrderListWidget({this.filters = const {}, Key? key}) : super(key: key);
final Map<String, String> filters;
@override
_SalesOrderListWidgetState createState() => _SalesOrderListWidgetState();
}
class _SalesOrderListWidgetState extends RefreshableState<SalesOrderListWidget> {
_SalesOrderListWidgetState();
@override
String getAppBarTitle() => L10().salesOrders;
@override
List<SpeedDialChild> actionButtons(BuildContext context) {
List<SpeedDialChild> actions = [];
if (InvenTreeSalesOrder().canCreate) {
actions.add(
SpeedDialChild(
child: FaIcon(FontAwesomeIcons.circlePlus),
label: L10().salesOrderCreate,
onTap: () {
_createSalesOrder(context);
}
)
);
}
return actions;
}
// Launch form to create a new SalesOrder
Future<void> _createSalesOrder(BuildContext context) async {
var fields = InvenTreeSalesOrder().formFields();
// Cannot set contact until company is locked in
fields.remove("contact");
InvenTreeSalesOrder().createForm(
context,
L10().salesOrderCreate,
fields: fields,
onSuccess: (result) async {
Map<String, dynamic> data = result as Map<String, dynamic>;
if (data.containsKey("pk")) {
var order = InvenTreeSalesOrder.fromJson(data);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SalesOrderDetailWidget(order)
)
);
}
}
);
}
@override
List<SpeedDialChild> barcodeButtons(BuildContext context) {
// TODO: return custom barcode actions
return [];
}
@override
Widget getBody(BuildContext context) {
return PaginatedSalesOrderList(widget.filters);
}
}
class PaginatedSalesOrderList extends PaginatedSearchWidget {
const PaginatedSalesOrderList(Map<String, String> filters) : super(filters: filters);
@override
String get searchTitle => L10().salesOrders;
@override
_PaginatedSalesOrderListState createState() => _PaginatedSalesOrderListState();
}
class _PaginatedSalesOrderListState extends PaginatedSearchState<PaginatedSalesOrderList> {
_PaginatedSalesOrderListState() : super();
@override
String get prefix => "so_";
@override
Map<String, String> get orderingOptions => {
"reference": L10().reference,
"status": L10().status,
"target_date": L10().targetDate,
"customer__name": L10().customer,
};
@override
Map<String, Map<String, dynamic>> get filterOptions => {
"outstanding": {
"label": L10().outstanding,
"help_text": L10().outstandingOrderDetail,
"tristate": true,
},
"overdue": {
"label": L10().overdue,
"help_text": L10().overdueDetail,
"tristate": true,
}
};
@override
Future<InvenTreePageResponse?> requestPage(int limit, int offset, Map<String, String> params) async {
await InvenTreeAPI().SalesOrderStatus.load();
final page = await InvenTreeSalesOrder().listPaginated(limit, offset, filters: params);
return page;
}
@override
Widget buildItem(BuildContext context, InvenTreeModel model) {
InvenTreeSalesOrder order = model as InvenTreeSalesOrder;
InvenTreeCompany? customer = order.customer;
return ListTile(
title: Text(order.reference),
subtitle: Text(order.description),
leading: customer == null ? null : InvenTreeAPI().getThumbnail(customer.thumbnail),
trailing: Text(
InvenTreeAPI().SalesOrderStatus.label(order.status),
style: TextStyle(
color: InvenTreeAPI().SalesOrderStatus.color(order.status),
)
),
onTap: () async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SalesOrderDetailWidget(order)
)
);
}
);
}
}

View File

@ -0,0 +1,164 @@
/*
* Widget for displaying detail view of a single SalesOrderLineItem
*/
import "package:flutter/material.dart";
import "package:flutter_speed_dial/flutter_speed_dial.dart";
import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:inventree/app_colors.dart";
import "package:inventree/l10.dart";
import "package:inventree/inventree/part.dart";
import "package:inventree/inventree/sales_order.dart";
import "package:inventree/widget/refreshable_state.dart";
import "package:inventree/widget/progress.dart";
import "package:inventree/widget/part/part_detail.dart";
import "package:inventree/helpers.dart";
import "package:inventree/widget/snacks.dart";
class SoLineDetailWidget extends StatefulWidget {
const SoLineDetailWidget(this.item, {Key? key}) : super(key: key);
final InvenTreeSOLineItem item;
@override
_SOLineDetailWidgetState createState() => _SOLineDetailWidgetState();
}
class _SOLineDetailWidgetState extends RefreshableState<SoLineDetailWidget> {
_SOLineDetailWidgetState();
@override
String getAppBarTitle() => L10().lineItem;
@override
List<Widget> appBarActions(BuildContext context) {
List<Widget> actions = [];
if (widget.item.canEdit) {
actions.add(
IconButton(
icon: Icon(Icons.edit_square),
onPressed: () {
_editLineItem(context);
}),
);
}
return actions;
}
Future<void> _editLineItem(BuildContext context) async {
var fields = widget.item.formFields();
// Prevent editing of the line item
if (widget.item.shipped > 0) {
fields["part"]?["hidden"] = true;
}
widget.item.editForm(
context,
L10().editLineItem,
fields: fields,
onSuccess: (data) async {
refresh(context);
showSnackIcon(L10().lineItemUpdated, success: true);
}
);
}
@override
List<SpeedDialChild> actionButtons(BuildContext context) {
// TODO
return [];
}
@override
Future<void> request(BuildContext context) async {
await widget.item.reload();
}
@override
List<Widget> getTiles(BuildContext context) {
List<Widget> tiles = [];
// Reference to the part
tiles.add(
ListTile(
title: Text(L10().part),
subtitle: Text(widget.item.partName),
leading: FaIcon(FontAwesomeIcons.shapes, color: COLOR_ACTION),
trailing: api.getThumbnail(widget.item.partImage),
onTap: () async {
showLoadingOverlay(context);
var part = await InvenTreePart().get(widget.item.partId);
hideLoadingOverlay();
if (part is InvenTreePart) {
Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
}
}
)
);
// Shipped quantity
tiles.add(
ListTile(
title: Text(L10().shipped),
subtitle: ProgressBar(widget.item.progressRatio),
trailing: Text(
widget.item.progressString,
style: TextStyle(
color: widget.item.isComplete ? COLOR_SUCCESS : COLOR_WARNING
),
),
leading: FaIcon(FontAwesomeIcons.truck)
)
);
// Reference
if (widget.item.reference.isNotEmpty) {
tiles.add(
ListTile(
title: Text(L10().reference),
subtitle: Text(widget.item.reference),
leading: FaIcon(FontAwesomeIcons.hashtag)
)
);
}
// Note
if (widget.item.notes.isNotEmpty) {
tiles.add(
ListTile(
title: Text(L10().notes),
subtitle: Text(widget.item.notes),
leading: FaIcon(FontAwesomeIcons.noteSticky),
)
);
}
// External link
if (widget.item.link.isNotEmpty) {
tiles.add(
ListTile(
title: Text(L10().link),
subtitle: Text(widget.item.link),
leading: FaIcon(FontAwesomeIcons.link, color: COLOR_ACTION),
onTap: () async {
await openLink(widget.item.link);
},
)
);
}
return tiles;
}
}

View File

@ -0,0 +1,86 @@
import "package:flutter/material.dart";
import "package:inventree/l10.dart";
import "package:inventree/widget/order/so_line_detail.dart";
import "package:inventree/widget/paginator.dart";
import "package:inventree/inventree/model.dart";
import "package:inventree/inventree/sales_order.dart";
import "package:inventree/inventree/part.dart";
import "package:inventree/api.dart";
import "package:inventree/app_colors.dart";
import "package:inventree/widget/progress.dart";
/*
* Paginated widget class for displaying a list of sales order line items
*/
class PaginatedSOLineList extends PaginatedSearchWidget {
const PaginatedSOLineList(Map<String, String> filters) : super(filters: filters);
@override
String get searchTitle => L10().lineItems;
@override
_PaginatedSOLineListState createState() => _PaginatedSOLineListState();
}
/*
* State class for PaginatedSOLineList
*/
class _PaginatedSOLineListState extends PaginatedSearchState<PaginatedSOLineList> {
_PaginatedSOLineListState() : super();
@override
String get prefix => "so_line_";
@override
Map<String, String> get orderingOptions => {
"part": L10().part,
"quantity": L10().quantity,
};
@override
Map<String, Map<String, dynamic>> get filterOptions => {
};
@override
Future<InvenTreePageResponse?> requestPage(int limit, int offset, Map<String, String> params) async {
final page = await InvenTreeSOLineItem().listPaginated(limit, offset, filters: params);
return page;
}
@override
Widget buildItem(BuildContext context, InvenTreeModel model) {
InvenTreeSOLineItem item = model as InvenTreeSOLineItem;
InvenTreePart? part = item.part;
if (part != null) {
return ListTile(
title: Text(part.name),
subtitle: Text(part.description),
leading: InvenTreeAPI().getThumbnail(part.thumbnail),
trailing: Text(item.progressString),
onTap: () async {
showLoadingOverlay(context);
await item.reload();
hideLoadingOverlay();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SoLineDetailWidget(item))
);
}
);
} else {
return ListTile(
title: Text(L10().error),
subtitle: Text("Missing part detail", style: TextStyle(color: COLOR_DANGER)),
);
}
}
}

View File

@ -11,7 +11,7 @@ import "package:inventree/inventree/model.dart";
import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/part.dart";
import "package:inventree/widget/paginator.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/progress.dart";
import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/refreshable_state.dart";

View File

@ -7,11 +7,11 @@ import "package:inventree/l10.dart";
import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/part.dart";
import "package:inventree/widget/category_list.dart"; import "package:inventree/widget/part/category_list.dart";
import "package:inventree/widget/part_list.dart"; import "package:inventree/widget/part/part_list.dart";
import "package:inventree/widget/progress.dart"; import "package:inventree/widget/progress.dart";
import "package:inventree/widget/snacks.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"; import "package:inventree/widget/refreshable_state.dart";

View File

@ -2,7 +2,7 @@ import "package:flutter/material.dart";
import "package:inventree/inventree/model.dart"; import "package:inventree/inventree/model.dart";
import "package:inventree/inventree/part.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/paginator.dart";
import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/refreshable_state.dart";

View File

@ -15,18 +15,18 @@ import "package:inventree/labels.dart";
import "package:inventree/preferences.dart"; import "package:inventree/preferences.dart";
import "package:inventree/widget/attachment_widget.dart"; import "package:inventree/widget/attachment_widget.dart";
import "package:inventree/widget/bom_list.dart"; import "package:inventree/widget/part/bom_list.dart";
import "package:inventree/widget/part_list.dart"; import "package:inventree/widget/part/part_list.dart";
import "package:inventree/widget/notes_widget.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/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/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/snacks.dart";
import "package:inventree/widget/stock_detail.dart"; import "package:inventree/widget/stock/stock_detail.dart";
import "package:inventree/widget/stock_list.dart"; import "package:inventree/widget/stock/stock_list.dart";
import "package:inventree/widget/supplier_part_list.dart"; import "package:inventree/widget/company/supplier_part_list.dart";
/* /*
@ -634,7 +634,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
fields.remove("serial"); fields.remove("serial");
// Hide the "part" field // Hide the "part" field
fields["part"]["hidden"] = true; fields["part"]?["hidden"] = true;
int? default_location = part.defaultLocation; int? default_location = part.defaultLocation;

View File

@ -7,7 +7,7 @@ import "package:inventree/inventree/model.dart";
import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/part.dart";
import "package:inventree/widget/paginator.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"; import "package:inventree/widget/refreshable_state.dart";

View File

@ -7,7 +7,7 @@ import "package:inventree/api.dart";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/part.dart";
import "package:inventree/inventree/company.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"; import "package:inventree/widget/refreshable_state.dart";
class PartSupplierWidget extends StatefulWidget { class PartSupplierWidget extends StatefulWidget {

View File

@ -2,6 +2,34 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_overlay_loader/flutter_overlay_loader.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 * Construct a circular progress indicator

View File

@ -11,13 +11,13 @@ import "package:inventree/inventree/part.dart";
import "package:inventree/inventree/purchase_order.dart"; import "package:inventree/inventree/purchase_order.dart";
import "package:inventree/inventree/stock.dart"; import "package:inventree/inventree/stock.dart";
import "package:inventree/widget/part_list.dart"; import "package:inventree/widget/part/part_list.dart";
import "package:inventree/widget/purchase_order_list.dart"; import "package:inventree/widget/order/purchase_order_list.dart";
import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/refreshable_state.dart";
import "package:inventree/widget/stock_list.dart"; import "package:inventree/widget/stock/stock_list.dart";
import "package:inventree/widget/category_list.dart"; import "package:inventree/widget/part/category_list.dart";
import "package:inventree/widget/company_list.dart"; import "package:inventree/widget/company/company_list.dart";
import "package:inventree/widget/location_list.dart"; import "package:inventree/widget/stock/location_list.dart";
// Widget for performing database-wide search // Widget for performing database-wide search

View File

@ -10,12 +10,12 @@ import "package:inventree/l10.dart";
import "package:inventree/inventree/stock.dart"; import "package:inventree/inventree/stock.dart";
import "package:inventree/preferences.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/progress.dart";
import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/refreshable_state.dart";
import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/snacks.dart";
import "package:inventree/widget/stock_detail.dart"; import "package:inventree/widget/stock/stock_detail.dart";
import "package:inventree/widget/stock_list.dart"; import "package:inventree/widget/stock/stock_list.dart";
import "package:inventree/labels.dart"; import "package:inventree/labels.dart";

View File

@ -2,7 +2,7 @@ import "package:flutter/material.dart";
import "package:inventree/inventree/model.dart"; import "package:inventree/inventree/model.dart";
import "package:inventree/inventree/stock.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/paginator.dart";
import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/refreshable_state.dart";

View File

@ -16,16 +16,16 @@ import "package:inventree/inventree/company.dart";
import "package:inventree/inventree/stock.dart"; import "package:inventree/inventree/stock.dart";
import "package:inventree/inventree/part.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/dialogs.dart";
import "package:inventree/widget/attachment_widget.dart"; import "package:inventree/widget/attachment_widget.dart";
import "package:inventree/widget/location_display.dart"; import "package:inventree/widget/stock/location_display.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/progress.dart";
import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/refreshable_state.dart";
import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/snacks.dart";
import "package:inventree/widget/stock_item_history.dart"; import "package:inventree/widget/stock/stock_item_history.dart";
import "package:inventree/widget/stock_item_test_results.dart"; import "package:inventree/widget/stock/stock_item_test_results.dart";
import "package:inventree/widget/notes_widget.dart"; import "package:inventree/widget/notes_widget.dart";

View File

@ -5,7 +5,7 @@ import "package:inventree/inventree/stock.dart";
import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/paginator.dart";
import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/refreshable_state.dart";
import "package:inventree/l10.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"; import "package:inventree/api.dart";