mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-27 21:16:48 +00:00
Sales order barcode (#466)
* Add barcode handler for allocating stock to sales order * Refactor sales order allocation fields * Improve barcode handling * Handle barcode scan from sales order line detail view * Remove debug statements
This commit is contained in:
parent
edde9a9585
commit
571b491846
@ -336,6 +336,9 @@ class InvenTreeAPI {
|
||||
// Does the server support adding line items to a PO using barcodes?
|
||||
bool get supportsBarcodePOAddLineEndpoint => isConnected() && apiVersion >= 153;
|
||||
|
||||
// Does the server support allocating stock to sales order using barcodes?
|
||||
bool get supportsBarcodeSOAllocateEndpoint => isConnected() && apiVersion >= 160;
|
||||
|
||||
// Cached list of plugins (refreshed when we connect to the server)
|
||||
List<InvenTreePlugin> _plugins = [];
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
import "package:flutter/material.dart";
|
||||
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||
import "package:inventree/api_form.dart";
|
||||
|
||||
import "package:inventree/inventree/part.dart";
|
||||
import "package:inventree/inventree/sales_order.dart";
|
||||
@ -77,5 +79,98 @@ class SOAddItemBarcodeHandler extends BarcodeHandler {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SOAllocateStockHandler extends BarcodeHandler {
|
||||
|
||||
SOAllocateStockHandler({this.salesOrder, this.lineItem, this.shipment});
|
||||
|
||||
InvenTreeSalesOrder? salesOrder;
|
||||
InvenTreeSOLineItem? lineItem;
|
||||
InvenTreeSalesOrderShipment? shipment;
|
||||
|
||||
@override
|
||||
String getOverlayText(BuildContext context) => L10().allocateStock;
|
||||
|
||||
@override
|
||||
Future<void> processBarcode(String barcode,
|
||||
{
|
||||
String url = "barcode/so-allocate/",
|
||||
Map<String, dynamic> extra_data = const {}}) {
|
||||
|
||||
final so_extra_data = {
|
||||
"sales_order": salesOrder?.pk,
|
||||
"shipment": shipment?.pk,
|
||||
"line": lineItem?.pk,
|
||||
...extra_data
|
||||
};
|
||||
|
||||
return super.processBarcode(barcode, url: url, extra_data: so_extra_data);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
|
||||
if (!data.containsKey("line_item")) {
|
||||
return onBarcodeUnknown(data);
|
||||
}
|
||||
|
||||
barcodeSuccessTone();
|
||||
showSnackIcon(L10().allocated, success: true);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onBarcodeUnhandled(Map<String, dynamic> data) async {
|
||||
|
||||
if (!data.containsKey("action_required") || !data.containsKey("line_item")) {
|
||||
return super.onBarcodeUnhandled(data);
|
||||
}
|
||||
|
||||
// Prompt user for extra information to create the allocation
|
||||
var fields = InvenTreeSOLineItem().allocateFormFields();
|
||||
|
||||
// Update fields with data gathered from the API response
|
||||
fields["line_item"]?["value"] = data["line_item"];
|
||||
|
||||
Map<String, dynamic> stock_filters = {
|
||||
"in_stock": true,
|
||||
"available": true,
|
||||
};
|
||||
|
||||
if (data.containsKey("part")) {
|
||||
stock_filters["part"] = data["part"];
|
||||
}
|
||||
|
||||
fields["stock_item"]?["filters"] = stock_filters;
|
||||
fields["stock_item"]?["value"] = data["stock_item"];
|
||||
|
||||
fields["quantity"]?["value"] = data["quantity"];
|
||||
|
||||
fields["shipment"]?["value"] = data["shipment"];
|
||||
fields["shipment"]?["filters"] = {
|
||||
"order": salesOrder!.pk.toString()
|
||||
};
|
||||
|
||||
final context = OneContext().context!;
|
||||
|
||||
launchApiForm(
|
||||
context,
|
||||
L10().allocateStock,
|
||||
salesOrder!.allocate_url,
|
||||
fields,
|
||||
method: "POST",
|
||||
icon: FontAwesomeIcons.rightToBracket,
|
||||
onSuccess: (data) async {
|
||||
showSnackIcon(L10().allocated, success: true);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onBarcodeUnknown(Map<String, dynamic> data) async {
|
||||
barcodeFailureTone();
|
||||
showSnackIcon(
|
||||
data["error"] as String? ?? L10().barcodeError,
|
||||
success: false
|
||||
);
|
||||
}
|
||||
}
|
@ -132,6 +132,29 @@ class InvenTreeSOLineItem extends InvenTreeOrderLine {
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, Map<String, dynamic>> allocateFormFields() {
|
||||
|
||||
return {
|
||||
"line_item": {
|
||||
"parent": "items",
|
||||
"nested": true,
|
||||
"hidden": true,
|
||||
},
|
||||
"stock_item": {
|
||||
"parent": "items",
|
||||
"nested": true,
|
||||
"filters": {},
|
||||
},
|
||||
"quantity": {
|
||||
"parent": "items",
|
||||
"nested": true,
|
||||
},
|
||||
"shipment": {
|
||||
"filters": {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, String> defaultGetFilters() {
|
||||
return {
|
||||
|
@ -146,6 +146,23 @@ class _SalesOrderDetailState extends RefreshableState<SalesOrderDetailWidget> {
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if (api.supportsBarcodeSOAllocateEndpoint) {
|
||||
actions.add(
|
||||
SpeedDialChild(
|
||||
child: FaIcon(FontAwesomeIcons.rightToBracket),
|
||||
label: L10().allocateStock,
|
||||
onTap: () async {
|
||||
scanBarcode(
|
||||
context,
|
||||
handler: SOAllocateStockHandler(
|
||||
salesOrder: widget.order,
|
||||
)
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
|
@ -7,6 +7,8 @@
|
||||
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/barcode/barcode.dart";
|
||||
import "package:inventree/barcode/sales_order.dart";
|
||||
|
||||
import "package:inventree/inventree/part.dart";
|
||||
import "package:inventree/inventree/sales_order.dart";
|
||||
@ -66,31 +68,17 @@ class _SOLineDetailWidgetState extends RefreshableState<SoLineDetailWidget> {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, dynamic> fields = {
|
||||
"line_item": {
|
||||
"parent": "items",
|
||||
"nested": true,
|
||||
"hidden": true,
|
||||
"value": widget.item.pk,
|
||||
},
|
||||
"stock_item": {
|
||||
"parent": "items",
|
||||
"nested": true,
|
||||
"filters": {
|
||||
"part": widget.item.partId,
|
||||
"in_stock": true,
|
||||
}
|
||||
},
|
||||
"quantity": {
|
||||
"parent": "items",
|
||||
"nested": true,
|
||||
"value": widget.item.unallocatedQuantity,
|
||||
},
|
||||
"shipment": {
|
||||
"filters": {
|
||||
"order": order!.pk.toString(),
|
||||
}
|
||||
},
|
||||
var fields = InvenTreeSOLineItem().allocateFormFields();
|
||||
|
||||
fields["line_item"]?["value"] = widget.item.pk.toString();
|
||||
fields["stock_item"]?["filters"] = {
|
||||
"in_stock": true,
|
||||
"available": true,
|
||||
"part": widget.item.partId.toString()
|
||||
};
|
||||
fields["quantity"]?["value"] = widget.item.unallocatedQuantity.toString();
|
||||
fields["shipment"]?["filters"] = {
|
||||
"order": order!.pk.toString()
|
||||
};
|
||||
|
||||
launchApiForm(
|
||||
@ -146,6 +134,34 @@ class _SOLineDetailWidgetState extends RefreshableState<SoLineDetailWidget> {
|
||||
return buttons;
|
||||
}
|
||||
|
||||
@override
|
||||
List<SpeedDialChild> barcodeButtons(BuildContext context) {
|
||||
List<SpeedDialChild> actions = [];
|
||||
|
||||
if (order != null && order!.isOpen && InvenTreeSOLineItem().canCreate) {
|
||||
|
||||
if (api.supportsBarcodeSOAllocateEndpoint) {
|
||||
actions.add(
|
||||
SpeedDialChild(
|
||||
child: FaIcon(FontAwesomeIcons.rightToBracket),
|
||||
label: L10().allocateStock,
|
||||
onTap: () async {
|
||||
scanBarcode(
|
||||
context,
|
||||
handler: SOAllocateStockHandler(
|
||||
salesOrder: order,
|
||||
lineItem: widget.item
|
||||
)
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> request(BuildContext context) async {
|
||||
await widget.item.reload();
|
||||
|
Loading…
x
Reference in New Issue
Block a user