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

Po barcode scan #458

Merged
merged 5 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions lib/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,12 @@ class InvenTreeAPI {
// Does the server support extra fields on stock adjustment actions?
bool get supportsStockAdjustExtraFields => isConnected() && apiVersion >= 133;

// Does the server support receiving items against a PO using barcodes?
bool get supportsBarcodePOReceiveEndpoint => isConnected() && apiVersion >= 139;

// Does the server support adding line items to a PO using barcodes?
bool get supportsBarcodePOAddLineEndpoint => isConnected() && apiVersion >= 153;

// Cached list of plugins (refreshed when we connect to the server)
List<InvenTreePlugin> _plugins = [];

Expand Down
128 changes: 17 additions & 111 deletions lib/barcode/barcode.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import "package:flutter/material.dart";

import "package:flutter_speed_dial/flutter_speed_dial.dart";
import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:inventree/inventree/sales_order.dart";
import "package:inventree/preferences.dart";
import "package:inventree/widget/order/sales_order_detail.dart";
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 @@ -166,6 +167,16 @@ class BarcodeScanHandler extends BarcodeHandler {
}
}

// Response when a SalesOrder instance is scanned
Future<void> handleSalesOrder(int pk) async {
var order = await InvenTreeSalesOrder().get(pk);

if (order is InvenTreeSalesOrder) {
OneContext().pop();
OneContext().push(MaterialPageRoute(
builder: (context) => SalesOrderDetailWidget(order)));
}
}

@override
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
Expand All @@ -184,6 +195,7 @@ class BarcodeScanHandler extends BarcodeHandler {

if (InvenTreeAPI().supportsOrderBarcodes) {
validModels.add("purchaseorder");
validModels.add("salesorder");
}

for (var key in validModels) {
Expand Down Expand Up @@ -219,6 +231,10 @@ class BarcodeScanHandler extends BarcodeHandler {
case "purchaseorder":
await handlePurchaseOrder(pk);
return;
case "salesorder":
await handleSalesOrder(pk);
return;
// TODO: Handle manufacturer part
default:
// Fall through to failure state
break;
Expand Down Expand Up @@ -478,116 +494,6 @@ 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)
*/
Expand Down
2 changes: 1 addition & 1 deletion lib/barcode/handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class BarcodeHandler {
barcodeFailureTone();

showSnackIcon(
L10().barcodeNoMatch,
(data["error"] ?? L10().barcodeNoMatch) as String,
success: false,
icon: Icons.qr_code,
);
Expand Down
203 changes: 203 additions & 0 deletions lib/barcode/purchase_order.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import "package:flutter/material.dart";

import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:one_context/one_context.dart";

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

import "package:inventree/barcode/handler.dart";
import "package:inventree/barcode/tones.dart";

import "package:inventree/inventree/purchase_order.dart";
import "package:inventree/inventree/stock.dart";

import "package:inventree/widget/snacks.dart";

/*
* 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 to add a line item to a purchase order
*/
class POAllocateBarcodeHandler extends BarcodeHandler {

POAllocateBarcodeHandler({this.purchaseOrder});

InvenTreePurchaseOrder? purchaseOrder;

@override
String getOverlayText(BuildContext context) => L10().scanSupplierPart;

@override
Future<void> processBarcode(String barcode, {
String url = "barcode/po-allocate/",
Map<String, dynamic> extra_data = const {}}
) {

final po_extra_data = {
"purchase_order": purchaseOrder?.pk,
...extra_data,
};

return super.processBarcode(
barcode,
url: url,
extra_data: po_extra_data,
);
}

@override
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
// Server must respond with a suppliertpart instance
if (!data.containsKey("supplierpart")) {
return onBarcodeUnknown(data);
}

dynamic supplier_part = data["supplierpart"];

int supplier_part_pk = -1;

if (supplier_part is Map<String, dynamic>) {
supplier_part_pk = (supplier_part["pk"] ?? -1) as int;
} else {
return onBarcodeUnknown(data);
}

// Dispose of the barcode scanner
if (OneContext.hasContext) {
OneContext().pop();
}

final context = OneContext().context!;

var fields = InvenTreePOLineItem().formFields();

fields["order"]?["value"] = purchaseOrder!.pk;
fields["part"]?["hidden"] = false;
fields["part"]?["value"] = supplier_part_pk;

InvenTreePOLineItem().createForm(
context,
L10().lineItemAdd,
fields: fields,
onSuccess: (data) async {},
);
}

@override
Future<void> onBarcodeUnhandled(Map<String, dynamic> data) async {

print("onBarcodeUnhandled:");
print(data.toString());

super.onBarcodeUnhandled(data);
}
}
Loading