Skip to content

Commit

Permalink
New barcode actions (#218)
Browse files Browse the repository at this point in the history
* Bump release notes

* Adds method for linking custom barcodes

* Custom getter method for determining if an item has barcode data

* Add method to check if the API supports "modern" barcodes

* Refactor custom barcode implementation for StockItem

- Needs testing

* Unit testing for linking and unlinking barcodes

* Fixes

* Refactor code for "custom barcode action" tile

* Add custom barcode action to StockLocation

* Add extra debug to debug the debugging

* Unit test fix

* Change scope I guess?

* remove handler test
  • Loading branch information
SchrodingersGat authored Dec 5, 2022
1 parent efb6fc3 commit 730521f
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 134 deletions.
4 changes: 3 additions & 1 deletion assets/release_notes.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
## InvenTree App Release Notes
---

### - December 2022
### 0.9.0 - November 2022
---

- Added support for custom barcodes for Parts
- Added support for custom barcode for Stock Locations
- Support Part parameters
- Add support for structural part categories
- Add support for structural stock locations
Expand Down
25 changes: 25 additions & 0 deletions lib/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ class InvenTreeAPI {
// Notification support requires API v25 or newer
bool get supportsNotifications => isConnected() && apiVersion >= 25;

// Supports 'modern' barcode API (v80 or newer)
bool get supportModernBarcodes => isConnected() && apiVersion >= 80;

// Structural categories requires API v83 or newer
bool get supportsStructuralCategories => isConnected() && apiVersion >= 83;

Expand Down Expand Up @@ -883,6 +886,27 @@ class InvenTreeAPI {
);
}

/*
* Perform a request to link a custom barcode to a particular item
*/
Future<bool> linkBarcode(Map<String, String> body) async {

HttpClientRequest? request = await apiRequest("/barcode/link/", "POST");

if (request == null) {
return false;
}

final response = await completeRequest(
request,
data: json.encode(body),
statusCode: 200
);

return response.isValid() && response.statusCode == 200;

}

/*
* Perform a request to unlink a custom barcode from a particular item
*/
Expand Down Expand Up @@ -1255,6 +1279,7 @@ class InvenTreeAPI {
);
}

// Return True if the API supports 'settings' (requires API v46)
bool get supportsSettings => isConnected() && apiVersion >= 46;

// Keep a record of which settings we have received from the server
Expand Down
67 changes: 59 additions & 8 deletions lib/barcode.dart
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import "dart:io";

import "package:inventree/inventree/sentry.dart";
import "package:inventree/widget/dialogs.dart";
import "package:inventree/widget/snacks.dart";
import "package:flutter/material.dart";
import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:one_context/one_context.dart";

import "package:qr_code_scanner/qr_code_scanner.dart";

import "package:inventree/inventree/stock.dart";
import "package:inventree/inventree/part.dart";
import "package:inventree/app_colors.dart";
import "package:inventree/api.dart";
import "package:inventree/helpers.dart";
import "package:inventree/l10.dart";
import "package:inventree/preferences.dart";

import "package:inventree/inventree/sentry.dart";
import "package:inventree/inventree/stock.dart";
import "package:inventree/inventree/part.dart";

import "package:inventree/widget/dialogs.dart";
import "package:inventree/widget/snacks.dart";
import "package:inventree/widget/location_display.dart";
import "package:inventree/widget/part_detail.dart";
import "package:inventree/widget/stock_detail.dart";
Expand Down Expand Up @@ -117,6 +117,8 @@ class BarcodeHandler {
expectedStatusCode: null, // Do not show an error on "unexpected code"
);

debug("Barcode scan response" + response.data.toString());

_controller?.resumeCamera();

Map<String, dynamic> data = response.asMap();
Expand Down Expand Up @@ -726,8 +728,57 @@ class _QRViewState extends State<InvenTreeQRView> {
}

Future<void> scanQrCode(BuildContext context) async {

Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreeQRView(BarcodeScanHandler())));

return;
}


/*
* Construct a generic ListTile widget to link or un-link a custom barcode from a model.
*/
Widget customBarcodeActionTile(BuildContext context, String barcode, String model, int pk) {

if (barcode.isEmpty) {
return ListTile(
title: Text(L10().barcodeAssign),
subtitle: Text(L10().barcodeAssignDetail),
leading: Icon(Icons.qr_code, color: COLOR_CLICK),
trailing: Icon(Icons.qr_code_scanner),
onTap: () {
var handler = UniqueBarcodeHandler((String barcode) {
InvenTreeAPI().linkBarcode({
model: pk.toString(),
"barcode": barcode,
}).then((bool result) {
showSnackIcon(
result ? L10().barcodeAssigned : L10().barcodeNotAssigned,
success: result
);
});
});

Navigator.push(
context,
MaterialPageRoute(
builder: (context) => InvenTreeQRView(handler)
)
);
}
);
} else {
return ListTile(
title: Text(L10().barcodeUnassign),
leading: Icon(Icons.qr_code, color: COLOR_CLICK),
onTap: () async {
InvenTreeAPI().unlinkBarcode({
model: pk.toString()
}).then((bool result) {
showSnackIcon(
result ? L10().requestSuccessful : L10().requestFailed,
);
});
},
);
}
}
5 changes: 4 additions & 1 deletion lib/helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ List<String> debug_messages = [];

void clearDebugMessage() => debug_messages.clear();

int debugMessageCount() => debug_messages.length;
int debugMessageCount() {
print("Debug Messages: ${debug_messages.length}");
return debug_messages.length;
}

