Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add POReceiveBarcodeHandler to support barcode/po-receive/ endpoint #421

Merged
merged 7 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,8 @@ class InvenTreeAPI {
// Does the server support extra fields on stock adjustment actions?
bool get supportsStockAdjustExtraFields => isConnected() && apiVersion >= 133;

bool get supportsBarcodePOReceiveEndpoint => isConnected() && apiVersion >= 139;

// Are plugins enabled on the server?
bool _pluginsEnabled = false;

Expand Down
111 changes: 111 additions & 0 deletions lib/barcode/barcode.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "package:one_context/one_context.dart";


import "package:inventree/api.dart";
import "package:inventree/api_form.dart";
import "package:inventree/helpers.dart";
import "package:inventree/l10.dart";

Expand Down Expand Up @@ -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;

SchrodingersGat marked this conversation as resolved.
Show resolved Hide resolved
@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 int?,
},
"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)
*/
Expand Down
6 changes: 4 additions & 2 deletions lib/barcode/handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ class BarcodeHandler {
*
* 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 {
SchrodingersGat marked this conversation as resolved.
Show resolved Hide resolved
debug("Scanned barcode data: '${barcode}'");

barcode = barcode.trim();
Expand All @@ -82,6 +83,7 @@ class BarcodeHandler {
url,
body: {
"barcode": barcode,
...extra_data,
},
expectedStatusCode: null, // Do not show an error on "unexpected code"
);
Expand Down
6 changes: 6 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@
"barcodeNotAssigned": "Barcode not assigned",
"@barcodeNotAssigned": {},

"barcodeReceivePart": "Scan barcode to receive part",
"@barcodeReceivePart": {},

"barcodeScanAssign": "Scan to assign barcode",
"@barcodeScanAssign": {},

Expand Down Expand Up @@ -988,6 +991,9 @@
"scanIntoLocationDetail": "Scan this item into location",
"@scanIntoLocationDetail": {},

"scanReceivedParts": "Scan Received Parts",
"@scanReceivedParts": {},

"search": "Search",
"@search": {
"description": "search"
Expand Down
15 changes: 15 additions & 0 deletions lib/widget/location_display.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
if (InvenTreeStockLocation().canEdit) {
actions.add(
Expand Down
24 changes: 24 additions & 0 deletions lib/widget/purchase_order_detail.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "package:inventree/widget/po_line_list.dart";

import "package:inventree/api.dart";
import "package:inventree/app_colors.dart";
import "package:inventree/barcode/barcode.dart";
import "package:inventree/helpers.dart";
import "package:inventree/l10.dart";

Expand Down Expand Up @@ -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
Future<void> request(BuildContext context) async {
await order.reload();
Expand Down
23 changes: 23 additions & 0 deletions lib/widget/purchase_order_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "package:inventree/widget/purchase_order_detail.dart";
import "package:inventree/widget/refreshable_state.dart";
import "package:inventree/l10.dart";
import "package:inventree/api.dart";
import "package:inventree/barcode/barcode.dart";
import "package:inventree/inventree/purchase_order.dart";

/*
Expand Down Expand Up @@ -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
Widget getBody(BuildContext context) {
return PaginatedPurchaseOrderList(filters);
Expand Down