2
0
mirror of https://github.com/inventree/inventree-app.git synced 2025-06-13 10:45:29 +00:00

Merge branch 'create-part'

This commit is contained in:
Oliver
2021-08-15 16:00:36 +10:00
17 changed files with 536 additions and 207 deletions

View File

@ -4,9 +4,13 @@
### 0.4.4 - August 2021
---
- Add ability to create new Part Categories
- Add ability to create new Parts
- Add ability to create new Stock Locations
- Add ability to create new Stock Items
- App bar now always displays "back" button
- Display "batch code" information for stock item
- Display "packagin" information for stock item
- Display "packaging" information for stock item
### 0.4.3 - August 2021
---

View File

@ -540,7 +540,7 @@ class InvenTreeAPI {
* Perform a HTTP POST request
* Returns a json object (or null if unsuccessful)
*/
Future<APIResponse> post(String url, {Map<String, dynamic> body = const {}, int expectedStatusCode=201}) async {
Future<APIResponse> post(String url, {Map<String, dynamic> body = const {}, int? expectedStatusCode=201}) async {
HttpClientRequest? request = await apiRequest(url, "POST");
@ -763,7 +763,7 @@ class InvenTreeAPI {
* Perform a HTTP GET request
* Returns a json object (or null if did not complete)
*/
Future<APIResponse> get(String url, {Map<String, String> params = const {}, int expectedStatusCode=200}) async {
Future<APIResponse> get(String url, {Map<String, String> params = const {}, int? expectedStatusCode=200}) async {
HttpClientRequest? request = await apiRequest(
url,

View File

@ -7,6 +7,7 @@ import 'package:inventree/api.dart';
import 'package:inventree/app_colors.dart';
import 'package:inventree/inventree/part.dart';
import 'package:inventree/inventree/stock.dart';
import 'package:inventree/widget/dialogs.dart';
import 'package:inventree/widget/fields.dart';
import 'package:inventree/l10.dart';
@ -153,6 +154,9 @@ class APIFormField {
return _constructBoolean();
case "related field":
return _constructRelatedField();
case "float":
case "decimal":
return _constructFloatField();
case "choice":
return _constructChoiceField();
default:
@ -202,6 +206,34 @@ class APIFormField {
);
}
// Construct a floating point numerical input field
Widget _constructFloatField() {
return TextFormField(
decoration: InputDecoration(
labelText: required ? label + "*" : label,
labelStyle: _labelStyle(),
helperText: helpText,
helperStyle: _helperStyle(),
hintText: placeholderText,
),
initialValue: (value ?? 0).toString(),
keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
validator: (value) {
double? quantity = double.tryParse(value.toString()) ?? null;
if (quantity == null) {
return L10().numberInvalid;
}
},
onSaved: (val) {
data["value"] = val;
},
);
}
// Construct an input for a related field
Widget _constructRelatedField() {
@ -244,7 +276,16 @@ class APIFormField {
onChanged: null,
showClearButton: !required,
itemAsString: (dynamic item) {
return item['pathstring'];
switch (model) {
case "part":
return InvenTreePart.fromJson(item).fullname;
case "partcategory":
return InvenTreePartCategory.fromJson(item).pathstring;
case "stocklocation":
return InvenTreeStockLocation.fromJson(item).pathstring;
default:
return "itemAsString not implemented for '${model}'";
}
},
dropdownBuilder: (context, item, itemAsString) {
return _renderRelatedField(item, true, false);
@ -287,6 +328,22 @@ class APIFormField {
}
switch (model) {
case "part":
var part = InvenTreePart.fromJson(item);
return ListTile(
title: Text(
part.fullname,
style: TextStyle(fontWeight: selected && extended ? FontWeight.bold : FontWeight.normal)
),
subtitle: extended ? Text(
part.description,
style: TextStyle(fontWeight: selected ? FontWeight.bold : FontWeight.normal),
) : null,
leading: extended ? InvenTreeAPI().getImage(part.thumbnail, width: 40, height: 40) : null,
);
case "partcategory":
var cat = InvenTreePartCategory.fromJson(item);
@ -420,7 +477,7 @@ Map<String, dynamic> extractFields(APIResponse response) {
* @param method is the HTTP method to use to send the form data to the server (e.g. POST / PATCH)
*/
Future<void> launchApiForm(BuildContext context, String title, String url, Map<String, dynamic> fields, {Map<String, dynamic> modelData = const {}, String method = "PATCH", Function? onSuccess, Function? onCancel}) async {
Future<void> launchApiForm(BuildContext context, String title, String url, Map<String, dynamic> fields, {Map<String, dynamic> modelData = const {}, String method = "PATCH", Function(Map<String, dynamic>)? onSuccess, Function? onCancel}) async {
var options = await InvenTreeAPI().options(url);
@ -503,6 +560,7 @@ Future<void> launchApiForm(BuildContext context, String title, String url, Map<S
title,
url,
formFields,
method,
onSuccess: onSuccess,
))
);
@ -517,14 +575,18 @@ class APIFormWidget extends StatefulWidget {
//! API URL
final String url;
//! API method
final String method;
final List<APIFormField> fields;
Function? onSuccess;
Function(Map<String, dynamic>)? onSuccess;
APIFormWidget(
this.title,
this.url,
this.fields,
this.method,
{
Key? key,
this.onSuccess,
@ -532,7 +594,7 @@ class APIFormWidget extends StatefulWidget {
) : super(key: key);
@override
_APIFormWidgetState createState() => _APIFormWidgetState(title, url, fields, onSuccess);
_APIFormWidgetState createState() => _APIFormWidgetState(title, url, fields, method, onSuccess);
}
@ -545,11 +607,13 @@ class _APIFormWidgetState extends State<APIFormWidget> {
String url;
String method;
List<APIFormField> fields;
Function? onSuccess;
Function(Map<String, dynamic>)? onSuccess;
_APIFormWidgetState(this.title, this.url, this.fields, this.onSuccess) : super();
_APIFormWidgetState(this.title, this.url, this.fields, this.method, this.onSuccess) : super();
List<Widget> _buildForm() {
@ -579,35 +643,60 @@ class _APIFormWidgetState extends State<APIFormWidget> {
);
}
}
// Add divider after some widgets
switch (field.type) {
case "related field":
case "choice":
widgets.add(Divider(height: 10));
break;
default:
break;
}
}
return widgets;
}
Future<APIResponse> _submit(Map<String, String> data) async {
if (method == "POST") {
return await InvenTreeAPI().post(
url,
body: data,
expectedStatusCode: null
);
} else {
return await InvenTreeAPI().patch(
url,
body: data,
expectedStatusCode: null
);
}
}
Future<void> _save(BuildContext context) async {
// Package up the form data
Map<String, String> _data = {};
Map<String, String> data = {};
for (var field in fields) {
dynamic value = field.value;
if (value == null) {
_data[field.name] = "";
data[field.name] = "";
} else {
_data[field.name] = value.toString();
data[field.name] = value.toString();
}
}
// TODO: Handle "POST" forms too!!
final response = await InvenTreeAPI().patch(
url,
body: _data,
);
final response = await _submit(data);
if (!response.isValid()) {
// TODO: Display an error message!
showServerError(L10().serverError, L10().responseInvalid);
return;
}
@ -625,11 +714,25 @@ class _APIFormWidgetState extends State<APIFormWidget> {
var successFunc = onSuccess;
if (successFunc != null) {
successFunc();
// Ensure the response is a valid JSON structure
Map<String, dynamic> json = {};
if (response.data != null && response.data is Map) {
for (dynamic key in response.data.keys) {
json[key.toString()] = response.data[key];
}
}
successFunc(json);
}
return;
case 400:
// Form submission / validation error
showSnackIcon(
L10().error,
success: false
);
// Update field errors
for (var field in fields) {

View File

@ -515,7 +515,7 @@ class _QRViewState extends State<InvenTreeQRView> {
return Scaffold(
appBar: AppBar(
title: Text(_handler.getOverlayText(context)),
title: Text(L10().scanBarcode),
),
body: Stack(
children: <Widget>[

View File

@ -13,7 +13,20 @@ class InvenTreeCompany extends InvenTreeModel {
String NAME = "Company";
@override
String URL = "company/";
String get URL => "company/";
@override
Map<String, dynamic> formFields() {
return {
"name": {},
"description": {},
"website": {},
"is_supplier": {},
"is_manufacturer": {},
"is_customer": {},
"currency": {},
};
}
InvenTreeCompany() : super();
@ -49,7 +62,7 @@ class InvenTreeCompany extends InvenTreeModel {
*/
class InvenTreeSupplierPart extends InvenTreeModel {
@override
String URL = "company/part/";
String get URL => "company/part/";
Map<String, String> _filters() {
return {

View File

@ -9,6 +9,7 @@ import 'package:url_launcher/url_launcher.dart';
import 'package:path/path.dart' as path;
import '../l10.dart';
import '../api_form.dart';
// Paginated response object
@ -37,7 +38,7 @@ class InvenTreePageResponse {
class InvenTreeModel {
// Override the endpoint URL for each subclass
String URL = "";
String get URL => "";
// Override the web URL for each subclass
// Note: If the WEB_URL is the same (except for /api/) as URL then just leave blank
@ -64,6 +65,49 @@ class InvenTreeModel {
}
// Fields for editing / creating this model
// Override per-model
Map<String, dynamic> formFields() {
return {};
}
Future<void> createForm(BuildContext context, String title, {Map<String, dynamic> fields=const{}, Map<String, dynamic> data=const {}, Function(dynamic)? onSuccess}) async {
if (fields.isEmpty) {
fields = formFields();
}
launchApiForm(
context,
title,
URL,
fields,
modelData: data,
onSuccess: onSuccess,
method: "POST",
);
}
Future<void> editForm(BuildContext context, String title, {Map<String, dynamic> fields=const {}, Function(dynamic)? onSuccess}) async {
if (fields.isEmpty) {
fields = formFields();
}
launchApiForm(
context,
title,
url,
fields,
modelData: jsondata,
onSuccess: onSuccess,
method: "PATCH"
);
}
// JSON data which defines this object
Map<String, dynamic> jsondata = {};
@ -155,17 +199,19 @@ class InvenTreeModel {
if (!response.isValid() || response.data == null || !(response.data is Map)) {
// Report error
await sentryReportMessage(
"InvenTreeModel.reload() returned invalid response",
context: {
"url": url,
"statusCode": response.statusCode.toString(),
"data": response.data?.toString() ?? "null",
"valid": response.isValid().toString(),
"error": response.error,
"errorDetail": response.errorDetail,
}
);
if (response.statusCode > 0) {
await sentryReportMessage(
"InvenTreeModel.reload() returned invalid response",
context: {
"url": url,
"statusCode": response.statusCode.toString(),
"data": response.data?.toString() ?? "null",
"valid": response.isValid().toString(),
"error": response.error,
"errorDetail": response.errorDetail,
}
);
}
showServerError(
L10().serverError,
@ -226,17 +272,19 @@ class InvenTreeModel {
if (!response.isValid() || response.data == null || !(response.data is Map)) {
await sentryReportMessage(
"InvenTreeModel.get() returned invalid response",
context: {
"url": url,
"statusCode": response.statusCode.toString(),
"data": response.data?.toString() ?? "null",
"valid": response.isValid().toString(),
"error": response.error,
"errorDetail": response.errorDetail,
}
);
if (response.statusCode > 0) {
await sentryReportMessage(
"InvenTreeModel.get() returned invalid response",
context: {
"url": url,
"statusCode": response.statusCode.toString(),
"data": response.data?.toString() ?? "null",
"valid": response.isValid().toString(),
"error": response.error,
"errorDetail": response.errorDetail,
}
);
}
showServerError(
L10().serverError,
@ -267,17 +315,19 @@ class InvenTreeModel {
// Invalid response returned from server
if (!response.isValid() || response.data == null || !(response.data is Map)) {
await sentryReportMessage(
"InvenTreeModel.create() returned invalid response",
context: {
"url": url,
"statusCode": response.statusCode.toString(),
"data": response.data?.toString() ?? "null",
"valid": response.isValid().toString(),
"error": response.error,
"errorDetail": response.errorDetail,
}
);
if (response.statusCode > 0) {
await sentryReportMessage(
"InvenTreeModel.create() returned invalid response",
context: {
"url": url,
"statusCode": response.statusCode.toString(),
"data": response.data?.toString() ?? "null",
"valid": response.isValid().toString(),
"error": response.error,
"errorDetail": response.errorDetail,
}
);
}
showServerError(
L10().serverError,

View File

@ -14,7 +14,17 @@ class InvenTreePartCategory extends InvenTreeModel {
String NAME = "PartCategory";
@override
String URL = "part/category/";
String get URL => "part/category/";
@override
Map<String, dynamic> formFields() {
return {
"name": {},
"description": {},
"parent": {}
};
}
@override
Map<String, String> defaultListFilters() {
@ -68,7 +78,7 @@ class InvenTreePartTestTemplate extends InvenTreeModel {
String NAME = "PartTestTemplate";
@override
String URL = "part/test-template/";
String get URL => "part/test-template/";
String get key => jsondata['key'] ?? '';
@ -125,7 +135,33 @@ class InvenTreePart extends InvenTreeModel {
String NAME = "Part";
@override
String URL = "part/";
String get URL => "part/";
@override
Map<String, dynamic> formFields() {
return {
"name": {},
"description": {},
"IPN": {},
"revision": {},
"keywords": {},
"link": {},
// Parent category
"category": {
},
// Checkbox fields
"active": {},
"assembly": {},
"component": {},
"purchaseable": {},
"salable": {},
"trackable": {},
"is_template": {},
"virtual": {},
};
}
@override
Map<String, String> defaultListFilters() {

View File

@ -18,7 +18,7 @@ class InvenTreeStockItemTestResult extends InvenTreeModel {
String NAME = "StockItemTestResult";
@override
String URL = "stock/test/";
String get URL => "stock/test/";
String get key => jsondata['key'] ?? '';
@ -103,11 +103,24 @@ class InvenTreeStockItem extends InvenTreeModel {
String NAME = "StockItem";
@override
String URL = "stock/";
String get URL => "stock/";
@override
String WEB_URL = "stock/item/";
@override
Map<String, dynamic> formFields() {
return {
"part": {},
"location": {},
"quantity": {},
"status": {},
"batch": {},
"packaging": {},
"link": {},
};
}
@override
Map<String, String> defaultGetFilters() {
@ -544,10 +557,19 @@ class InvenTreeStockLocation extends InvenTreeModel {
String NAME = "StockLocation";
@override
String URL = "stock/location/";
String get URL => "stock/location/";
String get pathstring => jsondata['pathstring'] ?? '';
@override
Map<String, dynamic> formFields() {
return {
"name": {},
"description": {},
"parent": {},
};
}
String get parentpathstring {
// TODO - Drive the refactor tractor through this
List<String> psplit = pathstring.split('/');

View File

@ -19,8 +19,6 @@ import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import '../api_form.dart';
class CategoryDisplayWidget extends StatefulWidget {
CategoryDisplayWidget(this.category, {Key? key}) : super(key: key);
@ -43,27 +41,6 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
List<Widget> actions = [];
/*
actions.add(
IconButton(
icon: FaIcon(FontAwesomeIcons.search),
onPressed: () {
Map<String, String> filters = {};
if (category != null) {
filters["category"] = "${category.pk}";
}
showSearch(
context: context,
delegate: PartSearchDelegate(context, filters: filters)
);
}
)
);
*/
if ((category != null) && InvenTreeAPI().checkPermission('part_category', 'change')) {
actions.add(
IconButton(
@ -81,7 +58,6 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
}
void _editCategoryDialog(BuildContext context) {
final _cat = category;
// Cannot edit top-level category
@ -89,17 +65,12 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
return;
}
launchApiForm(
context,
L10().editCategory,
_cat.url,
{
"name": {},
"description": {},
"parent": {},
},
modelData: _cat.jsondata,
onSuccess: refresh,
_cat.editForm(
context,
L10().editCategory,
onSuccess: (data) async {
refresh();
}
);
}
@ -206,12 +177,10 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
label: L10().parts,
),
// TODO - Add the "actions" item back in
/*
BottomNavigationBarItem(
icon: FaIcon(FontAwesomeIcons.wrench),
label: L10().actions
),
*/
]
);
}
@ -242,18 +211,103 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
return tiles;
}
List<Widget> actionTiles() {
Future<void> _newCategory(BuildContext context) async {
int pk = category?.pk ?? -1;
InvenTreePartCategory().createForm(
context,
L10().categoryCreate,
data: {
"parent": (pk > 0) ? pk : null,
},
onSuccess: (data) async {
if (data.containsKey("pk")) {
var cat = InvenTreePartCategory.fromJson(data);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CategoryDisplayWidget(cat)
)
);
} else {
refresh();
}
}
);
}
Future<void> _newPart() async {
int pk = category?.pk ?? -1;
InvenTreePart().createForm(
context,
L10().partCreate,
data: {
"category": (pk > 0) ? pk : null
},
onSuccess: (data) async {
if (data.containsKey("pk")) {
var part = InvenTreePart.fromJson(data);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PartDetailWidget(part)
)
);
}
}
);
}
List<Widget> actionTiles(BuildContext context) {
List<Widget> tiles = [
getCategoryDescriptionCard(extra: false),
ListTile(
title: Text(L10().actions,
style: TextStyle(fontWeight: FontWeight.bold)
)
)
];
// TODO - Actions!
if (InvenTreeAPI().checkPermission('part', 'add')) {
tiles.add(
ListTile(
title: Text(L10().categoryCreate),
subtitle: Text(L10().categoryCreateDetail),
leading: FaIcon(FontAwesomeIcons.sitemap, color: COLOR_CLICK),
onTap: () async {
_newCategory(context);
},
)
);
if (category != null) {
tiles.add(
ListTile(
title: Text(L10().partCreate),
subtitle: Text(L10().partCreateDetail),
leading: FaIcon(FontAwesomeIcons.shapes, color: COLOR_CLICK),
onTap: _newPart,
)
);
}
}
if (tiles.length == 0) {
tiles.add(
ListTile(
title: Text(
L10().actionsNone
),
subtitle: Text(
L10().permissionAccountDenied,
),
leading: FaIcon(FontAwesomeIcons.userTimes),
)
);
}
return tiles;
}
@ -274,7 +328,7 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
);
case 2:
return ListView(
children: actionTiles()
children: actionTiles(context)
);
default:
return ListView();

View File

@ -65,21 +65,12 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
void editCompany(BuildContext context) async {
launchApiForm(
company.editForm(
context,
L10().companyEdit,
company.url,
{
"name": {},
"description": {},
"website": {},
"is_supplier": {},
"is_manufacturer": {},
"is_customer": {},
"currency": {},
},
modelData: company.jsondata,
onSuccess: refresh
onSuccess: (data) async {
refresh();
}
);
}

View File

@ -1,5 +1,4 @@
import 'package:inventree/api.dart';
import 'package:inventree/api_form.dart';
import 'package:inventree/app_colors.dart';
import 'package:inventree/app_settings.dart';
import 'package:inventree/barcode.dart';
@ -84,17 +83,12 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
return;
}
launchApiForm(
_loc.editForm(
context,
L10().editLocation,
_loc.url,
{
"name": {},
"description": {},
"parent": {},
},
modelData: _loc.jsondata,
onSuccess: refresh
onSuccess: (data) async {
refresh();
}
);
}
@ -142,6 +136,61 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
setState(() {});
}
Future<void> _newLocation(BuildContext context) async {
int pk = location?.pk ?? -1;
InvenTreeStockLocation().createForm(
context,
L10().locationCreate,
data: {
"parent": (pk > 0) ? pk : null,
},
onSuccess: (data) async {
if (data.containsKey("pk")) {
var loc = InvenTreeStockLocation.fromJson(data);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LocationDisplayWidget(loc)
)
);
}
}
);
}
Future<void> _newStockItem(BuildContext context) async {
int pk = location?.pk ?? -1;
if (pk <= 0) {
return;
}
InvenTreeStockItem().createForm(
context,
L10().stockItemCreate,
data: {
"location": pk,
},
onSuccess: (data) async {
if (data.containsKey("pk")) {
var item = InvenTreeStockItem.fromJson(data);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StockDetailWidget(item)
)
);
}
}
);
}
Widget locationDescriptionCard({bool includeActions = true}) {
if (location == null) {
return Card(
@ -206,7 +255,6 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
icon: FaIcon(FontAwesomeIcons.boxes),
label: L10().stock,
),
// TODO - Add in actions when they are written...
BottomNavigationBarItem(
icon: FaIcon(FontAwesomeIcons.wrench),
label: L10().actions,
@ -283,6 +331,32 @@ List<Widget> detailTiles() {
tiles.add(locationDescriptionCard(includeActions: false));
if (InvenTreeAPI().checkPermission('stock', 'add')) {
tiles.add(
ListTile(
title: Text(L10().locationCreate),
subtitle: Text(L10().locationCreateDetail),
leading: FaIcon(FontAwesomeIcons.sitemap, color: COLOR_CLICK),
onTap: () async {
_newLocation(context);
},
)
);
tiles.add(
ListTile(
title: Text(L10().stockItemCreate),
subtitle: Text(L10().stockItemCreateDetail),
leading: FaIcon(FontAwesomeIcons.boxes, color: COLOR_CLICK),
onTap: () async {
_newStockItem(context);
},
)
);
}
if (location != null) {
// Stock adjustment actions
if (InvenTreeAPI().checkPermission('stock', 'change')) {

View File

@ -6,12 +6,10 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:inventree/app_colors.dart';
import 'package:inventree/l10.dart';
import 'package:inventree/api_form.dart';
import 'package:inventree/widget/part_notes.dart';
import 'package:inventree/widget/progress.dart';
import 'package:inventree/inventree/part.dart';
import 'package:inventree/widget/category_display.dart';
import 'package:inventree/widget/part_suppliers.dart';
import 'package:inventree/api.dart';
import 'package:inventree/widget/refreshable_state.dart';
import 'package:inventree/widget/part_image_widget.dart';
@ -100,33 +98,12 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
void _editPartDialog(BuildContext context) {
launchApiForm(
context,
L10().editPart,
part.url,
{
"name": {},
"description": {},
"IPN": {},
"revision": {},
"keywords": {},
"link": {},
"category": {
},
// Checkbox fields
"active": {},
"assembly": {},
"component": {},
"purchaseable": {},
"salable": {},
"trackable": {},
"is_template": {},
"virtual": {},
},
modelData: part.jsondata,
onSuccess: refresh,
part.editForm(
context,
L10().editPart,
onSuccess: (data) async {
refresh();
}
);
}
@ -305,16 +282,18 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
// Tiles for an "assembly" part
if (part.isAssembly) {
tiles.add(
ListTile(
title: Text(L10().billOfMaterials),
leading: FaIcon(FontAwesomeIcons.thList),
trailing: Text("${part.bomItemCount}"),
onTap: () {
// TODO
}
)
);
if (part.bomItemCount > 0) {
tiles.add(
ListTile(
title: Text(L10().billOfMaterials),
leading: FaIcon(FontAwesomeIcons.thList),
trailing: Text("${part.bomItemCount}"),
onTap: () {
// TODO
}
)
);
}
if (part.building > 0) {
tiles.add(
@ -331,7 +310,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
}
// Tiles for "component" part
if (part.isComponent) {
if (part.isComponent && part.usedInCount > 0) {
tiles.add(
ListTile(
@ -421,6 +400,19 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
},
),
);
if (false && !part.isActive && InvenTreeAPI().checkPermission('part', 'delete')) {
tiles.add(
ListTile(
title: Text(L10().deletePart),
subtitle: Text(L10().deletePartDetail),
leading: FaIcon(FontAwesomeIcons.trashAlt, color: COLOR_DANGER),
onTap: () {
// TODO
},
)
);
}
return tiles;
}
@ -469,12 +461,10 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
label: L10().stock
),
// TODO - Add part actions
/*
BottomNavigationBarItem(
icon: FaIcon(FontAwesomeIcons.wrench),
label: L10().actions,
),
*/
]
);
}

View File

@ -7,8 +7,6 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:inventree/l10.dart';
import '../api_form.dart';
class PartNotesWidget extends StatefulWidget {
@ -46,17 +44,15 @@ class _PartNotesState extends RefreshableState<PartNotesWidget> {
icon: FaIcon(FontAwesomeIcons.edit),
tooltip: L10().edit,
onPressed: () {
launchApiForm(
part.editForm(
context,
L10().editNotes,
part.url,
{
fields: {
"notes": {
"multiline": true,
}
},
modelData: part.jsondata,
onSuccess: () async {
onSuccess: (data) async {
refresh();
}
);

View File

@ -22,8 +22,6 @@ import 'package:inventree/api.dart';
import 'package:dropdown_search/dropdown_search.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import '../api_form.dart';
class StockDetailWidget extends StatefulWidget {
StockDetailWidget(this.item, {Key? key}) : super(key: key);
@ -109,18 +107,20 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
void _editStockItem(BuildContext context) async {
launchApiForm(
var fields = InvenTreeStockItem().formFields();
// Some fields we don't want to edit!
fields.remove("part");
fields.remove("quantity");
fields.remove("location");
item.editForm(
context,
L10().editItem,
item.url,
{
"status": {},
"batch": {},
"packaging": {},
"link": {},
},
modelData: item.jsondata,
onSuccess: refresh
fields: fields,
onSuccess: (data) async {
refresh();
}
);
}

View File

@ -8,7 +8,6 @@ import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:inventree/l10.dart';
import '../api.dart';
import '../api_form.dart';
class StockNotesWidget extends StatefulWidget {
@ -46,19 +45,17 @@ class _StockNotesState extends RefreshableState<StockNotesWidget> {
icon: FaIcon(FontAwesomeIcons.edit),
tooltip: L10().edit,
onPressed: () {
launchApiForm(
context,
L10().editNotes,
item.url,
{
"notes": {
"multiline": true,
}
},
modelData: item.jsondata,
onSuccess: () {
refresh();
item.editForm(
context,
L10().editNotes,
fields: {
"notes": {
"multiline": true,
}
},
onSuccess: (data) async {
refresh();
}
);
}
)

View File

@ -1,6 +1,5 @@
import 'package:email_validator/email_validator.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';