mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-28 05:26:47 +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?
|
// Does the server support adding line items to a PO using barcodes?
|
||||||
bool get supportsBarcodePOAddLineEndpoint => isConnected() && apiVersion >= 153;
|
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)
|
// Cached list of plugins (refreshed when we connect to the server)
|
||||||
List<InvenTreePlugin> _plugins = [];
|
List<InvenTreePlugin> _plugins = [];
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import "package:flutter/material.dart";
|
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/part.dart";
|
||||||
import "package:inventree/inventree/sales_order.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
|
@override
|
||||||
Map<String, String> defaultGetFilters() {
|
Map<String, String> defaultGetFilters() {
|
||||||
return {
|
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;
|
return actions;
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
||||||
import "package:font_awesome_flutter/font_awesome_flutter.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/part.dart";
|
||||||
import "package:inventree/inventree/sales_order.dart";
|
import "package:inventree/inventree/sales_order.dart";
|
||||||
@ -66,31 +68,17 @@ class _SOLineDetailWidgetState extends RefreshableState<SoLineDetailWidget> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> fields = {
|
var fields = InvenTreeSOLineItem().allocateFormFields();
|
||||||
"line_item": {
|
|
||||||
"parent": "items",
|
fields["line_item"]?["value"] = widget.item.pk.toString();
|
||||||
"nested": true,
|
fields["stock_item"]?["filters"] = {
|
||||||
"hidden": true,
|
"in_stock": true,
|
||||||
"value": widget.item.pk,
|
"available": true,
|
||||||
},
|
"part": widget.item.partId.toString()
|
||||||
"stock_item": {
|
};
|
||||||
"parent": "items",
|
fields["quantity"]?["value"] = widget.item.unallocatedQuantity.toString();
|
||||||
"nested": true,
|
fields["shipment"]?["filters"] = {
|
||||||
"filters": {
|
"order": order!.pk.toString()
|
||||||
"part": widget.item.partId,
|
|
||||||
"in_stock": true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"quantity": {
|
|
||||||
"parent": "items",
|
|
||||||
"nested": true,
|
|
||||||
"value": widget.item.unallocatedQuantity,
|
|
||||||
},
|
|
||||||
"shipment": {
|
|
||||||
"filters": {
|
|
||||||
"order": order!.pk.toString(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
launchApiForm(
|
launchApiForm(
|
||||||
@ -146,6 +134,34 @@ class _SOLineDetailWidgetState extends RefreshableState<SoLineDetailWidget> {
|
|||||||
return buttons;
|
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
|
@override
|
||||||
Future<void> request(BuildContext context) async {
|
Future<void> request(BuildContext context) async {
|
||||||
await widget.item.reload();
|
await widget.item.reload();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user