// Check if the debug log contains a given message
bool debugContains(String msg, {bool raiseAssert = true}) {
Expand Down
17 changes: 17 additions & 0 deletions lib/inventree/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,23 @@ class InvenTreeModel {
// Legacy API provided external link as "URL", while newer API uses "link"
String get link => (jsondata["link"] ?? jsondata["URL"] ?? "") as String;

/* Extract any custom barcode data available for the model.
* Note that old API used 'uid' (only for StockItem),
* but this was updated to use 'barcode_hash'
*/
String get customBarcode {
if (jsondata.containsKey("uid")) {
return jsondata["uid"] as String;
} else if (jsondata.containsKey("barcode_hash")) {
return jsondata["barcode_hash"] as String;
} else if (jsondata.containsKey("barcode")) {
return jsondata["barcode"] as String;
}

// Empty string if no match
return "";
}

Future <void> goToInvenTreePage() async {

if (await canLaunch(webUrl)) {
Expand Down
2 changes: 0 additions & 2 deletions lib/inventree/stock.dart
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,6 @@ class InvenTreeStockItem extends InvenTreeModel {
});
}

String get uid => (jsondata["uid"] ?? "") as String;

int get status => (jsondata["status"] ?? -1) as int;

String get packaging => (jsondata["packaging"] ?? "") as String;
Expand Down
3 changes: 3 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,9 @@
"request": "Request",
"@request": {},

"requestFailed": "Request Failed",
"@requestFailed": {},

"requestSuccessful": "Request successful",
"@requestSuccessful": {},

Expand Down
6 changes: 6 additions & 0 deletions lib/widget/location_display.dart
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,12 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
)
);
}

if (InvenTreeAPI().supportModernBarcodes) {
tiles.add(
customBarcodeActionTile(context, location!.customBarcode, "stocklocation", location!.pk)
);
}
}
}

Expand Down
29 changes: 3 additions & 26 deletions lib/widget/part_detail.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart";

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

Expand Down Expand Up @@ -695,35 +696,11 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
)
);

// TODO - Add this action back in once implemented
/*
tiles.add(
ListTile(
title: Text(L10().barcodeScanItem),
leading: FaIcon(FontAwesomeIcons.box),
trailing: Icon(Icons.qr_code),
onTap: () {
// TODO
},
),
);
*/

/*
// TODO: Implement part deletion
if (!part.isActive && InvenTreeAPI().checkPermission("part", "delete")) {
if (InvenTreeAPI().supportModernBarcodes) {
tiles.add(
ListTile(
title: Text(L10().deletePart),
subtitle: Text(L10().deletePartDetail),
leading: FaIcon(FontAwesomeIcons.trashAlt, color: COLOR_DANGER),
onTap: () {
// TODO
},
)
customBarcodeActionTile(context, part.customBarcode, "part", part.pk)
);
}
*/

return tiles;
}
Expand Down
97 changes: 2 additions & 95 deletions lib/widget/stock_detail.dart
Original file line number Diff line number Diff line change
Expand Up @@ -416,48 +416,6 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
);
}


/*
* Unassign (remove) a barcode from a StockItem.
*
* Note that for API version < 76 this action is performed on the StockItem endpoint.
* For API version 76 or above, this uses the barcode "unlink" endpoint
*/
Future<void> _unassignBarcode(BuildContext context) async {

if (InvenTreeAPI().apiVersion < 76) {
final response = await item.update(values: {"uid": ""});

switch (response.statusCode) {
case 200:
case 201:
showSnackIcon(
L10().stockItemUpdateSuccess,
success: true
);
break;
default:
showSnackIcon(
L10().stockItemUpdateFailure,
success: false,
);
break;
}
} else {
final bool result = await InvenTreeAPI().unlinkBarcode({
"stockitem": item.pk,
});

showSnackIcon(
result ? L10().stockItemUpdateSuccess : L10().stockItemUpdateFailure,
success: result,
);
}

refresh(context);
}


/*
* Launches an API Form to transfer this stock item to a new location
*/
Expand Down Expand Up @@ -844,59 +802,8 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
)
);

// Add or remove custom barcode
if (item.uid.isEmpty) {
tiles.add(
ListTile(
title: Text(L10().barcodeAssign),
subtitle: Text(L10().barcodeAssignDetail),
leading: Icon(Icons.qr_code),
trailing: Icon(Icons.qr_code_scanner),
onTap: () {

var handler = UniqueBarcodeHandler((String hash) {
item.update(
values: {
"uid": hash,
}
).then((response) {

switch (response.statusCode) {
case 200:
case 201:
barcodeSuccessTone();

showSnackIcon(
L10().barcodeAssigned,
success: true,
icon: Icons.qr_code,
);

refresh(context);
break;
default:
break;
}
});
});

Navigator.push(
context,
MaterialPageRoute(builder: (context) => InvenTreeQRView(handler))
);
}
)
);
} else {
tiles.add(
ListTile(
title: Text(L10().barcodeUnassign),
leading: Icon(Icons.qr_code, color: COLOR_CLICK),
onTap: () {
_unassignBarcode(context);
}
)
);
if (InvenTreeAPI().supportModernBarcodes) {
tiles.add(customBarcodeActionTile(context, item.customBarcode, "stockitem", item.pk));
}

// Print label (if label printing plugins exist)
Expand Down
Loading

0 comments on commit 730521f

Please sign in to comment.