mirror of
https://github.com/HendrikRauh/inventree-app.git
synced 2026-04-19 00:41:48 +00:00
Compare commits
No commits in common. "6380930ac7a5ed4db128f5e1490f583635a69224" and "ae457e82355d939f9388c2c4fbdf9a6f05c6e60e" have entirely different histories.
6380930ac7
...
ae457e8235
21 changed files with 147 additions and 864 deletions
9
.envrc
9
.envrc
|
|
@ -1,9 +0,0 @@
|
||||||
# When nix is available, use the flake for environment management
|
|
||||||
if command -v nix &> /dev/null; then
|
|
||||||
if type use_flake &> /dev/null; then
|
|
||||||
use flake
|
|
||||||
else
|
|
||||||
echo "Falling back to nix develop (nix-direnv not available)"
|
|
||||||
eval "$(nix develop --print-env 2>/dev/null || echo 'echo Failed to load nix environment')"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,5 +1,4 @@
|
||||||
# Miscellaneous
|
# Miscellaneous
|
||||||
*.bak
|
|
||||||
*.class
|
*.class
|
||||||
*.log
|
*.log
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
@ -7,7 +6,6 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.atom/
|
.atom/
|
||||||
.buildlog/
|
.buildlog/
|
||||||
.direnv/
|
|
||||||
.history
|
.history
|
||||||
.svn/
|
.svn/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,9 @@
|
||||||
## 0.22.1 - February 2026
|
### x.xx.x - Month Year
|
||||||
---
|
---
|
||||||
|
|
||||||
- Fixes bug related to fetching images from remote URLs
|
|
||||||
|
|
||||||
### 0.22.0 - February 2026
|
|
||||||
---
|
|
||||||
|
|
||||||
- Display overall part requirements on part detail view
|
|
||||||
- Support display of custom status codes
|
- Support display of custom status codes
|
||||||
- Fix default values for list sorting
|
- Fix default values for list sorting
|
||||||
- Fix bug related to null values in list filters
|
|
||||||
- Updated translations
|
|
||||||
|
|
||||||
### 0.21.2 - January 2026
|
### 0.21.2 - January 2026
|
||||||
---
|
---
|
||||||
|
|
|
||||||
78
flake.lock
generated
78
flake.lock
generated
|
|
@ -1,78 +0,0 @@
|
||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"flake-utils": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1731533236,
|
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1769170682,
|
|
||||||
"narHash": "sha256-oMmN1lVQU0F0W2k6OI3bgdzp2YOHWYUAw79qzDSjenU=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "c5296fdd05cfa2c187990dd909864da9658df755",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs-flutter": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1751285371,
|
|
||||||
"narHash": "sha256-/hDU+2AUeFFu5qGHO/UyFMc4UG/x5Cw5uXO36KGTk6c=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "b9c03fbbaf84d85bb28eee530c7e9edc4021ca1b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "b9c03fbbaf84d85bb28eee530c7e9edc4021ca1b",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nixpkgs": "nixpkgs",
|
|
||||||
"nixpkgs-flutter": "nixpkgs-flutter"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
||||||
113
flake.nix
113
flake.nix
|
|
@ -1,113 +0,0 @@
|
||||||
{
|
|
||||||
description = "InvenTree App Development Environment";
|
|
||||||
|
|
||||||
inputs = {
|
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
|
||||||
nixpkgs-flutter.url = "github:NixOS/nixpkgs/b9c03fbbaf84d85bb28eee530c7e9edc4021ca1b";
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs =
|
|
||||||
{
|
|
||||||
self,
|
|
||||||
nixpkgs,
|
|
||||||
flake-utils,
|
|
||||||
nixpkgs-flutter,
|
|
||||||
}:
|
|
||||||
flake-utils.lib.eachDefaultSystem (
|
|
||||||
system:
|
|
||||||
let
|
|
||||||
pkgs = import nixpkgs {
|
|
||||||
inherit system;
|
|
||||||
config = {
|
|
||||||
allowUnfree = true;
|
|
||||||
android_sdk.accept_license = true;
|
|
||||||
};
|
|
||||||
overlays = [
|
|
||||||
(final: prev: {
|
|
||||||
flutter-pkg = import (self.inputs.nixpkgs-flutter) { inherit system; };
|
|
||||||
})
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
androidSdk =
|
|
||||||
(pkgs.androidenv.composeAndroidPackages {
|
|
||||||
platformVersions = [
|
|
||||||
"35"
|
|
||||||
"34"
|
|
||||||
];
|
|
||||||
buildToolsVersions = [
|
|
||||||
"35.0.0"
|
|
||||||
"34.0.0"
|
|
||||||
];
|
|
||||||
includeNDK = true;
|
|
||||||
ndkVersions = [ "26.1.10909125" ];
|
|
||||||
cmakeVersions = [ "3.22.1" ];
|
|
||||||
}).androidsdk;
|
|
||||||
|
|
||||||
linuxOptionals = with pkgs; [
|
|
||||||
gtk3
|
|
||||||
glib
|
|
||||||
pcre
|
|
||||||
libepoxy
|
|
||||||
libxkbcommon
|
|
||||||
dbus
|
|
||||||
at-spi2-core
|
|
||||||
file
|
|
||||||
];
|
|
||||||
|
|
||||||
fvmScript = ''
|
|
||||||
case "$1" in
|
|
||||||
use)
|
|
||||||
echo "✓ Using Flutter from Nix ($(flutter --version | head -n1))"
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
flutter)
|
|
||||||
shift
|
|
||||||
exec flutter "$@"
|
|
||||||
;;
|
|
||||||
dart)
|
|
||||||
shift
|
|
||||||
exec dart "$@"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "fvm wrapper: command '$1' not implemented (using Nix-managed Flutter)" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
'';
|
|
||||||
fvm-wrapper = pkgs.writeShellScriptBin "fvm" fvmScript;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
devShells.default = pkgs.mkShell {
|
|
||||||
buildInputs =
|
|
||||||
with pkgs;
|
|
||||||
[
|
|
||||||
flutter-pkg.flutter
|
|
||||||
fvm-wrapper
|
|
||||||
jdk17
|
|
||||||
android-tools
|
|
||||||
gradle
|
|
||||||
python3
|
|
||||||
python3Packages.invoke
|
|
||||||
jq
|
|
||||||
git
|
|
||||||
curl
|
|
||||||
unzip
|
|
||||||
which
|
|
||||||
]
|
|
||||||
++ lib.optionals stdenv.isLinux linuxOptionals;
|
|
||||||
|
|
||||||
shellHook = ''
|
|
||||||
export ANDROID_HOME="${androidSdk}/libexec/android-sdk"
|
|
||||||
export ANDROID_SDK_ROOT="$ANDROID_HOME"
|
|
||||||
export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools/bin:$PATH"
|
|
||||||
export JAVA_HOME="${pkgs.jdk17}"
|
|
||||||
export GRADLE_OPTS="-Dorg.gradle.project.android.aapt2FromMavenOverride=$ANDROID_HOME/build-tools/35.0.0/aapt2"
|
|
||||||
'';
|
|
||||||
|
|
||||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath (with pkgs; linuxOptionals);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
73
lib/api.dart
73
lib/api.dart
|
|
@ -163,7 +163,6 @@ class InvenTreeFileService extends FileService {
|
||||||
*
|
*
|
||||||
* InvenTree implements token-based authentication, which is
|
* InvenTree implements token-based authentication, which is
|
||||||
* initialised using a username:password combination.
|
* initialised using a username:password combination.
|
||||||
* Alternatively, an existing token provided by the user can be used for authentication.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -214,27 +213,30 @@ class InvenTreeAPI {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve a relative or absolute URL
|
String _makeUrl(String url) {
|
||||||
String _makeUrl(String url, {String base = ""}) {
|
// Strip leading slash
|
||||||
final baseUri = Uri.parse(base.isNotEmpty ? base : baseUrl);
|
if (url.startsWith("/")) {
|
||||||
final pathUri = Uri.parse(url);
|
url = url.substring(1, url.length);
|
||||||
|
|
||||||
// If path is absolute (has scheme), ignore base
|
|
||||||
if (pathUri.hasScheme) {
|
|
||||||
return pathUri.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseUri.resolveUri(pathUri).toString();
|
// Prevent double-slash
|
||||||
|
url = url.replaceAll("//", "/");
|
||||||
|
|
||||||
|
return baseUrl + url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get apiUrl => _makeUrl("/api/");
|
||||||
|
|
||||||
|
String get imageUrl => _makeUrl("/image/");
|
||||||
|
|
||||||
String makeApiUrl(String endpoint) {
|
String makeApiUrl(String endpoint) {
|
||||||
String apiBase = makeUrl("/api/");
|
if (endpoint.startsWith("/api/") || endpoint.startsWith("api/")) {
|
||||||
|
return _makeUrl(endpoint);
|
||||||
return _makeUrl(endpoint, base: apiBase);
|
} else {
|
||||||
|
return _makeUrl("/api/${endpoint}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String get apiUrl => makeApiUrl("");
|
|
||||||
|
|
||||||
String makeUrl(String endpoint) => _makeUrl(endpoint);
|
String makeUrl(String endpoint) => _makeUrl(endpoint);
|
||||||
|
|
||||||
UserProfile? profile;
|
UserProfile? profile;
|
||||||
|
|
@ -351,9 +353,6 @@ class InvenTreeAPI {
|
||||||
// Supports separate search against "supplier" / "customer" / "manufacturer"
|
// Supports separate search against "supplier" / "customer" / "manufacturer"
|
||||||
bool get supportsSplitCompanySearch => apiVersion >= 315;
|
bool get supportsSplitCompanySearch => apiVersion >= 315;
|
||||||
|
|
||||||
// Supports "requirements" information for specific part
|
|
||||||
bool get supportsPartRequirements => apiVersion >= 350;
|
|
||||||
|
|
||||||
// Does the server support the "modern" (consolidated) parameter API?
|
// Does the server support the "modern" (consolidated) parameter API?
|
||||||
// Ref: https://github.com/inventree/InvenTree/pull/10699
|
// Ref: https://github.com/inventree/InvenTree/pull/10699
|
||||||
bool get supportsModernParameters => apiVersion >= 429;
|
bool get supportsModernParameters => apiVersion >= 429;
|
||||||
|
|
@ -541,40 +540,6 @@ class InvenTreeAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<APIResponse> checkToken(UserProfile userProfile, String token) async {
|
|
||||||
debug("Checking token @ ${_URL_ME}");
|
|
||||||
|
|
||||||
userProfile.token = token;
|
|
||||||
profile = userProfile;
|
|
||||||
|
|
||||||
final response = await get(_URL_ME);
|
|
||||||
|
|
||||||
if (!response.successful()) {
|
|
||||||
switch (response.statusCode) {
|
|
||||||
case 401:
|
|
||||||
case 403:
|
|
||||||
showServerError(
|
|
||||||
apiUrl,
|
|
||||||
L10().serverAuthenticationError,
|
|
||||||
L10().invalidToken,
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
showStatusCodeError(apiUrl, response.statusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
debug("Request failed: STATUS ${response.statusCode}");
|
|
||||||
|
|
||||||
// reset token
|
|
||||||
userProfile.token = "";
|
|
||||||
profile = userProfile;
|
|
||||||
} else {
|
|
||||||
// save token
|
|
||||||
await UserProfileDBManager().updateProfile(userProfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fetch a token from the server,
|
* Fetch a token from the server,
|
||||||
* with a temporary authentication header
|
* with a temporary authentication header
|
||||||
|
|
@ -1175,7 +1140,7 @@ class InvenTreeAPI {
|
||||||
* Perform a request to link a custom barcode to a particular item
|
* Perform a request to link a custom barcode to a particular item
|
||||||
*/
|
*/
|
||||||
Future<bool> linkBarcode(Map<String, String> body) async {
|
Future<bool> linkBarcode(Map<String, String> body) async {
|
||||||
HttpClientRequest? request = await apiRequest("barcode/link/", "POST");
|
HttpClientRequest? request = await apiRequest("/barcode/link/", "POST");
|
||||||
|
|
||||||
if (request == null) {
|
if (request == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1194,7 +1159,7 @@ class InvenTreeAPI {
|
||||||
* Perform a request to unlink a custom barcode from a particular item
|
* Perform a request to unlink a custom barcode from a particular item
|
||||||
*/
|
*/
|
||||||
Future<bool> unlinkBarcode(Map<String, dynamic> body) async {
|
Future<bool> unlinkBarcode(Map<String, dynamic> body) async {
|
||||||
HttpClientRequest? request = await apiRequest("barcode/unlink/", "POST");
|
HttpClientRequest? request = await apiRequest("/barcode/unlink/", "POST");
|
||||||
|
|
||||||
if (request == null) {
|
if (request == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -214,27 +214,6 @@ class InvenTreePart extends InvenTreeModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request requirements information for this part
|
|
||||||
Future<InvenTreePartRequirements?> getRequirements() async {
|
|
||||||
try {
|
|
||||||
final response = await InvenTreeAPI().get(
|
|
||||||
"/api/part/${pk}/requirements/",
|
|
||||||
);
|
|
||||||
if (response.isValid()) {
|
|
||||||
final requirementsData = response.data;
|
|
||||||
|
|
||||||
if (requirementsData is Map<String, dynamic>) {
|
|
||||||
return InvenTreePartRequirements.fromJson(requirementsData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
print("Exception while fetching requirements data for part $pk: $e");
|
|
||||||
sentryReportError("getRequirements", e, stackTrace);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request pricing data for this part
|
// Request pricing data for this part
|
||||||
Future<InvenTreePartPricing?> getPricing() async {
|
Future<InvenTreePartPricing?> getPricing() async {
|
||||||
try {
|
try {
|
||||||
|
|
@ -458,43 +437,6 @@ class InvenTreePart extends InvenTreeModel {
|
||||||
InvenTreePart.fromJson(json);
|
InvenTreePart.fromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Class representing requirements information for a given Part instance.
|
|
||||||
*/
|
|
||||||
class InvenTreePartRequirements extends InvenTreeModel {
|
|
||||||
InvenTreePartRequirements() : super();
|
|
||||||
|
|
||||||
InvenTreePartRequirements.fromJson(Map<String, dynamic> json)
|
|
||||||
: super.fromJson(json);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<String> get rolesRequired => ["part"];
|
|
||||||
|
|
||||||
@override
|
|
||||||
InvenTreeModel createFromJson(Map<String, dynamic> json) =>
|
|
||||||
InvenTreePartRequirements.fromJson(json);
|
|
||||||
|
|
||||||
// Data accessors
|
|
||||||
double get canBuild => getDouble("can_build");
|
|
||||||
|
|
||||||
double get ordering => getDouble("ordering");
|
|
||||||
|
|
||||||
double get building => getDouble("building");
|
|
||||||
|
|
||||||
double get scheduledToBuild => getDouble("scheduled_to_build");
|
|
||||||
|
|
||||||
double get requiredForBuildOrders => getDouble("required_for_build_orders");
|
|
||||||
|
|
||||||
double get allocatedToBuildOrders => getDouble("allocated_to_build_orders");
|
|
||||||
|
|
||||||
double get requiredForSalesOrders => getDouble("required_for_sales_orders");
|
|
||||||
|
|
||||||
double get allocatedToSalesOrders => getDouble("allocated_to_sales_orders");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Class representing pricing information for a given Part instance.
|
|
||||||
*/
|
|
||||||
class InvenTreePartPricing extends InvenTreeModel {
|
class InvenTreePartPricing extends InvenTreeModel {
|
||||||
InvenTreePartPricing() : super();
|
InvenTreePartPricing() : super();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -534,19 +534,19 @@ class InvenTreeStockItem extends InvenTreeModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> countStock(double q, {String? notes}) async {
|
Future<bool> countStock(double q, {String? notes}) async {
|
||||||
final bool result = await adjustStock("stock/count/", q, notes: notes);
|
final bool result = await adjustStock("/stock/count/", q, notes: notes);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> addStock(double q, {String? notes}) async {
|
Future<bool> addStock(double q, {String? notes}) async {
|
||||||
final bool result = await adjustStock("stock/add/", q, notes: notes);
|
final bool result = await adjustStock("/stock/add/", q, notes: notes);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> removeStock(double q, {String? notes}) async {
|
Future<bool> removeStock(double q, {String? notes}) async {
|
||||||
final bool result = await adjustStock("stock/remove/", q, notes: notes);
|
final bool result = await adjustStock("/stock/remove/", q, notes: notes);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -563,7 +563,7 @@ class InvenTreeStockItem extends InvenTreeModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
final bool result = await adjustStock(
|
final bool result = await adjustStock(
|
||||||
"stock/transfer/",
|
"/stock/transfer/",
|
||||||
q,
|
q,
|
||||||
notes: notes,
|
notes: notes,
|
||||||
location: location,
|
location: location,
|
||||||
|
|
|
||||||
|
|
@ -68,12 +68,6 @@
|
||||||
"allocatedStock": "Allocated Stock",
|
"allocatedStock": "Allocated Stock",
|
||||||
"@allocatedStock": {},
|
"@allocatedStock": {},
|
||||||
|
|
||||||
"allocatedToBuildOrders": "Allocated to Build Orders",
|
|
||||||
"@allocatedToBuildOrders": {},
|
|
||||||
|
|
||||||
"allocatedToSalesOrders": "Allocated to Sales Orders",
|
|
||||||
"@allocatedToSalesOrders": {},
|
|
||||||
|
|
||||||
"appReleaseNotes": "Display app release notes",
|
"appReleaseNotes": "Display app release notes",
|
||||||
"@appReleaseNotes": {},
|
"@appReleaseNotes": {},
|
||||||
|
|
||||||
|
|
@ -232,12 +226,6 @@
|
||||||
"cameraInternalDetail": "Use internal camera to read barcodes",
|
"cameraInternalDetail": "Use internal camera to read barcodes",
|
||||||
"@cameraInternalDetail": {},
|
"@cameraInternalDetail": {},
|
||||||
|
|
||||||
"canBuild": "Can Build",
|
|
||||||
"@canBuild": {},
|
|
||||||
|
|
||||||
"canBuildDetail": "Can be produced with current stock",
|
|
||||||
"@canBuildDetail": {},
|
|
||||||
|
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"@cancel": {
|
"@cancel": {
|
||||||
"description": "Cancel"
|
"description": "Cancel"
|
||||||
|
|
@ -430,9 +418,6 @@
|
||||||
"enterPassword": "Enter password",
|
"enterPassword": "Enter password",
|
||||||
"@enterPassword": {},
|
"@enterPassword": {},
|
||||||
|
|
||||||
"enterToken": "Enter API token",
|
|
||||||
"@enterToken": {},
|
|
||||||
|
|
||||||
"enterUsername": "Enter username",
|
"enterUsername": "Enter username",
|
||||||
"@enterUsername": {},
|
"@enterUsername": {},
|
||||||
|
|
||||||
|
|
@ -680,9 +665,6 @@
|
||||||
"invalidSupplierPart": "Invalid Supplier Part",
|
"invalidSupplierPart": "Invalid Supplier Part",
|
||||||
"@invalidSupplierPart": {},
|
"@invalidSupplierPart": {},
|
||||||
|
|
||||||
"invalidToken": "Invalid token",
|
|
||||||
"@invalidToken": {},
|
|
||||||
|
|
||||||
"invalidUsernamePassword": "Invalid username / password combination",
|
"invalidUsernamePassword": "Invalid username / password combination",
|
||||||
"@invalidUsernamePassword": {},
|
"@invalidUsernamePassword": {},
|
||||||
|
|
||||||
|
|
@ -788,9 +770,6 @@
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"@login": {},
|
"@login": {},
|
||||||
|
|
||||||
"loginMethod": "Login method",
|
|
||||||
"@loginMethod": {},
|
|
||||||
|
|
||||||
"loginEnter": "Enter login details",
|
"loginEnter": "Enter login details",
|
||||||
"@loginEnter": {},
|
"@loginEnter": {},
|
||||||
|
|
||||||
|
|
@ -957,12 +936,6 @@
|
||||||
"partPricingSettingDetail": "Display part pricing information",
|
"partPricingSettingDetail": "Display part pricing information",
|
||||||
"@pricingSettingDetail": {},
|
"@pricingSettingDetail": {},
|
||||||
|
|
||||||
"partRequirements": "Part Requirements",
|
|
||||||
"@partRequirements": {},
|
|
||||||
|
|
||||||
"partRequirementsSettingDetail": "Display part requirements",
|
|
||||||
"@partRequirementsSettingDetail": {},
|
|
||||||
|
|
||||||
"partSettings": "Part Settings",
|
"partSettings": "Part Settings",
|
||||||
"@partSettings": {},
|
"@partSettings": {},
|
||||||
|
|
||||||
|
|
@ -1562,9 +1535,6 @@
|
||||||
"stockLocations": "Stock Locations",
|
"stockLocations": "Stock Locations",
|
||||||
"@stockLocations": {},
|
"@stockLocations": {},
|
||||||
|
|
||||||
"stockSettings": "Stock Settings",
|
|
||||||
"@stockSettings": {},
|
|
||||||
|
|
||||||
"stockTopLevel": "Top level stock location",
|
"stockTopLevel": "Top level stock location",
|
||||||
"@stockTopLevel": {},
|
"@stockTopLevel": {},
|
||||||
|
|
||||||
|
|
@ -1674,12 +1644,6 @@
|
||||||
"toggleTorch": "Toggle Torch",
|
"toggleTorch": "Toggle Torch",
|
||||||
"@toggleTorch": {},
|
"@toggleTorch": {},
|
||||||
|
|
||||||
"token": "Token",
|
|
||||||
"@token": {},
|
|
||||||
|
|
||||||
"tokenEmpty": "Token cannot be empty",
|
|
||||||
"@tokenEmpty": {},
|
|
||||||
|
|
||||||
"tokenError": "Token Error",
|
"tokenError": "Token Error",
|
||||||
"@tokenError": {},
|
"@tokenError": {},
|
||||||
|
|
||||||
|
|
@ -1756,9 +1720,6 @@
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"@username": {},
|
"@username": {},
|
||||||
|
|
||||||
"usernameAndPassword": "Username & Password",
|
|
||||||
"@usernameAndPassword": {},
|
|
||||||
|
|
||||||
"usernameEmpty": "Username cannot be empty",
|
"usernameEmpty": "Username cannot be empty",
|
||||||
"@usernameEmpty": {},
|
"@usernameEmpty": {},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ const String INV_LABEL_DEFAULT_PLUGIN = "defaultLabelPlugin";
|
||||||
// Part settings
|
// Part settings
|
||||||
const String INV_PART_SHOW_BOM = "partShowBom";
|
const String INV_PART_SHOW_BOM = "partShowBom";
|
||||||
const String INV_PART_SHOW_PRICING = "partShowPricing";
|
const String INV_PART_SHOW_PRICING = "partShowPricing";
|
||||||
const String INV_PART_SHOW_REQUIREMENTS = "partShowRequirements";
|
|
||||||
|
|
||||||
// Stock settings
|
// Stock settings
|
||||||
const String INV_STOCK_SHOW_HISTORY = "stockShowHistory";
|
const String INV_STOCK_SHOW_HISTORY = "stockShowHistory";
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ import "package:inventree/api.dart";
|
||||||
import "package:inventree/widget/dialogs.dart";
|
import "package:inventree/widget/dialogs.dart";
|
||||||
import "package:inventree/widget/progress.dart";
|
import "package:inventree/widget/progress.dart";
|
||||||
|
|
||||||
enum LoginMethod { credentials, token }
|
|
||||||
|
|
||||||
class InvenTreeLoginWidget extends StatefulWidget {
|
class InvenTreeLoginWidget extends StatefulWidget {
|
||||||
const InvenTreeLoginWidget(this.profile) : super();
|
const InvenTreeLoginWidget(this.profile) : super();
|
||||||
|
|
||||||
|
|
@ -24,12 +22,9 @@ class _InvenTreeLoginState extends State<InvenTreeLoginWidget> {
|
||||||
|
|
||||||
String username = "";
|
String username = "";
|
||||||
String password = "";
|
String password = "";
|
||||||
String token = "";
|
|
||||||
|
|
||||||
bool _obscured = true;
|
bool _obscured = true;
|
||||||
|
|
||||||
LoginMethod _method = LoginMethod.credentials;
|
|
||||||
|
|
||||||
String error = "";
|
String error = "";
|
||||||
|
|
||||||
// Attempt login
|
// Attempt login
|
||||||
|
|
@ -49,19 +44,12 @@ class _InvenTreeLoginState extends State<InvenTreeLoginWidget> {
|
||||||
|
|
||||||
showLoadingOverlay();
|
showLoadingOverlay();
|
||||||
|
|
||||||
final APIResponse response;
|
// Attempt login
|
||||||
|
final response = await InvenTreeAPI().fetchToken(
|
||||||
if (_method == LoginMethod.credentials) {
|
widget.profile,
|
||||||
// Attempt login
|
username,
|
||||||
response = await InvenTreeAPI().fetchToken(
|
password,
|
||||||
widget.profile,
|
);
|
||||||
username,
|
|
||||||
password,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Check token validity
|
|
||||||
response = await InvenTreeAPI().checkToken(widget.profile, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
hideLoadingOverlay();
|
hideLoadingOverlay();
|
||||||
|
|
||||||
|
|
@ -135,120 +123,55 @@ class _InvenTreeLoginState extends State<InvenTreeLoginWidget> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
...before,
|
...before,
|
||||||
// Dropdown to select login method
|
TextFormField(
|
||||||
Padding(
|
decoration: InputDecoration(
|
||||||
padding: EdgeInsets.symmetric(vertical: 8),
|
labelText: L10().username,
|
||||||
child: DropdownButtonFormField<LoginMethod>(
|
labelStyle: TextStyle(fontWeight: FontWeight.bold),
|
||||||
value: _method,
|
hintText: L10().enterUsername,
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: L10().loginMethod,
|
|
||||||
labelStyle: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
items: [
|
|
||||||
DropdownMenuItem(
|
|
||||||
child: Text(L10().usernameAndPassword),
|
|
||||||
value: LoginMethod.credentials,
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
child: Text(L10().token),
|
|
||||||
value: LoginMethod.token,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onChanged: (val) {
|
|
||||||
setState(() {
|
|
||||||
_method = val ?? LoginMethod.credentials;
|
|
||||||
error = "";
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
initialValue: "",
|
||||||
|
keyboardType: TextInputType.text,
|
||||||
|
onSaved: (value) {
|
||||||
|
username = value?.trim() ?? "";
|
||||||
|
},
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return L10().usernameEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
),
|
),
|
||||||
|
TextFormField(
|
||||||
// Show fields depending on selected login method
|
decoration: InputDecoration(
|
||||||
if (_method == LoginMethod.credentials) ...[
|
labelText: L10().password,
|
||||||
TextFormField(
|
labelStyle: TextStyle(fontWeight: FontWeight.bold),
|
||||||
key: ValueKey("input-username"),
|
hintText: L10().enterPassword,
|
||||||
decoration: InputDecoration(
|
suffixIcon: IconButton(
|
||||||
labelText: L10().username,
|
icon: _obscured
|
||||||
labelStyle: TextStyle(fontWeight: FontWeight.bold),
|
? Icon(TablerIcons.eye)
|
||||||
hintText: L10().enterUsername,
|
: Icon(TablerIcons.eye_off),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_obscured = !_obscured;
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
initialValue: "",
|
|
||||||
keyboardType: TextInputType.text,
|
|
||||||
onSaved: (value) {
|
|
||||||
username = value?.trim() ?? "";
|
|
||||||
},
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.trim().isEmpty) {
|
|
||||||
return L10().usernameEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
TextFormField(
|
initialValue: "",
|
||||||
key: ValueKey("input-password"),
|
keyboardType: TextInputType.visiblePassword,
|
||||||
decoration: InputDecoration(
|
obscureText: _obscured,
|
||||||
labelText: L10().password,
|
onSaved: (value) {
|
||||||
labelStyle: TextStyle(fontWeight: FontWeight.bold),
|
password = value?.trim() ?? "";
|
||||||
hintText: L10().enterPassword,
|
},
|
||||||
suffixIcon: IconButton(
|
validator: (value) {
|
||||||
icon: _obscured
|
if (value == null || value.trim().isEmpty) {
|
||||||
? Icon(TablerIcons.eye)
|
return L10().passwordEmpty;
|
||||||
: Icon(TablerIcons.eye_off),
|
}
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_obscured = !_obscured;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
initialValue: "",
|
|
||||||
keyboardType: TextInputType.visiblePassword,
|
|
||||||
obscureText: _obscured,
|
|
||||||
onSaved: (value) {
|
|
||||||
password = value?.trim() ?? "";
|
|
||||||
},
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.trim().isEmpty) {
|
|
||||||
return L10().passwordEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
] else ...[
|
|
||||||
TextFormField(
|
|
||||||
key: ValueKey("input-token"),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: L10().token,
|
|
||||||
labelStyle: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
hintText: L10().enterToken,
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: _obscured
|
|
||||||
? Icon(TablerIcons.eye)
|
|
||||||
: Icon(TablerIcons.eye_off),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_obscured = !_obscured;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
initialValue: "",
|
|
||||||
keyboardType: TextInputType.visiblePassword,
|
|
||||||
obscureText: _obscured,
|
|
||||||
onSaved: (value) {
|
|
||||||
token = value?.trim() ?? "";
|
|
||||||
},
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.trim().isEmpty) {
|
|
||||||
return L10().tokenEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
...after,
|
...after,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,9 @@ class _InvenTreePartSettingsState extends State<InvenTreePartSettingsWidget> {
|
||||||
|
|
||||||
bool partShowBom = true;
|
bool partShowBom = true;
|
||||||
bool partShowPricing = true;
|
bool partShowPricing = true;
|
||||||
bool partShowRequirements = false;
|
bool stockShowHistory = false;
|
||||||
|
bool stockShowTests = false;
|
||||||
|
bool stockConfirmScan = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -33,8 +35,16 @@ class _InvenTreePartSettingsState extends State<InvenTreePartSettingsWidget> {
|
||||||
INV_PART_SHOW_PRICING,
|
INV_PART_SHOW_PRICING,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
partShowRequirements = await InvenTreeSettingsManager().getBool(
|
stockShowHistory = await InvenTreeSettingsManager().getBool(
|
||||||
INV_PART_SHOW_REQUIREMENTS,
|
INV_STOCK_SHOW_HISTORY,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
stockShowTests = await InvenTreeSettingsManager().getBool(
|
||||||
|
INV_STOCK_SHOW_TESTS,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
stockConfirmScan = await InvenTreeSettingsManager().getBool(
|
||||||
|
INV_STOCK_CONFIRM_SCAN,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -84,19 +94,54 @@ class _InvenTreePartSettingsState extends State<InvenTreePartSettingsWidget> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Divider(),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(L10().partRequirements),
|
title: Text(L10().stockItemHistory),
|
||||||
subtitle: Text(L10().partRequirementsSettingDetail),
|
subtitle: Text(L10().stockItemHistoryDetail),
|
||||||
leading: Icon(TablerIcons.list),
|
leading: Icon(TablerIcons.history),
|
||||||
trailing: Switch(
|
trailing: Switch(
|
||||||
value: partShowRequirements,
|
value: stockShowHistory,
|
||||||
onChanged: (bool value) {
|
onChanged: (bool value) {
|
||||||
InvenTreeSettingsManager().setValue(
|
InvenTreeSettingsManager().setValue(
|
||||||
INV_PART_SHOW_REQUIREMENTS,
|
INV_STOCK_SHOW_HISTORY,
|
||||||
value,
|
value,
|
||||||
);
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
partShowRequirements = value;
|
stockShowHistory = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().testResults),
|
||||||
|
subtitle: Text(L10().testResultsDetail),
|
||||||
|
leading: Icon(TablerIcons.test_pipe),
|
||||||
|
trailing: Switch(
|
||||||
|
value: stockShowTests,
|
||||||
|
onChanged: (bool value) {
|
||||||
|
InvenTreeSettingsManager().setValue(
|
||||||
|
INV_STOCK_SHOW_TESTS,
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
stockShowTests = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().confirmScan),
|
||||||
|
subtitle: Text(L10().confirmScanDetail),
|
||||||
|
leading: Icon(TablerIcons.qrcode),
|
||||||
|
trailing: Switch(
|
||||||
|
value: stockConfirmScan,
|
||||||
|
onChanged: (bool value) {
|
||||||
|
InvenTreeSettingsManager().setValue(
|
||||||
|
INV_STOCK_CONFIRM_SCAN,
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
stockConfirmScan = value;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||||
import "package:inventree/settings/stock_settings.dart";
|
|
||||||
import "package:package_info_plus/package_info_plus.dart";
|
import "package:package_info_plus/package_info_plus.dart";
|
||||||
|
|
||||||
import "package:inventree/app_colors.dart";
|
import "package:inventree/app_colors.dart";
|
||||||
|
|
@ -120,20 +119,6 @@ class _InvenTreeSettingsState extends State<InvenTreeSettingsWidget> {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
|
||||||
title: Text(L10().stock),
|
|
||||||
subtitle: Text(L10().stockSettings),
|
|
||||||
leading: Icon(TablerIcons.packages, color: COLOR_ACTION),
|
|
||||||
trailing: LinkIcon(),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => InvenTreeStockSettingsWidget(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(L10().purchaseOrder),
|
title: Text(L10().purchaseOrder),
|
||||||
subtitle: Text(L10().purchaseOrderSettings),
|
subtitle: Text(L10().purchaseOrderSettings),
|
||||||
|
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
import "package:flutter/material.dart";
|
|
||||||
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
|
||||||
import "package:inventree/app_colors.dart";
|
|
||||||
import "package:inventree/l10.dart";
|
|
||||||
import "package:inventree/preferences.dart";
|
|
||||||
|
|
||||||
class InvenTreeStockSettingsWidget extends StatefulWidget {
|
|
||||||
@override
|
|
||||||
_InvenTreeStockSettingsState createState() => _InvenTreeStockSettingsState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _InvenTreeStockSettingsState extends State<InvenTreeStockSettingsWidget> {
|
|
||||||
_InvenTreeStockSettingsState();
|
|
||||||
|
|
||||||
bool stockShowHistory = false;
|
|
||||||
bool stockShowTests = false;
|
|
||||||
bool stockConfirmScan = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
loadSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> loadSettings() async {
|
|
||||||
stockShowHistory = await InvenTreeSettingsManager().getBool(
|
|
||||||
INV_STOCK_SHOW_HISTORY,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
stockShowTests = await InvenTreeSettingsManager().getBool(
|
|
||||||
INV_STOCK_SHOW_TESTS,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
stockConfirmScan = await InvenTreeSettingsManager().getBool(
|
|
||||||
INV_STOCK_CONFIRM_SCAN,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(L10().stockSettings),
|
|
||||||
backgroundColor: COLOR_APP_BAR,
|
|
||||||
),
|
|
||||||
body: Container(
|
|
||||||
child: ListView(
|
|
||||||
children: [
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().stockItemHistory),
|
|
||||||
subtitle: Text(L10().stockItemHistoryDetail),
|
|
||||||
leading: Icon(TablerIcons.history),
|
|
||||||
trailing: Switch(
|
|
||||||
value: stockShowHistory,
|
|
||||||
onChanged: (bool value) {
|
|
||||||
InvenTreeSettingsManager().setValue(
|
|
||||||
INV_STOCK_SHOW_HISTORY,
|
|
||||||
value,
|
|
||||||
);
|
|
||||||
setState(() {
|
|
||||||
stockShowHistory = value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().testResults),
|
|
||||||
subtitle: Text(L10().testResultsDetail),
|
|
||||||
leading: Icon(TablerIcons.test_pipe),
|
|
||||||
trailing: Switch(
|
|
||||||
value: stockShowTests,
|
|
||||||
onChanged: (bool value) {
|
|
||||||
InvenTreeSettingsManager().setValue(
|
|
||||||
INV_STOCK_SHOW_TESTS,
|
|
||||||
value,
|
|
||||||
);
|
|
||||||
setState(() {
|
|
||||||
stockShowTests = value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().confirmScan),
|
|
||||||
subtitle: Text(L10().confirmScanDetail),
|
|
||||||
leading: Icon(TablerIcons.qrcode),
|
|
||||||
trailing: Switch(
|
|
||||||
value: stockConfirmScan,
|
|
||||||
onChanged: (bool value) {
|
|
||||||
InvenTreeSettingsManager().setValue(
|
|
||||||
INV_STOCK_CONFIRM_SCAN,
|
|
||||||
value,
|
|
||||||
);
|
|
||||||
setState(() {
|
|
||||||
stockConfirmScan = value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -61,14 +61,6 @@ abstract class PaginatedSearchState<T extends PaginatedSearchWidget>
|
||||||
backup,
|
backup,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result == "null") {
|
|
||||||
if (tristate) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return backup;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,7 +69,7 @@ abstract class PaginatedSearchState<T extends PaginatedSearchWidget>
|
||||||
final String settings_key = "${prefix}filter_${key}";
|
final String settings_key = "${prefix}filter_${key}";
|
||||||
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
await InvenTreeSettingsManager().setValue(settings_key, "null");
|
await InvenTreeSettingsManager().removeValue(settings_key);
|
||||||
} else {
|
} else {
|
||||||
await InvenTreeSettingsManager().setValue(settings_key, value);
|
await InvenTreeSettingsManager().setValue(settings_key, value);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,22 +123,10 @@ class _PaginatedBomListState extends PaginatedSearchState<PaginatedBomList> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, Map<String, dynamic>> get filterOptions => {
|
Map<String, Map<String, dynamic>> get filterOptions => {
|
||||||
"sub_part_active": {
|
|
||||||
"label": L10().filterActive,
|
|
||||||
"help_text": L10().filterActiveDetail,
|
|
||||||
"tristate": true,
|
|
||||||
"default": true,
|
|
||||||
},
|
|
||||||
"sub_part_assembly": {
|
"sub_part_assembly": {
|
||||||
"label": L10().filterAssembly,
|
"label": L10().filterAssembly,
|
||||||
"help_text": L10().filterAssemblyDetail,
|
"help_text": L10().filterAssemblyDetail,
|
||||||
},
|
},
|
||||||
"sub_part_virtual": {
|
|
||||||
"label": L10().filterVirtual,
|
|
||||||
"help_text": L10().filterVirtualDetail,
|
|
||||||
"tristate": true,
|
|
||||||
"default": true,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
||||||
bool allowLabelPrinting = false;
|
bool allowLabelPrinting = false;
|
||||||
bool showBom = false;
|
bool showBom = false;
|
||||||
bool showPricing = false;
|
bool showPricing = false;
|
||||||
bool showRequirements = false;
|
|
||||||
|
|
||||||
int parameterCount = 0;
|
int parameterCount = 0;
|
||||||
int attachmentCount = 0;
|
int attachmentCount = 0;
|
||||||
|
|
@ -63,7 +62,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
||||||
int variantCount = 0;
|
int variantCount = 0;
|
||||||
|
|
||||||
InvenTreePartPricing? partPricing;
|
InvenTreePartPricing? partPricing;
|
||||||
InvenTreePartRequirements? partRequirements;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle() => L10().partDetails;
|
String getAppBarTitle() => L10().partDetails;
|
||||||
|
|
@ -150,12 +148,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
||||||
final bool result = await part.reload();
|
final bool result = await part.reload();
|
||||||
|
|
||||||
// Load page settings from local storage
|
// Load page settings from local storage
|
||||||
|
|
||||||
showRequirements = await InvenTreeSettingsManager().getBool(
|
|
||||||
INV_PART_SHOW_REQUIREMENTS,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
showPricing = await InvenTreeSettingsManager().getBool(
|
showPricing = await InvenTreeSettingsManager().getBool(
|
||||||
INV_PART_SHOW_PRICING,
|
INV_PART_SHOW_PRICING,
|
||||||
true,
|
true,
|
||||||
|
|
@ -241,23 +233,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// If show requirements information?
|
|
||||||
if (showRequirements && api.supportsPartRequirements) {
|
|
||||||
part.getRequirements().then((InvenTreePartRequirements? requirements) {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
partRequirements = requirements;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
partRequirements = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If show pricing information?
|
// If show pricing information?
|
||||||
if (showPricing) {
|
if (showPricing) {
|
||||||
part.getPricing().then((InvenTreePartPricing? pricing) {
|
part.getPricing().then((InvenTreePartPricing? pricing) {
|
||||||
|
|
@ -267,12 +242,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
partPricing = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request the number of BOM items
|
// Request the number of BOM items
|
||||||
|
|
@ -465,103 +434,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Part "requirements"
|
|
||||||
if (showRequirements &&
|
|
||||||
api.supportsPartRequirements &&
|
|
||||||
partRequirements != null) {
|
|
||||||
// Assembly parts
|
|
||||||
if (part.isAssembly) {
|
|
||||||
// Scheduled to build
|
|
||||||
if (partRequirements!.building > 0 ||
|
|
||||||
partRequirements!.scheduledToBuild > 0) {
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().building),
|
|
||||||
subtitle: ProgressBar(
|
|
||||||
partRequirements!.building,
|
|
||||||
maximum: partRequirements!.scheduledToBuild,
|
|
||||||
),
|
|
||||||
leading: Icon(TablerIcons.tools),
|
|
||||||
trailing: ProgressText(
|
|
||||||
partRequirements!.building,
|
|
||||||
maximum: partRequirements!.scheduledToBuild,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can build
|
|
||||||
if (part.isActive) {
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().canBuild),
|
|
||||||
subtitle: Text(L10().canBuildDetail),
|
|
||||||
trailing: LargeText(
|
|
||||||
simpleNumberString(partRequirements!.canBuild),
|
|
||||||
),
|
|
||||||
leading: Icon(TablerIcons.check),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build requirements
|
|
||||||
if (partRequirements!.requiredForBuildOrders > 0 ||
|
|
||||||
partRequirements!.allocatedToBuildOrders > 0) {
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().allocatedToBuildOrders),
|
|
||||||
subtitle: ProgressBar(
|
|
||||||
partRequirements!.allocatedToBuildOrders,
|
|
||||||
maximum: partRequirements!.requiredForBuildOrders,
|
|
||||||
),
|
|
||||||
trailing: ProgressText(
|
|
||||||
partRequirements!.allocatedToBuildOrders,
|
|
||||||
maximum: partRequirements!.requiredForBuildOrders,
|
|
||||||
),
|
|
||||||
leading: Icon(TablerIcons.tools),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sales requirements
|
|
||||||
if (part.isSalable) {
|
|
||||||
if (partRequirements!.requiredForSalesOrders > 0 ||
|
|
||||||
partRequirements!.allocatedToSalesOrders > 0) {
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().allocatedToSalesOrders),
|
|
||||||
subtitle: ProgressBar(
|
|
||||||
partRequirements!.allocatedToSalesOrders,
|
|
||||||
maximum: partRequirements!.requiredForSalesOrders,
|
|
||||||
),
|
|
||||||
trailing: ProgressText(
|
|
||||||
partRequirements!.allocatedToSalesOrders,
|
|
||||||
maximum: partRequirements!.requiredForSalesOrders,
|
|
||||||
),
|
|
||||||
leading: Icon(TablerIcons.truck_delivery),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ordering stats
|
|
||||||
if (part.isPurchaseable && partRequirements!.ordering > 0) {
|
|
||||||
// On order
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().onOrder),
|
|
||||||
subtitle: Text(L10().onOrderDetails),
|
|
||||||
leading: Icon(TablerIcons.shopping_cart),
|
|
||||||
trailing: LargeText("${part.onOrderString}"),
|
|
||||||
onTap: () {
|
|
||||||
// TODO - Order views
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showPricing && partPricing != null) {
|
if (showPricing && partPricing != null) {
|
||||||
String pricing = formatPriceRange(
|
String pricing = formatPriceRange(
|
||||||
partPricing?.overallMin,
|
partPricing?.overallMin,
|
||||||
|
|
@ -590,6 +462,22 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tiles for "purchaseable" parts
|
||||||
|
if (part.isPurchaseable) {
|
||||||
|
// On order
|
||||||
|
tiles.add(
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().onOrder),
|
||||||
|
subtitle: Text(L10().onOrderDetails),
|
||||||
|
leading: Icon(TablerIcons.shopping_cart),
|
||||||
|
trailing: LargeText("${part.onOrderString}"),
|
||||||
|
onTap: () {
|
||||||
|
// TODO - Order views
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Tiles for an "assembly" part
|
// Tiles for an "assembly" part
|
||||||
if (part.isAssembly) {
|
if (part.isAssembly) {
|
||||||
if (showBom && bomCount > 0) {
|
if (showBom && bomCount > 0) {
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ import "dart:io";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_overlay_loader/flutter_overlay_loader.dart";
|
import "package:flutter_overlay_loader/flutter_overlay_loader.dart";
|
||||||
import "package:inventree/app_colors.dart";
|
import "package:inventree/app_colors.dart";
|
||||||
import "package:inventree/helpers.dart";
|
|
||||||
import "package:inventree/widget/link_icon.dart";
|
|
||||||
import "package:one_context/one_context.dart";
|
import "package:one_context/one_context.dart";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -27,15 +25,6 @@ Widget ProgressBar(double value, {double maximum = 1.0}) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget ProgressText(double value, {double maximum = 1.0}) {
|
|
||||||
Color textColor = value < maximum ? COLOR_WARNING : COLOR_SUCCESS;
|
|
||||||
|
|
||||||
String v = simpleNumberString(value);
|
|
||||||
String m = simpleNumberString(maximum);
|
|
||||||
|
|
||||||
return LargeText("${v} / ${m}", color: textColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Construct a circular progress indicator
|
* Construct a circular progress indicator
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
name: inventree
|
name: inventree
|
||||||
description: InvenTree stock management
|
description: InvenTree stock management
|
||||||
|
|
||||||
version: 0.22.1+110
|
version: 0.21.2+108
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.8.1
|
sdk: ^3.8.1
|
||||||
|
|
|
||||||
|
|
@ -23,27 +23,6 @@ void main() {
|
||||||
assert(await UserProfileDBManager().selectProfileByName(testServerName));
|
assert(await UserProfileDBManager().selectProfileByName(testServerName));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure that generated URLs are correct
|
|
||||||
group("URL Tests:", () {
|
|
||||||
test("Generate URLs", () async {
|
|
||||||
UserProfile profile = await setupServerProfile();
|
|
||||||
var api = InvenTreeAPI();
|
|
||||||
|
|
||||||
api.profile = profile;
|
|
||||||
|
|
||||||
Map<String, String> tests = {
|
|
||||||
"": "http://localhost:8000/api/",
|
|
||||||
"barcode/": "http://localhost:8000/api/barcode/",
|
|
||||||
"https://remote-server.com/media/image.png":
|
|
||||||
"https://remote-server.com/media/image.png",
|
|
||||||
};
|
|
||||||
|
|
||||||
for (var test in tests.entries) {
|
|
||||||
expect(api.makeApiUrl(test.key), equals(test.value));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group("Login Tests:", () {
|
group("Login Tests:", () {
|
||||||
test("Disconnected", () async {
|
test("Disconnected", () async {
|
||||||
// Test that calling disconnect() does the right thing
|
// Test that calling disconnect() does the right thing
|
||||||
|
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# This script adjusts flake.nix to use the flutter version
|
|
||||||
# pinned in .fvmrc. It fetches the appropriate commit hash.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
for cmd in jq curl sed nix; do
|
|
||||||
command -v $cmd >/dev/null 2>&1 || { echo "Error: $cmd is not installed!"; exit 1; }
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ ! -f .fvmrc ]; then
|
|
||||||
echo ".fvmrc file not found!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
FLUTTER_VERSION=$(jq -r .flutter .fvmrc)
|
|
||||||
echo "Requested Flutter version: $FLUTTER_VERSION"
|
|
||||||
|
|
||||||
API_URL="https://search.devbox.sh/v2/pkg?name=flutter"
|
|
||||||
RELEASES_JSON=$(curl -s "$API_URL")
|
|
||||||
|
|
||||||
ALL_VERSIONS=$(echo "$RELEASES_JSON" | jq -r '.releases[].version' | sort -V)
|
|
||||||
|
|
||||||
FOUND_VERSION=$(echo "$ALL_VERSIONS" | awk -v v="$FLUTTER_VERSION" '$0 >= v { print; exit }')
|
|
||||||
|
|
||||||
if [ -z "$FOUND_VERSION" ]; then
|
|
||||||
echo "Error: No matching Flutter version found."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$FOUND_VERSION" != "$FLUTTER_VERSION" ]; then
|
|
||||||
echo "Note: Exact version not found, using version $FOUND_VERSION instead."
|
|
||||||
fi
|
|
||||||
|
|
||||||
COMMIT=$(echo "$RELEASES_JSON" | jq -r --arg v "$FOUND_VERSION" '.releases[] | select(.version==$v) | .platforms[] | select(.system=="x86_64-linux") | .commit_hash' | head -n 1)
|
|
||||||
|
|
||||||
if [ -z "$COMMIT" ] || [ "$COMMIT" == "null" ]; then
|
|
||||||
echo "Error: No commit hash found for version $FOUND_VERSION and platform x86_64-linux."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Found commit: $COMMIT (Version: $FOUND_VERSION)"
|
|
||||||
|
|
||||||
|
|
||||||
sed -i.bak "s|nixpkgs-flutter.url = \"github:NixOS/nixpkgs/[a-f0-9]*\";|nixpkgs-flutter.url = \"github:NixOS/nixpkgs/$COMMIT\";|" flake.nix
|
|
||||||
|
|
||||||
nix flake update nixpkgs-flutter
|
|
||||||
|
|
||||||
if command -v direnv >/dev/null 2>&1; then
|
|
||||||
direnv reload
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Success! flake.nix now uses the commit for Flutter $FLUTTER_VERSION (or higher)."
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue