mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-28 05:26:47 +00:00
Merge branch 'barcode-actions'
This commit is contained in:
commit
13780b0c80
@ -41,6 +41,16 @@ class BarcodeHandler {
|
||||
Future<void> onBarcodeUnknown(Map<String, dynamic> data) {
|
||||
// Called when the server does not know about a barcode
|
||||
// Override this function
|
||||
showErrorDialog(
|
||||
_context,
|
||||
"Invalid Barcode",
|
||||
"Barcode does not match any known item",
|
||||
error: "Barcode Error",
|
||||
icon: FontAwesomeIcons.barcode,
|
||||
onDismissed: () {
|
||||
_controller.resumeCamera();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> onBarcodeUnhandled(Map<String, dynamic> data) {
|
||||
@ -339,6 +349,40 @@ class _QRViewState extends State<InvenTreeQRView> {
|
||||
}
|
||||
|
||||
|
||||
class StockItemScanIntoLocationHandler extends BarcodeHandler {
|
||||
/**
|
||||
* Barcode handler for scanning a provided StockItem into a scanned StockLocation
|
||||
*/
|
||||
|
||||
final InvenTreeStockItem item;
|
||||
|
||||
StockItemScanIntoLocationHandler(this.item);
|
||||
|
||||
@override
|
||||
Future<void> onBarcodeMatched(Map<String, dynamic> data) {
|
||||
// If the barcode points to a 'stocklocation', great!
|
||||
if (!data.containsKey('stocklocation')) {
|
||||
showErrorDialog(
|
||||
_context,
|
||||
"Invalid Barcode",
|
||||
"Barcode does not match a Stock Location",
|
||||
onDismissed: _controller.resumeCamera,
|
||||
);
|
||||
} else {
|
||||
// Extract location information
|
||||
int location = data['stocklocation']['pk'] as int;
|
||||
|
||||
// Transfer stock to specified location
|
||||
item.transferStock(location).then((response) {
|
||||
print("Response: ${response.statusCode}");
|
||||
_controller.dispose();
|
||||
Navigator.of(_context).pop();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> scanQrCode(BuildContext context) async {
|
||||
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreeQRView(BarcodeScanHandler())));
|
||||
|
@ -379,20 +379,26 @@ class InvenTreeStockItem extends InvenTreeModel {
|
||||
});
|
||||
}
|
||||
|
||||
Future<http.Response> transferStock(double q, int location, {String notes}) async {
|
||||
Future<http.Response> transferStock(int location, {double quantity, String notes}) async {
|
||||
if (quantity == null) {} else
|
||||
if ((quantity < 0) || (quantity > this.quantity)) {
|
||||
quantity = this.quantity;
|
||||
}
|
||||
|
||||
if ((q == null) || (q > quantity)) q = quantity;
|
||||
|
||||
return api.post("/stock/transfer/", body: {
|
||||
Map<String, dynamic> data = {
|
||||
"item": {
|
||||
"pk": "${pk}",
|
||||
"quantity": "${q}",
|
||||
},
|
||||
},
|
||||
"location": "${location}",
|
||||
"notes": notes ?? '',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (quantity != null) {
|
||||
data["item"]["quantity"] = "${quantity}";
|
||||
}
|
||||
|
||||
return api.post("/stock/transfer/", body: data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -141,7 +141,6 @@ class QuantityField extends TextFormField {
|
||||
labelText: label,
|
||||
hintText: hint,
|
||||
),
|
||||
initialValue: initial,
|
||||
controller: controller,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
validator: (value) {
|
||||
|
@ -133,11 +133,87 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget getBody(BuildContext context) {
|
||||
Widget getBottomNavBar(BuildContext context) {
|
||||
return BottomNavigationBar(
|
||||
currentIndex: tabIndex,
|
||||
onTap: onTabSelectionChanged,
|
||||
items: const <BottomNavigationBarItem> [
|
||||
BottomNavigationBarItem(
|
||||
icon: FaIcon(FontAwesomeIcons.boxes),
|
||||
title: Text("Stock"),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: FaIcon(FontAwesomeIcons.wrench),
|
||||
title: Text("Actions"),
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return ListView(
|
||||
children: <Widget> [
|
||||
locationDescriptionCard(),
|
||||
Widget getSelectedWidget(int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return ListView(
|
||||
children: detailTiles(),
|
||||
);
|
||||
case 1:
|
||||
return ListView(
|
||||
children: actionTiles(),
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget getBody(BuildContext context) {
|
||||
return getSelectedWidget(tabIndex);
|
||||
}
|
||||
|
||||
|
||||
List<Widget> detailTiles() {
|
||||
List<Widget> tiles = [];
|
||||
|
||||
// Location description
|
||||
tiles.add(locationDescriptionCard());
|
||||
|
||||
// Sublocation panel
|
||||
ExpansionPanel sublocations = ExpansionPanel(
|
||||
headerBuilder: (BuildContext context, bool isExpanded) {
|
||||
return ListTile(
|
||||
title: Text("Sublocations"),
|
||||
leading: FaIcon(FontAwesomeIcons.mapMarkerAlt),
|
||||
trailing: Text("${_sublocations.length}"),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
InvenTreePreferences().expandLocationList = !InvenTreePreferences().expandLocationList;
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
body: SublocationList(_sublocations),
|
||||
isExpanded: InvenTreePreferences().expandLocationList && _sublocations.length > 0,
|
||||
);
|
||||
|
||||
ExpansionPanel subitems = ExpansionPanel(
|
||||
headerBuilder: (BuildContext context, bool isExpanded) {
|
||||
return ListTile(
|
||||
title: Text("Stock Items"),
|
||||
leading: FaIcon(FontAwesomeIcons.boxes),
|
||||
trailing: Text("${_items.length}"),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
InvenTreePreferences().expandStockList = !InvenTreePreferences().expandStockList;
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
body: StockList(_items),
|
||||
isExpanded: InvenTreePreferences().expandStockList && _items.length > 0,
|
||||
);
|
||||
|
||||
// Sublocations and items
|
||||
tiles.add(
|
||||
ExpansionPanelList(
|
||||
expansionCallback: (int index, bool isExpanded) {
|
||||
setState(() {
|
||||
@ -152,49 +228,48 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
children: <ExpansionPanel> [
|
||||
ExpansionPanel(
|
||||
headerBuilder: (BuildContext context, bool isExpanded) {
|
||||
return ListTile(
|
||||
title: Text("Sublocations"),
|
||||
leading: FaIcon(FontAwesomeIcons.mapMarkerAlt),
|
||||
trailing: Text("${_sublocations.length}"),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
InvenTreePreferences().expandLocationList = !InvenTreePreferences().expandLocationList;
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
body: SublocationList(_sublocations),
|
||||
isExpanded: InvenTreePreferences().expandLocationList && _sublocations.length > 0,
|
||||
),
|
||||
ExpansionPanel(
|
||||
headerBuilder: (BuildContext context, bool isExpanded) {
|
||||
return ListTile(
|
||||
title: Text("Stock Items"),
|
||||
leading: FaIcon(FontAwesomeIcons.boxes),
|
||||
trailing: Text("${_items.length}"),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
InvenTreePreferences().expandStockList = !InvenTreePreferences().expandStockList;
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
body: StockList(_items),
|
||||
isExpanded: InvenTreePreferences().expandStockList && _items.length > 0,
|
||||
)
|
||||
sublocations,
|
||||
subitems,
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
return tiles;
|
||||
}
|
||||
|
||||
List<Widget> actionTiles() {
|
||||
List<Widget> tiles = [];
|
||||
|
||||
tiles.add(locationDescriptionCard());
|
||||
|
||||
// Scan items into location
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text("Scan in Stock Item"),
|
||||
leading: FaIcon(FontAwesomeIcons.exchangeAlt),
|
||||
trailing: FaIcon(FontAwesomeIcons.qrcode),
|
||||
onTap: null,
|
||||
)
|
||||
);
|
||||
|
||||
// Move location into another location
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text("Move Stock Location"),
|
||||
leading: FaIcon(FontAwesomeIcons.sitemap),
|
||||
trailing: FaIcon(FontAwesomeIcons.qrcode),
|
||||
)
|
||||
);
|
||||
|
||||
return tiles;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
class SublocationList extends StatelessWidget {
|
||||
final List<InvenTreeStockLocation> _locations;
|
||||
|
||||
|
@ -51,8 +51,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
||||
|
||||
InvenTreePart part;
|
||||
|
||||
int _tabIndex = 0;
|
||||
|
||||
@override
|
||||
Future<void> onBuild(BuildContext context) async {
|
||||
refresh();
|
||||
@ -302,12 +300,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
||||
|
||||
}
|
||||
|
||||
void _onTabSelectionChanged(int index) {
|
||||
setState(() {
|
||||
_tabIndex = index;
|
||||
});
|
||||
}
|
||||
|
||||
Widget getSelectedWidget(int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
@ -335,8 +327,8 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
||||
@override
|
||||
Widget getBottomNavBar(BuildContext context) {
|
||||
return BottomNavigationBar(
|
||||
currentIndex: _tabIndex,
|
||||
onTap: _onTabSelectionChanged,
|
||||
currentIndex: tabIndex,
|
||||
onTap: onTabSelectionChanged,
|
||||
items: const <BottomNavigationBarItem> [
|
||||
BottomNavigationBarItem(
|
||||
icon: FaIcon(FontAwesomeIcons.infoCircle),
|
||||
@ -356,6 +348,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
||||
|
||||
@override
|
||||
Widget getBody(BuildContext context) {
|
||||
return getSelectedWidget(_tabIndex);
|
||||
return getSelectedWidget(tabIndex);
|
||||
}
|
||||
}
|
@ -11,6 +11,16 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> {
|
||||
// Storage for context once "Build" is called
|
||||
BuildContext context;
|
||||
|
||||
// Current tab index (used for widgets which display bottom tabs)
|
||||
int tabIndex = 0;
|
||||
|
||||
// Update current tab selection
|
||||
void onTabSelectionChanged(int index) {
|
||||
setState(() {
|
||||
tabIndex = index;
|
||||
});
|
||||
}
|
||||
|
||||
List<Widget> getAppBarActions(BuildContext context) {
|
||||
return [];
|
||||
}
|
||||
|
@ -119,6 +119,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
void _addStockDialog() async {
|
||||
|
||||
_quantityController.clear();
|
||||
_notesController.clear();
|
||||
|
||||
showFormDialog(context, "Add Stock",
|
||||
key: _addStockKey,
|
||||
@ -165,6 +166,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
void _removeStockDialog() {
|
||||
|
||||
_quantityController.clear();
|
||||
_notesController.clear();
|
||||
|
||||
showFormDialog(context, "Remove Stock",
|
||||
key: _removeStockKey,
|
||||
@ -212,6 +214,9 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
|
||||
void _countStockDialog() async {
|
||||
|
||||
_quantityController.text = item.quantity.toString();
|
||||
_notesController.clear();
|
||||
|
||||
showFormDialog(context, "Count Stock",
|
||||
key: _countStockKey,
|
||||
actions: <Widget> [
|
||||
@ -248,7 +253,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
_quantityController.clear();
|
||||
_notesController.clear();
|
||||
|
||||
var response = await item.transferStock(quantity, location.pk, notes: notes);
|
||||
var response = await item.transferStock(location.pk, quantity: quantity, notes: notes);
|
||||
|
||||
// TODO - Error handling (potentially return false?)
|
||||
refresh();
|
||||
@ -331,23 +336,25 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget headerTile() {
|
||||
return Card(
|
||||
child: ListTile(
|
||||
title: Text("${item.partName}"),
|
||||
subtitle: Text("${item.partDescription}"),
|
||||
leading: InvenTreeAPI().getImage(item.partImage),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct a list of detail elements about this StockItem.
|
||||
* The number of elements may vary depending on the StockItem details
|
||||
*/
|
||||
List<Widget> stockTiles() {
|
||||
List<Widget> detailTiles() {
|
||||
List<Widget> tiles = [];
|
||||
|
||||
// Image / name / description
|
||||
tiles.add(
|
||||
Card(
|
||||
child: ListTile(
|
||||
title: Text("${item.partName}"),
|
||||
subtitle: Text("${item.partDescription}"),
|
||||
leading: InvenTreeAPI().getImage(item.partImage),
|
||||
)
|
||||
)
|
||||
);
|
||||
tiles.add(headerTile());
|
||||
|
||||
tiles.add(
|
||||
ListTile(
|
||||
@ -412,20 +419,6 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text("Add Barcode"),
|
||||
leading: FaIcon(FontAwesomeIcons.qrcode),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => InvenTreeQRView(StockItemBarcodeAssignmentHandler(item)))
|
||||
);
|
||||
//Navigator.push(context, MaterialPageRoute(builder: (context) => AssignBarcodeToStockItemView(item)));
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
// Supplier part?
|
||||
if (item.supplierPartId > 0) {
|
||||
tiles.add(
|
||||
@ -486,11 +479,87 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
return tiles;
|
||||
}
|
||||
|
||||
List<Widget> actionTiles() {
|
||||
List<Widget> tiles = [];
|
||||
|
||||
tiles.add(headerTile());
|
||||
|
||||
if (!item.isSerialized()) {
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text("Count Stock"),
|
||||
leading: FaIcon(FontAwesomeIcons.checkCircle),
|
||||
onTap: _countStockDialog,
|
||||
)
|
||||
);
|
||||
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text("Remove Stock"),
|
||||
leading: FaIcon(FontAwesomeIcons.minusCircle),
|
||||
onTap: _removeStockDialog,
|
||||
)
|
||||
);
|
||||
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text("Add Stock"),
|
||||
leading: FaIcon(FontAwesomeIcons.plusCircle),
|
||||
onTap: _addStockDialog,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text("Transfer Stock"),
|
||||
leading: FaIcon(FontAwesomeIcons.exchangeAlt),
|
||||
onTap: _transferStockDialog,
|
||||
)
|
||||
);
|
||||
|
||||
// Scan item into a location
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text("Scan Into Location"),
|
||||
leading: FaIcon(FontAwesomeIcons.exchangeAlt),
|
||||
trailing: FaIcon(FontAwesomeIcons.qrcode),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => InvenTreeQRView(StockItemScanIntoLocationHandler(item)))
|
||||
);
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
// Add or remove custom barcode
|
||||
if (item.uid.isEmpty) {
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text("Assign Barcode"),
|
||||
leading: FaIcon(FontAwesomeIcons.barcode),
|
||||
trailing: FaIcon(FontAwesomeIcons.qrcode),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => InvenTreeQRView(StockItemBarcodeAssignmentHandler(item)))
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return tiles;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a list of context-sensitive action buttons.
|
||||
* Not all buttons will be avaialable for a given StockItem,
|
||||
* depending on the properties of that StockItem
|
||||
*/
|
||||
|
||||
/*
|
||||
List<SpeedDialChild> actionButtons() {
|
||||
var buttons = List<SpeedDialChild>();
|
||||
|
||||
@ -525,32 +594,47 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
|
||||
return buttons;
|
||||
}
|
||||
*/
|
||||
|
||||
@override
|
||||
Widget getBottomNavBar(BuildContext context) {
|
||||
return BottomNavigationBar(
|
||||
currentIndex: 0,
|
||||
onTap: null,
|
||||
currentIndex: tabIndex,
|
||||
onTap: onTabSelectionChanged,
|
||||
items: const <BottomNavigationBarItem> [
|
||||
BottomNavigationBarItem(
|
||||
icon: FaIcon(FontAwesomeIcons.infoCircle),
|
||||
title: Text("Details"),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: FaIcon(FontAwesomeIcons.history),
|
||||
title: Text("History"),
|
||||
)
|
||||
icon: FaIcon(FontAwesomeIcons.wrench),
|
||||
title: Text("Actions"),
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget getBody(BuildContext context) {
|
||||
return ListView(
|
||||
children: stockTiles()
|
||||
);
|
||||
Widget getSelectedWidget(int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return ListView(
|
||||
children: detailTiles(),
|
||||
);
|
||||
case 1:
|
||||
return ListView(
|
||||
children: actionTiles(),
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget getBody(BuildContext context) {
|
||||
return getSelectedWidget(tabIndex);
|
||||
}
|
||||
|
||||
/*
|
||||
@override
|
||||
Widget getFab(BuildContext context) {
|
||||
return SpeedDial(
|
||||
@ -560,4 +644,5 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
children: actionButtons(),
|
||||
);
|
||||
}
|
||||
*/
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user