mirror of
https://github.com/HendrikRauh/inventree-app.git
synced 2026-04-17 16:10:34 +00:00
commit
84e8ccc43a
18 changed files with 257 additions and 48 deletions
14
lib/api.dart
14
lib/api.dart
|
|
@ -411,7 +411,7 @@ class InvenTreeAPI {
|
|||
* 5. Request information on available plugins
|
||||
*/
|
||||
Future<bool> _connectToServer() async {
|
||||
if (!await _checkServer()) {
|
||||
if (!await checkServer()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -451,8 +451,8 @@ class InvenTreeAPI {
|
|||
* Check that the remote server is available.
|
||||
* Ping the api/ endpoint, which does not require user authentication
|
||||
*/
|
||||
Future<bool> _checkServer() async {
|
||||
String address = profile?.server ?? "";
|
||||
Future<bool> checkServer({String? server}) async {
|
||||
String address = server ?? profile?.server ?? "";
|
||||
|
||||
if (address.isEmpty) {
|
||||
showSnackIcon(
|
||||
|
|
@ -463,8 +463,10 @@ class InvenTreeAPI {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!address.endsWith("/")) {
|
||||
address = address + "/";
|
||||
String url = _makeUrl("/api/", base: address);
|
||||
|
||||
if (!url.endsWith("/")) {
|
||||
url = url + "/";
|
||||
}
|
||||
|
||||
// Cache the "strictHttps" setting, so we can use it later without async requirement
|
||||
|
|
@ -474,7 +476,7 @@ class InvenTreeAPI {
|
|||
|
||||
debug("Connecting to ${apiUrl}");
|
||||
|
||||
APIResponse response = await get("", expectedStatusCode: 200);
|
||||
APIResponse response = await get(url, expectedStatusCode: 200);
|
||||
|
||||
if (!response.successful()) {
|
||||
debug("Server returned invalid response: ${response.statusCode}");
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ class APIFormField {
|
|||
return;
|
||||
}
|
||||
|
||||
String url = api_url + "/" + value.toString() + "/";
|
||||
String url = api_url + value.toString() + "/";
|
||||
|
||||
final APIResponse response = await InvenTreeAPI().get(url, params: filters);
|
||||
|
||||
|
|
@ -293,6 +293,8 @@ class APIFormField {
|
|||
return _constructString();
|
||||
case "boolean":
|
||||
return _constructBoolean();
|
||||
case "boolean filter":
|
||||
return _constructBooleanFilter();
|
||||
case "related field":
|
||||
return _constructRelatedField();
|
||||
case "integer":
|
||||
|
|
@ -874,25 +876,109 @@ class APIFormField {
|
|||
|
||||
// Construct a boolean input element
|
||||
Widget _constructBoolean() {
|
||||
bool? initial_value;
|
||||
bool v = false;
|
||||
|
||||
if (value is bool || value == null) {
|
||||
initial_value = value as bool?;
|
||||
if (value is bool) {
|
||||
v = value as bool;
|
||||
} else {
|
||||
String vs = value.toString().toLowerCase();
|
||||
initial_value = ["1", "true", "yes"].contains(vs);
|
||||
v = false;
|
||||
}
|
||||
|
||||
return CheckBoxField(
|
||||
label: label,
|
||||
labelStyle: _labelStyle(),
|
||||
helperText: helpText,
|
||||
helperStyle: _helperStyle(),
|
||||
initial: initial_value,
|
||||
tristate: (getParameter("tristate") ?? false) as bool,
|
||||
onSaved: (val) {
|
||||
setFieldValue(val);
|
||||
},
|
||||
return ListTile(
|
||||
title: Text(label),
|
||||
subtitle: Text(helpText),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
trailing: Switch(
|
||||
value: v,
|
||||
onChanged: (val) {
|
||||
setFieldValue(val);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Construct a tri-state boolean filter element
|
||||
Widget _constructBooleanFilter() {
|
||||
String initial_value = "null";
|
||||
|
||||
bool allow_null = (getParameter("tristate") ?? false) as bool;
|
||||
|
||||
if (value is bool) {
|
||||
initial_value = value.toString().toLowerCase();
|
||||
} else if (value == null) {
|
||||
if (allow_null) {
|
||||
initial_value = "null";
|
||||
} else {
|
||||
initial_value = "false";
|
||||
}
|
||||
} else {
|
||||
// Not a boolean value - may be a string
|
||||
if (["1", "true", "yes"].contains(value.toString().toLowerCase())) {
|
||||
initial_value = "true";
|
||||
} else if ([
|
||||
"0",
|
||||
"false",
|
||||
"no",
|
||||
].contains(value.toString().toLowerCase())) {
|
||||
initial_value = "false";
|
||||
} else if (allow_null) {
|
||||
initial_value = "null";
|
||||
} else {
|
||||
initial_value = "false";
|
||||
}
|
||||
}
|
||||
|
||||
List<ButtonSegment<String>> buttons = [];
|
||||
|
||||
if ((getParameter("tristate") ?? false) as bool) {
|
||||
buttons.add(
|
||||
ButtonSegment<String>(
|
||||
value: "null",
|
||||
icon: Icon(TablerIcons.minus, color: COLOR_GRAY_LIGHT),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
buttons.add(
|
||||
ButtonSegment<String>(
|
||||
value: "false",
|
||||
icon: Icon(TablerIcons.x, color: COLOR_DANGER),
|
||||
),
|
||||
);
|
||||
|
||||
buttons.add(
|
||||
ButtonSegment<String>(
|
||||
value: "true",
|
||||
icon: Icon(TablerIcons.check, color: COLOR_SUCCESS),
|
||||
),
|
||||
);
|
||||
|
||||
return ListTile(
|
||||
title: Text(label),
|
||||
subtitle: Text(helpText),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
trailing: SegmentedButton<String>(
|
||||
segments: buttons,
|
||||
selected: {initial_value},
|
||||
showSelectedIcon: false,
|
||||
multiSelectionEnabled: false,
|
||||
style: SegmentedButton.styleFrom(
|
||||
padding: EdgeInsets.all(0),
|
||||
// minimumSize: MaterialStateProperty.all(Size(0, 0)),
|
||||
// tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
onSelectionChanged: (Set<String> selection) {
|
||||
String element = selection.first;
|
||||
if (element == "null" && allow_null) {
|
||||
setFieldValue(null);
|
||||
} else if (element == "true") {
|
||||
setFieldValue(true);
|
||||
} else {
|
||||
setFieldValue(false);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1168,7 +1254,9 @@ class APIFormWidgetState extends State<APIFormWidget> {
|
|||
// Callback for when a field value is changed
|
||||
// Default implementation does nothing,
|
||||
// but custom form implementations may override this function
|
||||
void onValueChanged(String field, dynamic value) {}
|
||||
void onValueChanged(String field, dynamic value) {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Future<void> handleSuccess(
|
||||
Map<String, dynamic> submittedData,
|
||||
|
|
@ -1394,7 +1482,7 @@ class APIFormWidgetState extends State<APIFormWidget> {
|
|||
|
||||
if (field.isSimple) {
|
||||
// Simple top-level field data
|
||||
data[field.name] = field.data["value"];
|
||||
data[field.name] = field.data["value"] ?? field.defaultValue;
|
||||
} else {
|
||||
// Not so simple... (WHY DID I MAKE THE API SO COMPLEX?)
|
||||
if (field.parent.isNotEmpty) {
|
||||
|
|
|
|||
|
|
@ -154,9 +154,12 @@ class POAllocateBarcodeHandler extends BarcodeHandler {
|
|||
return onBarcodeUnknown(data);
|
||||
}
|
||||
|
||||
// Extract field data from the returned result
|
||||
dynamic supplier_part = data["supplierpart"];
|
||||
dynamic location = data["location"];
|
||||
|
||||
int supplier_part_pk = -1;
|
||||
int location_pk = -1;
|
||||
|
||||
if (supplier_part is Map<String, dynamic>) {
|
||||
supplier_part_pk = (supplier_part["pk"] ?? -1) as int;
|
||||
|
|
@ -164,6 +167,10 @@ class POAllocateBarcodeHandler extends BarcodeHandler {
|
|||
return onBarcodeUnknown(data);
|
||||
}
|
||||
|
||||
if (location is Map<String, dynamic>) {
|
||||
location_pk = (location["pk"] ?? -1) as int;
|
||||
}
|
||||
|
||||
// Dispose of the barcode scanner
|
||||
if (OneContext.hasContext) {
|
||||
OneContext().pop();
|
||||
|
|
@ -177,6 +184,10 @@ class POAllocateBarcodeHandler extends BarcodeHandler {
|
|||
fields["part"]?["hidden"] = false;
|
||||
fields["part"]?["value"] = supplier_part_pk;
|
||||
|
||||
if (location_pk > 0) {
|
||||
fields["location"]?["value"] = location_pk;
|
||||
}
|
||||
|
||||
InvenTreePOLineItem().createForm(
|
||||
context,
|
||||
L10().lineItemAdd,
|
||||
|
|
|
|||
|
|
@ -270,9 +270,9 @@ class InvenTreeSalesOrderShipment extends InvenTreeModel {
|
|||
InvenTreeSalesOrderShipment.fromJson(json);
|
||||
|
||||
@override
|
||||
String get URL => "/order/so/shipment/";
|
||||
String get URL => "order/so/shipment/";
|
||||
|
||||
String get SHIP_SHIPMENT_URL => "/order/so/shipment/${pk}/ship/";
|
||||
String get SHIP_SHIPMENT_URL => "order/so/shipment/${pk}/ship/";
|
||||
|
||||
@override
|
||||
Future<Object?> goToDetailPage(BuildContext context) async {
|
||||
|
|
@ -345,7 +345,7 @@ class InvenTreeSalesOrderAllocation extends InvenTreeModel {
|
|||
InvenTreeSalesOrderAllocation.fromJson(json);
|
||||
|
||||
@override
|
||||
String get URL => "/order/so-allocation/";
|
||||
String get URL => "order/so-allocation/";
|
||||
|
||||
@override
|
||||
List<String> get rolesRequired => ["sales_order"];
|
||||
|
|
|
|||
|
|
@ -294,6 +294,9 @@
|
|||
"confirmScanDetail": "Confirm stock transfer details when scanning barcodes",
|
||||
"@confirmScanDetail": {},
|
||||
|
||||
"connectionCheck": "Check Connection",
|
||||
"@connectionCheck": {},
|
||||
|
||||
"connectionRefused": "Connection Refused",
|
||||
"@connectionRefused": {},
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import "package:inventree/l10.dart";
|
|||
import "package:inventree/widget/progress.dart";
|
||||
import "package:inventree/widget/snacks.dart";
|
||||
|
||||
const String PRINT_LABEL_URL = "api/label/print/";
|
||||
const String PRINT_LABEL_URL = "label/print/";
|
||||
|
||||
/*
|
||||
* Custom form handler for label printing.
|
||||
|
|
@ -45,6 +45,8 @@ class LabelFormWidgetState extends APIFormWidgetState {
|
|||
if (field == "plugin") {
|
||||
onPluginChanged(value.toString());
|
||||
}
|
||||
|
||||
super.onValueChanged(field, value);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -298,6 +298,9 @@ class _ProfileEditState extends State<ProfileEditWidget> {
|
|||
String name = "";
|
||||
String server = "";
|
||||
|
||||
bool? serverStatus;
|
||||
bool serverChecking = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
|
@ -411,6 +414,49 @@ class _ProfileEditState extends State<ProfileEditWidget> {
|
|||
return null;
|
||||
},
|
||||
),
|
||||
Divider(),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
label: Text(L10().connectionCheck),
|
||||
icon: serverStatus == true
|
||||
? Icon(TablerIcons.circle_check, color: COLOR_SUCCESS)
|
||||
: serverStatus == false
|
||||
? Icon(TablerIcons.circle_x, color: COLOR_DANGER)
|
||||
: Icon(TablerIcons.question_mark, color: COLOR_WARNING),
|
||||
onPressed: serverChecking
|
||||
? null
|
||||
: () async {
|
||||
if (serverChecking) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
serverStatus = null;
|
||||
serverChecking = true;
|
||||
});
|
||||
}
|
||||
|
||||
formKey.currentState!.save();
|
||||
|
||||
InvenTreeAPI().checkServer(server: server).then((
|
||||
result,
|
||||
) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
serverStatus = result;
|
||||
serverChecking = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: EdgeInsets.all(16),
|
||||
|
|
|
|||
|
|
@ -92,9 +92,10 @@ abstract class PaginatedSearchState<T extends PaginatedSearchWidget>
|
|||
|
||||
// Skip null values
|
||||
if (value == null) {
|
||||
continue;
|
||||
f[k] = "null";
|
||||
} else {
|
||||
f[k] = value.toString();
|
||||
}
|
||||
f[k] = value.toString();
|
||||
}
|
||||
|
||||
return f;
|
||||
|
|
@ -206,7 +207,7 @@ abstract class PaginatedSearchState<T extends PaginatedSearchWidget>
|
|||
}
|
||||
|
||||
Map<String, dynamic> filter = {
|
||||
"type": "boolean",
|
||||
"type": "boolean filter",
|
||||
"display_name": label,
|
||||
"label": label,
|
||||
"help_text": help_text,
|
||||
|
|
@ -341,7 +342,16 @@ abstract class PaginatedSearchState<T extends PaginatedSearchWidget>
|
|||
Map<String, String> f = await constructFilters();
|
||||
|
||||
if (f.isNotEmpty) {
|
||||
params.addAll(f);
|
||||
for (String k in f.keys) {
|
||||
// Remove any existing filter keys
|
||||
dynamic value = f[k];
|
||||
|
||||
if (value == null || value == "null") {
|
||||
params.remove(k);
|
||||
} else {
|
||||
params[k] = value.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final page = await requestPage(_pageSize, pageKey, params);
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class _PaginatedPartCategoryListState
|
|||
@override
|
||||
Map<String, Map<String, dynamic>> get filterOptions => {
|
||||
"cascade": {
|
||||
"default": false,
|
||||
"default": true,
|
||||
"label": L10().includeSubcategories,
|
||||
"help_text": L10().includeSubcategoriesDetail,
|
||||
"tristate": false,
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ class _PaginatedStockLocationListState
|
|||
"label": L10().includeSublocations,
|
||||
"help_text": L10().includeSublocationsDetail,
|
||||
"tristate": false,
|
||||
"default": true,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||
// Request part information
|
||||
part = await InvenTreePart().get(widget.item.partId) as InvenTreePart?;
|
||||
|
||||
stockShowTests &= part?.isTrackable ?? false;
|
||||
stockShowTests &= part?.isTestable ?? false;
|
||||
|
||||
// Request default location
|
||||
int? defaultLocationId = part?.defaultLocation;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue