mirror of
https://github.com/inventree/inventree-app.git
synced 2025-06-15 03:35:28 +00:00
[refactor] Scan improvements (#577)
* Handle error on unexpected barcode response * Add ManufacturerPart detail view * Support barcode scanning for manufacturer part * Refactoring for null checks * Ignore selected errors in sentry * Fix API implementation for ManufacturerPart * Update release notes * More error handling * Decode quantity betterer * Refactoring * Add option to confirm checkin details * Improve response handlign * Cleanup * Remove unused imports * Fix async function * Fix for assigning custom barcode * Handle barcode scan result for company * Fix * Adjust scan priority * Refactoring MODEL_TYPE - Use instead of duplicated const strings * @override fix
This commit is contained in:
@ -4,7 +4,9 @@ import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
||||
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||
import "package:inventree/helpers.dart";
|
||||
import "package:inventree/inventree/sales_order.dart";
|
||||
import "package:inventree/inventree/sentry.dart";
|
||||
import "package:inventree/preferences.dart";
|
||||
import "package:inventree/widget/company/manufacturer_part_detail.dart";
|
||||
import "package:inventree/widget/order/sales_order_detail.dart";
|
||||
import "package:one_context/one_context.dart";
|
||||
|
||||
@ -30,6 +32,7 @@ import "package:inventree/widget/order/purchase_order_detail.dart";
|
||||
import "package:inventree/widget/refreshable_state.dart";
|
||||
import "package:inventree/widget/snacks.dart";
|
||||
import "package:inventree/widget/stock/stock_detail.dart";
|
||||
import "package:inventree/widget/company/company_detail.dart";
|
||||
import "package:inventree/widget/company/supplier_part_detail.dart";
|
||||
|
||||
|
||||
@ -176,15 +179,37 @@ class BarcodeScanHandler extends BarcodeHandler {
|
||||
*/
|
||||
Future<void> handleSupplierPart(int pk) async {
|
||||
|
||||
var supplierpart = await InvenTreeSupplierPart().get(pk);
|
||||
var supplierPart = await InvenTreeSupplierPart().get(pk);
|
||||
|
||||
if (supplierpart is InvenTreeSupplierPart) {
|
||||
if (supplierPart is InvenTreeSupplierPart) {
|
||||
OneContext().pop();
|
||||
OneContext().push(MaterialPageRoute(
|
||||
builder: (context) => SupplierPartDetailWidget(supplierpart)));
|
||||
builder: (context) => SupplierPartDetailWidget(supplierPart)));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Response when a "ManufacturerPart" instance is scanned
|
||||
*/
|
||||
Future<void> handleManufacturerPart(int pk) async {
|
||||
var manufacturerPart = await InvenTreeManufacturerPart().get(pk);
|
||||
|
||||
if (manufacturerPart is InvenTreeManufacturerPart) {
|
||||
OneContext().pop();
|
||||
OneContext().push(MaterialPageRoute(
|
||||
builder: (context) => ManufacturerPartDetailWidget(manufacturerPart)));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> handleCompany(int pk) async {
|
||||
var company = await InvenTreeCompany().get(pk);
|
||||
|
||||
if (company is InvenTreeCompany) {
|
||||
OneContext().pop();
|
||||
OneContext().push(MaterialPageRoute(
|
||||
builder: (context) => CompanyDetailWidget(company)));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Response when a "PurchaseOrder" instance is scanned
|
||||
@ -218,26 +243,32 @@ class BarcodeScanHandler extends BarcodeHandler {
|
||||
|
||||
// The following model types can be matched with barcodes
|
||||
List<String> validModels = [
|
||||
"part",
|
||||
"stockitem",
|
||||
"stocklocation",
|
||||
"supplierpart",
|
||||
InvenTreePart.MODEL_TYPE,
|
||||
InvenTreeCompany.MODEL_TYPE,
|
||||
InvenTreeStockItem.MODEL_TYPE,
|
||||
InvenTreeStockLocation.MODEL_TYPE,
|
||||
InvenTreeSupplierPart.MODEL_TYPE,
|
||||
InvenTreeManufacturerPart.MODEL_TYPE,
|
||||
];
|
||||
|
||||
|
||||
if (InvenTreeAPI().supportsOrderBarcodes) {
|
||||
validModels.add("purchaseorder");
|
||||
validModels.add("salesorder");
|
||||
validModels.add(InvenTreePurchaseOrder.MODEL_TYPE);
|
||||
validModels.add(InvenTreeSalesOrder.MODEL_TYPE);
|
||||
}
|
||||
|
||||
for (var key in validModels) {
|
||||
if (data.containsKey(key)) {
|
||||
pk = (data[key]?["pk"] ?? -1) as int;
|
||||
try {
|
||||
pk = (data[key]?["pk"] ?? -1) as int;
|
||||
|
||||
// Break on the first valid match found
|
||||
if (pk > 0) {
|
||||
model = key;
|
||||
break;
|
||||
// Break on the first valid match found
|
||||
if (pk > 0) {
|
||||
model = key;
|
||||
break;
|
||||
}
|
||||
} catch (error, stackTrace) {
|
||||
sentryReportError("onBarcodeMatched", error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -248,25 +279,30 @@ class BarcodeScanHandler extends BarcodeHandler {
|
||||
barcodeSuccessTone();
|
||||
|
||||
switch (model) {
|
||||
case "part":
|
||||
await handlePart(pk);
|
||||
return;
|
||||
case "stockitem":
|
||||
case InvenTreeStockItem.MODEL_TYPE:
|
||||
await handleStockItem(pk);
|
||||
return;
|
||||
case "stocklocation":
|
||||
await handleStockLocation(pk);
|
||||
return;
|
||||
case "supplierpart":
|
||||
await handleSupplierPart(pk);
|
||||
return;
|
||||
case "purchaseorder":
|
||||
case InvenTreePurchaseOrder.MODEL_TYPE:
|
||||
await handlePurchaseOrder(pk);
|
||||
return;
|
||||
case "salesorder":
|
||||
case InvenTreeSalesOrder.MODEL_TYPE:
|
||||
await handleSalesOrder(pk);
|
||||
return;
|
||||
// TODO: Handle manufacturer part
|
||||
case InvenTreeStockLocation.MODEL_TYPE:
|
||||
await handleStockLocation(pk);
|
||||
return;
|
||||
case InvenTreeSupplierPart.MODEL_TYPE:
|
||||
await handleSupplierPart(pk);
|
||||
return;
|
||||
case InvenTreeManufacturerPart.MODEL_TYPE:
|
||||
await handleManufacturerPart(pk);
|
||||
return;
|
||||
case InvenTreePart.MODEL_TYPE:
|
||||
await handlePart(pk);
|
||||
return;
|
||||
case InvenTreeCompany.MODEL_TYPE:
|
||||
await handleCompany(pk);
|
||||
return;
|
||||
default:
|
||||
// Fall through to failure state
|
||||
break;
|
||||
@ -324,21 +360,6 @@ class UniqueBarcodeHandler extends BarcodeHandler {
|
||||
|
||||
@override
|
||||
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
|
||||
|
||||
barcodeFailureTone();
|
||||
|
||||
// If the barcode is known, we can"t assign it to the stock item!
|
||||
showSnackIcon(
|
||||
L10().barcodeInUse,
|
||||
icon: Icons.qr_code,
|
||||
success: false
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onBarcodeUnknown(Map<String, dynamic> data) async {
|
||||
// If the barcode is unknown, we *can* assign it to the stock item!
|
||||
|
||||
if (!data.containsKey("hash") && !data.containsKey("barcode_hash")) {
|
||||
showServerError(
|
||||
"barcode/",
|
||||
@ -370,6 +391,12 @@ class UniqueBarcodeHandler extends BarcodeHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onBarcodeUnknown(Map<String, dynamic> data) async {
|
||||
await onBarcodeMatched(data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -93,7 +93,7 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState {
|
||||
/*
|
||||
* Callback function when a barcode is scanned
|
||||
*/
|
||||
void _onScanSuccess(Code? code) {
|
||||
Future<void> onScanSuccess(Code? code) async {
|
||||
|
||||
if (scanning_paused) {
|
||||
return;
|
||||
@ -122,18 +122,16 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
scanned_code = barcode;
|
||||
scanning_paused = barcode.isNotEmpty;
|
||||
});
|
||||
}
|
||||
|
||||
if (barcode.isNotEmpty) {
|
||||
|
||||
handleBarcodeData(barcode).then((_) {
|
||||
pauseScan();
|
||||
|
||||
await handleBarcodeData(barcode).then((_) {
|
||||
if (!single_scanning && mounted) {
|
||||
// Resume next scan
|
||||
setState(() {
|
||||
scanning_paused = false;
|
||||
});
|
||||
resumeScan();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -186,7 +184,7 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState {
|
||||
Widget BarcodeReader(BuildContext context) {
|
||||
|
||||
return ReaderWidget(
|
||||
onScan: _onScanSuccess,
|
||||
onScan: onScanSuccess,
|
||||
isMultiScan: false,
|
||||
tryHarder: true,
|
||||
tryInverted: true,
|
||||
|
@ -79,14 +79,27 @@ class BarcodeHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
var response = await InvenTreeAPI().post(
|
||||
APIResponse? response;
|
||||
|
||||
try {
|
||||
response = await InvenTreeAPI().post(
|
||||
url,
|
||||
body: {
|
||||
"barcode": barcode,
|
||||
...extra_data,
|
||||
},
|
||||
expectedStatusCode: null, // Do not show an error on "unexpected code"
|
||||
);
|
||||
expectedStatusCode: null, // Do not show an error on "unexpected code"
|
||||
);
|
||||
} catch (error, stackTrace) {
|
||||
sentryReportError("Barcode.processBarcode", error, stackTrace);
|
||||
response = null;
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
barcodeFailureTone();
|
||||
showSnackIcon(L10().barcodeError, success: false);
|
||||
return;
|
||||
}
|
||||
|
||||
debug("Barcode scan response" + response.data.toString());
|
||||
|
||||
@ -94,7 +107,7 @@ class BarcodeHandler {
|
||||
|
||||
// Handle strange response from the server
|
||||
if (!response.isValid() || !response.isMap()) {
|
||||
onBarcodeUnknown({});
|
||||
await onBarcodeUnknown({});
|
||||
|
||||
showSnackIcon(L10().serverError, success: false);
|
||||
|
||||
|
@ -1,9 +1,7 @@
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||
import "package:inventree/preferences.dart";
|
||||
import "package:one_context/one_context.dart";
|
||||
|
||||
import "package:inventree/l10.dart";
|
||||
import "package:inventree/api_form.dart";
|
||||
|
||||
import "package:inventree/barcode/barcode.dart";
|
||||
import "package:inventree/barcode/handler.dart";
|
||||
@ -23,10 +21,11 @@ import "package:inventree/widget/snacks.dart";
|
||||
*/
|
||||
class POReceiveBarcodeHandler extends BarcodeHandler {
|
||||
|
||||
POReceiveBarcodeHandler({this.purchaseOrder, this.location});
|
||||
POReceiveBarcodeHandler({this.purchaseOrder, this.location, this.lineItem});
|
||||
|
||||
InvenTreePurchaseOrder? purchaseOrder;
|
||||
InvenTreeStockLocation? location;
|
||||
InvenTreePOLineItem? lineItem;
|
||||
|
||||
@override
|
||||
String getOverlayText(BuildContext context) => L10().barcodeReceivePart;
|
||||
@ -34,11 +33,15 @@ class POReceiveBarcodeHandler extends BarcodeHandler {
|
||||
@override
|
||||
Future<void> processBarcode(String barcode,
|
||||
{String url = "barcode/po-receive/",
|
||||
Map<String, dynamic> extra_data = const {}}) {
|
||||
Map<String, dynamic> extra_data = const {}}) async {
|
||||
|
||||
final bool confirm = await InvenTreeSettingsManager().getBool(INV_PO_CONFIRM_SCAN, true);
|
||||
|
||||
final po_extra_data = {
|
||||
"purchase_order": purchaseOrder?.pk,
|
||||
"location": location?.pk,
|
||||
"line_item": lineItem?.pk,
|
||||
"auto_allocate": !confirm,
|
||||
...extra_data,
|
||||
};
|
||||
|
||||
@ -47,11 +50,13 @@ class POReceiveBarcodeHandler extends BarcodeHandler {
|
||||
|
||||
@override
|
||||
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
|
||||
if (!data.containsKey("lineitem")) {
|
||||
|
||||
if (data.containsKey("lineitem") || data.containsKey("success")) {
|
||||
barcodeSuccess(L10().receivedItem);
|
||||
return;
|
||||
} else {
|
||||
return onBarcodeUnknown(data);
|
||||
}
|
||||
|
||||
barcodeSuccess(L10().receivedItem);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -66,49 +71,41 @@ class POReceiveBarcodeHandler extends BarcodeHandler {
|
||||
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,
|
||||
// At minimum, we need the line item ID value
|
||||
final int? lineItemId = lineItemData["pk"] as int?;
|
||||
|
||||
if (lineItemId == null) {
|
||||
barcodeFailureTone();
|
||||
return;
|
||||
}
|
||||
|
||||
InvenTreePOLineItem? lineItem = await InvenTreePOLineItem().get(lineItemId) as InvenTreePOLineItem?;
|
||||
|
||||
if (lineItem == null) {
|
||||
barcodeFailureTone();
|
||||
return;
|
||||
}
|
||||
|
||||
// Next, extract the "optional" fields
|
||||
|
||||
// Extract information from the returned server response
|
||||
double? quantity = double.tryParse((lineItemData["quantity"] ?? "0").toString());
|
||||
int? destination = lineItemData["location"] as int?;
|
||||
String? barcode = data["barcode_data"] as String?;
|
||||
|
||||
// Discard the barcode scanner at this stage
|
||||
if (OneContext.hasContext) {
|
||||
OneContext().pop();
|
||||
}
|
||||
|
||||
await lineItem.receive(
|
||||
OneContext().context!,
|
||||
destination: destination,
|
||||
quantity: quantity,
|
||||
barcode: barcode,
|
||||
onSuccess: () {
|
||||
showSnackIcon(L10().receivedItem, success: true);
|
||||
}
|
||||
};
|
||||
|
||||
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: TablerIcons.transition_right,
|
||||
onSuccess: (data) async {
|
||||
showSnackIcon(L10().receivedItem, success: true);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ class BarcodeScanStockLocationHandler extends BarcodeHandler {
|
||||
|
||||
// We expect that the barcode points to a 'stocklocation'
|
||||
if (data.containsKey("stocklocation")) {
|
||||
int _loc = (data["stocklocation"]["pk"] ?? -1) as int;
|
||||
int _loc = (data["stocklocation"]?["pk"] ?? -1) as int;
|
||||
|
||||
// A valid stock location!
|
||||
if (_loc > 0) {
|
||||
@ -83,7 +83,7 @@ class BarcodeScanStockItemHandler extends BarcodeHandler {
|
||||
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
|
||||
// We expect that the barcode points to a 'stockitem'
|
||||
if (data.containsKey("stockitem")) {
|
||||
int _item = (data["stockitem"]["pk"] ?? -1) as int;
|
||||
int _item = (data["stockitem"]?["pk"] ?? -1) as int;
|
||||
|
||||
// A valid stock location!
|
||||
if (_item > 0) {
|
||||
|
Reference in New Issue
Block a user