mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-28 13:36:50 +00:00
Merge branch 'snackbars'
This commit is contained in:
commit
3ec7ed217e
@ -1,4 +1,5 @@
|
|||||||
import 'package:InvenTree/inventree/part.dart';
|
import 'package:InvenTree/inventree/part.dart';
|
||||||
|
import 'package:InvenTree/widget/dialogs.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'model.dart';
|
import 'model.dart';
|
||||||
@ -323,6 +324,15 @@ class InvenTreeStockItem extends InvenTreeModel {
|
|||||||
|
|
||||||
double get quantity => double.tryParse(jsondata['quantity'].toString() ?? '0');
|
double get quantity => double.tryParse(jsondata['quantity'].toString() ?? '0');
|
||||||
|
|
||||||
|
String get quantityString {
|
||||||
|
|
||||||
|
if (quantity.toInt() == quantity) {
|
||||||
|
return quantity.toInt().toString();
|
||||||
|
} else {
|
||||||
|
return quantity.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int get locationId => jsondata['location'] as int ?? -1;
|
int get locationId => jsondata['location'] as int ?? -1;
|
||||||
|
|
||||||
bool isSerialized() => serialNumber != null && quantity.toInt() == 1;
|
bool isSerialized() => serialNumber != null && quantity.toInt() == 1;
|
||||||
@ -382,51 +392,81 @@ class InvenTreeStockItem extends InvenTreeModel {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<http.Response> countStock(double quan, {String notes}) async {
|
/*
|
||||||
|
* Perform stocktake action:
|
||||||
|
*
|
||||||
|
* - Add
|
||||||
|
* - Remove
|
||||||
|
* - Count
|
||||||
|
*/
|
||||||
|
Future<bool> adjustStock(BuildContext context, String endpoint, double q, {String notes}) async {
|
||||||
|
|
||||||
// Cannot 'count' a serialized StockItem
|
// Serialized stock cannot be adjusted
|
||||||
if (isSerialized()) {
|
if (isSerialized()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot handle negative stock
|
||||||
|
if (q < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = await api.post(
|
||||||
|
endpoint,
|
||||||
|
body: {
|
||||||
|
"item": {
|
||||||
|
"pk": "${pk}",
|
||||||
|
"quantity": "${q}",
|
||||||
|
},
|
||||||
|
"notes": notes ?? '',
|
||||||
|
}).timeout(Duration(seconds: 10)).catchError((error) {
|
||||||
|
if (error is TimeoutException) {
|
||||||
|
showTimeoutError(context);
|
||||||
|
} else if (error is SocketException) {
|
||||||
|
showServerError(
|
||||||
|
context,
|
||||||
|
I18N.of(context).connectionRefused,
|
||||||
|
error.toString()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Re-throw the error, let sentry handle it!
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Null response if error
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
// Cannot count negative stock
|
|
||||||
if (quan < 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return api.post("/stock/count/", body: {
|
|
||||||
"item": {
|
|
||||||
"pk": "${pk}",
|
|
||||||
"quantity": "${quan}",
|
|
||||||
},
|
|
||||||
"notes": notes ?? '',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (response == null) return false;
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
showStatusCodeError(context, response.statusCode);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<http.Response> addStock(double quan, {String notes}) async {
|
// Stock adjustment succeeded!
|
||||||
|
return true;
|
||||||
if (isSerialized() || quan <= 0) return null;
|
|
||||||
|
|
||||||
return api.post("/stock/add/", body: {
|
|
||||||
"item": {
|
|
||||||
"pk": "${pk}",
|
|
||||||
"quantity": "${quan}",
|
|
||||||
},
|
|
||||||
"notes": notes ?? '',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<http.Response> removeStock(double quan, {String notes}) async {
|
Future<bool> countStock(BuildContext context, double q, {String notes}) async {
|
||||||
|
|
||||||
if (isSerialized() || quan <= 0) return null;
|
final bool result = await adjustStock(context, "/stock/count", q, notes: notes);
|
||||||
|
|
||||||
return api.post("/stock/remove/", body: {
|
return result;
|
||||||
"item": {
|
}
|
||||||
"pk": "${pk}",
|
|
||||||
"quantity": "${quan}",
|
Future<bool> addStock(BuildContext context, double q, {String notes}) async {
|
||||||
},
|
|
||||||
"notes": notes ?? '',
|
final bool result = await adjustStock(context, "/stock/add/", q, notes: notes);
|
||||||
});
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> removeStock(BuildContext context, double q, {String notes}) async {
|
||||||
|
|
||||||
|
final bool result = await adjustStock(context, "/stock/remove/", q, notes: notes);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<http.Response> transferStock(int location, {double quantity, String notes}) async {
|
Future<http.Response> transferStock(int location, {double quantity, String notes}) async {
|
||||||
|
2
lib/l10n
2
lib/l10n
@ -1 +1 @@
|
|||||||
Subproject commit 249e4964a08b79e53df7e1ea18b051de0d307905
|
Subproject commit ed3bd59b15b2c69b9a21649a0e0507efd811c1a1
|
@ -5,6 +5,9 @@ import 'package:InvenTree/preferences.dart';
|
|||||||
|
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
import 'package:InvenTree/widget/fields.dart';
|
||||||
|
import 'package:InvenTree/widget/dialogs.dart';
|
||||||
|
import 'package:InvenTree/widget/snacks.dart';
|
||||||
import 'package:InvenTree/widget/part_detail.dart';
|
import 'package:InvenTree/widget/part_detail.dart';
|
||||||
import 'package:InvenTree/widget/drawer.dart';
|
import 'package:InvenTree/widget/drawer.dart';
|
||||||
import 'package:InvenTree/widget/refreshable_state.dart';
|
import 'package:InvenTree/widget/refreshable_state.dart';
|
||||||
@ -28,6 +31,8 @@ class CategoryDisplayWidget extends StatefulWidget {
|
|||||||
|
|
||||||
class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
|
class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
|
||||||
|
|
||||||
|
final _editCategoryKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => I18N.of(context).partCategory;
|
String getAppBarTitle(BuildContext context) => I18N.of(context).partCategory;
|
||||||
|
|
||||||
@ -37,11 +42,54 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.edit),
|
icon: FaIcon(FontAwesomeIcons.edit),
|
||||||
tooltip: I18N.of(context).edit,
|
tooltip: I18N.of(context).edit,
|
||||||
onPressed: null,
|
onPressed: _editCategoryDialog,
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _editCategory(Map<String, String> values) async {
|
||||||
|
|
||||||
|
final bool result = await category.update(context, values: values);
|
||||||
|
|
||||||
|
showSnackIcon(
|
||||||
|
refreshableKey,
|
||||||
|
result ? "Category edited" : "Category editing failed",
|
||||||
|
success: result
|
||||||
|
);
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _editCategoryDialog() {
|
||||||
|
|
||||||
|
var _name;
|
||||||
|
var _description;
|
||||||
|
|
||||||
|
showFormDialog(
|
||||||
|
context,
|
||||||
|
I18N.of(context).editCategory,
|
||||||
|
key: _editCategoryKey,
|
||||||
|
callback: () {
|
||||||
|
_editCategory({
|
||||||
|
"name": _name,
|
||||||
|
"description": _description
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fields: <Widget>[
|
||||||
|
StringField(
|
||||||
|
label: I18N.of(context).name,
|
||||||
|
initial: category.name,
|
||||||
|
onSaved: (value) => _name = value
|
||||||
|
),
|
||||||
|
StringField(
|
||||||
|
label: I18N.of(context).description,
|
||||||
|
initial: category.description,
|
||||||
|
onSaved: (value) => _description = value
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
_CategoryDisplayState(this.category) {}
|
_CategoryDisplayState(this.category) {}
|
||||||
|
|
||||||
// The local InvenTreePartCategory object
|
// The local InvenTreePartCategory object
|
||||||
@ -61,6 +109,11 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
|
|||||||
|
|
||||||
int pk = category?.pk ?? -1;
|
int pk = category?.pk ?? -1;
|
||||||
|
|
||||||
|
// Update the category
|
||||||
|
if (category != null) {
|
||||||
|
await category.reload(context);
|
||||||
|
}
|
||||||
|
|
||||||
// Request a list of sub-categories under this one
|
// Request a list of sub-categories under this one
|
||||||
await InvenTreePartCategory().list(context, filters: {"parent": "$pk"}).then((var cats) {
|
await InvenTreePartCategory().list(context, filters: {"parent": "$pk"}).then((var cats) {
|
||||||
_subcategories.clear();
|
_subcategories.clear();
|
||||||
@ -266,7 +319,7 @@ class PartList extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text("${part.name}"),
|
title: Text(part.fullname),
|
||||||
subtitle: Text("${part.description}"),
|
subtitle: Text("${part.description}"),
|
||||||
trailing: Text("${part.inStockString}"),
|
trailing: Text("${part.inStockString}"),
|
||||||
leading: InvenTreeAPI().getImage(
|
leading: InvenTreeAPI().getImage(
|
||||||
|
@ -161,7 +161,39 @@ void hideProgressDialog(BuildContext context) {
|
|||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
void showFormDialog(BuildContext context, String title, {GlobalKey<FormState> key, List<Widget> fields, List<Widget> actions}) {
|
void showFormDialog(BuildContext context, String title, {GlobalKey<FormState> key, List<Widget> fields, List<Widget> actions, Function callback}) {
|
||||||
|
|
||||||
|
// Undefined actions = OK + Cancel
|
||||||
|
if (actions == null) {
|
||||||
|
actions = <Widget>[
|
||||||
|
FlatButton(
|
||||||
|
child: Text(I18N.of(context).cancel),
|
||||||
|
onPressed: () {
|
||||||
|
// Close the form
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
FlatButton(
|
||||||
|
child: Text(I18N.of(context).save),
|
||||||
|
onPressed: () {
|
||||||
|
if (key.currentState.validate()) {
|
||||||
|
key.currentState.save();
|
||||||
|
|
||||||
|
// Close the dialog
|
||||||
|
Navigator.pop(context);
|
||||||
|
|
||||||
|
// Callback
|
||||||
|
if (callback != null) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
import 'package:InvenTree/api.dart';
|
import 'package:InvenTree/api.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/refreshable_state.dart';
|
||||||
|
import 'package:InvenTree/widget/fields.dart';
|
||||||
|
import 'package:InvenTree/widget/dialogs.dart';
|
||||||
|
import 'package:InvenTree/widget/snacks.dart';
|
||||||
import 'package:InvenTree/widget/stock_detail.dart';
|
import 'package:InvenTree/widget/stock_detail.dart';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:InvenTree/widget/refreshable_state.dart';
|
|
||||||
|
|
||||||
class LocationDisplayWidget extends StatefulWidget {
|
class LocationDisplayWidget extends StatefulWidget {
|
||||||
|
|
||||||
@ -25,6 +30,8 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
|
|
||||||
final InvenTreeStockLocation location;
|
final InvenTreeStockLocation location;
|
||||||
|
|
||||||
|
final _editLocationKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) { return "Stock Location"; }
|
String getAppBarTitle(BuildContext context) { return "Stock Location"; }
|
||||||
|
|
||||||
@ -33,13 +40,53 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
return <Widget>[
|
return <Widget>[
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.edit),
|
icon: FaIcon(FontAwesomeIcons.edit),
|
||||||
tooltip: "Edit",
|
tooltip: I18N.of(context).edit,
|
||||||
// TODO - Edit stock location
|
onPressed: _editLocationDialog,
|
||||||
onPressed: null,
|
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _editLocation(Map<String, String> values) async {
|
||||||
|
|
||||||
|
final bool result = await location.update(context, values: values);
|
||||||
|
|
||||||
|
showSnackIcon(
|
||||||
|
refreshableKey,
|
||||||
|
result ? "Location edited" : "Location editing failed",
|
||||||
|
success: result
|
||||||
|
);
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _editLocationDialog() {
|
||||||
|
// Values which an be edited
|
||||||
|
var _name;
|
||||||
|
var _description;
|
||||||
|
|
||||||
|
showFormDialog(context, I18N.of(context).editLocation,
|
||||||
|
key: _editLocationKey,
|
||||||
|
callback: () {
|
||||||
|
_editLocation({
|
||||||
|
"name": _name,
|
||||||
|
"description": _description
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fields: <Widget> [
|
||||||
|
StringField(
|
||||||
|
label: I18N.of(context).name,
|
||||||
|
initial: location.name,
|
||||||
|
onSaved: (value) => _name = value,
|
||||||
|
),
|
||||||
|
StringField(
|
||||||
|
label: I18N.of(context).description,
|
||||||
|
initial: location.description,
|
||||||
|
onSaved: (value) => _description = value,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
_LocationDisplayState(this.location) {}
|
_LocationDisplayState(this.location) {}
|
||||||
|
|
||||||
List<InvenTreeStockLocation> _sublocations = List<InvenTreeStockLocation>();
|
List<InvenTreeStockLocation> _sublocations = List<InvenTreeStockLocation>();
|
||||||
@ -67,6 +114,11 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
|
|
||||||
int pk = location?.pk ?? -1;
|
int pk = location?.pk ?? -1;
|
||||||
|
|
||||||
|
// Reload location information
|
||||||
|
if (location != null) {
|
||||||
|
await location.reload(context);
|
||||||
|
}
|
||||||
|
|
||||||
// Request a list of sub-locations under this one
|
// Request a list of sub-locations under this one
|
||||||
await InvenTreeStockLocation().list(context, filters: {"parent": "$pk"}).then((var locs) {
|
await InvenTreeStockLocation().list(context, filters: {"parent": "$pk"}).then((var locs) {
|
||||||
_sublocations.clear();
|
_sublocations.clear();
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:InvenTree/widget/snacks.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -74,9 +76,13 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
|
|
||||||
void _savePart(Map<String, String> values) async {
|
void _savePart(Map<String, String> values) async {
|
||||||
|
|
||||||
Navigator.of(context).pop();
|
final bool result = await part.update(context, values: values);
|
||||||
|
|
||||||
var response = await part.update(context, values: values);
|
showSnackIcon(
|
||||||
|
refreshableKey,
|
||||||
|
result ? "Part edited" : "Part editing failed",
|
||||||
|
success: result
|
||||||
|
);
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
@ -96,34 +102,18 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
var _name;
|
var _name;
|
||||||
var _description;
|
var _description;
|
||||||
var _ipn;
|
var _ipn;
|
||||||
var _revision;
|
|
||||||
var _keywords;
|
var _keywords;
|
||||||
|
|
||||||
showFormDialog(context, I18N.of(context).editPart,
|
showFormDialog(context, I18N.of(context).editPart,
|
||||||
key: _editPartKey,
|
key: _editPartKey,
|
||||||
actions: <Widget>[
|
callback: () {
|
||||||
FlatButton(
|
|
||||||
child: Text(I18N.of(context).cancel),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
FlatButton(
|
|
||||||
child: Text(I18N.of(context).save),
|
|
||||||
onPressed: () {
|
|
||||||
if (_editPartKey.currentState.validate()) {
|
|
||||||
_editPartKey.currentState.save();
|
|
||||||
|
|
||||||
_savePart({
|
_savePart({
|
||||||
"name": _name,
|
"name": _name,
|
||||||
"description": _description,
|
"description": _description,
|
||||||
"IPN": _ipn,
|
"IPN": _ipn,
|
||||||
"keywords": _keywords,
|
"keywords": _keywords
|
||||||
});
|
});
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
|
||||||
],
|
|
||||||
fields: <Widget>[
|
fields: <Widget>[
|
||||||
StringField(
|
StringField(
|
||||||
label: I18N.of(context).name,
|
label: I18N.of(context).name,
|
||||||
@ -163,7 +153,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) => FullScreenWidget(part.name, part.image))
|
MaterialPageRoute(builder: (context) => FullScreenWidget(part.fullname, part.image))
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
@ -58,7 +58,7 @@ class _PartStockDisplayState extends RefreshableState<PartStockDetailWidget> {
|
|||||||
title: Text(part.fullname),
|
title: Text(part.fullname),
|
||||||
subtitle: Text(part.description),
|
subtitle: Text(part.description),
|
||||||
leading: InvenTreeAPI().getImage(part.thumbnail),
|
leading: InvenTreeAPI().getImage(part.thumbnail),
|
||||||
trailing: Text('${part.inStock}'),
|
trailing: Text(part.inStockString),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
PartStockList(part.stockItems),
|
PartStockList(part.stockItems),
|
||||||
|
@ -8,6 +8,8 @@ import 'package:InvenTree/widget/drawer.dart';
|
|||||||
|
|
||||||
abstract class RefreshableState<T extends StatefulWidget> extends State<T> {
|
abstract class RefreshableState<T extends StatefulWidget> extends State<T> {
|
||||||
|
|
||||||
|
final refreshableKey = GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
// Storage for context once "Build" is called
|
// Storage for context once "Build" is called
|
||||||
BuildContext context;
|
BuildContext context;
|
||||||
|
|
||||||
@ -80,6 +82,7 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> {
|
|||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
key: refreshableKey,
|
||||||
appBar: getAppBar(context),
|
appBar: getAppBar(context),
|
||||||
drawer: getDrawer(context),
|
drawer: getDrawer(context),
|
||||||
floatingActionButton: getFab(context),
|
floatingActionButton: getFab(context),
|
||||||
|
36
lib/widget/snacks.dart
Normal file
36
lib/widget/snacks.dart
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Display a snackbar with:
|
||||||
|
*
|
||||||
|
* a) Text on the left
|
||||||
|
* b) Icon on the right
|
||||||
|
*
|
||||||
|
* | Text <icon> |
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
|
||||||
|
void showSnackIcon(GlobalKey<ScaffoldState> key, String text, {IconData icon, bool success}) {
|
||||||
|
|
||||||
|
// Hide the current snackbar
|
||||||
|
key.currentState.hideCurrentSnackBar();
|
||||||
|
|
||||||
|
// If icon not specified, use the success status
|
||||||
|
if (icon == null) {
|
||||||
|
icon = (success == false) ? FontAwesomeIcons.timesCircle : FontAwesomeIcons.checkCircle;
|
||||||
|
}
|
||||||
|
|
||||||
|
key.currentState.showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Row(
|
||||||
|
children: [
|
||||||
|
Text(text),
|
||||||
|
Spacer(),
|
||||||
|
FaIcon(icon)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:InvenTree/barcode.dart';
|
import 'package:InvenTree/barcode.dart';
|
||||||
import 'package:InvenTree/inventree/stock.dart';
|
import 'package:InvenTree/inventree/stock.dart';
|
||||||
@ -8,6 +9,7 @@ import 'package:InvenTree/widget/fields.dart';
|
|||||||
import 'package:InvenTree/widget/location_display.dart';
|
import 'package:InvenTree/widget/location_display.dart';
|
||||||
import 'package:InvenTree/widget/part_detail.dart';
|
import 'package:InvenTree/widget/part_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/stock_item_test_results.dart';
|
import 'package:InvenTree/widget/stock_item_test_results.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -22,6 +24,7 @@ import 'package:flutter_typeahead/flutter_typeahead.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
|
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
|
||||||
class StockDetailWidget extends StatefulWidget {
|
class StockDetailWidget extends StatefulWidget {
|
||||||
|
|
||||||
@ -67,14 +70,12 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
double quantity = double.parse(_quantityController.text);
|
double quantity = double.parse(_quantityController.text);
|
||||||
_quantityController.clear();
|
_quantityController.clear();
|
||||||
|
|
||||||
// Await response to prevent the button from being pressed multiple times
|
final bool result = await item.addStock(context, quantity, notes: _notesController.text);
|
||||||
var response = await item.addStock(quantity, notes: _notesController.text);
|
|
||||||
_notesController.clear();
|
_notesController.clear();
|
||||||
|
|
||||||
// TODO - Handle error cases
|
_stockUpdateMessage(result);
|
||||||
refresh();
|
|
||||||
|
|
||||||
// TODO - Display a snackbar here indicating the action was successful (or otherwise)
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addStockDialog() async {
|
void _addStockDialog() async {
|
||||||
@ -108,20 +109,27 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _stockUpdateMessage(bool result) {
|
||||||
|
|
||||||
|
showSnackIcon(
|
||||||
|
refreshableKey,
|
||||||
|
result ? "Stock item updated" : "Stock item updated failed",
|
||||||
|
success: result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _removeStock() async {
|
void _removeStock() async {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
double quantity = double.parse(_quantityController.text);
|
double quantity = double.parse(_quantityController.text);
|
||||||
_quantityController.clear();
|
_quantityController.clear();
|
||||||
|
|
||||||
var response = await item.removeStock(quantity, notes: _notesController.text);
|
final bool result = await item.removeStock(context, quantity, notes: _notesController.text);
|
||||||
_notesController.clear();
|
|
||||||
|
|
||||||
// TODO - Handle error cases
|
_stockUpdateMessage(result);
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
|
|
||||||
// TODO - Display a snackbar here indicating the action was successful (or otherwise)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _removeStockDialog() {
|
void _removeStockDialog() {
|
||||||
@ -163,19 +171,16 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
double quantity = double.parse(_quantityController.text);
|
double quantity = double.parse(_quantityController.text);
|
||||||
_quantityController.clear();
|
_quantityController.clear();
|
||||||
|
|
||||||
var response = await item.countStock(quantity, notes: _notesController.text);
|
final bool result = await item.countStock(context, quantity, notes: _notesController.text);
|
||||||
_notesController.clear();
|
|
||||||
|
|
||||||
// TODO - Handle error cases, timeout, etc
|
_stockUpdateMessage(result);
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
|
|
||||||
// TODO - Display a snackbar here indicating the action was successful (or otherwise)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _countStockDialog() async {
|
void _countStockDialog() async {
|
||||||
|
|
||||||
_quantityController.text = item.quantity.toString();
|
_quantityController.text = item.quantityString;
|
||||||
_notesController.clear();
|
_notesController.clear();
|
||||||
|
|
||||||
showFormDialog(context, I18N.of(context).countStock,
|
showFormDialog(context, I18N.of(context).countStock,
|
||||||
@ -191,7 +196,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
fields: <Widget> [
|
fields: <Widget> [
|
||||||
QuantityField(
|
QuantityField(
|
||||||
label: I18N.of(context).countStock,
|
label: I18N.of(context).countStock,
|
||||||
hint: "${item.quantity}",
|
hint: "${item.quantityString}",
|
||||||
controller: _quantityController,
|
controller: _quantityController,
|
||||||
),
|
),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
@ -230,7 +235,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
|
|
||||||
InvenTreeStockLocation selectedLocation;
|
InvenTreeStockLocation selectedLocation;
|
||||||
|
|
||||||
_quantityController.text = "${item.quantity}";
|
_quantityController.text = "${item.quantityString}";
|
||||||
|
|
||||||
showFormDialog(context, I18N.of(context).transferStock,
|
showFormDialog(context, I18N.of(context).transferStock,
|
||||||
key: _moveStockKey,
|
key: _moveStockKey,
|
||||||
@ -382,7 +387,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
ListTile(
|
ListTile(
|
||||||
title: Text(I18N.of(context).quantity),
|
title: Text(I18N.of(context).quantity),
|
||||||
leading: FaIcon(FontAwesomeIcons.cubes),
|
leading: FaIcon(FontAwesomeIcons.cubes),
|
||||||
trailing: Text("${item.quantity}"),
|
trailing: Text("${item.quantityString}"),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import 'package:InvenTree/inventree/model.dart';
|
|||||||
import 'package:InvenTree/api.dart';
|
import 'package:InvenTree/api.dart';
|
||||||
import 'package:InvenTree/widget/dialogs.dart';
|
import 'package:InvenTree/widget/dialogs.dart';
|
||||||
import 'package:InvenTree/widget/fields.dart';
|
import 'package:InvenTree/widget/fields.dart';
|
||||||
|
import 'package:InvenTree/widget/snacks.dart';
|
||||||
|
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
@ -46,24 +47,20 @@ class _StockItemTestResultDisplayState extends RefreshableState<StockItemTestRes
|
|||||||
|
|
||||||
void uploadTestResult(String name, bool result, String value, String notes, File attachment) async {
|
void uploadTestResult(String name, bool result, String value, String notes, File attachment) async {
|
||||||
|
|
||||||
item.uploadTestResult(
|
final success = await item.uploadTestResult(
|
||||||
context,
|
context, name, result,
|
||||||
name,
|
|
||||||
result,
|
|
||||||
value: value,
|
value: value,
|
||||||
notes: notes,
|
notes: notes,
|
||||||
attachment: attachment
|
attachment: attachment
|
||||||
).then((bool success) {
|
);
|
||||||
if (success) {
|
|
||||||
// TODO - Show a SnackBar here!
|
showSnackIcon(
|
||||||
|
refreshableKey,
|
||||||
|
success ? "Test result uploaded" : "Could not upload test result",
|
||||||
|
success: success
|
||||||
|
);
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
} else {
|
|
||||||
showErrorDialog(
|
|
||||||
context,
|
|
||||||
I18N.of(context).error,
|
|
||||||
"Could not upload test result to server");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void addTestResult({String name = '', bool nameIsEditable = true, bool result = false, String value = '', bool valueRequired = false, bool attachmentRequired = false}) async {
|
void addTestResult({String name = '', bool nameIsEditable = true, bool result = false, String value = '', bool valueRequired = false, bool attachmentRequired = false}) async {
|
||||||
@ -76,24 +73,9 @@ class _StockItemTestResultDisplayState extends RefreshableState<StockItemTestRes
|
|||||||
|
|
||||||
showFormDialog(context, "Add Test Data",
|
showFormDialog(context, "Add Test Data",
|
||||||
key: _addResultKey,
|
key: _addResultKey,
|
||||||
actions: <Widget>[
|
callback: () {
|
||||||
FlatButton(
|
|
||||||
child: Text(I18N.of(context).cancel),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
FlatButton(
|
|
||||||
child: Text(I18N.of(context).save),
|
|
||||||
onPressed: () {
|
|
||||||
if (_addResultKey.currentState.validate()) {
|
|
||||||
_addResultKey.currentState.save();
|
|
||||||
Navigator.pop(context);
|
|
||||||
uploadTestResult(_name, _result, _value, _notes, _attachment);
|
uploadTestResult(_name, _result, _value, _notes, _attachment);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
)
|
|
||||||
],
|
|
||||||
fields: <Widget>[
|
fields: <Widget>[
|
||||||
StringField(
|
StringField(
|
||||||
label: "Test Name",
|
label: "Test Name",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user