mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-28 05:26:47 +00:00
Add POReceiveBarcodeHandler to support barcode/po-receive/ endpoint (#421)
* Add POReceiveBarcodeHandler to support barcode/po-receive/ endpoint * Remove german translation * Add api version checks * Add getOverlayText method to barcode handler * Bump required API version to 139 * Update barcode.dart The "quantity" field is not an integer, and can cause the app to crash if not handled correctly --------- Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
This commit is contained in:
parent
c76309341b
commit
67fd6a564a
@ -328,6 +328,8 @@ class InvenTreeAPI {
|
|||||||
// Does the server support extra fields on stock adjustment actions?
|
// Does the server support extra fields on stock adjustment actions?
|
||||||
bool get supportsStockAdjustExtraFields => isConnected() && apiVersion >= 133;
|
bool get supportsStockAdjustExtraFields => isConnected() && apiVersion >= 133;
|
||||||
|
|
||||||
|
bool get supportsBarcodePOReceiveEndpoint => isConnected() && apiVersion >= 139;
|
||||||
|
|
||||||
// Are plugins enabled on the server?
|
// Are plugins enabled on the server?
|
||||||
bool _pluginsEnabled = false;
|
bool _pluginsEnabled = false;
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import "package:one_context/one_context.dart";
|
|||||||
|
|
||||||
|
|
||||||
import "package:inventree/api.dart";
|
import "package:inventree/api.dart";
|
||||||
|
import "package:inventree/api_form.dart";
|
||||||
import "package:inventree/helpers.dart";
|
import "package:inventree/helpers.dart";
|
||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
|
|
||||||
@ -462,6 +463,116 @@ class ScanParentLocationHandler extends BarcodeScanStockLocationHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Barcode handler class for scanning a supplier barcode to receive a part
|
||||||
|
*
|
||||||
|
* - The class can be initialized by optionally passing a valid, placed PurchaseOrder object
|
||||||
|
* - Expects to scan supplier barcode, possibly containing order_number and quantity
|
||||||
|
* - If location or quantity information wasn't provided, show a form to fill it in
|
||||||
|
*/
|
||||||
|
class POReceiveBarcodeHandler extends BarcodeHandler {
|
||||||
|
|
||||||
|
POReceiveBarcodeHandler({this.purchaseOrder, this.location});
|
||||||
|
|
||||||
|
InvenTreePurchaseOrder? purchaseOrder;
|
||||||
|
InvenTreeStockLocation? location;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getOverlayText(BuildContext context) => L10().barcodeReceivePart;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> processBarcode(String barcode,
|
||||||
|
{String url = "barcode/po-receive/",
|
||||||
|
Map<String, dynamic> extra_data = const {}}) {
|
||||||
|
|
||||||
|
final po_extra_data = {
|
||||||
|
"purchase_order": purchaseOrder?.pk,
|
||||||
|
"location": location?.pk,
|
||||||
|
...extra_data,
|
||||||
|
};
|
||||||
|
|
||||||
|
return super.processBarcode(barcode, url: url, extra_data: po_extra_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
|
||||||
|
if (!data.containsKey("lineitem")) {
|
||||||
|
return onBarcodeUnknown(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
barcodeSuccessTone();
|
||||||
|
showSnackIcon(L10().receivedItem, success: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onBarcodeUnhandled(Map<String, dynamic> data) async {
|
||||||
|
if (!data.containsKey("action_required") || !data.containsKey("lineitem")) {
|
||||||
|
return super.onBarcodeUnhandled(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
final lineItemData = data["lineitem"] as Map<String, dynamic>;
|
||||||
|
if (!lineItemData.containsKey("pk") || !lineItemData.containsKey("purchase_order")) {
|
||||||
|
barcodeFailureTone();
|
||||||
|
showSnackIcon(L10().missingData, success: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct fields to receive
|
||||||
|
Map<String, dynamic> fields = {
|
||||||
|
"line_item": {
|
||||||
|
"parent": "items",
|
||||||
|
"nested": true,
|
||||||
|
"hidden": true,
|
||||||
|
"value": lineItemData["pk"] as int,
|
||||||
|
},
|
||||||
|
"quantity": {
|
||||||
|
"parent": "items",
|
||||||
|
"nested": true,
|
||||||
|
"value": lineItemData["quantity"] as double?,
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"parent": "items",
|
||||||
|
"nested": true,
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"value": lineItemData["location"] as int?,
|
||||||
|
},
|
||||||
|
"barcode": {
|
||||||
|
"parent": "items",
|
||||||
|
"nested": true,
|
||||||
|
"hidden": true,
|
||||||
|
"type": "barcode",
|
||||||
|
"value": data["barcode_data"] as String,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final context = OneContext().context!;
|
||||||
|
final purchase_order_pk = lineItemData["purchase_order"];
|
||||||
|
final receive_url = "${InvenTreePurchaseOrder().URL}${purchase_order_pk}/receive/";
|
||||||
|
|
||||||
|
launchApiForm(
|
||||||
|
context,
|
||||||
|
L10().receiveItem,
|
||||||
|
receive_url,
|
||||||
|
fields,
|
||||||
|
method: "POST",
|
||||||
|
icon: FontAwesomeIcons.rightToBracket,
|
||||||
|
onSuccess: (data) async {
|
||||||
|
showSnackIcon(L10().receivedItem, success: true);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onBarcodeUnknown(Map<String, dynamic> data) async {
|
||||||
|
barcodeFailureTone();
|
||||||
|
showSnackIcon(
|
||||||
|
data["error"] as String? ?? L10().barcodeError,
|
||||||
|
success: false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Barcode handler for finding a "unique" barcode (one that does not match an item in the database)
|
* Barcode handler for finding a "unique" barcode (one that does not match an item in the database)
|
||||||
*/
|
*/
|
||||||
|
@ -58,8 +58,9 @@ class BarcodeHandler {
|
|||||||
*
|
*
|
||||||
* Returns true only if the barcode scanner should remain open
|
* Returns true only if the barcode scanner should remain open
|
||||||
*/
|
*/
|
||||||
Future<void> processBarcode(String barcode, {String url = "barcode/"}) async {
|
Future<void> processBarcode(String barcode,
|
||||||
|
{String url = "barcode/",
|
||||||
|
Map<String, dynamic> extra_data = const {}}) async {
|
||||||
debug("Scanned barcode data: '${barcode}'");
|
debug("Scanned barcode data: '${barcode}'");
|
||||||
|
|
||||||
barcode = barcode.trim();
|
barcode = barcode.trim();
|
||||||
@ -82,6 +83,7 @@ class BarcodeHandler {
|
|||||||
url,
|
url,
|
||||||
body: {
|
body: {
|
||||||
"barcode": barcode,
|
"barcode": barcode,
|
||||||
|
...extra_data,
|
||||||
},
|
},
|
||||||
expectedStatusCode: null, // Do not show an error on "unexpected code"
|
expectedStatusCode: null, // Do not show an error on "unexpected code"
|
||||||
);
|
);
|
||||||
|
@ -112,6 +112,9 @@
|
|||||||
"barcodeNotAssigned": "Barcode not assigned",
|
"barcodeNotAssigned": "Barcode not assigned",
|
||||||
"@barcodeNotAssigned": {},
|
"@barcodeNotAssigned": {},
|
||||||
|
|
||||||
|
"barcodeReceivePart": "Scan barcode to receive part",
|
||||||
|
"@barcodeReceivePart": {},
|
||||||
|
|
||||||
"barcodeScanAssign": "Scan to assign barcode",
|
"barcodeScanAssign": "Scan to assign barcode",
|
||||||
"@barcodeScanAssign": {},
|
"@barcodeScanAssign": {},
|
||||||
|
|
||||||
@ -988,6 +991,9 @@
|
|||||||
"scanIntoLocationDetail": "Scan this item into location",
|
"scanIntoLocationDetail": "Scan this item into location",
|
||||||
"@scanIntoLocationDetail": {},
|
"@scanIntoLocationDetail": {},
|
||||||
|
|
||||||
|
"scanReceivedParts": "Scan Received Parts",
|
||||||
|
"@scanReceivedParts": {},
|
||||||
|
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
"@search": {
|
"@search": {
|
||||||
"description": "search"
|
"description": "search"
|
||||||
|
@ -104,6 +104,21 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (api.supportsBarcodePOReceiveEndpoint) {
|
||||||
|
actions.add(
|
||||||
|
SpeedDialChild(
|
||||||
|
child: Icon(Icons.barcode_reader),
|
||||||
|
label: L10().scanReceivedParts,
|
||||||
|
onTap:() async {
|
||||||
|
scanBarcode(
|
||||||
|
context,
|
||||||
|
handler: POReceiveBarcodeHandler(location: location),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Scan this location into another one
|
// Scan this location into another one
|
||||||
if (InvenTreeStockLocation().canEdit) {
|
if (InvenTreeStockLocation().canEdit) {
|
||||||
actions.add(
|
actions.add(
|
||||||
|
@ -6,6 +6,7 @@ import "package:inventree/widget/po_line_list.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/barcode/barcode.dart";
|
||||||
import "package:inventree/helpers.dart";
|
import "package:inventree/helpers.dart";
|
||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
|
|
||||||
@ -132,6 +133,29 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<SpeedDialChild> barcodeButtons(BuildContext context) {
|
||||||
|
List<SpeedDialChild> actions = [];
|
||||||
|
|
||||||
|
if (api.supportsBarcodePOReceiveEndpoint) {
|
||||||
|
actions.add(
|
||||||
|
SpeedDialChild(
|
||||||
|
child: Icon(Icons.barcode_reader),
|
||||||
|
label: L10().scanReceivedParts,
|
||||||
|
onTap:() async {
|
||||||
|
scanBarcode(
|
||||||
|
context,
|
||||||
|
handler: POReceiveBarcodeHandler(purchaseOrder: order),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> request(BuildContext context) async {
|
Future<void> request(BuildContext context) async {
|
||||||
await order.reload();
|
await order.reload();
|
||||||
|
@ -9,6 +9,7 @@ import "package:inventree/widget/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";
|
||||||
|
import "package:inventree/barcode/barcode.dart";
|
||||||
import "package:inventree/inventree/purchase_order.dart";
|
import "package:inventree/inventree/purchase_order.dart";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -79,6 +80,28 @@ class _PurchaseOrderListWidgetState extends RefreshableState<PurchaseOrderListWi
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<SpeedDialChild> barcodeButtons(BuildContext context) {
|
||||||
|
List<SpeedDialChild> actions = [];
|
||||||
|
|
||||||
|
if (api.supportsBarcodePOReceiveEndpoint) {
|
||||||
|
actions.add(
|
||||||
|
SpeedDialChild(
|
||||||
|
child: Icon(Icons.barcode_reader),
|
||||||
|
label: L10().scanReceivedParts,
|
||||||
|
onTap:() async {
|
||||||
|
scanBarcode(
|
||||||
|
context,
|
||||||
|
handler: POReceiveBarcodeHandler(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget getBody(BuildContext context) {
|
Widget getBody(BuildContext context) {
|
||||||
return PaginatedPurchaseOrderList(filters);
|
return PaginatedPurchaseOrderList(filters);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user